Address PR comments

This commit is contained in:
Carlos Monastyrski
2025-09-05 10:48:02 -03:00
parent 392b72bdbd
commit 34e9d68700
3 changed files with 172 additions and 192 deletions
@@ -31,8 +31,6 @@ import { compileUsernameTemplate } from "./templateUtils";
// AWS STS duration constants (in seconds) // AWS STS duration constants (in seconds)
const AWS_STS_MIN_DURATION = 900; 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 generateUsername = (usernameTemplate?: string | null, identity?: { name: string }) => {
const randomUsername = alphaNumericNanoId(32); const randomUsername = alphaNumericNanoId(32);
@@ -229,8 +227,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
let stsClient: STSClient; let stsClient: STSClient;
let entityId: string; let entityId: string;
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Date.now();
const requestedDuration = expireAt - currentTime; const requestedDuration = Math.floor((expireAt - currentTime) / 1000);
if (requestedDuration <= 0) { if (requestedDuration <= 0) {
throw new BadRequestError({ message: "Expiration time must be in the future" }); throw new BadRequestError({ message: "Expiration time must be in the future" });
@@ -239,8 +237,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
let durationSeconds: number; let durationSeconds: number;
if (providerInputs.method === AwsIamAuthType.AssumeRole) { if (providerInputs.method === AwsIamAuthType.AssumeRole) {
// AssumeRole has a lower maximum duration when using temporary credentials durationSeconds = requestedDuration;
durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_ASSUME_ROLE);
const appCfg = getConfig(); const appCfg = getConfig();
stsClient = new STSClient({ stsClient = new STSClient({
region: providerInputs.region, region: providerInputs.region,
@@ -259,7 +256,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
new AssumeRoleCommand({ new AssumeRoleCommand({
RoleArn: providerInputs.roleArn, RoleArn: providerInputs.roleArn,
RoleSessionName: `infisical-temp-cred-${crypto.nativeCrypto.randomUUID()}`, RoleSessionName: `infisical-temp-cred-${crypto.nativeCrypto.randomUUID()}`,
DurationSeconds: Math.max(durationSeconds, AWS_STS_MIN_DURATION), DurationSeconds: durationSeconds,
ExternalId: metadata.projectId ExternalId: metadata.projectId
}) })
); );
@@ -283,8 +280,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
}; };
} }
if (providerInputs.method === AwsIamAuthType.AccessKey) { if (providerInputs.method === AwsIamAuthType.AccessKey) {
// GetSessionToken supports longer durations durationSeconds = requestedDuration;
durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN);
stsClient = new STSClient({ stsClient = new STSClient({
region: providerInputs.region, region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(), useFipsEndpoint: crypto.isFipsModeEnabled(),
@@ -297,7 +293,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
const sessionTokenRes = await stsClient.send( const sessionTokenRes = await stsClient.send(
new GetSessionTokenCommand({ 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) { if (providerInputs.method === AwsIamAuthType.IRSA) {
// GetSessionToken supports longer durations durationSeconds = requestedDuration;
durationSeconds = Math.min(requestedDuration, AWS_STS_MAX_DURATION_SESSION_TOKEN);
stsClient = new STSClient({ stsClient = new STSClient({
region: providerInputs.region, region: providerInputs.region,
useFipsEndpoint: crypto.isFipsModeEnabled(), useFipsEndpoint: crypto.isFipsModeEnabled(),
@@ -330,7 +325,7 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
const sessionTokenRes = await stsClient.send( const sessionTokenRes = await stsClient.send(
new GetSessionTokenCommand({ 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) { if (providerInputs.method === AwsIamAuthType.AssumeRole) {
sensitiveTokens.push(providerInputs.roleArn); 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({ const sanitizedErrorMessage = sanitizeString({
unsanitizedString: (err as Error)?.message, unsanitizedString: errorMessage,
tokens: sensitiveTokens tokens: sensitiveTokens
}); });
throw new BadRequestError({ throw new BadRequestError({
@@ -25,74 +25,85 @@ import { WorkspaceEnv } from "@app/hooks/api/types";
import { MetadataForm } from "../../DynamicSecretListView/MetadataForm"; import { MetadataForm } from "../../DynamicSecretListView/MetadataForm";
const formSchema = z const formSchema = z.object({
.object({ provider: z.discriminatedUnion("method", [
provider: z.discriminatedUnion("method", [ z.object({
z.object({ method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
method: z.literal(DynamicSecretAwsIamAuth.AccessKey), credentialType: z
credentialType: z .nativeEnum(DynamicSecretAwsIamCredentialType)
.nativeEnum(DynamicSecretAwsIamCredentialType) .default(DynamicSecretAwsIamCredentialType.IamUser),
.default(DynamicSecretAwsIamCredentialType.IamUser), accessKey: z.string().trim().min(1),
accessKey: z.string().trim().min(1), secretAccessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1), region: z.string().trim().min(1),
region: z.string().trim().min(1), awsPath: z.string().trim().optional(),
awsPath: z.string().trim().optional(), 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
tags: z .array(
.array( z.object({
z.object({ key: z.string().trim().min(1).max(128),
key: z.string().trim().min(1).max(128), value: z.string().trim().min(1).max(256)
value: z.string().trim().min(1).max(256) })
}) )
) .optional()
.optional() }),
}), z.object({
z.object({ method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole), credentialType: z
credentialType: z .nativeEnum(DynamicSecretAwsIamCredentialType)
.nativeEnum(DynamicSecretAwsIamCredentialType) .default(DynamicSecretAwsIamCredentialType.IamUser),
.default(DynamicSecretAwsIamCredentialType.IamUser), roleArn: z.string().trim().min(1),
roleArn: z.string().trim().min(1), region: z.string().trim().min(1),
region: z.string().trim().min(1), awsPath: z.string().trim().optional(),
awsPath: z.string().trim().optional(), 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
tags: z .array(
.array( z.object({
z.object({ key: z.string().trim().min(1).max(128),
key: z.string().trim().min(1).max(128), value: z.string().trim().min(1).max(256)
value: z.string().trim().min(1).max(256) })
}) )
) .optional()
.optional() }),
}), z.object({
z.object({ method: z.literal(DynamicSecretAwsIamAuth.IRSA),
method: z.literal(DynamicSecretAwsIamAuth.IRSA), credentialType: z
credentialType: z .nativeEnum(DynamicSecretAwsIamCredentialType)
.nativeEnum(DynamicSecretAwsIamCredentialType) .default(DynamicSecretAwsIamCredentialType.IamUser),
.default(DynamicSecretAwsIamCredentialType.IamUser), region: z.string().trim().min(1),
region: z.string().trim().min(1), awsPath: z.string().trim().optional(),
awsPath: z.string().trim().optional(), 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
tags: z .array(
.array( z.object({
z.object({ key: z.string().trim().min(1).max(128),
key: z.string().trim().min(1).max(128), value: z.string().trim().min(1).max(256)
value: z.string().trim().min(1).max(256) })
}) )
) .optional()
.optional() })
}) ]),
]), defaultTTL: z.string().superRefine((val, ctx) => {
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); const valMs = ms(val);
if (valMs < 60 * 1000) if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); 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) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
maxTTL: z name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
.string() environment: z.object({ name: z.string(), slug: z.string() }),
.optional() usernameTemplate: z.string().nullable().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 TForm = z.infer<typeof formSchema>;
type Props = { type Props = {
@@ -16,94 +16,82 @@ import { slugSchema } from "@app/lib/schemas";
import { MetadataForm } from "../MetadataForm"; import { MetadataForm } from "../MetadataForm";
const formSchema = z const formSchema = z.object({
.object({ inputs: z.discriminatedUnion("method", [
inputs: z.discriminatedUnion("method", [ z.object({
z.object({ method: z.literal(DynamicSecretAwsIamAuth.AccessKey),
method: z.literal(DynamicSecretAwsIamAuth.AccessKey), credentialType: z
credentialType: z .nativeEnum(DynamicSecretAwsIamCredentialType)
.nativeEnum(DynamicSecretAwsIamCredentialType) .default(DynamicSecretAwsIamCredentialType.IamUser),
.default(DynamicSecretAwsIamCredentialType.IamUser), accessKey: z.string().trim().min(1),
accessKey: z.string().trim().min(1), secretAccessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1), region: z.string().trim().min(1),
region: z.string().trim().min(1), awsPath: z.string().trim().optional(),
awsPath: z.string().trim().optional(), 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
tags: z .array(z.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) }))
.array(z.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) })) .optional()
.optional() }),
}), z.object({
z.object({ method: z.literal(DynamicSecretAwsIamAuth.AssumeRole),
method: z.literal(DynamicSecretAwsIamAuth.AssumeRole), credentialType: z
credentialType: z .nativeEnum(DynamicSecretAwsIamCredentialType)
.nativeEnum(DynamicSecretAwsIamCredentialType) .default(DynamicSecretAwsIamCredentialType.IamUser),
.default(DynamicSecretAwsIamCredentialType.IamUser), roleArn: z.string().trim().min(1),
roleArn: z.string().trim().min(1), region: z.string().trim().min(1),
region: z.string().trim().min(1), awsPath: z.string().trim().optional(),
awsPath: z.string().trim().optional(), 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
tags: z .array(z.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) }))
.array(z.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) })) .optional()
.optional() }),
}), z.object({
z.object({ method: z.literal(DynamicSecretAwsIamAuth.IRSA),
method: z.literal(DynamicSecretAwsIamAuth.IRSA), credentialType: z
credentialType: z .nativeEnum(DynamicSecretAwsIamCredentialType)
.nativeEnum(DynamicSecretAwsIamCredentialType) .default(DynamicSecretAwsIamCredentialType.IamUser),
.default(DynamicSecretAwsIamCredentialType.IamUser), region: z.string().trim().min(1),
region: z.string().trim().min(1), awsPath: z.string().trim().optional(),
awsPath: z.string().trim().optional(), 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
tags: z .array(z.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) }))
.array(z.object({ key: z.string().trim().min(1), value: z.string().trim().min(1) })) .optional()
.optional() })
}) ]),
]), defaultTTL: z.string().superRefine((val, ctx) => {
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); const valMs = ms(val);
if (valMs < 60 * 1000) if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day // a day
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), })
maxTTL: z .nullable(),
.string() newName: slugSchema().optional(),
.optional() usernameTemplate: z.string().trim().nullable().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 TForm = z.infer<typeof formSchema>;
type Props = { type Props = {