Merge pull request #3470 from Infisical/feat/awsSecretRotationV2

feat(secret-rotation-v2): Add AWS IAM User Secret rotation
This commit is contained in:
carlosmonastyrski
2025-04-25 16:43:22 -03:00
committed by GitHub
55 changed files with 1036 additions and 169 deletions
@@ -0,0 +1,19 @@
import {
AwsIamUserSecretRotationGeneratedCredentialsSchema,
AwsIamUserSecretRotationSchema,
CreateAwsIamUserSecretRotationSchema,
UpdateAwsIamUserSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerAwsIamUserSecretRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.AwsIamUserSecret,
server,
responseSchema: AwsIamUserSecretRotationSchema,
createSchema: CreateAwsIamUserSecretRotationSchema,
updateSchema: UpdateAwsIamUserSecretRotationSchema,
generatedCredentialsSchema: AwsIamUserSecretRotationGeneratedCredentialsSchema
});
@@ -1,6 +1,7 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
@@ -12,5 +13,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
> = {
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter
};
@@ -2,6 +2,7 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
@@ -13,7 +14,8 @@ import { AuthMode } from "@app/services/auth/auth-type";
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationListItemSchema,
MsSqlCredentialsRotationListItemSchema,
Auth0ClientSecretRotationListItemSchema
Auth0ClientSecretRotationListItemSchema,
AwsIamUserSecretRotationListItemSchema
]);
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
@@ -0,0 +1,15 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
name: "AWS IAM User Secret",
type: SecretRotation.AwsIamUserSecret,
connection: AppConnection.AWS,
template: {
secretsMapping: {
accessKeyId: "AWS_ACCESS_KEY_ID",
secretAccessKey: "AWS_SECRET_ACCESS_KEY"
}
}
};
@@ -0,0 +1,123 @@
import AWS from "aws-sdk";
import {
TAwsIamUserSecretRotationGeneratedCredentials,
TAwsIamUserSecretRotationWithConnection
} from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret/aws-iam-user-secret-rotation-types";
import {
TRotationFactory,
TRotationFactoryGetSecretsPayload,
TRotationFactoryIssueCredentials,
TRotationFactoryRevokeCredentials,
TRotationFactoryRotateCredentials
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { getAwsConnectionConfig } from "@app/services/app-connection/aws";
const getCreateDate = (key: AWS.IAM.AccessKeyMetadata): number => {
return key.CreateDate ? new Date(key.CreateDate).getTime() : 0;
};
export const awsIamUserSecretRotationFactory: TRotationFactory<
TAwsIamUserSecretRotationWithConnection,
TAwsIamUserSecretRotationGeneratedCredentials
> = (secretRotation) => {
const {
parameters: { region, userName },
connection,
secretsMapping
} = secretRotation;
const $rotateClientSecret = async () => {
const { credentials } = await getAwsConnectionConfig(connection, region);
const iam = new AWS.IAM({ credentials });
const { AccessKeyMetadata } = await iam.listAccessKeys({ UserName: userName }).promise();
if (AccessKeyMetadata && AccessKeyMetadata.length > 0) {
// Sort keys by creation date (oldest first)
const sortedKeys = [...AccessKeyMetadata].sort((a, b) => getCreateDate(a) - getCreateDate(b));
// If we already have 2 keys, delete the oldest one
if (sortedKeys.length >= 2) {
const accessId = sortedKeys[0].AccessKeyId || sortedKeys[1].AccessKeyId;
if (accessId) {
await iam
.deleteAccessKey({
UserName: userName,
AccessKeyId: accessId
})
.promise();
}
}
}
const { AccessKey } = await iam.createAccessKey({ UserName: userName }).promise();
return {
accessKeyId: AccessKey.AccessKeyId,
secretAccessKey: AccessKey.SecretAccessKey
};
};
const issueCredentials: TRotationFactoryIssueCredentials<TAwsIamUserSecretRotationGeneratedCredentials> = async (
callback
) => {
const credentials = await $rotateClientSecret();
return callback(credentials);
};
const revokeCredentials: TRotationFactoryRevokeCredentials<TAwsIamUserSecretRotationGeneratedCredentials> = async (
generatedCredentials,
callback
) => {
const { credentials } = await getAwsConnectionConfig(connection, region);
const iam = new AWS.IAM({ credentials });
await Promise.all(
generatedCredentials.map((generatedCredential) =>
iam
.deleteAccessKey({
UserName: userName,
AccessKeyId: generatedCredential.accessKeyId
})
.promise()
)
);
return callback();
};
const rotateCredentials: TRotationFactoryRotateCredentials<TAwsIamUserSecretRotationGeneratedCredentials> = async (
_,
callback
) => {
const credentials = await $rotateClientSecret();
return callback(credentials);
};
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAwsIamUserSecretRotationGeneratedCredentials> = (
generatedCredentials
) => {
const secrets = [
{
key: secretsMapping.accessKeyId,
value: generatedCredentials.accessKeyId
},
{
key: secretsMapping.secretAccessKey,
value: generatedCredentials.secretAccessKey
}
];
return secrets;
};
return {
issueCredentials,
revokeCredentials,
rotateCredentials,
getSecretsPayload
};
};
@@ -0,0 +1,68 @@
import { z } from "zod";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import {
BaseCreateSecretRotationSchema,
BaseSecretRotationSchema,
BaseUpdateSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
import { SecretRotations } from "@app/lib/api-docs";
import { SecretNameSchema } from "@app/server/lib/schemas";
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
export const AwsIamUserSecretRotationGeneratedCredentialsSchema = z
.object({
accessKeyId: z.string(),
secretAccessKey: z.string()
})
.array()
.min(1)
.max(2);
const AwsIamUserSecretRotationParametersSchema = z.object({
userName: z
.string()
.trim()
.min(1, "Client Name Required")
.describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.userName),
region: z.nativeEnum(AWSRegion).describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.region).optional()
});
const AwsIamUserSecretRotationSecretsMappingSchema = z.object({
accessKeyId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AWS_IAM_USER_SECRET.accessKeyId),
secretAccessKey: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AWS_IAM_USER_SECRET.secretAccessKey)
});
export const AwsIamUserSecretRotationTemplateSchema = z.object({
secretsMapping: z.object({
accessKeyId: z.string(),
secretAccessKey: z.string()
})
});
export const AwsIamUserSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.AwsIamUserSecret).extend({
type: z.literal(SecretRotation.AwsIamUserSecret),
parameters: AwsIamUserSecretRotationParametersSchema,
secretsMapping: AwsIamUserSecretRotationSecretsMappingSchema
});
export const CreateAwsIamUserSecretRotationSchema = BaseCreateSecretRotationSchema(
SecretRotation.AwsIamUserSecret
).extend({
parameters: AwsIamUserSecretRotationParametersSchema,
secretsMapping: AwsIamUserSecretRotationSecretsMappingSchema
});
export const UpdateAwsIamUserSecretRotationSchema = BaseUpdateSecretRotationSchema(
SecretRotation.AwsIamUserSecret
).extend({
parameters: AwsIamUserSecretRotationParametersSchema.optional(),
secretsMapping: AwsIamUserSecretRotationSecretsMappingSchema.optional()
});
export const AwsIamUserSecretRotationListItemSchema = z.object({
name: z.literal("AWS IAM User Secret"),
connection: z.literal(AppConnection.AWS),
type: z.literal(SecretRotation.AwsIamUserSecret),
template: AwsIamUserSecretRotationTemplateSchema
});
@@ -0,0 +1,24 @@
import { z } from "zod";
import { TAwsConnection } from "@app/services/app-connection/aws";
import {
AwsIamUserSecretRotationGeneratedCredentialsSchema,
AwsIamUserSecretRotationListItemSchema,
AwsIamUserSecretRotationSchema,
CreateAwsIamUserSecretRotationSchema
} from "./aws-iam-user-secret-rotation-schemas";
export type TAwsIamUserSecretRotation = z.infer<typeof AwsIamUserSecretRotationSchema>;
export type TAwsIamUserSecretRotationInput = z.infer<typeof CreateAwsIamUserSecretRotationSchema>;
export type TAwsIamUserSecretRotationListItem = z.infer<typeof AwsIamUserSecretRotationListItemSchema>;
export type TAwsIamUserSecretRotationWithConnection = TAwsIamUserSecretRotation & {
connection: TAwsConnection;
};
export type TAwsIamUserSecretRotationGeneratedCredentials = z.infer<
typeof AwsIamUserSecretRotationGeneratedCredentialsSchema
>;
@@ -0,0 +1,3 @@
export * from "./aws-iam-user-secret-rotation-constants";
export * from "./aws-iam-user-secret-rotation-schemas";
export * from "./aws-iam-user-secret-rotation-types";
@@ -1,7 +1,8 @@
export enum SecretRotation {
PostgresCredentials = "postgres-credentials",
MsSqlCredentials = "mssql-credentials",
Auth0ClientSecret = "auth0-client-secret"
Auth0ClientSecret = "auth0-client-secret",
AwsIamUserSecret = "aws-iam-user-secret"
}
export enum SecretRotationStatus {
@@ -4,6 +4,7 @@ import { getConfig } from "@app/lib/config/env";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
@@ -18,7 +19,8 @@ import {
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION
};
export const listSecretRotationOptions = () => {
@@ -3,12 +3,14 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials",
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret"
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
};
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
};
@@ -77,6 +77,7 @@ import {
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
export type TSecretRotationV2ServiceFactoryDep = {
@@ -114,7 +115,8 @@ type TRotationFactoryImplementation = TRotationFactory<
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation
};
export const secretRotationV2ServiceFactory = ({
@@ -12,6 +12,13 @@ import {
TAuth0ClientSecretRotationListItem,
TAuth0ClientSecretRotationWithConnection
} from "./auth0-client-secret";
import {
TAwsIamUserSecretRotation,
TAwsIamUserSecretRotationGeneratedCredentials,
TAwsIamUserSecretRotationInput,
TAwsIamUserSecretRotationListItem,
TAwsIamUserSecretRotationWithConnection
} from "./aws-iam-user-secret";
import {
TMsSqlCredentialsRotation,
TMsSqlCredentialsRotationInput,
@@ -27,26 +34,34 @@ import {
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
import { SecretRotation } from "./secret-rotation-v2-enums";
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation | TAuth0ClientSecretRotation;
export type TSecretRotationV2 =
| TPostgresCredentialsRotation
| TMsSqlCredentialsRotation
| TAuth0ClientSecretRotation
| TAwsIamUserSecretRotation;
export type TSecretRotationV2WithConnection =
| TPostgresCredentialsRotationWithConnection
| TMsSqlCredentialsRotationWithConnection
| TAuth0ClientSecretRotationWithConnection;
| TAuth0ClientSecretRotationWithConnection
| TAwsIamUserSecretRotationWithConnection;
export type TSecretRotationV2GeneratedCredentials =
| TSqlCredentialsRotationGeneratedCredentials
| TAuth0ClientSecretRotationGeneratedCredentials;
| TAuth0ClientSecretRotationGeneratedCredentials
| TAwsIamUserSecretRotationGeneratedCredentials;
export type TSecretRotationV2Input =
| TPostgresCredentialsRotationInput
| TMsSqlCredentialsRotationInput
| TAuth0ClientSecretRotationInput;
| TAuth0ClientSecretRotationInput
| TAwsIamUserSecretRotationInput;
export type TSecretRotationV2ListItem =
| TPostgresCredentialsRotationListItem
| TMsSqlCredentialsRotationListItem
| TAuth0ClientSecretRotationListItem;
| TAuth0ClientSecretRotationListItem
| TAwsIamUserSecretRotationListItem;
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
@@ -4,8 +4,11 @@ import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
PostgresCredentialsRotationSchema,
MsSqlCredentialsRotationSchema,
Auth0ClientSecretRotationSchema
Auth0ClientSecretRotationSchema,
AwsIamUserSecretRotationSchema
]);