Address greptile comments

This commit is contained in:
Carlos Monastyrski
2025-09-03 23:48:38 -03:00
parent 78493bf32a
commit 392b72bdbd
5 changed files with 370 additions and 262 deletions

View File

@@ -31,7 +31,8 @@ import { compileUsernameTemplate } from "./templateUtils";
// AWS STS duration constants (in seconds)
const AWS_STS_MIN_DURATION = 900;
const AWS_STS_MAX_DURATION_SESSION_TOKEN = 43200;
const AWS_STS_MAX_DURATION_SESSION_TOKEN = 43200; // 12 hours for GetSessionToken
const AWS_STS_MAX_DURATION_ASSUME_ROLE = 3600; // 1 hour for AssumeRole when using temp credentials
const generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => {
const randomUsername = alphaNumericNanoId(32);
@@ -200,14 +201,6 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
if (providerInputs.credentialType === AwsIamCredentialType.TemporaryCredentials) {
if (providerInputs.method === AwsIamAuthType.AccessKey) {
sensitiveTokens.push(providerInputs.accessKey, providerInputs.secretAccessKey);
}
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn);
}
}
const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message,
tokens: sensitiveTokens
@@ -243,9 +236,11 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
throw new BadRequestError({ message: "Expiration time must be in the future" });
}
let durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN);
let durationSeconds: number;
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
// AssumeRole has a lower maximum duration when using temporary credentials
durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_ASSUME_ROLE);
const appCfg = getConfig();
stsClient = new STSClient({
region: providerInputs.region,
@@ -260,8 +255,6 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
: undefined
});
durationSeconds = Math.min(durationSeconds, AWS_STS_MAX_DURATION_SESSION_TOKEN);
const assumeRoleRes = await stsClient.send(
new AssumeRoleCommand({
RoleArn: providerInputs.roleArn,
@@ -290,6 +283,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
};
}
if (providerInputs.method === AwsIamAuthType.AccessKey) {
// GetSessionToken supports longer durations
durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN);
stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),
@@ -325,6 +320,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
};
}
if (providerInputs.method === AwsIamAuthType.IRSA) {
// GetSessionToken supports longer durations
durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN);
stsClient = new STSClient({
region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(),

View File

@@ -5,6 +5,17 @@ description: "Learn how to dynamically generate AWS IAM Users."
The Infisical AWS IAM dynamic secret allows you to generate AWS IAM Users and temporary credentials on demand based on a configured AWS policy. Infisical supports several authentication methods to connect to your AWS account, including assuming an IAM Role, using IAM Roles for Service Accounts (IRSA) on EKS, or static Access Keys.
## AWS STS Duration Limits
When using **Temporary Credentials**, AWS STS has specific maximum duration limits:
- **AssumeRole operations**: Maximum 1 hour (3600 seconds) when using temporary credentials
- **GetSessionToken operations** (Access Key & IRSA): Maximum 12 hours (43200 seconds)
<Info>
**Automatic Duration Adjustment**: If you specify a TTL that exceeds these AWS limits, Infisical will automatically use the maximum allowed duration instead of failing the operation. This ensures your dynamic secrets work reliably within AWS constraints.
</Info>
## Prerequisite
Infisical needs an AWS IAM principal (a user or a role) with the required permissions to create and manage other IAM users and temporary credentials. This principal will be responsible for the lifecycle of the dynamically generated users and temporary credentials.
@@ -267,6 +278,10 @@ Infisical needs an AWS IAM principal (a user or a role) with the required permis
- Include an AWS Session Token
- Be valid for the duration specified in Default TTL
</Info>
<Warning>
**Duration Limit**: AssumeRole temporary credentials are limited to 1 hour maximum by AWS. TTL values exceeding this limit will be automatically adjusted to 1 hour.
</Warning>
</Tab>
</Tabs>
</Step>
@@ -479,6 +494,10 @@ Infisical needs an AWS IAM principal (a user or a role) with the required permis
- Include an AWS Session Token
- Be valid for the duration specified in Default TTL
</Info>
<Note>
**Duration Limit**: IRSA temporary credentials support up to 12 hours maximum via GetSessionToken. TTL values exceeding this limit will be automatically adjusted.
</Note>
</Tab>
</Tabs>
</Step>
@@ -606,6 +625,10 @@ Infisical needs an AWS IAM principal (a user or a role) with the required permis
- Include an AWS Session Token
- Be valid for the duration specified in Default TTL
</Info>
<Note>
**Duration Limit**: Access Key temporary credentials support up to 12 hours maximum via GetSessionToken. TTL values exceeding this limit will be automatically adjusted.
</Note>
</Tab>
</Tabs>

View File

@@ -102,7 +102,7 @@ export type TDynamicSecretProvider =
inputs:
| {
method: DynamicSecretAwsIamAuth.AccessKey;
credentialType?: DynamicSecretAwsIamCredentialType;
credentialType: DynamicSecretAwsIamCredentialType;
accessKey: string;
secretAccessKey: string;
region: string;
@@ -113,7 +113,7 @@ export type TDynamicSecretProvider =
}
| {
method: DynamicSecretAwsIamAuth.AssumeRole;
credentialType?: DynamicSecretAwsIamCredentialType;
credentialType: DynamicSecretAwsIamCredentialType;
roleArn: string;
region: string;
awsPath?: string;
@@ -123,7 +123,7 @@ export type TDynamicSecretProvider =
}
| {
method: DynamicSecretAwsIamAuth.IRSA;
credentialType?: DynamicSecretAwsIamCredentialType;
credentialType: DynamicSecretAwsIamCredentialType;
region: string;
awsPath?: string;
policyDocument?: string;

View File

@@ -25,85 +25,74 @@ import { WorkspaceEnv } from "@app/hooks/api/types";
import { MetadataForm } from "../../DynamicSecretListView/MetadataForm";
const formSchema = z.object({
provider: z.discriminatedUnion("method", [
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
roleArn: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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.IRSA),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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) => {
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
maxTTL: z
.string()
.optional()
.superRefine((val, ctx) => {
if (!val) return;
const formSchema = z
.object({
provider: z.discriminatedUnion("method", [
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
roleArn: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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.IRSA),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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) => {
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
@@ -111,10 +100,34 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() }),
usernameTemplate: z.string().nullable().optional()
});
maxTTL: z
.string()
.optional()
.superRefine((val, ctx) => {
if (!val) return;
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() }),
usernameTemplate: z.string().nullable().optional()
})
.refine(
(data) => {
if (data.provider.credentialType === DynamicSecretAwsIamCredentialType.TemporaryCredentials) {
return !data.provider.awsPath || data.provider.awsPath === "";
}
return true;
},
{
message: "AWS IAM Path cannot be set when using temporary credentials",
path: ["provider", "awsPath"]
}
);
type TForm = z.infer<typeof formSchema>;
type Props = {

View File

@@ -7,77 +7,103 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
import { useGetServerConfig, useUpdateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretAwsIamAuth, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
import {
DynamicSecretAwsIamAuth,
DynamicSecretAwsIamCredentialType,
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({
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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),
roleArn: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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.IRSA),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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) => {
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
maxTTL: z
.string()
.optional()
.superRefine((val, ctx) => {
if (!val) return;
const formSchema = z
.object({
inputs: z.discriminatedUnion("method", [
z.object({
method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
roleArn: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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.IRSA),
credentialType: z
.nativeEnum(DynamicSecretAwsIamCredentialType)
.default(DynamicSecretAwsIamCredentialType.IamUser),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: 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) => {
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
})
.nullable(),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
});
}),
maxTTL: z
.string()
.optional()
.superRefine((val, ctx) => {
if (!val) return;
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
})
.nullable(),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
})
.refine(
(data) => {
if (data.inputs.credentialType === DynamicSecretAwsIamCredentialType.TemporaryCredentials) {
return !data.inputs.awsPath || data.inputs.awsPath === "";
}
return true;
},
{
message: "AWS IAM Path cannot be set when using temporary credentials",
path: ["inputs", "awsPath"]
}
);
type TForm = z.infer<typeof formSchema>;
type Props = {
@@ -115,6 +141,7 @@ export const EditDynamicSecretAwsIamForm = ({
}
});
const method = watch("inputs.method");
const credentialType = watch("inputs.credentialType");
const updateDynamicSecret = useUpdateDynamicSecret();
@@ -235,6 +262,39 @@ export const EditDynamicSecretAwsIamForm = ({
</FormControl>
)}
/>
<Controller
name="inputs.credentialType"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Credential Type"
>
<>
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value={DynamicSecretAwsIamCredentialType.IamUser}>
IAM User
</SelectItem>
<SelectItem value={DynamicSecretAwsIamCredentialType.TemporaryCredentials}>
Temporary Credentials
</SelectItem>
</Select>
<div className="mt-1 text-xs text-mineshaft-300">
{value === DynamicSecretAwsIamCredentialType.IamUser
? "Creates temporary IAM users with access keys"
: "Uses STS to generate temporary credentials from your connection. Duration is controlled by the Default TTL setting above."}
</div>
</>
</FormControl>
)}
/>
{method === DynamicSecretAwsIamAuth.AccessKey && (
<div className="flex items-center space-x-2">
<Controller
@@ -289,21 +349,24 @@ export const EditDynamicSecretAwsIamForm = ({
</div>
)}
<div className="flex items-center space-x-2">
<Controller
control={control}
name="inputs.awsPath"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Path"
className="flex-grow"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<Controller
control={control}
name="inputs.awsPath"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Path"
className="flex-grow"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
)}
<Controller
control={control}
name="inputs.region"
@@ -311,7 +374,11 @@ export const EditDynamicSecretAwsIamForm = ({
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Region"
className="flex-grow"
className={
credentialType === DynamicSecretAwsIamCredentialType.TemporaryCredentials
? "w-full"
: "flex-grow"
}
isError={Boolean(error?.message)}
errorText={error?.message}
>
@@ -320,93 +387,101 @@ export const EditDynamicSecretAwsIamForm = ({
)}
/>
</div>
<Controller
control={control}
name="inputs.userGroups"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Groups"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given groups."
>
<Input {...field} placeholder="group1,group2" />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.permissionBoundaryPolicyArn"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM User Permission Boundary ARN"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyArns"
defaultValue="datacenter1"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Policy ARNs"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given policy arns."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyDocument"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Policy Document"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="Generated users will have the inline policy."
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
<MetadataForm control={control} name="inputs.tags" title="Tags" isValueRequired />
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<>
<Controller
control={control}
name="inputs.userGroups"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Groups"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given groups."
>
<Input {...field} placeholder="group1,group2" />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.permissionBoundaryPolicyArn"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM User Permission Boundary ARN"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="ARN to be attached to the generated user for AWS Permission Boundary."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyArns"
defaultValue="datacenter1"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS Policy ARNs"
isError={Boolean(error?.message)}
isOptional
errorText={error?.message}
helperText="Generated users will get attached to given policy arns."
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.policyDocument"
render={({ field, fieldState: { error } }) => (
<FormControl
label="AWS IAM Policy Document"
isOptional
isError={Boolean(error?.message)}
errorText={error?.message}
helperText="Generated users will have the inline policy."
>
<TextArea
{...field}
reSize="none"
rows={3}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
</>
)}
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<Controller
control={control}
name="usernameTemplate"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Username Template"
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || undefined}
className="border-mineshaft-600 bg-mineshaft-900 text-sm"
/>
</FormControl>
)}
/>
)}
{credentialType !== DynamicSecretAwsIamCredentialType.TemporaryCredentials && (
<MetadataForm control={control} name="inputs.tags" title="Tags" isValueRequired />
)}
</div>
</div>
<div className="mt-4 flex items-center space-x-4">