mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 22:28:47 +00:00
Merge pull request #3809 from Infisical/feat/dynamicSecretAwsIamCustomTags
feat(dynamic-secret): Add custom tags to AWS IAM dynamic secret
This commit is contained in:
@@ -128,11 +128,21 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const username = generateUsername(usernameTemplate, identity);
|
const username = generateUsername(usernameTemplate, identity);
|
||||||
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
|
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(
|
const createUserRes = await client.send(
|
||||||
new CreateUserCommand({
|
new CreateUserCommand({
|
||||||
Path: awsPath,
|
Path: awsPath,
|
||||||
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
|
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
|
||||||
Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }],
|
Tags: awsTags,
|
||||||
UserName: username
|
UserName: username
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import RE2 from "re2";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
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";
|
import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
|
||||||
|
|
||||||
@@ -207,7 +208,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
|||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
policyDocument: z.string().trim().optional(),
|
policyDocument: z.string().trim().optional(),
|
||||||
userGroups: z.string().trim().optional(),
|
userGroups: z.string().trim().optional(),
|
||||||
policyArns: z.string().trim().optional()
|
policyArns: z.string().trim().optional(),
|
||||||
|
tags: ResourceMetadataSchema.optional()
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(AwsIamAuthType.AssumeRole),
|
method: z.literal(AwsIamAuthType.AssumeRole),
|
||||||
@@ -217,7 +219,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
|||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
policyDocument: z.string().trim().optional(),
|
policyDocument: z.string().trim().optional(),
|
||||||
userGroups: z.string().trim().optional(),
|
userGroups: z.string().trim().optional(),
|
||||||
policyArns: z.string().trim().optional()
|
policyArns: z.string().trim().optional(),
|
||||||
|
tags: ResourceMetadataSchema.optional()
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -161,6 +161,11 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
|
|||||||
{{replace identity.name 'user' 'replace'}} // testreplace
|
{{replace identity.name 'user' 'replace'}} // testreplace
|
||||||
```
|
```
|
||||||
</ParamField>
|
</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>
|
<ParamField path="Method" type="string" required>
|
||||||
Select *Assume Role* method.
|
Select *Assume Role* method.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
@@ -304,6 +309,10 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
|
|||||||
- `{{unixTimestamp}}`: Current Unix timestamp
|
- `{{unixTimestamp}}`: Current Unix timestamp
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Tags" type="map[string]string">
|
||||||
|
Tags to be added to the created IAM User resource.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step title="Click 'Submit'">
|
<Step title="Click 'Submit'">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type TDynamicSecret = {
|
|||||||
maxTTL: string;
|
maxTTL: string;
|
||||||
usernameTemplate?: string | null;
|
usernameTemplate?: string | null;
|
||||||
metadata?: { key: string; value: string }[];
|
metadata?: { key: string; value: string }[];
|
||||||
|
tags?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
@@ -89,6 +90,7 @@ export type TDynamicSecretProvider =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: DynamicSecretProviders.AwsIam;
|
type: DynamicSecretProviders.AwsIam;
|
||||||
|
tags?: { key: string; value: string }[];
|
||||||
inputs:
|
inputs:
|
||||||
| {
|
| {
|
||||||
method: DynamicSecretAwsIamAuth.AccessKey;
|
method: DynamicSecretAwsIamAuth.AccessKey;
|
||||||
@@ -345,6 +347,7 @@ export type TCreateDynamicSecretDTO = {
|
|||||||
name: string;
|
name: string;
|
||||||
metadata?: { key: string; value: string }[];
|
metadata?: { key: string; value: string }[];
|
||||||
usernameTemplate?: string;
|
usernameTemplate?: string;
|
||||||
|
tags?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateDynamicSecretDTO = {
|
export type TUpdateDynamicSecretDTO = {
|
||||||
@@ -359,6 +362,7 @@ export type TUpdateDynamicSecretDTO = {
|
|||||||
maxTTL?: string | null;
|
maxTTL?: string | null;
|
||||||
inputs?: unknown;
|
inputs?: unknown;
|
||||||
usernameTemplate?: string | null;
|
usernameTemplate?: string | null;
|
||||||
|
tags?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+15
-2
@@ -21,6 +21,8 @@ import {
|
|||||||
} from "@app/hooks/api/dynamicSecret/types";
|
} from "@app/hooks/api/dynamicSecret/types";
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/types";
|
import { WorkspaceEnv } from "@app/hooks/api/types";
|
||||||
|
|
||||||
|
import { MetadataForm } from "../../DynamicSecretListView/MetadataForm";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
provider: z.discriminatedUnion("method", [
|
provider: z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
@@ -32,7 +34,12 @@ const formSchema = z.object({
|
|||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
policyDocument: z.string().trim().optional(),
|
policyDocument: z.string().trim().optional(),
|
||||||
userGroups: 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({
|
z.object({
|
||||||
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
|
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
|
||||||
@@ -42,7 +49,12 @@ const formSchema = z.object({
|
|||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
policyDocument: z.string().trim().optional(),
|
policyDocument: z.string().trim().optional(),
|
||||||
userGroups: 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) => {
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
@@ -398,6 +410,7 @@ export const AwsIamInputForm = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<MetadataForm control={control} name="provider.tags" title="Tags" isValueRequired />
|
||||||
{!isSingleEnvironmentMode && (
|
{!isSingleEnvironmentMode && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+14
-4
@@ -10,6 +10,8 @@ import { useUpdateDynamicSecret } from "@app/hooks/api";
|
|||||||
import { DynamicSecretAwsIamAuth, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
import { DynamicSecretAwsIamAuth, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
|
|
||||||
|
import { MetadataForm } from "../MetadataForm";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
inputs: z.discriminatedUnion("method", [
|
inputs: z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
@@ -21,7 +23,10 @@ const formSchema = z.object({
|
|||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
policyDocument: z.string().trim().optional(),
|
policyDocument: z.string().trim().optional(),
|
||||||
userGroups: 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({
|
z.object({
|
||||||
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
|
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
|
||||||
@@ -31,7 +36,10 @@ const formSchema = z.object({
|
|||||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||||
policyDocument: z.string().trim().optional(),
|
policyDocument: z.string().trim().optional(),
|
||||||
userGroups: 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) => {
|
defaultTTL: z.string().superRefine((val, ctx) => {
|
||||||
@@ -89,7 +97,7 @@ export const EditDynamicSecretAwsIamForm = ({
|
|||||||
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
|
usernameTemplate: dynamicSecret?.usernameTemplate || "{{randomUsername}}",
|
||||||
inputs: {
|
inputs: {
|
||||||
...(dynamicSecret.inputs as TForm["inputs"])
|
...(dynamicSecret.inputs as TForm["inputs"])
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const isAccessKeyMethod = watch("inputs.method") === DynamicSecretAwsIamAuth.AccessKey;
|
const isAccessKeyMethod = watch("inputs.method") === DynamicSecretAwsIamAuth.AccessKey;
|
||||||
@@ -117,7 +125,8 @@ export const EditDynamicSecretAwsIamForm = ({
|
|||||||
defaultTTL,
|
defaultTTL,
|
||||||
inputs,
|
inputs,
|
||||||
newName: newName === dynamicSecret.name ? undefined : newName,
|
newName: newName === dynamicSecret.name ? undefined : newName,
|
||||||
usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
|
usernameTemplate:
|
||||||
|
!usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
@@ -380,6 +389,7 @@ export const EditDynamicSecretAwsIamForm = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<MetadataForm control={control} name="inputs.tags" title="Tags" isValueRequired />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center space-x-4">
|
<div className="mt-4 flex items-center space-x-4">
|
||||||
|
|||||||
+20
-6
@@ -4,14 +4,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
|
|
||||||
import { FormControl, FormLabel, IconButton, Input } from "@app/components/v2";
|
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({
|
const metadataFormFields = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: "metadata"
|
name
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl label="Metadata">
|
<FormControl label={title}>
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
|
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
|
||||||
<div key={metadataFieldId} className="flex items-end space-x-2">
|
<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>}
|
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`metadata.${i}.key`}
|
name={`${name}.${i}.key`}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
@@ -33,11 +43,15 @@ export const MetadataForm = ({ control }: { control: Control<any> }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
{i === 0 && (
|
{i === 0 && (
|
||||||
<FormLabel label="Value" className="text-xs text-mineshaft-400" isOptional />
|
<FormLabel
|
||||||
|
label="Value"
|
||||||
|
className="text-xs text-mineshaft-400"
|
||||||
|
isOptional={!isValueRequired}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={`metadata.${i}.value`}
|
name={`${name}.${i}.value`}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
|
|||||||
Reference in New Issue
Block a user