PR fix suggestions for aws secret rotations

This commit is contained in:
carlosmonastyrski
2025-04-22 17:40:15 -03:00
parent b85809293c
commit 5819b8c576
15 changed files with 66 additions and 48 deletions
@@ -8,7 +8,7 @@ export const AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem
connection: AppConnection.AWS, connection: AppConnection.AWS,
template: { template: {
secretsMapping: { secretsMapping: {
accessKeyId: "AWS_ACCESS_KEY", accessKeyId: "AWS_ACCESS_KEY_ID",
secretAccessKey: "AWS_SECRET_ACCESS_KEY" secretAccessKey: "AWS_SECRET_ACCESS_KEY"
} }
} }
@@ -18,7 +18,7 @@ export const awsIamUserSecretRotationFactory: TRotationFactory<
TAwsIamUserSecretRotationGeneratedCredentials TAwsIamUserSecretRotationGeneratedCredentials
> = (secretRotation) => { > = (secretRotation) => {
const { const {
parameters: { region, clientName }, parameters: { region, userName },
connection, connection,
secretsMapping secretsMapping
} = secretRotation; } = secretRotation;
@@ -27,26 +27,29 @@ export const awsIamUserSecretRotationFactory: TRotationFactory<
const { credentials } = await getAwsConnectionConfig(connection, region); const { credentials } = await getAwsConnectionConfig(connection, region);
const iam = new AWS.IAM({ credentials }); const iam = new AWS.IAM({ credentials });
const { AccessKeyMetadata } = await iam.listAccessKeys({ UserName: clientName }).promise(); const { AccessKeyMetadata } = await iam.listAccessKeys({ UserName: userName }).promise();
if (AccessKeyMetadata && AccessKeyMetadata.length > 0) { if (AccessKeyMetadata && AccessKeyMetadata.length > 0) {
for (const key of AccessKeyMetadata) { // Delete inactive keys
if (key.Status === "Inactive" && key.AccessKeyId) { await Promise.all(
// eslint-disable-next-line no-await-in-loop AccessKeyMetadata.map((key) => {
await iam if (key.Status === "Inactive" && key.AccessKeyId) {
.deleteAccessKey({ return iam
UserName: clientName, .deleteAccessKey({
AccessKeyId: key.AccessKeyId UserName: userName,
}) AccessKeyId: key.AccessKeyId
.promise(); })
} .promise();
} }
return Promise.resolve();
})
);
const activeKey = AccessKeyMetadata.find((k) => k.Status === "Active"); const activeKey = AccessKeyMetadata.find((k) => k.Status === "Active");
if (activeKey && activeKey.AccessKeyId) { if (activeKey && activeKey.AccessKeyId) {
await iam await iam
.updateAccessKey({ .updateAccessKey({
UserName: clientName, UserName: userName,
AccessKeyId: activeKey.AccessKeyId, AccessKeyId: activeKey.AccessKeyId,
Status: "Inactive" Status: "Inactive"
}) })
@@ -54,7 +57,7 @@ export const awsIamUserSecretRotationFactory: TRotationFactory<
} }
} }
const { AccessKey } = await iam.createAccessKey({ UserName: clientName }).promise(); const { AccessKey } = await iam.createAccessKey({ UserName: userName }).promise();
return { return {
accessKeyId: AccessKey.AccessKeyId, accessKeyId: AccessKey.AccessKeyId,
@@ -77,15 +80,16 @@ export const awsIamUserSecretRotationFactory: TRotationFactory<
const { credentials } = await getAwsConnectionConfig(connection, region); const { credentials } = await getAwsConnectionConfig(connection, region);
const iam = new AWS.IAM({ credentials }); const iam = new AWS.IAM({ credentials });
for (const generatedCredential of generatedCredentials) { await Promise.all(
// eslint-disable-next-line no-await-in-loop generatedCredentials.map((generatedCredential) =>
await iam iam
.deleteAccessKey({ .deleteAccessKey({
UserName: clientName, UserName: userName,
AccessKeyId: generatedCredential.accessKeyId AccessKeyId: generatedCredential.accessKeyId
}) })
.promise(); .promise()
} )
);
return callback(); return callback();
}; };
@@ -20,11 +20,11 @@ export const AwsIamUserSecretRotationGeneratedCredentialsSchema = z
.max(2); .max(2);
const AwsIamUserSecretRotationParametersSchema = z.object({ const AwsIamUserSecretRotationParametersSchema = z.object({
clientName: z userName: z
.string() .string()
.trim() .trim()
.min(1, "Client Name Required") .min(1, "Client Name Required")
.describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.clientName), .describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.userName),
region: z.nativeEnum(AWSRegion).describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.region) region: z.nativeEnum(AWSRegion).describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.region)
}); });
@@ -3,7 +3,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = { export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials", [SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials", [SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret", [SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret" [SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
}; };
+1 -1
View File
@@ -2017,7 +2017,7 @@ export const SecretRotations = {
clientId: "The client ID of the Auth0 Application to rotate the client secret for." clientId: "The client ID of the Auth0 Application to rotate the client secret for."
}, },
AWS_IAM_USER_SECRET: { AWS_IAM_USER_SECRET: {
clientName: "The name of the client to rotate credentials for.", userName: "The name of the client to rotate credentials for.",
region: "The AWS region to rotate credentials for." region: "The AWS region to rotate credentials for."
} }
}, },
@@ -69,7 +69,17 @@ export const registerAwsConnectionRouter = async (server: FastifyZodProvider) =>
schema: { schema: {
params: z.object({ params: z.object({
connectionId: z.string().uuid() connectionId: z.string().uuid()
}) }),
response: {
200: z.object({
iamUsers: z
.object({
UserName: z.string(),
Arn: z.string()
})
.array()
})
}
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
@@ -78,9 +78,16 @@ const listAwsIamUsers = async (appConnection: TAwsConnection) => {
const iam = new AWS.IAM({ credentials }); const iam = new AWS.IAM({ credentials });
const users = await iam.listUsers().promise(); const userEntries: AWS.IAM.User[] = [];
let userMarker: string | undefined;
do {
// eslint-disable-next-line no-await-in-loop
const response = await iam.listUsers({ MaxItems: 100, Marker: userMarker }).promise();
userEntries.push(...(response.Users || []));
userMarker = response.Marker;
} while (userMarker);
return users.Users; return userEntries;
}; };
export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) => { export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
@@ -114,7 +114,7 @@ In the following steps, we explore the end-to-end workflow for setting up this s
"minutes": 29.5 "minutes": 29.5
}, },
"parameters": { "parameters": {
"clientName": "<string>", "userName": "<string>",
"region": "us-east-1" "region": "us-east-1"
}, },
"secretsMapping": { "secretsMapping": {
@@ -171,7 +171,7 @@ In the following steps, we explore the end-to-end workflow for setting up this s
"lastRotationMessage": "<string>", "lastRotationMessage": "<string>",
"type": "aws-iam-user-secret", "type": "aws-iam-user-secret",
"parameters": { "parameters": {
"clientName": "<string>", "userName": "<string>",
"region": "us-east-1" "region": "us-east-1"
} }
} }
@@ -25,7 +25,7 @@ export const AwsIamUserSecretRotationParametersFields = () => {
return ( return (
<> <>
<Controller <Controller
name="parameters.clientName" name="parameters.userName"
control={control} control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl <FormControl
@@ -70,13 +70,13 @@ export const AwsIamUserSecretRotationParametersFields = () => {
)} )}
/> />
<Controller <Controller
control={control}
name="parameters.region"
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message} label="Region"> <FormControl isError={Boolean(error)} errorText={error?.message} label="Region">
<AwsRegionSelect value={value} onChange={onChange} /> <AwsRegionSelect value={value} onChange={onChange} />
</FormControl> </FormControl>
)} )}
control={control}
name="parameters.region"
/> />
</> </>
); );
@@ -19,11 +19,11 @@ export const AwsIamUserSecretRotationReviewFields = () => {
<> <>
<SecretRotationReviewSection label="Parameters"> <SecretRotationReviewSection label="Parameters">
<GenericFieldLabel label="Region">{parameters.region}</GenericFieldLabel> <GenericFieldLabel label="Region">{parameters.region}</GenericFieldLabel>
<GenericFieldLabel label="User Name">{parameters.clientName}</GenericFieldLabel> <GenericFieldLabel label="User Name">{parameters.userName}</GenericFieldLabel>
</SecretRotationReviewSection> </SecretRotationReviewSection>
<SecretRotationReviewSection label="Secrets Mapping"> <SecretRotationReviewSection label="Secrets Mapping">
<GenericFieldLabel label="Access Key ID">{accessKeyId}</GenericFieldLabel> <GenericFieldLabel label="Secret Access Key ID">{accessKeyId}</GenericFieldLabel>
<GenericFieldLabel label="Access Key Secret">{secretAccessKey}</GenericFieldLabel> <GenericFieldLabel label="Secret Access Key">{secretAccessKey}</GenericFieldLabel>
</SecretRotationReviewSection> </SecretRotationReviewSection>
</> </>
); );
@@ -35,7 +35,7 @@ export const AwsIamUserSecretRotationSecretsMappingFields = () => {
) )
}, },
{ {
name: "Client Secret", name: "Secret Access Key",
input: ( input: (
<Controller <Controller
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
@@ -7,7 +7,7 @@ export const AwsIamUserSecretRotationSchema = z
.object({ .object({
type: z.literal(SecretRotation.AwsIamUserSecret), type: z.literal(SecretRotation.AwsIamUserSecret),
parameters: z.object({ parameters: z.object({
clientName: z.string().trim().min(1, "Client Name required"), userName: z.string().trim().min(1, "User Name required"),
region: z.string().trim().min(1, "Region required") region: z.string().trim().min(1, "Region required")
}), }),
secretsMapping: z.object({ secretsMapping: z.object({
@@ -28,7 +28,4 @@ export type TAwsConnectionListIamUsersResponse = {
iamUsers: TAwsConnectionIamUser[]; iamUsers: TAwsConnectionIamUser[];
}; };
export type TAwsIamUserSecret = { export type TAwsIamUserSecret = TAwsConnectionIamUser;
arn: string;
UserName: string;
};
@@ -9,7 +9,7 @@ export type TAwsIamUserSecretRotation = TSecretRotationV2Base & {
type: SecretRotation.AwsIamUserSecret; type: SecretRotation.AwsIamUserSecret;
parameters: { parameters: {
region: string; region: string;
clientName: string; userName: string;
}; };
secretsMapping: { secretsMapping: {
accessKeyId: string; accessKeyId: string;
@@ -8,7 +8,7 @@ import {
TAwsIamUserSecretRotation, TAwsIamUserSecretRotation,
TAwsIamUserSecretRotationGeneratedCredentialsResponse, TAwsIamUserSecretRotationGeneratedCredentialsResponse,
TAwsIamUserSecretRotationOption TAwsIamUserSecretRotationOption
} from "@app/hooks/api/secretRotationsV2/types/aws-iam-access-key-rotation"; } from "@app/hooks/api/secretRotationsV2/types/aws-iam-user-secret-rotation";
import { import {
TMsSqlCredentialsRotation, TMsSqlCredentialsRotation,
TMsSqlCredentialsRotationGeneratedCredentialsResponse TMsSqlCredentialsRotationGeneratedCredentialsResponse