Merge pull request #3809 from Infisical/feat/dynamicSecretAwsIamCustomTags

feat(dynamic-secret): Add custom tags to AWS IAM dynamic secret
This commit is contained in:
carlosmonastyrski
2025-06-19 16:42:53 -03:00
committed by GitHub
7 changed files with 78 additions and 15 deletions

View File

@@ -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
})
);

View File

@@ -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()
})
])
);

View File

@@ -161,6 +161,11 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
{{replace identity.name 'user' 'replace'}} // testreplace
```
</ParamField>
<ParamField path="Tags" type="map<string, string>[]">
Tags to be added to the created IAM User resource.
</ParamField>
<ParamField path="Method" type="string" required>
Select *Assume Role* method.
</ParamField>
@@ -304,6 +309,10 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Tags" type="map[string]string">
Tags to be added to the created IAM User resource.
</ParamField>
</Step>
<Step title="Click 'Submit'">

View File

@@ -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 }[];
};
};

View File

@@ -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 = ({
</FormControl>
)}
/>
<MetadataForm control={control} name="provider.tags" title="Tags" isValueRequired />
{!isSingleEnvironmentMode && (
<Controller
control={control}

View File

@@ -10,6 +10,8 @@ import { useUpdateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretAwsIamAuth, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
import { slugSchema } from "@app/lib/schemas";
import { MetadataForm } from "../MetadataForm";
const formSchema = z.object({
inputs: z.discriminatedUnion("method", [
z.object({
@@ -21,7 +23,10 @@ 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), value: z.string().trim().min(1) }))
.optional(),
}),
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
@@ -31,7 +36,10 @@ 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), value: z.string().trim().min(1) }))
.optional()
})
]),
defaultTTL: z.string().superRefine((val, ctx) => {
@@ -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 = ({
</FormControl>
)}
/>
<MetadataForm control={control} name="inputs.tags" title="Tags" isValueRequired />
</div>
</div>
<div className="mt-4 flex items-center space-x-4">

View File

@@ -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<any> }) => {
export const MetadataForm = ({
control,
name = "metadata",
title = "Metadata",
isValueRequired = false
}: {
control: Control<any>;
name?: string;
title?: string;
isValueRequired?: boolean;
}) => {
const metadataFormFields = useFieldArray({
control,
name: "metadata"
name
});
return (
<FormControl label="Metadata">
<FormControl label={title}>
<div className="flex flex-col space-y-2">
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
<div key={metadataFieldId} className="flex items-end space-x-2">
@@ -19,7 +29,7 @@ export const MetadataForm = ({ control }: { control: Control<any> }) => {
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
<Controller
control={control}
name={`metadata.${i}.key`}
name={`${name}.${i}.key`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
@@ -33,11 +43,15 @@ export const MetadataForm = ({ control }: { control: Control<any> }) => {
</div>
<div className="flex-grow">
{i === 0 && (
<FormLabel label="Value" className="text-xs text-mineshaft-400" isOptional />
<FormLabel
label="Value"
className="text-xs text-mineshaft-400"
isOptional={!isValueRequired}
/>
)}
<Controller
control={control}
name={`metadata.${i}.value`}
name={`${name}.${i}.value`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}