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 601a5da41..08686ffc4 100644 --- a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -31,8 +31,6 @@ import { compileUsernameTemplate } from "./templateUtils"; // AWS STS duration constants (in seconds) const AWS_STS_MIN_DURATION = 900; -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); @@ -229,8 +227,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => { let stsClient: STSClient; let entityId: string; - const currentTime = Math.floor(Date.now() / 1000); - const requestedDuration = expireAt - currentTime; + const currentTime = Date.now(); + const requestedDuration = Math.floor((expireAt - currentTime) / 1000); if (requestedDuration <= 0) { throw new BadRequestError({ message: "Expiration time must be in the future" }); @@ -239,8 +237,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { 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); + durationSeconds = requestedDuration; const appCfg = getConfig(); stsClient = new STSClient({ region: providerInputs.region, @@ -259,7 +256,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { new AssumeRoleCommand({ RoleArn: providerInputs.roleArn, RoleSessionName: `infisical-temp-cred-${crypto.nativeCrypto.randomUUID()}`, - DurationSeconds: Math.max(durationSeconds, AWS_STS_MIN_DURATION), + DurationSeconds: durationSeconds, ExternalId: metadata.projectId }) ); @@ -283,8 +280,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { }; } if (providerInputs.method === AwsIamAuthType.AccessKey) { - // GetSessionToken supports longer durations - durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN); + durationSeconds = requestedDuration; stsClient = new STSClient({ region: providerInputs.region, useFipsEndpoint: crypto.isFipsModeEnabled(), @@ -297,7 +293,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { const sessionTokenRes = await stsClient.send( new GetSessionTokenCommand({ - DurationSeconds: Math.max(durationSeconds, AWS_STS_MIN_DURATION) + DurationSeconds: durationSeconds }) ); @@ -320,8 +316,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { }; } if (providerInputs.method === AwsIamAuthType.IRSA) { - // GetSessionToken supports longer durations - durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN); + durationSeconds = requestedDuration; stsClient = new STSClient({ region: providerInputs.region, useFipsEndpoint: crypto.isFipsModeEnabled(), @@ -330,7 +325,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => { const sessionTokenRes = await stsClient.send( new GetSessionTokenCommand({ - DurationSeconds: Math.max(durationSeconds, AWS_STS_MIN_DURATION) + DurationSeconds: durationSeconds }) ); @@ -364,8 +359,18 @@ export const AwsIamProvider = (): TDynamicProviderFns => { if (providerInputs.method === AwsIamAuthType.AssumeRole) { sensitiveTokens.push(providerInputs.roleArn); } + + let errorMessage = (err as Error)?.message || "Unknown error"; + + if (err && typeof err === "object" && "name" in err && "$metadata" in err) { + const awsError = err as { name?: string; message?: string; $metadata?: object }; + if (awsError.name) { + errorMessage = `${awsError.name}: ${errorMessage}`; + } + } + const sanitizedErrorMessage = sanitizeString({ - unsanitizedString: (err as Error)?.message, + unsanitizedString: errorMessage, tokens: sensitiveTokens }); throw new BadRequestError({ 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 f1c72b580..616010f93 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 @@ -25,74 +25,85 @@ 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 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 valMs = ms(val); if (valMs < 60 * 1000) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); @@ -100,34 +111,10 @@ const formSchema = z 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 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"] - } - ); + 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() +}); type TForm = z.infer; type Props = { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx index 2354a2731..a443a8f51 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx @@ -16,94 +16,82 @@ 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), - 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 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" }); + }), + 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" }); - }), - 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"] - } - ); + }) + .nullable(), + newName: slugSchema().optional(), + usernameTemplate: z.string().trim().nullable().optional() +}); + type TForm = z.infer; type Props = {