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..6debd2402 --- /dev/null +++ b/backend/src/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns.ts @@ -0,0 +1,104 @@ +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 { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; +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 { audience } = connection.credentials; + await blockLocalAndPrivateIpAddresses(audience); + const clientSecret = generatePassword(); + + await request.request({ + method: "PATCH", + url: `${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); + const { audience } = connection.credentials; + await blockLocalAndPrivateIpAddresses(audience); + + // we just trigger an auth0 rotation to negate our credentials + await request.request({ + method: "POST", + url: `${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..3a0ba265b --- /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 1f55ac526..a828acb32 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 }); @@ -740,32 +754,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, @@ -773,9 +792,9 @@ export const secretRotationV2ServiceFactory = ({ kmsService }); - await rotationFactory.revokeCredentials(generatedCredentials, async () => deleteTransaction); + await rotationFactory.revokeCredentials(generatedCredentials, deleteTransaction); } else { - await deleteTransaction; + await deleteTransaction(); } if (deleteSecrets) { @@ -840,10 +859,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]; @@ -851,7 +874,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 7c0f47efe..125564ab3 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1782,6 +1782,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.", @@ -1997,12 +2003,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 9d8585361..a5aa66cb3 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1549,7 +1549,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 1d400ee90..7d60ac8f8 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, @@ -56,6 +57,7 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedPostgresConnectionSchema.options, ...SanitizedMsSqlConnectionSchema.options, ...SanitizedCamundaConnectionSchema.options, + ...SanitizedAuth0ConnectionSchema.options, ...SanitizedAzureClientSecretsConnectionSchema.options ]); @@ -72,6 +74,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ PostgresConnectionListItemSchema, MsSqlConnectionListItemSchema, CamundaConnectionListItemSchema, + Auth0ConnectionListItemSchema, AzureClientSecretsConnectionListItemSchema ]); 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..db17c8eac --- /dev/null +++ b/backend/src/server/routes/v1/app-connection-routers/auth0-connection-router.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; + +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + CreateAuth0ConnectionSchema, + SanitizedAuth0ConnectionSchema, + UpdateAuth0ConnectionSchema +} from "@app/services/app-connection/auth0"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; + +export const registerAuth0ConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.Auth0, + server, + sanitizedResponseSchema: SanitizedAuth0ConnectionSchema, + createSchema: CreateAuth0ConnectionSchema, + updateSchema: UpdateAuth0ConnectionSchema + }); + + // The below endpoints are not exposed and for Infisical App use + + server.route({ + method: "GET", + url: `/:connectionId/clients`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z.object({ + clients: z.object({ name: z.string(), id: z.string() }).array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + + const clients = await server.services.appConnection.auth0.listClients(connectionId, req.permission); + + return { clients }; + } + }); +}; 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 0c60d2311..2732daa77 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"; @@ -30,5 +31,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { getPostgresConnectionListItem(), getMsSqlConnectionListItem(), getCamundaConnectionListItem(), - getAzureClientSecretsConnectionListItem() + getAzureClientSecretsConnectionListItem(), + getAuth0ConnectionListItem() ].sort((a, b) => a.name.localeCompare(b.name)); }; @@ -115,27 +117,30 @@ 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.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.AzureClientSecrets]: - validateAzureClientSecretsConnectionCredentials 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.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.AzureClientSecrets]: + validateAzureClientSecretsConnectionCredentials as TAppConnectionCredentialsValidator + }; + + return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); +}; export const getAppConnectionMethodName = (method: TAppConnection["method"]) => { switch (method) { @@ -163,6 +168,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}`); @@ -206,5 +213,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported, [AppConnection.Camunda]: platformManagedCredentialsNotSupported, [AppConnection.Vercel]: platformManagedCredentialsNotSupported, - [AppConnection.AzureClientSecrets]: platformManagedCredentialsNotSupported + [AppConnection.AzureClientSecrets]: 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 ca30577ee..d2d1c7530 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -13,5 +13,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 804ead1c4..f32f9e044 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -14,6 +14,7 @@ import { TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM, validateAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; +import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TAppConnectionDALFactory } from "./app-connection-dal"; @@ -27,6 +28,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"; @@ -70,7 +72,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record>>; @@ -110,6 +117,7 @@ export type TAppConnectionInput = { id: string } & ( | TMsSqlConnectionInput | TCamundaConnectionInput | TAzureClientSecretsConnectionInput + | TAuth0ConnectionInput ); export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput; @@ -132,10 +140,11 @@ export type TAppConnectionConfig = | TDatabricksConnectionConfig | THumanitecConnectionConfig | TTerraformCloudConnectionConfig - | TVercelConnectionConfig | TSqlConnectionConfig | TCamundaConnectionConfig - | TAzureClientSecretsConnectionConfig; + | TAzureClientSecretsConnectionConfig + | TVercelConnectionConfig + | TAuth0ConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -148,9 +157,10 @@ export type TValidateAppConnectionCredentialsSchema = | TValidatePostgresConnectionCredentialsSchema | TValidateMsSqlConnectionCredentialsSchema | TValidateCamundaConnectionCredentialsSchema - | TValidateTerraformCloudConnectionCredentialsSchema | TValidateVercelConnectionCredentialsSchema - | TValidateAzureClientSecretsConnectionCredentialsSchema; + | TValidateTerraformCloudConnectionCredentialsSchema + | TValidateAzureClientSecretsConnectionCredentialsSchema + | 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..5a9989b43 --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-fns.ts @@ -0,0 +1,97 @@ +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, + // cap token lifespan to 10 minutes + expiresAt: Math.min(data.expires_in * 1000, 600000) + Date.now() + }; +}; + +export const getAuth0ConnectionAccessToken = async ( + { id, orgId, credentials }: TAuth0Connection, + appConnectionDAL: Pick, + kmsService: Pick +) => { + const { expiresAt, accessToken } = credentials; + + // get new token if expired or less than 5 minutes until expiry + if (Date.now() < expiresAt - 300000) { + return accessToken; + } + + 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 Error).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-service.ts b/backend/src/services/app-connection/auth0/auth0-connection-service.ts new file mode 100644 index 000000000..693c55ea6 --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-service.ts @@ -0,0 +1,71 @@ +import { request } from "@app/lib/config/request"; +import { OrgServiceActor } from "@app/lib/types"; +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 { getAuth0ConnectionAccessToken } from "@app/services/app-connection/auth0/auth0-connection-fns"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; + +import { TAuth0Connection, TAuth0ListClient, TAuth0ListClientsResponse } from "./auth0-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +const listAuth0Clients = async ( + appConnection: TAuth0Connection, + appConnectionDAL: Pick, + kmsService: Pick +) => { + const accessToken = await getAuth0ConnectionAccessToken(appConnection, appConnectionDAL, kmsService); + + const { audience, clientId: connectionClientId } = appConnection.credentials; + await blockLocalAndPrivateIpAddresses(audience); + + const clients: TAuth0ListClient[] = []; + let hasMore = true; + let page = 0; + + while (hasMore) { + // eslint-disable-next-line no-await-in-loop + const { data: clientsPage } = await request.get(`${audience}clients`, { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json" + }, + params: { + include_totals: true, + per_page: 100, + page + } + }); + + clients.push(...clientsPage.clients); + page += 1; + hasMore = clientsPage.total > clients.length; + } + + return ( + clients.filter((client) => client.client_id !== connectionClientId && client.name !== "All Applications") ?? [] + ); +}; + +export const auth0ConnectionService = ( + getAppConnection: TGetAppConnectionFunc, + appConnectionDAL: Pick, + kmsService: Pick +) => { + const listClients = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.Auth0, connectionId, actor); + + const clients = await listAuth0Clients(appConnection, appConnectionDAL, kmsService); + + return clients.map((client) => ({ id: client.client_id, name: client.name })); + }; + + return { + listClients + }; +}; 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..ebb601946 --- /dev/null +++ b/backend/src/services/app-connection/auth0/auth0-connection-types.ts @@ -0,0 +1,39 @@ +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; +}; + +export type TAuth0ListClient = { + name: string; + client_id: string; +}; + +export type TAuth0ListClientsResponse = { + total: number; + clients: TAuth0ListClient[]; +}; 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/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..3845a3879 --- /dev/null +++ b/docs/documentation/platform/secret-rotation/auth0-client-secret.mdx @@ -0,0 +1,154 @@ +--- +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 + +- Create an [Auth0 Connection](/integrations/app-connections/auth0) with the required **Secret Rotation** audience and permissions + +## 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. + + Due to Auth0 Client Secret Rotations rotating a single credential set, auto-rotation may result in service interruptions. If you need to ensure service continuity, we recommend disabling this option. + + + + 4. Select the Auth0 application whose Client Secret you want to rotate. Then click **Next**. + ![Rotation Parameters](/images/secret-rotations-v2/auth0-client-secret/auth0-client-secret-parameters.png) + + 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. + + You will first need the **Client ID** of the Auth0 application you want to rotate the secret for. This can be obtained from the Applications dashboard. + ![Auth0 Client ID](/images/secret-rotations-v2/auth0-client-secret/auth0-app-client-id.png) + + + ### 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-auth0-rotation", + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "description": "my client secret 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" + } + }' + ``` + + + Due to Auth0 Client Secret Rotations rotating a single credential set, auto-rotation may result in service interruptions. If you need to ensure service continuity, we recommend disabling this option. + + + ### Sample response + + ```bash Response + { + "secretRotation": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "name": "my-auth0-rotation", + "description": "my client secret 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/overview.mdx b/docs/documentation/platform/secret-rotation/overview.mdx index ee1fd9e42..d11334440 100644 --- a/docs/documentation/platform/secret-rotation/overview.mdx +++ b/docs/documentation/platform/secret-rotation/overview.mdx @@ -47,6 +47,11 @@ Each set of credentials transitions through three distinct states: - **Active**: The primary credentials that will be used for new connections - **Inactive**: These credentials are still valid but are no longer issued for new connections + + Some rotation providers utilize a single credential set due to technical constraints. As a result, inactive credentials for these providers will immediately become invalid once rotated. + + To avoid service interruptions, Infisical recommends manually rotating these credentials to prevent downtime. + - **Revoked**: Permanently invalidated and deleted from the system ### Rotation Cycle Example (30-Day Interval) @@ -95,3 +100,16 @@ Using a __30-Day__ rotation interval as an example, here's how the process unfol - [PostgreSQL Credentials](./postgres) - [Microsoft SQL Server Credentials](./mssql) + +## FAQ + + + + Some credential providers have limitations that affect rotation patterns: + + - The third-party provider's API only supports managing one active credential set at a time + - The specific use-case (such as personal login accounts) is inherently limited to a single active credential + + In either scenario, when service continuity is critical, Infisical recommends disabling auto-rotation and performing manual credential rotation during scheduled maintenance windows. + + 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..42f3ee1e4 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..1f2fc64f9 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..42e78cb66 --- /dev/null +++ b/docs/integrations/app-connections/auth0.mdx @@ -0,0 +1,101 @@ +--- +title: "Auth0 Connection" +description: "Learn how to configure an 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` and `read:clients` 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 3b70602d2..c224ee23c 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", @@ -844,6 +846,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": [ @@ -888,6 +903,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 (