Add AWS secret rotation V2
@@ -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",
|
||||
secretAccessKey: "AWS_SECRET_ACCESS_KEY"
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
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";
|
||||
|
||||
export const awsIamUserSecretRotationFactory: TRotationFactory<
|
||||
TAwsIamUserSecretRotationWithConnection,
|
||||
TAwsIamUserSecretRotationGeneratedCredentials
|
||||
> = (secretRotation) => {
|
||||
const {
|
||||
parameters: { region, clientName },
|
||||
connection,
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
const $rotateClientSecret = async () => {
|
||||
const { credentials } = await getAwsConnectionConfig(connection, region);
|
||||
const iam = new AWS.IAM({ credentials });
|
||||
|
||||
const { AccessKeyMetadata } = await iam.listAccessKeys({ UserName: clientName }).promise();
|
||||
|
||||
if (AccessKeyMetadata && AccessKeyMetadata.length > 0) {
|
||||
for (const key of AccessKeyMetadata) {
|
||||
if (key.Status === "Inactive" && key.AccessKeyId) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await iam
|
||||
.deleteAccessKey({
|
||||
UserName: clientName,
|
||||
AccessKeyId: key.AccessKeyId
|
||||
})
|
||||
.promise();
|
||||
}
|
||||
}
|
||||
|
||||
const activeKey = AccessKeyMetadata.find((k) => k.Status === "Active");
|
||||
if (activeKey && activeKey.AccessKeyId) {
|
||||
await iam
|
||||
.updateAccessKey({
|
||||
UserName: clientName,
|
||||
AccessKeyId: activeKey.AccessKeyId,
|
||||
Status: "Inactive"
|
||||
})
|
||||
.promise();
|
||||
}
|
||||
}
|
||||
|
||||
const { AccessKey } = await iam.createAccessKey({ UserName: clientName }).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 });
|
||||
|
||||
for (const generatedCredential of generatedCredentials) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await iam
|
||||
.deleteAccessKey({
|
||||
UserName: clientName,
|
||||
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({
|
||||
clientName: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Client Name Required")
|
||||
.describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.clientName),
|
||||
region: z.nativeEnum(AWSRegion).describe(SecretRotations.PARAMETERS.AWS_IAM_USER_SECRET.region)
|
||||
});
|
||||
|
||||
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 = () => {
|
||||
|
||||
@@ -4,11 +4,13 @@ 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.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
|
||||
]);
|
||||
|
||||
@@ -2015,6 +2015,10 @@ export const SecretRotations = {
|
||||
},
|
||||
AUTH0_CLIENT_SECRET: {
|
||||
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
||||
},
|
||||
AWS_IAM_USER_SECRET: {
|
||||
clientName: "The name of the client to rotate credentials for.",
|
||||
region: "The AWS region to rotate credentials for."
|
||||
}
|
||||
},
|
||||
SECRETS_MAPPING: {
|
||||
@@ -2025,6 +2029,10 @@ export const SecretRotations = {
|
||||
AUTH0_CLIENT_SECRET: {
|
||||
clientId: "The name of the secret that the client ID will be mapped to.",
|
||||
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
|
||||
},
|
||||
AWS_IAM_USER_SECRET: {
|
||||
accessKeyId: "The name of the secret that the access key ID will be mapped to.",
|
||||
secretAccessKey: "The name of the secret that the rotated secret access key will be mapped to."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,4 +59,30 @@ export const registerAwsConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
return { kmsKeys };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: `/:connectionId/users`,
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
connectionId: z.string().uuid()
|
||||
})
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { connectionId } = req.params;
|
||||
|
||||
const iamUsers = await server.services.appConnection.aws.listIamUsers(
|
||||
{
|
||||
connectionId
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
return { iamUsers };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -168,6 +168,10 @@ export type TListAwsConnectionKmsKeys = {
|
||||
destination: SecretSync.AWSParameterStore | SecretSync.AWSSecretsManager;
|
||||
};
|
||||
|
||||
export type TListAwsConnectionIamUsers = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type TAppConnectionCredentialsValidator = (
|
||||
appConnection: TAppConnectionConfig
|
||||
) => Promise<TAppConnection["credentials"]>;
|
||||
|
||||
@@ -2,7 +2,10 @@ import AWS from "aws-sdk";
|
||||
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { TListAwsConnectionKmsKeys } from "@app/services/app-connection/app-connection-types";
|
||||
import {
|
||||
TListAwsConnectionIamUsers,
|
||||
TListAwsConnectionKmsKeys
|
||||
} from "@app/services/app-connection/app-connection-types";
|
||||
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
||||
import { TAwsConnection } from "@app/services/app-connection/aws/aws-connection-types";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
@@ -70,6 +73,16 @@ const listAwsKmsKeys = async (
|
||||
return kmsKeys;
|
||||
};
|
||||
|
||||
const listAwsIamUsers = async (appConnection: TAwsConnection) => {
|
||||
const { credentials } = await getAwsConnectionConfig(appConnection);
|
||||
|
||||
const iam = new AWS.IAM({ credentials });
|
||||
|
||||
const users = await iam.listUsers().promise();
|
||||
|
||||
return users.Users;
|
||||
};
|
||||
|
||||
export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||
const listKmsKeys = async (
|
||||
{ connectionId, region, destination }: TListAwsConnectionKmsKeys,
|
||||
@@ -82,7 +95,16 @@ export const awsConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
|
||||
return kmsKeys;
|
||||
};
|
||||
|
||||
const listIamUsers = async ({ connectionId }: TListAwsConnectionIamUsers, actor: OrgServiceActor) => {
|
||||
const appConnection = await getAppConnection(AppConnection.AWS, connectionId, actor);
|
||||
|
||||
const iamUsers = await listAwsIamUsers(appConnection);
|
||||
|
||||
return iamUsers;
|
||||
};
|
||||
|
||||
return {
|
||||
listKmsKeys
|
||||
listKmsKeys,
|
||||
listIamUsers
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v2/secret-rotations/aws-iam-user-secret"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Check out the configuration docs for [AWS IAM User Secret Rotations](/documentation/platform/secret-rotation/aws-iam-user-secret) to learn how to obtain the
|
||||
required parameters.
|
||||
</Note>
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get by ID"
|
||||
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get by Name"
|
||||
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret/rotation-name/{rotationName}"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get Credentials by ID"
|
||||
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}/generated-credentials"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v2/secret-rotations/aws-iam-user-secret"
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Rotate Secrets"
|
||||
openapi: "POST /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}/rotate-secrets"
|
||||
---
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v2/secret-rotations/aws-iam-user-secret/{rotationId}"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Check out the configuration docs for [AWS IAM User Secret Rotations](/documentation/platform/secret-rotation/aws-iam-user-secret) to learn how to obtain the
|
||||
required parameters.
|
||||
</Note>
|
||||
@@ -6,14 +6,41 @@ description: "Learn how to automatically rotate Access Key Id and Secret Key of
|
||||
Infisical's AWS IAM User secret rotation capability lets you update the **Access key** and **Secret access key** credentials of a target IAM user from within Infisical
|
||||
at a specified interval or on-demand.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Create an [AWS Connection](/integrations/app-connections/aws) with the required **Secret Rotation** audience and permissions
|
||||
- Add the following permissions to your IAM Role/IAM User Permission policy set used by your AWS Connection:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"iam:ListAccessKeys",
|
||||
"iam:CreateAccessKey",
|
||||
"iam:UpdateAccessKey",
|
||||
"iam:DeleteAccessKey"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "iam:ListUsers",
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
The typical workflow for using the AWS IAM User rotation strategy consists of four steps:
|
||||
|
||||
1. Creating the target IAM user whose credentials you wish to rotate.
|
||||
2. Creating the managing IAM user used by Infisical to rotate the credentials of the target IAM user.
|
||||
3. Configuring the rotation strategy in Infisical with the credentials of the managing IAM user.
|
||||
4. Pressing the **Rotate** button in the Infisical dashboard to trigger the rotation of the target IAM user's credentials. The strategy can also be configured to rotate the credentials automatically at a specified interval.
|
||||
2. Configuring the rotation strategy in Infisical with the credentials of the managing IAM user.
|
||||
3. Pressing the **Rotate** button in the Infisical dashboard to trigger the rotation of the target IAM user's credentials. The strategy can also be configured to rotate the credentials automatically at a specified interval.
|
||||
|
||||
In the following steps, we explore the end-to-end workflow for setting up this strategy in Infisical.
|
||||
|
||||
@@ -22,122 +49,152 @@ In the following steps, we explore the end-to-end workflow for setting up this s
|
||||
To begin, create an IAM user whose credentials you wish to rotate. If you already have an IAM user,
|
||||
then you can skip this step.
|
||||
</Step>
|
||||
<Step title="Create the managing IAM user">
|
||||
Next, create another IAM user to be used by Infisical to rotate the credentials of the IAM user in the previous step.
|
||||
|
||||
2.1. In your AWS console, head to IAM > Access management > Users and press **Create user**.
|
||||
|
||||

|
||||
|
||||
2.2. Next, give the user a username like **infisical-rotation-manager** and press **Next**.
|
||||
|
||||

|
||||
|
||||
2.3. Next, in the **Set permissions** step, select **Attach policies directly** and then press **Create policy**.
|
||||
|
||||

|
||||
|
||||
2.4. Next, in the **Policy editor**, paste the following JSON and press **Next**:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"iam:DeleteAccessKey",
|
||||
"iam:GetAccessKeyLastUsed",
|
||||
"iam:CreateAccessKey"
|
||||
],
|
||||
"Resource": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
The IAM policy above uses the wildcard option in Resource: "*".
|
||||
|
||||
You may want to restrict the policy to a specific path, and make any adjustments as necessary, to control access for the managing user in production.
|
||||
|
||||
Read more about this [here](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/).
|
||||
</Note>
|
||||
|
||||
In the **Review and create** step, give the policy a name like **infisical-rotation-manager**, press **Create policy** to finish creating the policy.
|
||||
|
||||

|
||||
|
||||
2.5. Back in the **Set permissions** step from step 2.3, refresh the policy list and search for the policy you just created from step 2.4.
|
||||
|
||||
Select the policy and press **Next**.
|
||||
|
||||

|
||||
|
||||
In the **Review and create** step, press **Create user** to finish creating the IAM user.
|
||||
|
||||

|
||||
|
||||
2.5. Having created the user, head to its Security credentials > Access keys and press **Create access key**.
|
||||
|
||||
Follow the subsequent steps to create the **access key** and **secret access key** credential pair for the user.
|
||||
|
||||

|
||||
|
||||
At the end of the flow, copy the **Access key** and **Secret access key** to use when configuring the AWS IAM User rotation strategy back in Infisical next.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Configure the AWS IAM User secret rotation strategy in Infisical">
|
||||
3.1. Back in Infisical, head to the Project > Secrets > Environment and path where you want the rotated AWS IAM credentials to appear and create two placeholder secrets.
|
||||
|
||||
In this example, we'll create two secrets called `AWS_ACCESS_KEY` and `AWS_SECRET_ACCESS_KEY`.
|
||||
|
||||

|
||||
|
||||
3.2. Next, in the **Secret Rotation** tab, press on the **AWS IAM** tile to configure the AWS IAM User rotation strategy.
|
||||
|
||||

|
||||
|
||||
3.3. Input the configuration details for the AWS IAM User rotation strategy obtained from steps 1 and 2:
|
||||
|
||||

|
||||
|
||||
Here's some guidance on each field:
|
||||
|
||||
- Manager User Access Key: The managing IAM user's access key from step 2.5.
|
||||
- Manager User Secret Key: The managing IAM user's secret access key from step 2.5.
|
||||
- Manager User AWS Region: The [AWS region](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) for Infisical to make requests to such as `us-east-1`.
|
||||
- IAM Username: The IAM username of the user from step 1.
|
||||
|
||||
Next, specify the output secret mappings configuration for the rotated AWS IAM credentials; this is the secrets whose values will be replaced with new credentials after each rotation.
|
||||
Here, you can also specify a rotation interval for the credentials to be automatically rotated periodically.
|
||||
<Tabs>
|
||||
<Tab title="Infisical UI">
|
||||
1. Navigate to your Secret Manager Project's Dashboard and select **Add Secret Rotation** from the actions dropdown.
|
||||

|
||||
|
||||
In this example, we want to map the output of the rotated AWS IAM credentials to the secrets that we created in step 3.1 (i.e. `AWS_ACCESS_KEY` and `AWS_SECRET_ACCESS_KEY`).
|
||||
|
||||

|
||||
|
||||
Finally, press **Submit** to create the secret rotation strategy.
|
||||
</Step>
|
||||
<Step title="Rotate secrets in Infisical">
|
||||
You should now see the AWS IAM User rotation strategy listed in the **Secret Rotation** tab.
|
||||
|
||||
To manually trigger a rotation, you can press the **Rotate** button on the strategy.
|
||||
Once triggered, the secrets in step 3.1 should be updated with new rotated credential values.
|
||||
|
||||

|
||||
2. Select the **AWS IAM User Secret** option.
|
||||

|
||||
|
||||
3. Select the **AWS Connection** to use and configure the rotation behavior. Then click **Next**.
|
||||

|
||||
|
||||
- **AWS Connection** - the connection that will perform the rotation of the specified application's Client Secret.
|
||||
- **Rotation Interval** - the interval, in days, that once elapsed will trigger a rotation.
|
||||
- **Rotate At** - the local time of day when rotation should occur once the interval has elapsed.
|
||||
- **Auto-Rotation Enabled** - whether secrets should automatically be rotated once the rotation interval has elapsed. Disable this option to manually rotate secrets or pause secret rotation.
|
||||
|
||||
4. Select the AWS IAM user and the region of the user whose credentials you want to rotate. Then click **Next**.
|
||||

|
||||
|
||||
5. Specify the secret names that the client credentials should be mapped to. Then click **Next**.
|
||||

|
||||
|
||||
- **Client ID** - the name of the secret that the application Client ID will be mapped to.
|
||||
- **Client Secret** - the name of the secret that the rotated Client Secret will be mapped to.
|
||||
|
||||
6. Give your rotation a name and description (optional). Then click **Next**.
|
||||

|
||||
|
||||
- **Name** - the name of the secret rotation configuration. Must be slug-friendly.
|
||||
- **Description** (optional) - a description of this rotation configuration.
|
||||
|
||||
7. Review your configuration, then click **Create Secret Rotation**.
|
||||

|
||||
|
||||
8. Your **AWS IAM User** credentials are now available for use via the mapped secrets.
|
||||

|
||||
</Tab>
|
||||
<Tab title="API">
|
||||
To create an AWS IAM User Rotation, make an API request to the [Create AWS IAM User Rotation](/api-reference/endpoints/secret-rotations/aws-iam-user-secret/create) API endpoint.
|
||||
|
||||
You will first need the **User Name** of the AWS IAM user you want to rotate the secret for. This can be obtained from the IAM console, on Users tab.
|
||||

|
||||
|
||||
|
||||
### Sample request
|
||||
|
||||
```bash Request
|
||||
curl --request POST \
|
||||
--url https://us.infisical.com/api/v2/secret-rotations/aws-iam-user-secret \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"name": "<string>",
|
||||
"projectId": "<string>",
|
||||
"description": "<string>",
|
||||
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"environment": "<string>",
|
||||
"secretPath": "<string>",
|
||||
"isAutoRotationEnabled": true,
|
||||
"rotationInterval": 2,
|
||||
"rotateAtUtc": {
|
||||
"hours": 11.5,
|
||||
"minutes": 29.5
|
||||
},
|
||||
"parameters": {
|
||||
"clientName": "<string>",
|
||||
"region": "us-east-1"
|
||||
},
|
||||
"secretsMapping": {
|
||||
"accessKeyId": "<string>",
|
||||
"secretAccessKey": "<string>"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Sample response
|
||||
|
||||
```bash Response
|
||||
{
|
||||
"secretRotation": {
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"name": "<string>",
|
||||
"description": "<string>",
|
||||
"secretsMapping": {
|
||||
"accessKeyId": "<string>",
|
||||
"secretAccessKey": "<string>"
|
||||
},
|
||||
"isAutoRotationEnabled": true,
|
||||
"activeIndex": 0,
|
||||
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||
"createdAt": "2023-11-07T05:31:56Z",
|
||||
"updatedAt": "2023-11-07T05:31:56Z",
|
||||
"rotationInterval": 123,
|
||||
"rotationStatus": "<string>",
|
||||
"lastRotationAttemptedAt": "2023-11-07T05:31:56Z",
|
||||
"lastRotatedAt": "2023-11-07T05:31:56Z",
|
||||
"lastRotationJobId": "<string>",
|
||||
"nextRotationAt": "2023-11-07T05:31:56Z",
|
||||
"isLastRotationManual": true,
|
||||
"connection": {
|
||||
"app": "aws",
|
||||
"name": "<string>",
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||
},
|
||||
"environment": {
|
||||
"slug": "<string>",
|
||||
"name": "<string>",
|
||||
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||
},
|
||||
"projectId": "<string>",
|
||||
"folder": {
|
||||
"id": "<string>",
|
||||
"path": "<string>"
|
||||
},
|
||||
"rotateAtUtc": {
|
||||
"hours": 11.5,
|
||||
"minutes": 29.5
|
||||
},
|
||||
"lastRotationMessage": "<string>",
|
||||
"type": "aws-iam-user-secret",
|
||||
"parameters": {
|
||||
"clientName": "<string>",
|
||||
"region": "us-east-1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
**FAQ**
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Why are my AWS IAM credentials not rotating?">
|
||||
There are a few reasons for why this might happen:
|
||||
|
||||
- The strategy configuration is invalid (e.g. the managing IAM user's credentials are incorrect, the target IAM username is incorrect, etc.).
|
||||
- The managing IAM user is insufficently permissioned to rotate the credentials of the target IAM user. For instance, you may have setup [paths](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) for the managing IAM user and the policy does not have the necessary permissions to rotate the credentials.
|
||||
- The target IAM user already has 2 access keys configured in AWS; you should delete one of the access keys to allow for rotation.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
<Accordion title="Why are my AWS IAM credentials not rotating?">
|
||||
There are a few reasons for why this might happen: - The strategy
|
||||
configuration is invalid (e.g. the managing IAM user's credentials are
|
||||
incorrect, the target AWS region is incorrect, etc.). - The managing IAM
|
||||
user is insufficently permissioned to rotate the credentials of the target
|
||||
IAM user. For instance, you may have setup
|
||||
[paths](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/)
|
||||
for the managing IAM user and the policy does not have the necessary
|
||||
permissions to rotate the credentials. - The target IAM user already has 2
|
||||
access keys configured in AWS; you should delete one of the access keys to
|
||||
allow for rotation.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
After Width: | Height: | Size: 622 KiB |
|
After Width: | Height: | Size: 637 KiB |
|
After Width: | Height: | Size: 999 KiB |
|
After Width: | Height: | Size: 590 KiB |
|
After Width: | Height: | Size: 582 KiB |
|
After Width: | Height: | Size: 584 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 338 KiB |
@@ -180,7 +180,8 @@
|
||||
"documentation/platform/secret-rotation/overview",
|
||||
"documentation/platform/secret-rotation/auth0-client-secret",
|
||||
"documentation/platform/secret-rotation/postgres-credentials",
|
||||
"documentation/platform/secret-rotation/mssql-credentials"
|
||||
"documentation/platform/secret-rotation/mssql-credentials",
|
||||
"documentation/platform/secret-rotation/aws-iam"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -861,6 +862,19 @@
|
||||
"api-reference/endpoints/secret-rotations/auth0-client-secret/update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "AWS IAM User Secret",
|
||||
"pages": [
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/create",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/delete",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/get-by-id",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/get-by-name",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/get-generated-credentials-by-id",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/list",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/rotate-secrets",
|
||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/update"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Microsoft SQL Server Credentials",
|
||||
"pages": [
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { SingleValue } from "react-select";
|
||||
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||
import { AwsRegionSelect } from "@app/components/secret-syncs/forms/SecretSyncDestinationFields/shared";
|
||||
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
|
||||
import { TAwsIamUserSecret, useListAwsConnectionIamUsers } from "@app/hooks/api/appConnections/aws";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
export const AwsIamUserSecretRotationParametersFields = () => {
|
||||
const { control, watch } = useFormContext<
|
||||
TSecretRotationV2Form & {
|
||||
type: SecretRotation.AwsIamUserSecret;
|
||||
}
|
||||
>();
|
||||
|
||||
const connectionId = watch("connection.id");
|
||||
|
||||
const { data: clients, isPending: isClientsPending } = useListAwsConnectionIamUsers({
|
||||
connectionId
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Controller
|
||||
name="parameters.clientName"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="IAM User"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<>
|
||||
Ensure that your connection has the correct permissions and the IAM user exists
|
||||
in the connection's audience.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the IAM user you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isClientsPending && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={clients?.find((client) => client.UserName === value) ?? ""}
|
||||
onChange={(option) => {
|
||||
onChange((option as SingleValue<TAwsIamUserSecret>)?.UserName ?? "");
|
||||
}}
|
||||
options={clients}
|
||||
placeholder="Select an IAM user..."
|
||||
getOptionLabel={(option) =>
|
||||
(option as SingleValue<TAwsIamUserSecret>)?.UserName ?? ""
|
||||
}
|
||||
getOptionValue={(option) =>
|
||||
(option as SingleValue<TAwsIamUserSecret>)?.UserName ?? ""
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Region">
|
||||
<AwsRegionSelect value={value} onChange={onChange} />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="parameters.region"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
import { TSecretRotationV2Form } from "../schemas";
|
||||
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
|
||||
import { AwsIamUserSecretRotationParametersFields } from "./AwsIamUserSecretRotationParametersFields";
|
||||
import { SqlCredentialsRotationParametersFields } from "./shared";
|
||||
|
||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
|
||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields,
|
||||
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationParametersFields
|
||||
};
|
||||
|
||||
export const SecretRotationV2ParametersFields = () => {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||
import { GenericFieldLabel } from "@app/components/v2";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
import { SecretRotationReviewSection } from "./shared";
|
||||
|
||||
export const AwsIamUserSecretRotationReviewFields = () => {
|
||||
const { watch } = useFormContext<
|
||||
TSecretRotationV2Form & {
|
||||
type: SecretRotation.AwsIamUserSecret;
|
||||
}
|
||||
>();
|
||||
|
||||
const [parameters, { accessKeyId, secretAccessKey }] = watch(["parameters", "secretsMapping"]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretRotationReviewSection label="Parameters">
|
||||
<GenericFieldLabel label="Region">{parameters.region}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="User Name">{parameters.clientName}</GenericFieldLabel>
|
||||
</SecretRotationReviewSection>
|
||||
<SecretRotationReviewSection label="Secrets Mapping">
|
||||
<GenericFieldLabel label="Access Key ID">{accessKeyId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Access Key Secret">{secretAccessKey}</GenericFieldLabel>
|
||||
</SecretRotationReviewSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -7,12 +7,14 @@ import { getRotateAtLocal } from "@app/helpers/secretRotationsV2";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
|
||||
import { AwsIamUserSecretRotationReviewFields } from "./AwsIamUserSecretRotationReviewFields";
|
||||
import { SqlCredentialsRotationReviewFields } from "./shared";
|
||||
|
||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationReviewFields,
|
||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationReviewFields,
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields,
|
||||
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationReviewFields
|
||||
};
|
||||
|
||||
export const SecretRotationV2ReviewFields = () => {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||
import { FormControl, Input } from "@app/components/v2";
|
||||
import { SecretRotation, useSecretRotationV2Option } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
import { SecretsMappingTable } from "./shared";
|
||||
|
||||
export const AwsIamUserSecretRotationSecretsMappingFields = () => {
|
||||
const { control } = useFormContext<
|
||||
TSecretRotationV2Form & {
|
||||
type: SecretRotation.AwsIamUserSecret;
|
||||
}
|
||||
>();
|
||||
|
||||
const { rotationOption } = useSecretRotationV2Option(SecretRotation.AwsIamUserSecret);
|
||||
|
||||
const items = [
|
||||
{
|
||||
name: "Access Key ID",
|
||||
input: (
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={rotationOption?.template.secretsMapping.accessKeyId}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="secretsMapping.accessKeyId"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "Client Secret",
|
||||
input: (
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={rotationOption?.template.secretsMapping.secretAccessKey}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="secretsMapping.secretAccessKey"
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return <SecretsMappingTable items={items} />;
|
||||
};
|
||||
@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
import { TSecretRotationV2Form } from "../schemas";
|
||||
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
|
||||
import { AwsIamUserSecretRotationSecretsMappingFields } from "./AwsIamUserSecretRotationSecretsMappingFields";
|
||||
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
|
||||
|
||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields
|
||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields,
|
||||
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationSecretsMappingFields
|
||||
};
|
||||
|
||||
export const SecretRotationV2SecretsMappingFields = () => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { BaseSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/base-secret-rotation-v2-schema";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
|
||||
export const AwsIamUserSecretRotationSchema = z
|
||||
.object({
|
||||
type: z.literal(SecretRotation.AwsIamUserSecret),
|
||||
parameters: z.object({
|
||||
clientName: z.string().trim().min(1, "Client Name required"),
|
||||
region: z.string().trim().min(1, "Region required")
|
||||
}),
|
||||
secretsMapping: z.object({
|
||||
accessKeyId: z.string().trim().min(1, "Access Key ID required"),
|
||||
secretAccessKey: z.string().trim().min(1, "Secret Access Key required")
|
||||
})
|
||||
})
|
||||
.merge(BaseSecretRotationSchema);
|
||||
@@ -1,13 +1,15 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { Auth0ClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/auth0-client-secret-rotation-schema";
|
||||
import { AwsIamUserSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/aws-iam-user-secret-rotation-schema";
|
||||
import { MsSqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mssql-credentials-rotation-schema";
|
||||
import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema";
|
||||
|
||||
const SecretRotationUnionSchema = z.discriminatedUnion("type", [
|
||||
PostgresCredentialsRotationSchema,
|
||||
MsSqlCredentialsRotationSchema,
|
||||
Auth0ClientSecretRotationSchema
|
||||
Auth0ClientSecretRotationSchema,
|
||||
AwsIamUserSecretRotationSchema
|
||||
]);
|
||||
|
||||
export const SecretRotationV2FormSchema = SecretRotationUnionSchema;
|
||||
|
||||
@@ -19,20 +19,27 @@ export const SECRET_ROTATION_MAP: Record<
|
||||
name: "Auth0 Client Secret",
|
||||
image: "Auth0.png",
|
||||
size: 35
|
||||
},
|
||||
[SecretRotation.AwsIamUserSecret]: {
|
||||
name: "AWS IAM User Secret",
|
||||
image: "Amazon Web Services.png",
|
||||
size: 35
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
// if a rotation can potentially have downtime due to rotating a single credential set this to false
|
||||
export const IS_ROTATION_DUAL_CREDENTIALS: Record<SecretRotation, boolean> = {
|
||||
[SecretRotation.PostgresCredentials]: true,
|
||||
[SecretRotation.MsSqlCredentials]: true,
|
||||
[SecretRotation.Auth0ClientSecret]: false
|
||||
[SecretRotation.Auth0ClientSecret]: false,
|
||||
[SecretRotation.AwsIamUserSecret]: true
|
||||
};
|
||||
|
||||
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
|
||||
|
||||
@@ -4,15 +4,20 @@ import { apiRequest } from "@app/config/request";
|
||||
import { appConnectionKeys } from "@app/hooks/api/appConnections";
|
||||
|
||||
import {
|
||||
TAwsConnectionIamUser,
|
||||
TAwsConnectionKmsKey,
|
||||
TAwsConnectionListIamUsersResponse,
|
||||
TAwsConnectionListKmsKeysResponse,
|
||||
TListAwsConnectionIamUsers,
|
||||
TListAwsConnectionKmsKeys
|
||||
} from "./types";
|
||||
|
||||
const awsConnectionKeys = {
|
||||
all: [...appConnectionKeys.all, "aws"] as const,
|
||||
listKmsKeys: (params: TListAwsConnectionKmsKeys) =>
|
||||
[...awsConnectionKeys.all, "kms-keys", params] as const
|
||||
[...awsConnectionKeys.all, "kms-keys", params] as const,
|
||||
listIamUsers: (params: TListAwsConnectionIamUsers) =>
|
||||
[...awsConnectionKeys.all, "iam-users", params] as const
|
||||
};
|
||||
|
||||
export const useListAwsConnectionKmsKeys = (
|
||||
@@ -40,3 +45,28 @@ export const useListAwsConnectionKmsKeys = (
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useListAwsConnectionIamUsers = (
|
||||
{ connectionId }: TListAwsConnectionIamUsers,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TAwsConnectionIamUser[],
|
||||
unknown,
|
||||
TAwsConnectionIamUser[],
|
||||
ReturnType<typeof awsConnectionKeys.listIamUsers>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: awsConnectionKeys.listIamUsers({ connectionId }),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TAwsConnectionListIamUsersResponse>(
|
||||
`/api/v1/app-connections/aws/${connectionId}/users`
|
||||
);
|
||||
|
||||
return data.iamUsers;
|
||||
},
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,3 +14,21 @@ export type TAwsConnectionKmsKey = {
|
||||
export type TAwsConnectionListKmsKeysResponse = {
|
||||
kmsKeys: TAwsConnectionKmsKey[];
|
||||
};
|
||||
|
||||
export type TListAwsConnectionIamUsers = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type TAwsConnectionIamUser = {
|
||||
arn: string;
|
||||
UserName: string;
|
||||
};
|
||||
|
||||
export type TAwsConnectionListIamUsersResponse = {
|
||||
iamUsers: TAwsConnectionIamUser[];
|
||||
};
|
||||
|
||||
export type TAwsIamUserSecret = {
|
||||
arn: string;
|
||||
UserName: string;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||
import {
|
||||
TSecretRotationV2Base,
|
||||
TSecretRotationV2GeneratedCredentialsResponseBase
|
||||
} from "@app/hooks/api/secretRotationsV2/types/shared";
|
||||
|
||||
export type TAwsIamUserSecretRotation = TSecretRotationV2Base & {
|
||||
type: SecretRotation.AwsIamUserSecret;
|
||||
parameters: {
|
||||
region: string;
|
||||
clientName: string;
|
||||
};
|
||||
secretsMapping: {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TAwsIamUserSecretRotationGeneratedCredentials = {
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
};
|
||||
|
||||
export type TAwsIamUserSecretRotationGeneratedCredentialsResponse =
|
||||
TSecretRotationV2GeneratedCredentialsResponseBase<
|
||||
SecretRotation.AwsIamUserSecret,
|
||||
TAwsIamUserSecretRotationGeneratedCredentials
|
||||
>;
|
||||
|
||||
export type TAwsIamUserSecretRotationOption = {
|
||||
name: string;
|
||||
type: SecretRotation.AwsIamUserSecret;
|
||||
connection: AppConnection.AWS;
|
||||
template: {
|
||||
secretsMapping: TAwsIamUserSecretRotation["secretsMapping"];
|
||||
};
|
||||
};
|
||||
@@ -4,6 +4,11 @@ import {
|
||||
TAuth0ClientSecretRotationGeneratedCredentialsResponse,
|
||||
TAuth0ClientSecretRotationOption
|
||||
} from "@app/hooks/api/secretRotationsV2/types/auth0-client-secret-rotation";
|
||||
import {
|
||||
TAwsIamUserSecretRotation,
|
||||
TAwsIamUserSecretRotationGeneratedCredentialsResponse,
|
||||
TAwsIamUserSecretRotationOption
|
||||
} from "@app/hooks/api/secretRotationsV2/types/aws-iam-access-key-rotation";
|
||||
import {
|
||||
TMsSqlCredentialsRotation,
|
||||
TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||
@@ -20,13 +25,15 @@ export type TSecretRotationV2 = (
|
||||
| TPostgresCredentialsRotation
|
||||
| TMsSqlCredentialsRotation
|
||||
| TAuth0ClientSecretRotation
|
||||
| TAwsIamUserSecretRotation
|
||||
) & {
|
||||
secrets: (SecretV3RawSanitized | null)[];
|
||||
};
|
||||
|
||||
export type TSecretRotationV2Option =
|
||||
| TSqlCredentialsRotationOption
|
||||
| TAuth0ClientSecretRotationOption;
|
||||
| TAuth0ClientSecretRotationOption
|
||||
| TAwsIamUserSecretRotationOption;
|
||||
|
||||
export type TListSecretRotationV2Options = { secretRotationOptions: TSecretRotationV2Option[] };
|
||||
|
||||
@@ -35,7 +42,8 @@ export type TSecretRotationV2Response = { secretRotation: TSecretRotationV2 };
|
||||
export type TViewSecretRotationGeneratedCredentialsResponse =
|
||||
| TPostgresCredentialsRotationGeneratedCredentialsResponse
|
||||
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||
| TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
||||
| TAuth0ClientSecretRotationGeneratedCredentialsResponse
|
||||
| TAwsIamUserSecretRotationGeneratedCredentialsResponse;
|
||||
|
||||
export type TCreateSecretRotationV2DTO = DiscriminativePick<
|
||||
TSecretRotationV2,
|
||||
@@ -82,10 +90,12 @@ export type TSecretRotationOptionMap = {
|
||||
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
|
||||
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
|
||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
|
||||
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationOption;
|
||||
};
|
||||
|
||||
export type TSecretRotationGeneratedCredentialsResponseMap = {
|
||||
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
||||
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationGeneratedCredentialsResponse;
|
||||
};
|
||||
|
||||