diff --git a/README.md b/README.md index e79517cf8..f1393495d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ We're on a mission to make security tooling more accessible to everyone, not jus - **[Dashboard](https://infisical.com/docs/documentation/platform/project)**: Manage secrets across projects and environments (e.g. development, production, etc.) through a user-friendly interface. - **[Native Integrations](https://infisical.com/docs/integrations/overview)**: Sync secrets to platforms like [GitHub](https://infisical.com/docs/integrations/cicd/githubactions), [Vercel](https://infisical.com/docs/integrations/cloud/vercel), [AWS](https://infisical.com/docs/integrations/cloud/aws-secret-manager), and use tools like [Terraform](https://infisical.com/docs/integrations/frameworks/terraform), [Ansible](https://infisical.com/docs/integrations/platforms/ansible), and more. - **[Secret versioning](https://infisical.com/docs/documentation/platform/secret-versioning)** and **[Point-in-Time Recovery](https://infisical.com/docs/documentation/platform/pit-recovery)**: Keep track of every secret and project state; roll back when needed. -- **[Secret Rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview)**: Rotate secrets at regular intervals for services like [PostgreSQL](https://infisical.com/docs/documentation/platform/secret-rotation/postgres), [MySQL](https://infisical.com/docs/documentation/platform/secret-rotation/mysql), [AWS IAM](https://infisical.com/docs/documentation/platform/secret-rotation/aws-iam), and more. +- **[Secret Rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview)**: Rotate secrets at regular intervals for services like [PostgreSQL](https://infisical.com/docs/documentation/platform/secret-rotation/postgres-credentials), [MySQL](https://infisical.com/docs/documentation/platform/secret-rotation/mysql), [AWS IAM](https://infisical.com/docs/documentation/platform/secret-rotation/aws-iam), and more. - **[Dynamic Secrets](https://infisical.com/docs/documentation/platform/dynamic-secrets/overview)**: Generate ephemeral secrets on-demand for services like [PostgreSQL](https://infisical.com/docs/documentation/platform/dynamic-secrets/postgresql), [MySQL](https://infisical.com/docs/documentation/platform/dynamic-secrets/mysql), [RabbitMQ](https://infisical.com/docs/documentation/platform/dynamic-secrets/rabbit-mq), and more. - **[Secret Scanning and Leak Prevention](https://infisical.com/docs/cli/scanning-overview)**: Prevent secrets from leaking to git. - **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments. diff --git a/backend/src/ee/routes/v2/secret-rotation-v2-routers/auth0-client-secret-rotation-router.ts b/backend/src/ee/routes/v2/secret-rotation-v2-routers/auth0-client-secret-rotation-router.ts new file mode 100644 index 000000000..6bcf1ea5e --- /dev/null +++ b/backend/src/ee/routes/v2/secret-rotation-v2-routers/auth0-client-secret-rotation-router.ts @@ -0,0 +1,19 @@ +import { + Auth0ClientSecretRotationGeneratedCredentialsSchema, + Auth0ClientSecretRotationSchema, + CreateAuth0ClientSecretRotationSchema, + UpdateAuth0ClientSecretRotationSchema +} from "@app/ee/services/secret-rotation-v2/auth0-client-secret"; +import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums"; + +import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints"; + +export const registerAuth0ClientSecretRotationRouter = async (server: FastifyZodProvider) => + registerSecretRotationEndpoints({ + type: SecretRotation.Auth0ClientSecret, + server, + responseSchema: Auth0ClientSecretRotationSchema, + createSchema: CreateAuth0ClientSecretRotationSchema, + updateSchema: UpdateAuth0ClientSecretRotationSchema, + generatedCredentialsSchema: Auth0ClientSecretRotationGeneratedCredentialsSchema + }); diff --git a/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts b/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts index d641ec689..1dacf1bd2 100644 --- a/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts +++ b/backend/src/ee/routes/v2/secret-rotation-v2-routers/index.ts @@ -1,5 +1,6 @@ import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums"; +import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router"; import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router"; import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router"; @@ -10,5 +11,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record< (server: FastifyZodProvider) => Promise > = { [SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter, - [SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter + [SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter, + [SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter }; diff --git a/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts b/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts index abdfc14f6..bfb8b38c0 100644 --- a/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts +++ b/backend/src/ee/routes/v2/secret-rotation-v2-routers/secret-rotation-v2-router.ts @@ -1,6 +1,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 { 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"; @@ -11,7 +12,8 @@ import { AuthMode } from "@app/services/auth/auth-type"; const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [ PostgresCredentialsRotationListItemSchema, - MsSqlCredentialsRotationListItemSchema + MsSqlCredentialsRotationListItemSchema, + Auth0ClientSecretRotationListItemSchema ]); export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-constants.ts b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-constants.ts new file mode 100644 index 000000000..a4d0a956c --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-constants.ts @@ -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" + } + } +}; diff --git a/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns.ts new file mode 100644 index 000000000..92c24145e --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns.ts @@ -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 = async ( + callback + ) => { + const credentials = await $rotateClientSecret(); + + return callback(credentials); + }; + + const revokeCredentials: TRotationFactoryRevokeCredentials = 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 = async ( + _, + callback + ) => { + const credentials = await $rotateClientSecret(); + + return callback(credentials); + }; + + const getSecretsPayload: TRotationFactoryGetSecretsPayload = ( + generatedCredentials + ) => { + const secrets = [ + { + key: secretsMapping.clientId, + value: generatedCredentials.clientId + }, + { + key: secretsMapping.clientSecret, + value: generatedCredentials.clientSecret + } + ]; + + return secrets; + }; + + return { + issueCredentials, + revokeCredentials, + rotateCredentials, + getSecretsPayload + }; +}; diff --git a/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-schemas.ts b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-schemas.ts new file mode 100644 index 000000000..4d4ceb79e --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-schemas.ts @@ -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 +}); diff --git a/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-types.ts b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-types.ts new file mode 100644 index 000000000..b3bb4ec35 --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-types.ts @@ -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; + +export type TAuth0ClientSecretRotationInput = z.infer; + +export type TAuth0ClientSecretRotationListItem = z.infer; + +export type TAuth0ClientSecretRotationWithConnection = TAuth0ClientSecretRotation & { + connection: TAuth0Connection; +}; + +export type TAuth0ClientSecretRotationGeneratedCredentials = z.infer< + typeof Auth0ClientSecretRotationGeneratedCredentialsSchema +>; diff --git a/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/index.ts b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/index.ts new file mode 100644 index 000000000..0c595be48 --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/index.ts @@ -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"; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts index 178a12516..d43cacb3a 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-enums.ts @@ -1,6 +1,7 @@ export enum SecretRotation { PostgresCredentials = "postgres-credentials", - MsSqlCredentials = "mssql-credentials" + MsSqlCredentials = "mssql-credentials", + Auth0ClientSecret = "auth0-client-secret" } export enum SecretRotationStatus { diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts index 376b497f1..603b77cc1 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-fns.ts @@ -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.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 = () => { diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts index c0d59332b..1050c3419 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts @@ -3,10 +3,12 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums export const SECRET_ROTATION_NAME_MAP: Record = { [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.PostgresCredentials]: AppConnection.Postgres, - [SecretRotation.MsSqlCredentials]: AppConnection.MsSql + [SecretRotation.MsSqlCredentials]: AppConnection.MsSql, + [SecretRotation.Auth0ClientSecret]: AppConnection.Auth0 }; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts index d0b38808e..9956916b7 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts @@ -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; snapshotService: Pick; queueService: Pick; + appConnectionDAL: Pick; }; export type TSecretRotationV2ServiceFactory = ReturnType; const MAX_GENERATED_CREDENTIALS_LENGTH = 2; -const SECRET_ROTATION_FACTORY_MAP: Record = { - [SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory, - [SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory +type TRotationFactoryImplementation = TRotationFactory< + TSecretRotationV2WithConnection, + TSecretRotationV2GeneratedCredentials +>; +const SECRET_ROTATION_FACTORY_MAP: Record = { + [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 }); diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-types.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-types.ts index 7102f7de3..c52fa5465 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-types.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-types.ts @@ -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>>; @@ -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 +export type TRotationFactoryIssueCredentials = ( + callback: (newCredentials: T[number]) => Promise ) => Promise; -export type TRotationFactoryRevokeCredentials = ( - generatedCredentials: TSecretRotationV2GeneratedCredentials, +export type TRotationFactoryRevokeCredentials = ( + generatedCredentials: T, callback: () => Promise ) => Promise; -export type TRotationFactoryRotateCredentials = ( - credentialsToRevoke: TSecretRotationV2GeneratedCredentials[number] | undefined, - callback: (newCredentials: TSecretRotationV2GeneratedCredentials[number]) => Promise +export type TRotationFactoryRotateCredentials = ( + credentialsToRevoke: T[number] | undefined, + callback: (newCredentials: T[number]) => Promise ) => Promise; -export type TRotationFactoryGetSecretsPayload = ( - generatedCredentials: TSecretRotationV2GeneratedCredentials[number] +export type TRotationFactoryGetSecretsPayload = ( + 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, + kmsService: Pick +) => { + issueCredentials: TRotationFactoryIssueCredentials; + revokeCredentials: TRotationFactoryRevokeCredentials; + rotateCredentials: TRotationFactoryRotateCredentials; + getSecretsPayload: TRotationFactoryGetSecretsPayload; }; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema.ts index 0c4bbd014..2db9c0251 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema.ts @@ -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 ]); diff --git a/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts index 17983eb43..12e9b5964 100644 --- a/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts @@ -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((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 = 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 = 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 = 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 = ( + generatedCredentials + ) => { const { username, password } = secretsMapping; const secrets = [ @@ -226,7 +156,6 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot issueCredentials, revokeCredentials, rotateCredentials, - getSecretsPayload, - validateCredentials + getSecretsPayload }; }; diff --git a/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts b/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts new file mode 100644 index 000000000..dfe4c22ed --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts @@ -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((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}`); + } +}; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 066314228..99e34cf0f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1759,6 +1759,12 @@ export const AppConnections = { connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to be deleted.` }), CREDENTIALS: { + AUTH0_CONNECTION: { + domain: "The domain of the Auth0 instance to connect to.", + clientId: "Your Auth0 application's Client ID.", + clientSecret: "Your Auth0 application's Client Secret.", + audience: "The unique identifier of the target API you want to access." + }, SQL_CONNECTION: { host: "The hostname of the database server.", port: "The port number of the database.", @@ -1962,12 +1968,19 @@ export const SecretRotations = { "The username of the first login to rotate passwords for. This user must already exists in your database.", username2: "The username of the second login to rotate passwords for. This user must already exists in your database." + }, + AUTH0_CLIENT_SECRET: { + clientId: "The client ID of the Auth0 Application to rotate the client secret for." } }, SECRETS_MAPPING: { SQL_CREDENTIALS: { username: "The name of the secret that the active username will be mapped to.", password: "The name of the secret that the generated password will be mapped to." + }, + 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." } } }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 595a9626c..c70cda5d2 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1548,7 +1548,8 @@ export const registerRoutes = async ( resourceMetadataDAL, snapshotService, secretQueueService, - queueService + queueService, + appConnectionDAL }); await secretRotationV2QueueServiceFactory({ diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index 040d32bf4..452b50207 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { Auth0ConnectionListItemSchema, SanitizedAuth0ConnectionSchema } from "@app/services/app-connection/auth0"; import { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws"; import { AzureAppConfigurationConnectionListItemSchema, @@ -46,7 +47,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedVercelConnectionSchema.options, ...SanitizedPostgresConnectionSchema.options, ...SanitizedMsSqlConnectionSchema.options, - ...SanitizedCamundaConnectionSchema.options + ...SanitizedCamundaConnectionSchema.options, + ...SanitizedAuth0ConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -60,7 +62,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ VercelConnectionListItemSchema, PostgresConnectionListItemSchema, MsSqlConnectionListItemSchema, - CamundaConnectionListItemSchema + CamundaConnectionListItemSchema, + Auth0ConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/auth0-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/auth0-connection-router.ts new file mode 100644 index 000000000..a940012fd --- /dev/null +++ b/backend/src/server/routes/v1/app-connection-routers/auth0-connection-router.ts @@ -0,0 +1,18 @@ +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + CreateAuth0ConnectionSchema, + SanitizedAuth0ConnectionSchema, + UpdateAuth0ConnectionSchema +} from "@app/services/app-connection/auth0"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; + +export const registerAuth0ConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.Auth0, + server, + sanitizedResponseSchema: SanitizedAuth0ConnectionSchema, + createSchema: CreateAuth0ConnectionSchema, + updateSchema: UpdateAuth0ConnectionSchema + }); +}; diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index aa5297800..2605fc76b 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -1,3 +1,4 @@ +import { registerAuth0ConnectionRouter } from "@app/server/routes/v1/app-connection-routers/auth0-connection-router"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { registerAwsConnectionRouter } from "./aws-connection-router"; @@ -26,5 +27,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { getVercelConnectionListItem(), getPostgresConnectionListItem(), getMsSqlConnectionListItem(), - getCamundaConnectionListItem() + getCamundaConnectionListItem(), + getAuth0ConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -103,24 +105,27 @@ export const decryptAppConnectionCredentials = async ({ return JSON.parse(decryptedPlainTextBlob.toString()) as TAppConnection["credentials"]; }; -const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record = { - [AppConnection.AWS]: validateAwsConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Databricks]: validateDatabricksConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.GitHub]: validateGitHubConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.GCP]: validateGcpConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.AzureKeyVault]: validateAzureKeyVaultConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.AzureAppConfiguration]: - validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator -}; - export const validateAppConnectionCredentials = async ( appConnection: TAppConnectionConfig -): Promise => VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); +): Promise => { + const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record = { + [AppConnection.AWS]: validateAwsConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Databricks]: validateDatabricksConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.GitHub]: validateGitHubConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.GCP]: validateGcpConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.AzureKeyVault]: validateAzureKeyVaultConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.AzureAppConfiguration]: + validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator + }; + + return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); +}; export const getAppConnectionMethodName = (method: TAppConnection["method"]) => { switch (method) { @@ -146,6 +151,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case PostgresConnectionMethod.UsernameAndPassword: case MsSqlConnectionMethod.UsernameAndPassword: return "Username & Password"; + case Auth0ConnectionMethod.ClientCredentials: + return "Client Credentials"; default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new Error(`Unhandled App Connection Method: ${method}`); @@ -187,5 +194,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, [AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform, [AppConnection.Camunda]: platformManagedCredentialsNotSupported, - [AppConnection.Vercel]: platformManagedCredentialsNotSupported + [AppConnection.Vercel]: platformManagedCredentialsNotSupported, + [AppConnection.Auth0]: platformManagedCredentialsNotSupported }; diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 9df7037c2..3847eef90 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -11,5 +11,6 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.Vercel]: "Vercel", [AppConnection.Postgres]: "PostgreSQL", [AppConnection.MsSql]: "Microsoft SQL Server", - [AppConnection.Camunda]: "Camunda" + [AppConnection.Camunda]: "Camunda", + [AppConnection.Auth0]: "Auth0" }; diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 3114b4731..025f01734 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -27,6 +27,7 @@ import { TUpdateAppConnectionDTO, TValidateAppConnectionCredentialsSchema } from "./app-connection-types"; +import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0"; import { ValidateAwsConnectionCredentialsSchema } from "./aws"; import { awsConnectionService } from "./aws/aws-connection-service"; import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration"; @@ -65,7 +66,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record>>; @@ -94,6 +101,7 @@ export type TAppConnectionInput = { id: string } & ( | TPostgresConnectionInput | TMsSqlConnectionInput | TCamundaConnectionInput + | TAuth0ConnectionInput ); export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput; @@ -117,7 +125,8 @@ export type TAppConnectionConfig = | THumanitecConnectionConfig | TSqlConnectionConfig | TCamundaConnectionConfig - | TVercelConnectionConfig; + | TVercelConnectionConfig + | TAuth0ConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -130,7 +139,8 @@ export type TValidateAppConnectionCredentialsSchema = | TValidatePostgresConnectionCredentialsSchema | TValidateMsSqlConnectionCredentialsSchema | TValidateCamundaConnectionCredentialsSchema - | TValidateVercelConnectionCredentialsSchema; + | TValidateVercelConnectionCredentialsSchema + | TValidateAuth0ConnectionCredentialsSchema; export type TListAwsConnectionKmsKeys = { connectionId: string; diff --git a/backend/src/services/app-connection/auth0/auth0-connection-enums.ts b/backend/src/services/app-connection/auth0/auth0-connection-enums.ts new file mode 100644 index 000000000..07d725bea --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-enums.ts @@ -0,0 +1,3 @@ +export enum Auth0ConnectionMethod { + ClientCredentials = "client-credentials" +} diff --git a/backend/src/services/app-connection/auth0/auth0-connection-fns.ts b/backend/src/services/app-connection/auth0/auth0-connection-fns.ts new file mode 100644 index 000000000..755df5c50 --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-fns.ts @@ -0,0 +1,90 @@ +import { AxiosError } from "axios"; + +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; +import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; + +import { Auth0ConnectionMethod } from "./auth0-connection-enums"; +import { TAuth0AccessTokenResponse, TAuth0Connection, TAuth0ConnectionConfig } from "./auth0-connection-types"; + +export const getAuth0ConnectionListItem = () => { + return { + name: "Auth0" as const, + app: AppConnection.Auth0 as const, + methods: Object.values(Auth0ConnectionMethod) as [Auth0ConnectionMethod.ClientCredentials] + }; +}; + +const authorizeAuth0Connection = async ({ + clientId, + clientSecret, + domain, + audience +}: TAuth0ConnectionConfig["credentials"]) => { + const instanceUrl = domain.startsWith("http") ? domain : `https://${domain}`; + await blockLocalAndPrivateIpAddresses(instanceUrl); + + const { data } = await request.request({ + method: "POST", + url: `${removeTrailingSlash(instanceUrl)}/oauth/token`, + headers: { "content-type": "application/x-www-form-urlencoded" }, + data: new URLSearchParams({ + grant_type: "client_credentials", // this will need to be resolved if we support methods other than client credentials + client_id: clientId, + client_secret: clientSecret, + audience + }) + }); + + if (data.token_type !== "Bearer") { + throw new Error(`Unhandled token type: ${data.token_type}`); + } + + return { accessToken: data.access_token, expiresAt: data.expires_in * 1000 + Date.now() }; +}; + +export const getAuth0ConnectionAccessToken = async ( + { id, orgId, credentials }: TAuth0Connection, + appConnectionDAL: Pick, + kmsService: Pick +) => { + // just getting a new auth token every time because if permissions change the previous token doesn't have the changes + + const authData = await authorizeAuth0Connection(credentials); + + const updatedCredentials: TAuth0Connection["credentials"] = { + ...credentials, + ...authData + }; + + const encryptedCredentials = await encryptAppConnectionCredentials({ + credentials: updatedCredentials, + orgId, + kmsService + }); + + await appConnectionDAL.updateById(id, { encryptedCredentials }); + + return authData.accessToken; +}; + +export const validateAuth0ConnectionCredentials = async ({ credentials }: TAuth0ConnectionConfig) => { + try { + const { accessToken, expiresAt } = await authorizeAuth0Connection(credentials); + + return { + ...credentials, + accessToken, + expiresAt + }; + } catch (e: unknown) { + throw new BadRequestError({ + message: (e as AxiosError).message ?? `Unable to validate connection: verify credentials` + }); + } +}; diff --git a/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts b/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts new file mode 100644 index 000000000..67992a503 --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-schemas.ts @@ -0,0 +1,94 @@ +import { z } from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { Auth0ConnectionMethod } from "./auth0-connection-enums"; + +export const Auth0ConnectionClientCredentialsInputCredentialsSchema = z.object({ + domain: z.string().trim().min(1, "Domain required").describe(AppConnections.CREDENTIALS.AUTH0_CONNECTION.domain), + clientId: z + .string() + .trim() + .min(1, "Client ID required") + .describe(AppConnections.CREDENTIALS.AUTH0_CONNECTION.clientId), + clientSecret: z + .string() + .trim() + .min(1, "Client Secret required") + .describe(AppConnections.CREDENTIALS.AUTH0_CONNECTION.clientSecret), + audience: z + .string() + .trim() + .url() + .min(1, "Audience required") + .describe(AppConnections.CREDENTIALS.AUTH0_CONNECTION.audience) +}); + +const Auth0ConnectionClientCredentialsOutputCredentialsSchema = z + .object({ + accessToken: z.string(), + expiresAt: z.number() + }) + .merge(Auth0ConnectionClientCredentialsInputCredentialsSchema); + +const BaseAuth0ConnectionSchema = BaseAppConnectionSchema.extend({ + app: z.literal(AppConnection.Auth0) +}); + +export const Auth0ConnectionSchema = z.intersection( + BaseAuth0ConnectionSchema, + z.discriminatedUnion("method", [ + z.object({ + method: z.literal(Auth0ConnectionMethod.ClientCredentials), + credentials: Auth0ConnectionClientCredentialsOutputCredentialsSchema + }) + ]) +); + +export const SanitizedAuth0ConnectionSchema = z.discriminatedUnion("method", [ + BaseAuth0ConnectionSchema.extend({ + method: z.literal(Auth0ConnectionMethod.ClientCredentials), + credentials: Auth0ConnectionClientCredentialsInputCredentialsSchema.pick({ + domain: true, + clientId: true, + audience: true + }) + }) +]); + +export const ValidateAuth0ConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z + .literal(Auth0ConnectionMethod.ClientCredentials) + .describe(AppConnections.CREATE(AppConnection.Auth0).method), + credentials: Auth0ConnectionClientCredentialsInputCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.Auth0).credentials + ) + }) +]); + +export const CreateAuth0ConnectionSchema = ValidateAuth0ConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.Auth0) +); + +export const UpdateAuth0ConnectionSchema = z + .object({ + credentials: Auth0ConnectionClientCredentialsInputCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.Auth0).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Auth0)); + +export const Auth0ConnectionListItemSchema = z.object({ + name: z.literal("Auth0"), + app: z.literal(AppConnection.Auth0), + // the below is preferable but currently breaks with our zod to json schema parser + // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), + methods: z.nativeEnum(Auth0ConnectionMethod).array() +}); diff --git a/backend/src/services/app-connection/auth0/auth0-connection-types.ts b/backend/src/services/app-connection/auth0/auth0-connection-types.ts new file mode 100644 index 000000000..a77bbe40c --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-types.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +import { DiscriminativePick } from "@app/lib/types"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; + +import { + Auth0ConnectionSchema, + CreateAuth0ConnectionSchema, + ValidateAuth0ConnectionCredentialsSchema +} from "./auth0-connection-schemas"; + +export type TAuth0Connection = z.infer; + +export type TAuth0ConnectionInput = z.infer & { + app: AppConnection.Auth0; +}; + +export type TValidateAuth0ConnectionCredentialsSchema = typeof ValidateAuth0ConnectionCredentialsSchema; + +export type TAuth0ConnectionConfig = DiscriminativePick & { + orgId: string; +}; + +export type TAuth0AccessTokenResponse = { + access_token: string; + expires_in: number; + scope: string; + token_type: string; +}; diff --git a/backend/src/services/app-connection/auth0/index.ts b/backend/src/services/app-connection/auth0/index.ts new file mode 100644 index 000000000..310ae3ea8 --- /dev/null +++ b/backend/src/services/app-connection/auth0/index.ts @@ -0,0 +1,4 @@ +export * from "./auth0-connection-enums"; +export * from "./auth0-connection-fns"; +export * from "./auth0-connection-schemas"; +export * from "./auth0-connection-types"; diff --git a/backend/src/services/app-connection/databricks/databricks-connection-fns.ts b/backend/src/services/app-connection/databricks/databricks-connection-fns.ts index fc8da062d..a35cc4aec 100644 --- a/backend/src/services/app-connection/databricks/databricks-connection-fns.ts +++ b/backend/src/services/app-connection/databricks/databricks-connection-fns.ts @@ -1,6 +1,7 @@ import { request } from "@app/lib/config/request"; import { BadRequestError } from "@app/lib/errors"; import { removeTrailingSlash } from "@app/lib/fn"; +import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; @@ -26,6 +27,8 @@ const authorizeDatabricksConnection = async ({ clientSecret, workspaceUrl }: Pick) => { + await blockLocalAndPrivateIpAddresses(workspaceUrl); + const { data } = await request.post( `${removeTrailingSlash(workspaceUrl)}/oidc/v1/token`, "grant_type=client_credentials&scope=all-apis", diff --git a/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts b/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts index d876b9749..2ac58b070 100644 --- a/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts +++ b/backend/src/services/app-connection/databricks/databricks-connection-schemas.ts @@ -16,7 +16,7 @@ export const DatabricksConnectionServicePrincipalInputCredentialsSchema = z.obje workspaceUrl: z.string().trim().url().min(1, "Workspace URL required") }); -export const DatabricksConnectionServicePrincipalOutputCredentialsSchema = z +const DatabricksConnectionServicePrincipalOutputCredentialsSchema = z .object({ accessToken: z.string(), expiresAt: z.number() diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 4195f3dfc..4346ae2c4 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -1,11 +1,11 @@ import { Knex } from "knex"; -import { ProjectType, TProjectKeys, SortDirection } from "@app/db/schemas"; +import { ProjectType, SortDirection, TProjectKeys } from "@app/db/schemas"; import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; +import { OrgServiceActor, TProjectPermission } from "@app/lib/types"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; -import { OrgServiceActor, TProjectPermission } from "@app/lib/types"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; diff --git a/docs/api-reference/endpoints/app-connections/auth0/available.mdx b/docs/api-reference/endpoints/app-connections/auth0/available.mdx new file mode 100644 index 000000000..6694976dc --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/available.mdx @@ -0,0 +1,4 @@ +--- +title: "Available" +openapi: "GET /api/v1/app-connections/auth0/available" +--- diff --git a/docs/api-reference/endpoints/app-connections/auth0/create.mdx b/docs/api-reference/endpoints/app-connections/auth0/create.mdx new file mode 100644 index 000000000..11e003163 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/create.mdx @@ -0,0 +1,9 @@ +--- +title: "Create" +openapi: "POST /api/v1/app-connections/auth0" +--- + + + Check out the configuration docs for [Auth0 Connections](/integrations/app-connections/auth0) to learn how to obtain the + required credentials. + \ No newline at end of file diff --git a/docs/api-reference/endpoints/app-connections/auth0/delete.mdx b/docs/api-reference/endpoints/app-connections/auth0/delete.mdx new file mode 100644 index 000000000..f1e79e125 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/app-connections/auth0/{connectionId}" +--- diff --git a/docs/api-reference/endpoints/app-connections/auth0/get-by-id.mdx b/docs/api-reference/endpoints/app-connections/auth0/get-by-id.mdx new file mode 100644 index 000000000..0b3a1d355 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v1/app-connections/auth0/{connectionId}" +--- diff --git a/docs/api-reference/endpoints/app-connections/auth0/get-by-name.mdx b/docs/api-reference/endpoints/app-connections/auth0/get-by-name.mdx new file mode 100644 index 000000000..691791523 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/get-by-name.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by Name" +openapi: "GET /api/v1/app-connections/auth0/connection-name/{connectionName}" +--- diff --git a/docs/api-reference/endpoints/app-connections/auth0/list.mdx b/docs/api-reference/endpoints/app-connections/auth0/list.mdx new file mode 100644 index 000000000..9590b0487 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/app-connections/auth0" +--- diff --git a/docs/api-reference/endpoints/app-connections/auth0/update.mdx b/docs/api-reference/endpoints/app-connections/auth0/update.mdx new file mode 100644 index 000000000..e7046ba19 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/auth0/update.mdx @@ -0,0 +1,9 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/app-connections/auth0/{connectionId}" +--- + + + Check out the configuration docs for [Auth0 Connections](/integrations/app-connections/auth0) to learn how to obtain the + required credentials. + \ No newline at end of file diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/create.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/create.mdx new file mode 100644 index 000000000..c279da181 --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/create.mdx @@ -0,0 +1,9 @@ +--- +title: "Create" +openapi: "POST /api/v2/secret-rotations/auth0-client-secret" +--- + + + Check out the configuration docs for [Auth0 Client Secret Rotations](/documentation/platform/secret-rotation/auth0-client-secret) to learn how to obtain the + required parameters. + \ No newline at end of file diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/delete.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/delete.mdx new file mode 100644 index 000000000..8cf4227d7 --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v2/secret-rotations/auth0-client-secret/{rotationId}" +--- diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-id.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-id.mdx new file mode 100644 index 000000000..60d9ad0a1 --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v2/secret-rotations/auth0-client-secret/{rotationId}" +--- diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-name.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-name.mdx new file mode 100644 index 000000000..e513f74ec --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-name.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by Name" +openapi: "GET /api/v2/secret-rotations/auth0-client-secret/rotation-name/{rotationName}" +--- diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-generated-credentials-by-id.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-generated-credentials-by-id.mdx new file mode 100644 index 000000000..a4489053f --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/get-generated-credentials-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get Credentials by ID" +openapi: "GET /api/v2/secret-rotations/auth0-client-secret/{rotationId}/generated-credentials" +--- diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/list.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/list.mdx new file mode 100644 index 000000000..a1b1a70cc --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v2/secret-rotations/auth0-client-secret" +--- diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/rotate-secrets.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/rotate-secrets.mdx new file mode 100644 index 000000000..45349ca30 --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/rotate-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Rotate Secrets" +openapi: "POST /api/v2/secret-rotations/auth0-client-secret/{rotationId}/rotate-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/update.mdx b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/update.mdx new file mode 100644 index 000000000..514730100 --- /dev/null +++ b/docs/api-reference/endpoints/secret-rotations/auth0-client-secret/update.mdx @@ -0,0 +1,9 @@ +--- +title: "Update" +openapi: "PATCH /api/v2/secret-rotations/auth0-client-secret/{rotationId}" +--- + + + Check out the configuration docs for [Auth0 Client Secret Rotations](/documentation/platform/secret-rotation/auth0-client-secret) to learn how to obtain the + required parameters. + \ No newline at end of file diff --git a/docs/api-reference/endpoints/secret-rotations/mssql-credentials/create.mdx b/docs/api-reference/endpoints/secret-rotations/mssql-credentials/create.mdx index 8b6c8bc88..5ed8c08b9 100644 --- a/docs/api-reference/endpoints/secret-rotations/mssql-credentials/create.mdx +++ b/docs/api-reference/endpoints/secret-rotations/mssql-credentials/create.mdx @@ -5,6 +5,6 @@ openapi: "POST /api/v2/secret-rotations/mssql-credentials" Check out the configuration docs for [Microsoft SQL Server - Credentials Rotations](/documentation/platform/secret-rotation/mssql) to learn how to obtain the + Credentials Rotations](/documentation/platform/secret-rotation/mssql-credentials) to learn how to obtain the required parameters. diff --git a/docs/api-reference/endpoints/secret-rotations/mssql-credentials/update.mdx b/docs/api-reference/endpoints/secret-rotations/mssql-credentials/update.mdx index 4dd5f3267..027d83a1f 100644 --- a/docs/api-reference/endpoints/secret-rotations/mssql-credentials/update.mdx +++ b/docs/api-reference/endpoints/secret-rotations/mssql-credentials/update.mdx @@ -5,6 +5,6 @@ openapi: "PATCH /api/v2/secret-rotations/mssql-credentials/{rotationId}" Check out the configuration docs for [Microsoft SQL Server - Credentials Rotations](/documentation/platform/secret-rotation/mssql) to learn how to obtain the + Credentials Rotations](/documentation/platform/secret-rotation/mssql-credentials) to learn how to obtain the required parameters. diff --git a/docs/api-reference/endpoints/secret-rotations/postgres-credentials/create.mdx b/docs/api-reference/endpoints/secret-rotations/postgres-credentials/create.mdx index fabd51f94..e22b89f81 100644 --- a/docs/api-reference/endpoints/secret-rotations/postgres-credentials/create.mdx +++ b/docs/api-reference/endpoints/secret-rotations/postgres-credentials/create.mdx @@ -5,6 +5,6 @@ openapi: "POST /api/v2/secret-rotations/postgres-credentials" Check out the configuration docs for [PostgreSQL - Credentials Rotations](/documentation/platform/secret-rotation/postgres) to learn how to obtain the + Credentials Rotations](/documentation/platform/secret-rotation/postgres-credentials) to learn how to obtain the required parameters. \ No newline at end of file diff --git a/docs/api-reference/endpoints/secret-rotations/postgres-credentials/update.mdx b/docs/api-reference/endpoints/secret-rotations/postgres-credentials/update.mdx index 7aebcb72c..46438427f 100644 --- a/docs/api-reference/endpoints/secret-rotations/postgres-credentials/update.mdx +++ b/docs/api-reference/endpoints/secret-rotations/postgres-credentials/update.mdx @@ -5,6 +5,6 @@ openapi: "PATCH /api/v2/secret-rotations/postgres-credentials/{rotationId}" Check out the configuration docs for [PostgreSQL - Credentials Rotations](/documentation/platform/secret-rotation/postgres) to learn how to obtain the + Credentials Rotations](/documentation/platform/secret-rotation/postgres-credentials) to learn how to obtain the required parameters. \ No newline at end of file diff --git a/docs/changelog/overview.mdx b/docs/changelog/overview.mdx index 822a8b24a..6d1440ff1 100644 --- a/docs/changelog/overview.mdx +++ b/docs/changelog/overview.mdx @@ -173,7 +173,7 @@ The changelog below reflects new product developments and updates on a monthly b - Replaced internal [Winston](https://github.com/winstonjs/winston) with [Pino](https://github.com/pinojs/pino) logging library with external logging to AWS CloudWatch - Added admin panel to self-hosting experience. -- Released [secret rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview) feature with preliminary support for rotating [SendGrid](https://infisical.com/docs/documentation/platform/secret-rotation/sendgrid), [PostgreSQL/CockroachDB](https://infisical.com/docs/documentation/platform/secret-rotation/postgres), and [MySQL/MariaDB](https://infisical.com/docs/documentation/platform/secret-rotation/mysql) credentials. +- Released [secret rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview) feature with preliminary support for rotating [SendGrid](https://infisical.com/docs/documentation/platform/secret-rotation/sendgrid), [PostgreSQL/CockroachDB](https://infisical.com/docs/documentation/platform/secret-rotation/postgres-credentials), and [MySQL/MariaDB](https://infisical.com/docs/documentation/platform/secret-rotation/mysql) credentials. - Released secret reminders feature. ## Oct 2023 diff --git a/docs/documentation/platform/secret-rotation/auth0-client-secret.mdx b/docs/documentation/platform/secret-rotation/auth0-client-secret.mdx new file mode 100644 index 000000000..362bc24b2 --- /dev/null +++ b/docs/documentation/platform/secret-rotation/auth0-client-secret.mdx @@ -0,0 +1,148 @@ +--- +title: "Auth0 Client Secret" +description: "Learn how to automatically rotate Auth0 Client Secrets." +--- + + + Due to how Auth0 client secrets are rotated, retired credentials will not be able to + authenticate with Auth0 during their [inactive period](./overview#how-rotation-works). + + This is a limitation of the Auth0 platform and cannot be + rectified by Infisical. + + +## Prerequisites + +1. Create an [Auth0 Connection](/integrations/app-connections/auth0) with the required **Secret Rotation** audience and permissions +2. Copy the **Client ID** of the application in Auth0 you want to rotate the Client Secret for: + + ![Auth0 Client ID](/images/secret-rotations-v2/auth0-client-secret/auth0-app-client-id.png) + + +## Create an Auth0 Client Secret Rotation in Infisical + + + + 1. Navigate to your Secret Manager Project's Dashboard and select **Add Secret Rotation** from the actions dropdown. + ![Secret Manager Dashboard](/images/secret-rotations-v2/generic/add-secret-rotation.png) + + 2. Select the **Auth0 Client Secret** option. + ![Select Auth0 Client Secret](/images/secret-rotations-v2/auth0-client-secret/select-auth0-client-secret-option.png) + + 3. Select the **Auth0 Connection** to use and configure the rotation behavior. Then click **Next**. + ![Rotation Configuration](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-configuration.png) + + - **Auth0 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. Input the Client ID of the Auth0 application acquired above that will have its Client Secret rotated. Then click **Next**. + ![Rotation Parameters](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-parameters.png) + + - **Client ID** - the Client ID of the application whose Client Secret will be rotated. + + 5. Specify the secret names that the client credentials should be mapped to. Then click **Next**. + ![Rotation Secrets Mapping](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-secrets-mapping.png) + + - **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**. + ![Rotation Details](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-details.png) + + - **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**. + ![Rotation Review](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-confirm.png) + + 8. Your **Auth0 Client Secret** credentials are now available for use via the mapped secrets. + ![Rotation Created](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-created.png) + + + To create an Auth0 Client Secret Rotation, make an API request to the [Create Auth0 + Client Secret Rotation](/api-reference/endpoints/secret-rotations/auth0-client-secret/create) API endpoint. + + ### Sample request + + ```bash Request + curl --request POST \ + --url https://us.infisical.com/api/v2/secret-rotations/auth0-client-secret \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "my-pg-rotation", + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "description": "my database credentials rotation", + "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "environment": "dev", + "secretPath": "/", + "isAutoRotationEnabled": true, + "rotationInterval": 30, + "rotateAtUtc": { + "hours": 0, + "minutes": 0 + }, + "parameters": { + "clientId": "...", + }, + "secretsMapping": { + "clientId": "AUTH0_CLIENT_ID", + "clientSecret": "AUTH0_CLIENT_SECRET" + } + }' + ``` + + ### Sample response + + ```bash Response + { + "secretRotation": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "name": "my-pg-rotation", + "description": "my database credentials rotation", + "secretsMapping": { + "clientId": "AUTH0_CLIENT_ID", + "clientSecret": "AUTH0_CLIENT_SECRET" + }, + "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": 30, + "rotationStatus": "success", + "lastRotationAttemptedAt": "2023-11-07T05:31:56Z", + "lastRotatedAt": "2023-11-07T05:31:56Z", + "lastRotationJobId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "nextRotationAt": "2023-11-07T05:31:56Z", + "connection": { + "app": "auth0", + "name": "my-auth0-connection", + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" + }, + "environment": { + "slug": "dev", + "name": "Development", + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" + }, + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "folder": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "path": "/" + }, + "rotateAtUtc": { + "hours": 0, + "minutes": 0 + }, + "lastRotationMessage": null, + "type": "auth0-client-secret", + "parameters": { + "clientId": "...", + } + } + } + ``` + + diff --git a/docs/documentation/platform/secret-rotation/mssql.mdx b/docs/documentation/platform/secret-rotation/mssql-credentials.mdx similarity index 100% rename from docs/documentation/platform/secret-rotation/mssql.mdx rename to docs/documentation/platform/secret-rotation/mssql-credentials.mdx diff --git a/docs/documentation/platform/secret-rotation/postgres.mdx b/docs/documentation/platform/secret-rotation/postgres-credentials.mdx similarity index 100% rename from docs/documentation/platform/secret-rotation/postgres.mdx rename to docs/documentation/platform/secret-rotation/postgres-credentials.mdx diff --git a/docs/images/app-connections/auth0/auth0-audience.png b/docs/images/app-connections/auth0/auth0-audience.png new file mode 100644 index 000000000..1c5226aac Binary files /dev/null and b/docs/images/app-connections/auth0/auth0-audience.png differ diff --git a/docs/images/app-connections/auth0/auth0-client-credentials.png b/docs/images/app-connections/auth0/auth0-client-credentials.png new file mode 100644 index 000000000..3fea1096f Binary files /dev/null and b/docs/images/app-connections/auth0/auth0-client-credentials.png differ diff --git a/docs/images/app-connections/auth0/auth0-dashboard-applications.png b/docs/images/app-connections/auth0/auth0-dashboard-applications.png new file mode 100644 index 000000000..225113a04 Binary files /dev/null and b/docs/images/app-connections/auth0/auth0-dashboard-applications.png differ diff --git a/docs/images/app-connections/auth0/auth0-secret-rotation-api-selection.png b/docs/images/app-connections/auth0/auth0-secret-rotation-api-selection.png new file mode 100644 index 000000000..1653a498b Binary files /dev/null and b/docs/images/app-connections/auth0/auth0-secret-rotation-api-selection.png differ diff --git a/docs/images/app-connections/auth0/auth0-select-m2m.png b/docs/images/app-connections/auth0/auth0-select-m2m.png new file mode 100644 index 000000000..2d83c6de9 Binary files /dev/null and b/docs/images/app-connections/auth0/auth0-select-m2m.png differ diff --git a/docs/images/app-connections/auth0/client-credentials-create.png b/docs/images/app-connections/auth0/client-credentials-create.png new file mode 100644 index 000000000..2c4466cd2 Binary files /dev/null and b/docs/images/app-connections/auth0/client-credentials-create.png differ diff --git a/docs/images/app-connections/auth0/client_credentials_connection.png b/docs/images/app-connections/auth0/client_credentials_connection.png new file mode 100644 index 000000000..a4b115523 Binary files /dev/null and b/docs/images/app-connections/auth0/client_credentials_connection.png differ diff --git a/docs/images/app-connections/auth0/select-auth0-connection.png b/docs/images/app-connections/auth0/select-auth0-connection.png new file mode 100644 index 000000000..47d72d2a5 Binary files /dev/null and b/docs/images/app-connections/auth0/select-auth0-connection.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-app-client-id.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-app-client-id.png new file mode 100644 index 000000000..5540b7312 Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-app-client-id.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-configuration.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-configuration.png new file mode 100644 index 000000000..f254c244c Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-configuration.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-confirm.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-confirm.png new file mode 100644 index 000000000..1dcd4715c Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-confirm.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-created.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-created.png new file mode 100644 index 000000000..fd82360da Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-created.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-details.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-details.png new file mode 100644 index 000000000..42c49f808 Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-details.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-parameters.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-parameters.png new file mode 100644 index 000000000..19ed0e9e7 Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-parameters.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-secrets-mapping.png b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-secrets-mapping.png new file mode 100644 index 000000000..c91e54559 Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-secrets-mapping.png differ diff --git a/docs/images/secret-rotations-v2/auth0-client-secret/select-auth0-client-secret-option.png b/docs/images/secret-rotations-v2/auth0-client-secret/select-auth0-client-secret-option.png new file mode 100644 index 000000000..d042f46fb Binary files /dev/null and b/docs/images/secret-rotations-v2/auth0-client-secret/select-auth0-client-secret-option.png differ diff --git a/docs/integrations/app-connections/auth0.mdx b/docs/integrations/app-connections/auth0.mdx new file mode 100644 index 000000000..9dd321d04 --- /dev/null +++ b/docs/integrations/app-connections/auth0.mdx @@ -0,0 +1,101 @@ +--- +title: "Auth0 Connection" +description: "Learn how to configure a Auth0 Connection for Infisical." +--- + +Infisical supports the use of [Client Credentials](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow) to connect with your Auth0 applications. + +## Configure a Machine-to-Machine Application in Auth0 + + + + Navigate to the **Applications** page in Auth0 via the sidebar and click **Create Application**. + ![Applications Page](/images/app-connections/auth0/auth0-dashboard-applications.png) + + + Give your application a name and select **Machine-to-Machine** for the application type. + + ![Create Machine-to-Machine Application](/images/app-connections/auth0/auth0-select-m2m.png) + + + Depending on your connection use case, authorize your application for the applicable API and grant the relevant permissions. Once done, click **Authorize**. + + + + Select the **Auth0 Management API** option from the dropdown and grant the `update:client_keys` permission. + ![Secret Rotation Authorization](/images/app-connections/auth0/auth0-secret-rotation-api-selection.png) + + + + + On your application page, select the **Settings** tab and copy the **Domain**, **Client ID** and **Client Secret** for later. + + ![Client Credentials](/images/app-connections/auth0/auth0-client-credentials.png) + + + Next, select the **APIs** tab and copy the **API Identifier**. + ![Application Audience](/images/app-connections/auth0/auth0-audience.png) + + + +## Setup Auth0 Connection in Infisical + + + + 1. Navigate to the App Connections tab on the Organization Settings page. + ![App Connections Tab](/images/app-connections/general/add-connection.png) + + 2. Select the **Auth0 Connection** option. + ![Select Auth0 Connection](/images/app-connections/auth0/select-auth0-connection.png) + + 3. Select the **Client Credentials** method option and provide the details obtained from the previous section and press **Connect to Auth0**. + ![Create Auth0 Connection](/images/app-connections/auth0/client-credentials-create.png) + + 4. Your **Auth0 Connection** is now available for use. + ![Assume Role Auth0 Connection](/images/app-connections/auth0/client_credentials_connection.png) + + + To create a Auth0 Connection, make an API request to the [Create Auth0 + Connection](/api-reference/endpoints/app-connections/auth0/create) API endpoint. + + ### Sample request + + ```bash Request + curl --request POST \ + --url https://app.infisical.com/api/v1/app-connections/auth0 \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "my-auth0-connection", + "method": "client-credentials", + "credentials": { + "domain": "xxx-xxxxxxxxx.us.auth0.com", + "clientId": "...", + "clientSecret": "...", + "audience": "https://xxx-xxxxxxxxx.us.auth0.com/api/v2/" + }, + }' + ``` + + ### Sample response + + ```bash Response + { + "appConnection": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "name": "my-auth0-connection", + "version": 1, + "orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "createdAt": "2023-11-07T05:31:56Z", + "updatedAt": "2023-11-07T05:31:56Z", + "app": "auth0", + "method": "client-credentials", + "credentials": { + "domain": "xxx-xxxxxxxxx.us.auth0.com", + "clientId": "...", + "audience": "https://xxx-xxxxxxxxx.us.auth0.com/api/v2/" + } + } + } + ``` + + diff --git a/docs/mint.json b/docs/mint.json index d0ad859b7..beb1dc563 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -178,8 +178,9 @@ "group": "Secret Rotation", "pages": [ "documentation/platform/secret-rotation/overview", - "documentation/platform/secret-rotation/postgres", - "documentation/platform/secret-rotation/mssql" + "documentation/platform/secret-rotation/auth0-client-secret", + "documentation/platform/secret-rotation/postgres-credentials", + "documentation/platform/secret-rotation/mssql-credentials" ] }, { @@ -414,6 +415,7 @@ { "group": "Connections", "pages": [ + "integrations/app-connections/auth0", "integrations/app-connections/aws", "integrations/app-connections/azure-app-configuration", "integrations/app-connections/azure-key-vault", @@ -836,6 +838,19 @@ "pages": [ "api-reference/endpoints/secret-rotations/list", "api-reference/endpoints/secret-rotations/options", + { + "group": "Auth0 Client Secret", + "pages": [ + "api-reference/endpoints/secret-rotations/auth0-client-secret/create", + "api-reference/endpoints/secret-rotations/auth0-client-secret/delete", + "api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-id", + "api-reference/endpoints/secret-rotations/auth0-client-secret/get-by-name", + "api-reference/endpoints/secret-rotations/auth0-client-secret/get-generated-credentials-by-id", + "api-reference/endpoints/secret-rotations/auth0-client-secret/list", + "api-reference/endpoints/secret-rotations/auth0-client-secret/rotate-secrets", + "api-reference/endpoints/secret-rotations/auth0-client-secret/update" + ] + }, { "group": "Microsoft SQL Server Credentials", "pages": [ @@ -880,6 +895,18 @@ "pages": [ "api-reference/endpoints/app-connections/list", "api-reference/endpoints/app-connections/options", + { + "group": "Auth0", + "pages": [ + "api-reference/endpoints/app-connections/auth0/list", + "api-reference/endpoints/app-connections/auth0/available", + "api-reference/endpoints/app-connections/auth0/get-by-id", + "api-reference/endpoints/app-connections/auth0/get-by-name", + "api-reference/endpoints/app-connections/auth0/create", + "api-reference/endpoints/app-connections/auth0/update", + "api-reference/endpoints/app-connections/auth0/delete" + ] + }, { "group": "AWS", "pages": [ diff --git a/frontend/public/images/integrations/Auth0.png b/frontend/public/images/integrations/Auth0.png new file mode 100644 index 000000000..e86d76c06 Binary files /dev/null and b/frontend/public/images/integrations/Auth0.png differ diff --git a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx index 8a824d101..210257d7d 100644 --- a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx @@ -1,4 +1,6 @@ import { useState } from "react"; +import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms"; import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader"; @@ -54,7 +56,24 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }: selectedRotation ? ( ) : ( - "Add Secret Rotation" +
+ Add Secret Rotation + +
+ + Docs + +
+
+
) } onPointerDownOutside={(e) => e.preventDefault()} diff --git a/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx b/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx index 319607d70..db51e27ca 100644 --- a/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx +++ b/frontend/src/components/secret-rotations-v2/SecretRotationV2ModalHeader.tsx @@ -24,7 +24,7 @@ export const SecretRotationV2ModalHeader = ({ type, isConfigured }: Props) => { {destinationDetails.name} Rotation diff --git a/frontend/src/components/secret-rotations-v2/SecretRotationV2Select.tsx b/frontend/src/components/secret-rotations-v2/SecretRotationV2Select.tsx index c464279ed..5679940fd 100644 --- a/frontend/src/components/secret-rotations-v2/SecretRotationV2Select.tsx +++ b/frontend/src/components/secret-rotations-v2/SecretRotationV2Select.tsx @@ -24,17 +24,7 @@ export const SecretRotationV2Select = ({ onSelect }: Props) => { return (
{secretRotationOptions?.map(({ type }) => { - const { image, name } = SECRET_ROTATION_MAP[type]; - - let size: number; - - switch (type) { - case SecretRotation.MsSqlCredentials: - size = 50; - break; - default: - size = 45; - } + const { image, name, size } = SECRET_ROTATION_MAP[type]; return (