diff --git a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts index f7383d4ac..ec75bb2e4 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -128,11 +128,21 @@ export const AwsIamProvider = (): TDynamicProviderFns => { const username = generateUsername(usernameTemplate, identity); const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; + const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }]; + + if (providerInputs.tags && Array.isArray(providerInputs.tags)) { + const additionalTags = providerInputs.tags.map((tag) => ({ + Key: tag.key, + Value: tag.value + })); + awsTags.push(...additionalTags); + } + const createUserRes = await client.send( new CreateUserCommand({ Path: awsPath, PermissionsBoundary: permissionBoundaryPolicyArn || undefined, - Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }], + Tags: awsTags, UserName: username }) ); diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index 8f361e166..c52630701 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -2,6 +2,7 @@ import RE2 from "re2"; import { z } from "zod"; import { CharacterType, characterValidator } from "@app/lib/validator/validate-string"; +import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types"; @@ -207,7 +208,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess( permissionBoundaryPolicyArn: z.string().trim().optional(), policyDocument: z.string().trim().optional(), userGroups: z.string().trim().optional(), - policyArns: z.string().trim().optional() + policyArns: z.string().trim().optional(), + tags: ResourceMetadataSchema.optional() }), z.object({ method: z.literal(AwsIamAuthType.AssumeRole), @@ -217,7 +219,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess( permissionBoundaryPolicyArn: z.string().trim().optional(), policyDocument: z.string().trim().optional(), userGroups: z.string().trim().optional(), - policyArns: z.string().trim().optional() + policyArns: z.string().trim().optional(), + tags: ResourceMetadataSchema.optional() }) ]) ); diff --git a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx index a02d80a5c..03bc5df84 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx @@ -161,6 +161,11 @@ Replace **\** with your AWS account id and **\** w {{replace identity.name 'user' 'replace'}} // testreplace ``` + + + Tags to be added to the created IAM User resource. + + Select *Assume Role* method. @@ -304,6 +309,10 @@ Replace **\** with your AWS account id and **\** w - `{{unixTimestamp}}`: Current Unix timestamp + + Tags to be added to the created IAM User resource. + + diff --git a/frontend/src/hooks/api/dynamicSecret/types.ts b/frontend/src/hooks/api/dynamicSecret/types.ts index e8d80e632..a569e94ba 100644 --- a/frontend/src/hooks/api/dynamicSecret/types.ts +++ b/frontend/src/hooks/api/dynamicSecret/types.ts @@ -15,6 +15,7 @@ export type TDynamicSecret = { maxTTL: string; usernameTemplate?: string | null; metadata?: { key: string; value: string }[]; + tags?: { key: string; value: string }[]; }; export enum DynamicSecretProviders { @@ -89,6 +90,7 @@ export type TDynamicSecretProvider = } | { type: DynamicSecretProviders.AwsIam; + tags?: { key: string; value: string }[]; inputs: | { method: DynamicSecretAwsIamAuth.AccessKey; @@ -345,6 +347,7 @@ export type TCreateDynamicSecretDTO = { name: string; metadata?: { key: string; value: string }[]; usernameTemplate?: string; + tags?: { key: string; value: string }[]; }; export type TUpdateDynamicSecretDTO = { @@ -359,6 +362,7 @@ export type TUpdateDynamicSecretDTO = { maxTTL?: string | null; inputs?: unknown; usernameTemplate?: string | null; + tags?: { key: string; value: string }[]; }; }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx index 80c80ee61..48e942d0a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx @@ -21,6 +21,8 @@ import { } from "@app/hooks/api/dynamicSecret/types"; import { WorkspaceEnv } from "@app/hooks/api/types"; +import { MetadataForm } from "../../DynamicSecretListView/MetadataForm"; + const formSchema = z.object({ provider: z.discriminatedUnion("method", [ z.object({ @@ -32,7 +34,12 @@ const formSchema = z.object({ permissionBoundaryPolicyArn: z.string().trim().optional(), policyDocument: z.string().trim().optional(), userGroups: z.string().trim().optional(), - policyArns: z.string().trim().optional() + policyArns: z.string().trim().optional(), + tags: z + .array( + z.object({ key: z.string().trim().min(1).max(128), value: z.string().trim().min(1).max(256) }) + ) + .optional() }), z.object({ method: z.literal(DynamicSecretAwsIamAuth.AssumeRole), @@ -42,7 +49,12 @@ const formSchema = z.object({ permissionBoundaryPolicyArn: z.string().trim().optional(), policyDocument: z.string().trim().optional(), userGroups: z.string().trim().optional(), - policyArns: z.string().trim().optional() + policyArns: z.string().trim().optional(), + tags: z + .array( + z.object({ key: z.string().trim().min(1).max(128), value: z.string().trim().min(1).max(256) }) + ) + .optional() }) ]), defaultTTL: z.string().superRefine((val, ctx) => { @@ -398,6 +410,7 @@ export const AwsIamInputForm = ({ )} /> + {!isSingleEnvironmentMode && ( { @@ -89,7 +97,7 @@ export const EditDynamicSecretAwsIamForm = ({ usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}", inputs: { ...(dynamicSecret.inputs as TForm["inputs"]) - } + }, } }); const isAccessKeyMethod = watch("inputs.method") === DynamicSecretAwsIamAuth.AccessKey; @@ -117,7 +125,8 @@ export const EditDynamicSecretAwsIamForm = ({ defaultTTL, inputs, newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate } }); onClose(); @@ -380,6 +389,7 @@ export const EditDynamicSecretAwsIamForm = ({ )} /> +
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/MetadataForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/MetadataForm.tsx index 986ba9e46..510bc2a05 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/MetadataForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/MetadataForm.tsx @@ -4,14 +4,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FormControl, FormLabel, IconButton, Input } from "@app/components/v2"; -export const MetadataForm = ({ control }: { control: Control }) => { +export const MetadataForm = ({ + control, + name = "metadata", + title = "Metadata", + isValueRequired = false +}: { + control: Control; + name?: string; + title?: string; + isValueRequired?: boolean; +}) => { const metadataFormFields = useFieldArray({ control, - name: "metadata" + name }); return ( - +
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
@@ -19,7 +29,7 @@ export const MetadataForm = ({ control }: { control: Control }) => { {i === 0 && Key} ( }) => {
{i === 0 && ( - + )} (