mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 12:38:36 +00:00
rebase
This commit is contained in:
+15
@@ -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 AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||
name: "Auth0 Client Secret",
|
||||
type: SecretRotation.Auth0ClientSecret,
|
||||
connection: AppConnection.Auth0,
|
||||
template: {
|
||||
secretsMapping: {
|
||||
clientId: "AUTH0_CLIENT_ID",
|
||||
clientSecret: "AUTH0_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
};
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
TAuth0ClientSecretRotationGeneratedCredentials,
|
||||
TAuth0ClientSecretRotationWithConnection
|
||||
} from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-types";
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
TRotationFactoryIssueCredentials,
|
||||
TRotationFactoryRevokeCredentials,
|
||||
TRotationFactoryRotateCredentials
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { getAuth0ConnectionAccessToken } from "@app/services/app-connection/auth0";
|
||||
|
||||
import { generatePassword } from "../shared/utils";
|
||||
|
||||
export const auth0ClientSecretRotationFactory: TRotationFactory<
|
||||
TAuth0ClientSecretRotationWithConnection,
|
||||
TAuth0ClientSecretRotationGeneratedCredentials
|
||||
> = (secretRotation, appConnectionDAL, kmsService) => {
|
||||
const {
|
||||
connection,
|
||||
parameters: { clientId },
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
const $rotateClientSecret = async () => {
|
||||
const accessToken = await getAuth0ConnectionAccessToken(connection, appConnectionDAL, kmsService);
|
||||
const clientSecret = generatePassword();
|
||||
|
||||
await request.request({
|
||||
method: "PATCH",
|
||||
url: `${connection.credentials.audience}clients/${clientId}`,
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
data: {
|
||||
client_secret: clientSecret
|
||||
}
|
||||
});
|
||||
|
||||
return { clientId, clientSecret };
|
||||
};
|
||||
|
||||
const issueCredentials: TRotationFactoryIssueCredentials<TAuth0ClientSecretRotationGeneratedCredentials> = async (
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotateClientSecret();
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const revokeCredentials: TRotationFactoryRevokeCredentials<TAuth0ClientSecretRotationGeneratedCredentials> = async (
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
const accessToken = await getAuth0ConnectionAccessToken(connection, appConnectionDAL, kmsService);
|
||||
|
||||
// we just trigger an auth0 rotation to negate our credentials
|
||||
await request.request({
|
||||
method: "POST",
|
||||
url: `${connection.credentials.audience}clients/${clientId}/rotate-secret`,
|
||||
headers: { authorization: `Bearer ${accessToken}` }
|
||||
});
|
||||
|
||||
return callback();
|
||||
};
|
||||
|
||||
const rotateCredentials: TRotationFactoryRotateCredentials<TAuth0ClientSecretRotationGeneratedCredentials> = async (
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotateClientSecret();
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAuth0ClientSecretRotationGeneratedCredentials> = (
|
||||
generatedCredentials
|
||||
) => {
|
||||
const secrets = [
|
||||
{
|
||||
key: secretsMapping.clientId,
|
||||
value: generatedCredentials.clientId
|
||||
},
|
||||
{
|
||||
key: secretsMapping.clientSecret,
|
||||
value: generatedCredentials.clientSecret
|
||||
}
|
||||
];
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
||||
return {
|
||||
issueCredentials,
|
||||
revokeCredentials,
|
||||
rotateCredentials,
|
||||
getSecretsPayload
|
||||
};
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
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 } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const Auth0ClientSecretRotationGeneratedCredentialsSchema = z
|
||||
.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.max(2);
|
||||
|
||||
const Auth0ClientSecretRotationParametersSchema = z.object({
|
||||
clientId: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Client ID Required")
|
||||
.describe(SecretRotations.PARAMETERS.AUTH0_CLIENT_SECRET.clientId)
|
||||
});
|
||||
|
||||
const Auth0ClientSecretRotationSecretsMappingSchema = z.object({
|
||||
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AUTH0_CLIENT_SECRET.clientID),
|
||||
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AUTH0_CLIENT_SECRET.clientSecret)
|
||||
});
|
||||
|
||||
export const Auth0ClientSecretRotationTemplateSchema = z.object({
|
||||
secretsMapping: z.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string()
|
||||
})
|
||||
});
|
||||
|
||||
export const Auth0ClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.Auth0ClientSecret).extend({
|
||||
type: z.literal(SecretRotation.Auth0ClientSecret),
|
||||
parameters: Auth0ClientSecretRotationParametersSchema,
|
||||
secretsMapping: Auth0ClientSecretRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const CreateAuth0ClientSecretRotationSchema = BaseCreateSecretRotationSchema(
|
||||
SecretRotation.Auth0ClientSecret
|
||||
).extend({
|
||||
parameters: Auth0ClientSecretRotationParametersSchema,
|
||||
secretsMapping: Auth0ClientSecretRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const UpdateAuth0ClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
|
||||
SecretRotation.Auth0ClientSecret
|
||||
).extend({
|
||||
parameters: Auth0ClientSecretRotationParametersSchema.optional(),
|
||||
secretsMapping: Auth0ClientSecretRotationSecretsMappingSchema.optional()
|
||||
});
|
||||
|
||||
export const Auth0ClientSecretRotationListItemSchema = z.object({
|
||||
name: z.literal("Auth0 Client Secret"),
|
||||
connection: z.literal(AppConnection.Auth0),
|
||||
type: z.literal(SecretRotation.Auth0ClientSecret),
|
||||
template: Auth0ClientSecretRotationTemplateSchema
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TAuth0Connection } from "@app/services/app-connection/auth0";
|
||||
|
||||
import {
|
||||
Auth0ClientSecretRotationGeneratedCredentialsSchema,
|
||||
Auth0ClientSecretRotationListItemSchema,
|
||||
Auth0ClientSecretRotationSchema,
|
||||
CreateAuth0ClientSecretRotationSchema
|
||||
} from "./auth0-client-secret-rotation-schemas";
|
||||
|
||||
export type TAuth0ClientSecretRotation = z.infer<typeof Auth0ClientSecretRotationSchema>;
|
||||
|
||||
export type TAuth0ClientSecretRotationInput = z.infer<typeof CreateAuth0ClientSecretRotationSchema>;
|
||||
|
||||
export type TAuth0ClientSecretRotationListItem = z.infer<typeof Auth0ClientSecretRotationListItemSchema>;
|
||||
|
||||
export type TAuth0ClientSecretRotationWithConnection = TAuth0ClientSecretRotation & {
|
||||
connection: TAuth0Connection;
|
||||
};
|
||||
|
||||
export type TAuth0ClientSecretRotationGeneratedCredentials = z.infer<
|
||||
typeof Auth0ClientSecretRotationGeneratedCredentialsSchema
|
||||
>;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./auth0-client-secret-rotation-constants";
|
||||
export * from "./auth0-client-secret-rotation-schemas";
|
||||
export * from "./auth0-client-secret-rotation-types";
|
||||
@@ -1,6 +1,7 @@
|
||||
export enum SecretRotation {
|
||||
PostgresCredentials = "postgres-credentials",
|
||||
MsSqlCredentials = "mssql-credentials"
|
||||
MsSqlCredentials = "mssql-credentials",
|
||||
Auth0ClientSecret = "auth0-client-secret"
|
||||
}
|
||||
|
||||
export enum SecretRotationStatus {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AxiosError } from "axios";
|
||||
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 { 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";
|
||||
@@ -16,7 +17,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.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION
|
||||
};
|
||||
|
||||
export const listSecretRotationOptions = () => {
|
||||
|
||||
@@ -3,10 +3,12 @@ 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.MsSqlCredentials]: "Microsoft SQL Sever Credentials",
|
||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret"
|
||||
};
|
||||
|
||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql
|
||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ProjectPermissionSecretRotationActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
|
||||
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import {
|
||||
calculateNextRotationAt,
|
||||
@@ -41,6 +42,7 @@ import {
|
||||
TRotationFactory,
|
||||
TSecretRotationRotateGeneratedCredentials,
|
||||
TSecretRotationV2,
|
||||
TSecretRotationV2GeneratedCredentials,
|
||||
TSecretRotationV2Raw,
|
||||
TSecretRotationV2WithConnection,
|
||||
TUpdateSecretRotationV2DTO
|
||||
@@ -53,6 +55,7 @@ import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||
import { BadRequestError, DatabaseError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||
import { QueueJobs, TQueueServiceFactory } from "@app/queue";
|
||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||
import { decryptAppConnection } from "@app/services/app-connection/app-connection-fns";
|
||||
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
@@ -97,15 +100,21 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
||||
};
|
||||
|
||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||
|
||||
const MAX_GENERATED_CREDENTIALS_LENGTH = 2;
|
||||
|
||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactory> = {
|
||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory,
|
||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory
|
||||
type TRotationFactoryImplementation = TRotationFactory<
|
||||
TSecretRotationV2WithConnection,
|
||||
TSecretRotationV2GeneratedCredentials
|
||||
>;
|
||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation
|
||||
};
|
||||
|
||||
export const secretRotationV2ServiceFactory = ({
|
||||
@@ -125,7 +134,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
secretQueueService,
|
||||
snapshotService,
|
||||
keyStore,
|
||||
queueService
|
||||
queueService,
|
||||
appConnectionDAL
|
||||
}: TSecretRotationV2ServiceFactoryDep) => {
|
||||
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
||||
const appCfg = getConfig();
|
||||
@@ -429,11 +439,15 @@ export const secretRotationV2ServiceFactory = ({
|
||||
// validates permission to connect and app is valid for rotation type
|
||||
const connection = await appConnectionService.connectAppConnectionById(typeApp, payload.connectionId, actor);
|
||||
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type]({
|
||||
parameters: payload.parameters,
|
||||
secretsMapping,
|
||||
connection
|
||||
} as TSecretRotationV2WithConnection);
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type](
|
||||
{
|
||||
parameters: payload.parameters,
|
||||
secretsMapping,
|
||||
connection
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
||||
try {
|
||||
const currentTime = new Date();
|
||||
@@ -441,7 +455,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
// callback structure to support transactional rollback when possible
|
||||
const secretRotation = await rotationFactory.issueCredentials(async (newCredentials) => {
|
||||
const encryptedGeneratedCredentials = await encryptSecretRotationCredentials({
|
||||
generatedCredentials: [newCredentials],
|
||||
generatedCredentials: [newCredentials] as TSecretRotationV2GeneratedCredentials,
|
||||
projectId,
|
||||
kmsService
|
||||
});
|
||||
@@ -738,32 +752,37 @@ export const secretRotationV2ServiceFactory = ({
|
||||
message: `Secret Rotation with ID "${rotationId}" is not configured for ${SECRET_ROTATION_NAME_MAP[type]}`
|
||||
});
|
||||
|
||||
const deleteTransaction = secretRotationV2DAL.transaction(async (tx) => {
|
||||
if (deleteSecrets) {
|
||||
await fnSecretBulkDelete({
|
||||
secretDAL: secretV2BridgeDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: Object.values(secretsMapping as TSecretRotationV2["secretsMapping"]).map((secretKey) => ({
|
||||
secretKey,
|
||||
type: SecretType.Shared
|
||||
})),
|
||||
projectId,
|
||||
folderId,
|
||||
actorId: actor.id, // not actually used since rotated secrets are shared
|
||||
tx
|
||||
});
|
||||
}
|
||||
const deleteTransaction = async () =>
|
||||
secretRotationV2DAL.transaction(async (tx) => {
|
||||
if (deleteSecrets) {
|
||||
await fnSecretBulkDelete({
|
||||
secretDAL: secretV2BridgeDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: Object.values(secretsMapping as TSecretRotationV2["secretsMapping"]).map((secretKey) => ({
|
||||
secretKey,
|
||||
type: SecretType.Shared
|
||||
})),
|
||||
projectId,
|
||||
folderId,
|
||||
actorId: actor.id, // not actually used since rotated secrets are shared
|
||||
tx
|
||||
});
|
||||
}
|
||||
|
||||
return secretRotationV2DAL.deleteById(rotationId, tx);
|
||||
});
|
||||
return secretRotationV2DAL.deleteById(rotationId, tx);
|
||||
});
|
||||
|
||||
if (revokeGeneratedCredentials) {
|
||||
const appConnection = await decryptAppConnection(connection, kmsService);
|
||||
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type]({
|
||||
...secretRotation,
|
||||
connection: appConnection
|
||||
} as TSecretRotationV2WithConnection);
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type](
|
||||
{
|
||||
...secretRotation,
|
||||
connection: appConnection
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
||||
const generatedCredentials = await decryptSecretRotationCredentials({
|
||||
encryptedGeneratedCredentials,
|
||||
@@ -771,9 +790,9 @@ export const secretRotationV2ServiceFactory = ({
|
||||
kmsService
|
||||
});
|
||||
|
||||
await rotationFactory.revokeCredentials(generatedCredentials, async () => deleteTransaction);
|
||||
await rotationFactory.revokeCredentials(generatedCredentials, deleteTransaction);
|
||||
} else {
|
||||
await deleteTransaction;
|
||||
await deleteTransaction();
|
||||
}
|
||||
|
||||
if (deleteSecrets) {
|
||||
@@ -837,10 +856,14 @@ export const secretRotationV2ServiceFactory = ({
|
||||
|
||||
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
||||
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type as SecretRotation]({
|
||||
...secretRotation,
|
||||
connection: appConnection
|
||||
} as TSecretRotationV2WithConnection);
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type as SecretRotation](
|
||||
{
|
||||
...secretRotation,
|
||||
connection: appConnection
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
||||
const updatedRotation = await rotationFactory.rotateCredentials(inactiveCredentials, async (newCredentials) => {
|
||||
const updatedCredentials = [...generatedCredentials];
|
||||
@@ -848,7 +871,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
|
||||
const encryptedUpdatedCredentials = await encryptSecretRotationCredentials({
|
||||
projectId,
|
||||
generatedCredentials: updatedCredentials,
|
||||
generatedCredentials: updatedCredentials as TSecretRotationV2GeneratedCredentials,
|
||||
kmsService
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TSqlCredentialsRotationGeneratedCredentials } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
|
||||
import {
|
||||
TAuth0ClientSecretRotation,
|
||||
TAuth0ClientSecretRotationGeneratedCredentials,
|
||||
TAuth0ClientSecretRotationInput,
|
||||
TAuth0ClientSecretRotationListItem,
|
||||
TAuth0ClientSecretRotationWithConnection
|
||||
} from "./auth0-client-secret";
|
||||
import {
|
||||
TMsSqlCredentialsRotation,
|
||||
TMsSqlCredentialsRotationInput,
|
||||
@@ -18,17 +27,26 @@ import {
|
||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
import { SecretRotation } from "./secret-rotation-v2-enums";
|
||||
|
||||
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation;
|
||||
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation | TAuth0ClientSecretRotation;
|
||||
|
||||
export type TSecretRotationV2WithConnection =
|
||||
| TPostgresCredentialsRotationWithConnection
|
||||
| TMsSqlCredentialsRotationWithConnection;
|
||||
| TMsSqlCredentialsRotationWithConnection
|
||||
| TAuth0ClientSecretRotationWithConnection;
|
||||
|
||||
export type TSecretRotationV2GeneratedCredentials = TSqlCredentialsRotationGeneratedCredentials;
|
||||
export type TSecretRotationV2GeneratedCredentials =
|
||||
| TSqlCredentialsRotationGeneratedCredentials
|
||||
| TAuth0ClientSecretRotationGeneratedCredentials;
|
||||
|
||||
export type TSecretRotationV2Input = TPostgresCredentialsRotationInput | TMsSqlCredentialsRotationInput;
|
||||
export type TSecretRotationV2Input =
|
||||
| TPostgresCredentialsRotationInput
|
||||
| TMsSqlCredentialsRotationInput
|
||||
| TAuth0ClientSecretRotationInput;
|
||||
|
||||
export type TSecretRotationV2ListItem = TPostgresCredentialsRotationListItem | TMsSqlCredentialsRotationListItem;
|
||||
export type TSecretRotationV2ListItem =
|
||||
| TPostgresCredentialsRotationListItem
|
||||
| TMsSqlCredentialsRotationListItem
|
||||
| TAuth0ClientSecretRotationListItem;
|
||||
|
||||
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
||||
|
||||
@@ -129,27 +147,34 @@ export type TSecretRotationSendNotificationJobPayload = {
|
||||
// transactional behavior. By passing in the rotation mutation, if this mutation fails we can roll back the
|
||||
// third party credential changes (when supported), preventing credentials getting out of sync
|
||||
|
||||
export type TRotationFactoryIssueCredentials = (
|
||||
callback: (newCredentials: TSecretRotationV2GeneratedCredentials[number]) => Promise<TSecretRotationV2Raw>
|
||||
export type TRotationFactoryIssueCredentials<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||
callback: (newCredentials: T[number]) => Promise<TSecretRotationV2Raw>
|
||||
) => Promise<TSecretRotationV2Raw>;
|
||||
|
||||
export type TRotationFactoryRevokeCredentials = (
|
||||
generatedCredentials: TSecretRotationV2GeneratedCredentials,
|
||||
export type TRotationFactoryRevokeCredentials<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||
generatedCredentials: T,
|
||||
callback: () => Promise<TSecretRotationV2Raw>
|
||||
) => Promise<TSecretRotationV2Raw>;
|
||||
|
||||
export type TRotationFactoryRotateCredentials = (
|
||||
credentialsToRevoke: TSecretRotationV2GeneratedCredentials[number] | undefined,
|
||||
callback: (newCredentials: TSecretRotationV2GeneratedCredentials[number]) => Promise<TSecretRotationV2Raw>
|
||||
export type TRotationFactoryRotateCredentials<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||
credentialsToRevoke: T[number] | undefined,
|
||||
callback: (newCredentials: T[number]) => Promise<TSecretRotationV2Raw>
|
||||
) => Promise<TSecretRotationV2Raw>;
|
||||
|
||||
export type TRotationFactoryGetSecretsPayload = (
|
||||
generatedCredentials: TSecretRotationV2GeneratedCredentials[number]
|
||||
export type TRotationFactoryGetSecretsPayload<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||
generatedCredentials: T[number]
|
||||
) => { key: string; value: string }[];
|
||||
|
||||
export type TRotationFactory = (secretRotation: TSecretRotationV2WithConnection) => {
|
||||
issueCredentials: TRotationFactoryIssueCredentials;
|
||||
revokeCredentials: TRotationFactoryRevokeCredentials;
|
||||
rotateCredentials: TRotationFactoryRotateCredentials;
|
||||
getSecretsPayload: TRotationFactoryGetSecretsPayload;
|
||||
export type TRotationFactory<
|
||||
T extends TSecretRotationV2WithConnection,
|
||||
C extends TSecretRotationV2GeneratedCredentials
|
||||
> = (
|
||||
secretRotation: T,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
||||
revokeCredentials: TRotationFactoryRevokeCredentials<C>;
|
||||
rotateCredentials: TRotationFactoryRotateCredentials<C>;
|
||||
getSecretsPayload: TRotationFactoryGetSecretsPayload<C>;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
|
||||
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||
PostgresCredentialsRotationSchema,
|
||||
MsSqlCredentialsRotationSchema
|
||||
MsSqlCredentialsRotationSchema,
|
||||
Auth0ClientSecretRotationSchema
|
||||
]);
|
||||
|
||||
+24
-95
@@ -1,6 +1,5 @@
|
||||
import { randomInt } from "crypto";
|
||||
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
TRotationFactoryIssueCredentials,
|
||||
TRotationFactoryRevokeCredentials,
|
||||
@@ -8,94 +7,12 @@ import {
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { getSqlConnectionClient, SQL_CONNECTION_ALTER_LOGIN_STATEMENT } from "@app/services/app-connection/shared/sql";
|
||||
|
||||
import { generatePassword } from "../utils";
|
||||
import {
|
||||
TSqlCredentialsRotationGeneratedCredentials,
|
||||
TSqlCredentialsRotationWithConnection
|
||||
} from "./sql-credentials-rotation-types";
|
||||
|
||||
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
||||
length: 48,
|
||||
required: {
|
||||
lowercase: 1,
|
||||
uppercase: 1,
|
||||
digits: 1,
|
||||
symbols: 0
|
||||
},
|
||||
allowedSymbols: "-_.~!*"
|
||||
};
|
||||
|
||||
const generatePassword = () => {
|
||||
try {
|
||||
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
||||
|
||||
const chars = {
|
||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
digits: "0123456789",
|
||||
symbols: allowedSymbols || "-_.~!*"
|
||||
};
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (required.lowercase > 0) {
|
||||
parts.push(
|
||||
...Array(required.lowercase)
|
||||
.fill(0)
|
||||
.map(() => chars.lowercase[randomInt(chars.lowercase.length)])
|
||||
);
|
||||
}
|
||||
|
||||
if (required.uppercase > 0) {
|
||||
parts.push(
|
||||
...Array(required.uppercase)
|
||||
.fill(0)
|
||||
.map(() => chars.uppercase[randomInt(chars.uppercase.length)])
|
||||
);
|
||||
}
|
||||
|
||||
if (required.digits > 0) {
|
||||
parts.push(
|
||||
...Array(required.digits)
|
||||
.fill(0)
|
||||
.map(() => chars.digits[randomInt(chars.digits.length)])
|
||||
);
|
||||
}
|
||||
|
||||
if (required.symbols > 0) {
|
||||
parts.push(
|
||||
...Array(required.symbols)
|
||||
.fill(0)
|
||||
.map(() => chars.symbols[randomInt(chars.symbols.length)])
|
||||
);
|
||||
}
|
||||
|
||||
const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0);
|
||||
const remainingLength = Math.max(length - requiredTotal, 0);
|
||||
|
||||
const allowedChars = Object.entries(chars)
|
||||
.filter(([key]) => required[key as keyof typeof required] > 0)
|
||||
.map(([, value]) => value)
|
||||
.join("");
|
||||
|
||||
parts.push(
|
||||
...Array(remainingLength)
|
||||
.fill(0)
|
||||
.map(() => allowedChars[randomInt(allowedChars.length)])
|
||||
);
|
||||
|
||||
// shuffle the array to mix up the characters
|
||||
for (let i = parts.length - 1; i > 0; i -= 1) {
|
||||
const j = randomInt(i + 1);
|
||||
[parts[i], parts[j]] = [parts[j], parts[i]];
|
||||
}
|
||||
|
||||
return parts.join("");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error";
|
||||
throw new Error(`Failed to generate password: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGeneratedCredentials) => {
|
||||
const error = e as Error;
|
||||
|
||||
@@ -110,7 +27,10 @@ const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGenerat
|
||||
return redactedMessage;
|
||||
};
|
||||
|
||||
export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRotationWithConnection) => {
|
||||
export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
TSqlCredentialsRotationWithConnection,
|
||||
TSqlCredentialsRotationGeneratedCredentials
|
||||
> = (secretRotation) => {
|
||||
const {
|
||||
connection,
|
||||
parameters: { username1, username2 },
|
||||
@@ -118,7 +38,7 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
const validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
||||
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
||||
const client = await getSqlConnectionClient({
|
||||
...connection,
|
||||
credentials: {
|
||||
@@ -136,7 +56,9 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
||||
}
|
||||
};
|
||||
|
||||
const issueCredentials: TRotationFactoryIssueCredentials = async (callback) => {
|
||||
const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||
callback
|
||||
) => {
|
||||
const client = await getSqlConnectionClient(connection);
|
||||
|
||||
// For SQL, since we get existing users, we change both their passwords
|
||||
@@ -159,13 +81,16 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
||||
}
|
||||
|
||||
for await (const credentials of credentialsSet) {
|
||||
await validateCredentials(credentials);
|
||||
await $validateCredentials(credentials);
|
||||
}
|
||||
|
||||
return callback(credentialsSet[0]);
|
||||
};
|
||||
|
||||
const revokeCredentials: TRotationFactoryRevokeCredentials = async (credentialsToRevoke, callback) => {
|
||||
const revokeCredentials: TRotationFactoryRevokeCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||
credentialsToRevoke,
|
||||
callback
|
||||
) => {
|
||||
const client = await getSqlConnectionClient(connection);
|
||||
|
||||
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() }));
|
||||
@@ -186,7 +111,10 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
||||
return callback();
|
||||
};
|
||||
|
||||
const rotateCredentials: TRotationFactoryRotateCredentials = async (_, callback) => {
|
||||
const rotateCredentials: TRotationFactoryRotateCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
const client = await getSqlConnectionClient(connection);
|
||||
|
||||
// generate new password for the next active user
|
||||
@@ -200,12 +128,14 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
await validateCredentials(credentials);
|
||||
await $validateCredentials(credentials);
|
||||
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload = (generatedCredentials) => {
|
||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TSqlCredentialsRotationGeneratedCredentials> = (
|
||||
generatedCredentials
|
||||
) => {
|
||||
const { username, password } = secretsMapping;
|
||||
|
||||
const secrets = [
|
||||
@@ -226,7 +156,6 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
||||
issueCredentials,
|
||||
revokeCredentials,
|
||||
rotateCredentials,
|
||||
getSecretsPayload,
|
||||
validateCredentials
|
||||
getSecretsPayload
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { randomInt } from "crypto";
|
||||
|
||||
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
||||
length: 48,
|
||||
required: {
|
||||
lowercase: 1,
|
||||
uppercase: 1,
|
||||
digits: 1,
|
||||
symbols: 0
|
||||
},
|
||||
allowedSymbols: "-_.~!*"
|
||||
};
|
||||
|
||||
export const generatePassword = () => {
|
||||
try {
|
||||
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
||||
|
||||
const chars = {
|
||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
digits: "0123456789",
|
||||
symbols: allowedSymbols || "-_.~!*"
|
||||
};
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (required.lowercase > 0) {
|
||||
parts.push(
|
||||
...Array(required.lowercase)
|
||||
.fill(0)
|
||||
.map(() => chars.lowercase[randomInt(chars.lowercase.length)])
|
||||
);
|
||||
}
|
||||
|
||||
if (required.uppercase > 0) {
|
||||
parts.push(
|
||||
...Array(required.uppercase)
|
||||
.fill(0)
|
||||
.map(() => chars.uppercase[randomInt(chars.uppercase.length)])
|
||||
);
|
||||
}
|
||||
|
||||
if (required.digits > 0) {
|
||||
parts.push(
|
||||
...Array(required.digits)
|
||||
.fill(0)
|
||||
.map(() => chars.digits[randomInt(chars.digits.length)])
|
||||
);
|
||||
}
|
||||
|
||||
if (required.symbols > 0) {
|
||||
parts.push(
|
||||
...Array(required.symbols)
|
||||
.fill(0)
|
||||
.map(() => chars.symbols[randomInt(chars.symbols.length)])
|
||||
);
|
||||
}
|
||||
|
||||
const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0);
|
||||
const remainingLength = Math.max(length - requiredTotal, 0);
|
||||
|
||||
const allowedChars = Object.entries(chars)
|
||||
.filter(([key]) => required[key as keyof typeof required] > 0)
|
||||
.map(([, value]) => value)
|
||||
.join("");
|
||||
|
||||
parts.push(
|
||||
...Array(remainingLength)
|
||||
.fill(0)
|
||||
.map(() => allowedChars[randomInt(allowedChars.length)])
|
||||
);
|
||||
|
||||
// shuffle the array to mix up the characters
|
||||
for (let i = parts.length - 1; i > 0; i -= 1) {
|
||||
const j = randomInt(i + 1);
|
||||
[parts[i], parts[j]] = [parts[j], parts[i]];
|
||||
}
|
||||
|
||||
return parts.join("");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error";
|
||||
throw new Error(`Failed to generate password: ${message}`);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user