Compare commits
9 Commits
fix/improv
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
ae1ee25687 | |||
4650ba9fdd | |||
e7742afcd3 | |||
927eb0407d | |||
17ddb79def | |||
5eb9a1a667 | |||
03ad6f822a | |||
98447e9402 | |||
581e4b35f9 |
@ -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.
|
- **[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.
|
- **[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 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.
|
- **[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.
|
- **[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.
|
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.OidcConfig, "jwtSignatureAlgorithm"))) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
|
||||||
|
t.string("jwtSignatureAlgorithm").defaultTo(OIDCJWTSignatureAlgorithm.RS256).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.OidcConfig, "jwtSignatureAlgorithm")) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
|
||||||
|
t.dropColumn("jwtSignatureAlgorithm");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -30,9 +30,10 @@ export const OidcConfigsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
lastUsed: z.date().nullable().optional(),
|
lastUsed: z.date().nullable().optional(),
|
||||||
manageGroupMemberships: z.boolean().default(false),
|
|
||||||
encryptedOidcClientId: zodBuffer,
|
encryptedOidcClientId: zodBuffer,
|
||||||
encryptedOidcClientSecret: zodBuffer
|
encryptedOidcClientSecret: zodBuffer,
|
||||||
|
manageGroupMemberships: z.boolean().default(false),
|
||||||
|
jwtSignatureAlgorithm: z.string().default("RS256")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
||||||
|
@ -12,7 +12,7 @@ import RedisStore from "connect-redis";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OidcConfigsSchema } from "@app/db/schemas";
|
import { OidcConfigsSchema } from "@app/db/schemas";
|
||||||
import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types";
|
import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@ -30,7 +30,8 @@ const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({
|
|||||||
orgId: true,
|
orgId: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
allowedEmailDomains: true,
|
allowedEmailDomains: true,
|
||||||
manageGroupMemberships: true
|
manageGroupMemberships: true,
|
||||||
|
jwtSignatureAlgorithm: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||||
@ -170,7 +171,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
isActive: true,
|
isActive: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
allowedEmailDomains: true,
|
allowedEmailDomains: true,
|
||||||
manageGroupMemberships: true
|
manageGroupMemberships: true,
|
||||||
|
jwtSignatureAlgorithm: true
|
||||||
}).extend({
|
}).extend({
|
||||||
clientId: z.string(),
|
clientId: z.string(),
|
||||||
clientSecret: z.string()
|
clientSecret: z.string()
|
||||||
@ -225,7 +227,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientId: z.string().trim(),
|
clientId: z.string().trim(),
|
||||||
clientSecret: z.string().trim(),
|
clientSecret: z.string().trim(),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
manageGroupMemberships: z.boolean().optional()
|
manageGroupMemberships: z.boolean().optional(),
|
||||||
|
jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional()
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.merge(z.object({ orgSlug: z.string() })),
|
.merge(z.object({ orgSlug: z.string() })),
|
||||||
@ -292,7 +295,11 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientSecret: z.string().trim(),
|
clientSecret: z.string().trim(),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
orgSlug: z.string().trim(),
|
orgSlug: z.string().trim(),
|
||||||
manageGroupMemberships: z.boolean().optional().default(false)
|
manageGroupMemberships: z.boolean().optional().default(false),
|
||||||
|
jwtSignatureAlgorithm: z
|
||||||
|
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||||
|
.optional()
|
||||||
|
.default(OIDCJWTSignatureAlgorithm.RS256)
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
||||||
|
@ -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
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
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 { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-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<void>
|
(server: FastifyZodProvider) => Promise<void>
|
||||||
> = {
|
> = {
|
||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
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 { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-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";
|
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", [
|
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
|
Auth0ClientSecretRotationListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
@ -165,7 +165,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
allowedEmailDomains: oidcCfg.allowedEmailDomains,
|
allowedEmailDomains: oidcCfg.allowedEmailDomains,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
manageGroupMemberships: oidcCfg.manageGroupMemberships,
|
||||||
|
jwtSignatureAlgorithm: oidcCfg.jwtSignatureAlgorithm
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -481,7 +482,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
manageGroupMemberships
|
manageGroupMemberships,
|
||||||
|
jwtSignatureAlgorithm
|
||||||
}: TUpdateOidcCfgDTO) => {
|
}: TUpdateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({
|
||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
@ -536,7 +538,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
jwksUri,
|
jwksUri,
|
||||||
isActive,
|
isActive,
|
||||||
lastUsed: null,
|
lastUsed: null,
|
||||||
manageGroupMemberships
|
manageGroupMemberships,
|
||||||
|
jwtSignatureAlgorithm
|
||||||
};
|
};
|
||||||
|
|
||||||
if (clientId !== undefined) {
|
if (clientId !== undefined) {
|
||||||
@ -569,7 +572,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
manageGroupMemberships
|
manageGroupMemberships,
|
||||||
|
jwtSignatureAlgorithm
|
||||||
}: TCreateOidcCfgDTO) => {
|
}: TCreateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({
|
||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
@ -613,6 +617,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
|
jwtSignatureAlgorithm,
|
||||||
encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob,
|
encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob,
|
||||||
encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob
|
encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob
|
||||||
});
|
});
|
||||||
@ -676,7 +681,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
const client = new issuer.Client({
|
const client = new issuer.Client({
|
||||||
client_id: oidcCfg.clientId,
|
client_id: oidcCfg.clientId,
|
||||||
client_secret: oidcCfg.clientSecret,
|
client_secret: oidcCfg.clientSecret,
|
||||||
redirect_uris: [`${appCfg.SITE_URL}/api/v1/sso/oidc/callback`]
|
redirect_uris: [`${appCfg.SITE_URL}/api/v1/sso/oidc/callback`],
|
||||||
|
id_token_signed_response_alg: oidcCfg.jwtSignatureAlgorithm
|
||||||
});
|
});
|
||||||
|
|
||||||
const strategy = new OpenIdStrategy(
|
const strategy = new OpenIdStrategy(
|
||||||
|
@ -5,6 +5,12 @@ export enum OIDCConfigurationType {
|
|||||||
DISCOVERY_URL = "discoveryURL"
|
DISCOVERY_URL = "discoveryURL"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OIDCJWTSignatureAlgorithm {
|
||||||
|
RS256 = "RS256",
|
||||||
|
HS256 = "HS256",
|
||||||
|
RS512 = "RS512"
|
||||||
|
}
|
||||||
|
|
||||||
export type TOidcLoginDTO = {
|
export type TOidcLoginDTO = {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -40,6 +46,7 @@ export type TCreateOidcCfgDTO = {
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
manageGroupMemberships: boolean;
|
manageGroupMemberships: boolean;
|
||||||
|
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||||
} & TGenericPermission;
|
} & TGenericPermission;
|
||||||
|
|
||||||
export type TUpdateOidcCfgDTO = Partial<{
|
export type TUpdateOidcCfgDTO = Partial<{
|
||||||
@ -56,5 +63,6 @@ export type TUpdateOidcCfgDTO = Partial<{
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
manageGroupMemberships: boolean;
|
manageGroupMemberships: boolean;
|
||||||
|
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||||
}> &
|
}> &
|
||||||
TGenericPermission;
|
TGenericPermission;
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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<TAuth0ClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const credentials = await $rotateClientSecret();
|
||||||
|
|
||||||
|
return callback(credentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeCredentials: TRotationFactoryRevokeCredentials<TAuth0ClientSecretRotationGeneratedCredentials> = 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<TAuth0ClientSecretRotationGeneratedCredentials> = async (
|
||||||
|
_,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
const credentials = await $rotateClientSecret();
|
||||||
|
|
||||||
|
return callback(credentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAuth0ClientSecretRotationGeneratedCredentials> = (
|
||||||
|
generatedCredentials
|
||||||
|
) => {
|
||||||
|
const secrets = [
|
||||||
|
{
|
||||||
|
key: secretsMapping.clientId,
|
||||||
|
value: generatedCredentials.clientId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: secretsMapping.clientSecret,
|
||||||
|
value: generatedCredentials.clientSecret
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
issueCredentials,
|
||||||
|
revokeCredentials,
|
||||||
|
rotateCredentials,
|
||||||
|
getSecretsPayload
|
||||||
|
};
|
||||||
|
};
|
@ -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
|
||||||
|
});
|
@ -0,0 +1,24 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAuth0Connection } from "@app/services/app-connection/auth0";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Auth0ClientSecretRotationGeneratedCredentialsSchema,
|
||||||
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
|
Auth0ClientSecretRotationSchema,
|
||||||
|
CreateAuth0ClientSecretRotationSchema
|
||||||
|
} from "./auth0-client-secret-rotation-schemas";
|
||||||
|
|
||||||
|
export type TAuth0ClientSecretRotation = z.infer<typeof Auth0ClientSecretRotationSchema>;
|
||||||
|
|
||||||
|
export type TAuth0ClientSecretRotationInput = z.infer<typeof CreateAuth0ClientSecretRotationSchema>;
|
||||||
|
|
||||||
|
export type TAuth0ClientSecretRotationListItem = z.infer<typeof Auth0ClientSecretRotationListItemSchema>;
|
||||||
|
|
||||||
|
export type TAuth0ClientSecretRotationWithConnection = TAuth0ClientSecretRotation & {
|
||||||
|
connection: TAuth0Connection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAuth0ClientSecretRotationGeneratedCredentials = z.infer<
|
||||||
|
typeof Auth0ClientSecretRotationGeneratedCredentialsSchema
|
||||||
|
>;
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./auth0-client-secret-rotation-constants";
|
||||||
|
export * from "./auth0-client-secret-rotation-schemas";
|
||||||
|
export * from "./auth0-client-secret-rotation-types";
|
@ -1,6 +1,7 @@
|
|||||||
export enum SecretRotation {
|
export enum SecretRotation {
|
||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials"
|
MsSqlCredentials = "mssql-credentials",
|
||||||
|
Auth0ClientSecret = "auth0-client-secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
@ -3,6 +3,7 @@ import { AxiosError } from "axios";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
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 { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||||
@ -16,7 +17,8 @@ import {
|
|||||||
|
|
||||||
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
||||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
[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 = () => {
|
export const listSecretRotationOptions = () => {
|
||||||
|
@ -3,10 +3,12 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
|
|||||||
|
|
||||||
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials"
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials",
|
||||||
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql
|
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||||
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
ProjectPermissionSecretRotationActions,
|
ProjectPermissionSecretRotationActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} 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 { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
import {
|
import {
|
||||||
calculateNextRotationAt,
|
calculateNextRotationAt,
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
TRotationFactory,
|
TRotationFactory,
|
||||||
TSecretRotationRotateGeneratedCredentials,
|
TSecretRotationRotateGeneratedCredentials,
|
||||||
TSecretRotationV2,
|
TSecretRotationV2,
|
||||||
|
TSecretRotationV2GeneratedCredentials,
|
||||||
TSecretRotationV2Raw,
|
TSecretRotationV2Raw,
|
||||||
TSecretRotationV2WithConnection,
|
TSecretRotationV2WithConnection,
|
||||||
TUpdateSecretRotationV2DTO
|
TUpdateSecretRotationV2DTO
|
||||||
@ -53,6 +55,7 @@ import { DatabaseErrorCode } from "@app/lib/error-codes";
|
|||||||
import { BadRequestError, DatabaseError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
|
||||||
import { QueueJobs, TQueueServiceFactory } from "@app/queue";
|
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 { decryptAppConnection } from "@app/services/app-connection/app-connection-fns";
|
||||||
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
@ -97,15 +100,21 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
|||||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||||
|
|
||||||
const MAX_GENERATED_CREDENTIALS_LENGTH = 2;
|
const MAX_GENERATED_CREDENTIALS_LENGTH = 2;
|
||||||
|
|
||||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactory> = {
|
type TRotationFactoryImplementation = TRotationFactory<
|
||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory,
|
TSecretRotationV2WithConnection,
|
||||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory
|
TSecretRotationV2GeneratedCredentials
|
||||||
|
>;
|
||||||
|
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||||
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secretRotationV2ServiceFactory = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
@ -125,7 +134,8 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
secretQueueService,
|
secretQueueService,
|
||||||
snapshotService,
|
snapshotService,
|
||||||
keyStore,
|
keyStore,
|
||||||
queueService
|
queueService,
|
||||||
|
appConnectionDAL
|
||||||
}: TSecretRotationV2ServiceFactoryDep) => {
|
}: TSecretRotationV2ServiceFactoryDep) => {
|
||||||
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@ -429,11 +439,15 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
// validates permission to connect and app is valid for rotation type
|
// validates permission to connect and app is valid for rotation type
|
||||||
const connection = await appConnectionService.connectAppConnectionById(typeApp, payload.connectionId, actor);
|
const connection = await appConnectionService.connectAppConnectionById(typeApp, payload.connectionId, actor);
|
||||||
|
|
||||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type]({
|
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type](
|
||||||
|
{
|
||||||
parameters: payload.parameters,
|
parameters: payload.parameters,
|
||||||
secretsMapping,
|
secretsMapping,
|
||||||
connection
|
connection
|
||||||
} as TSecretRotationV2WithConnection);
|
} as TSecretRotationV2WithConnection,
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
@ -441,7 +455,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
// callback structure to support transactional rollback when possible
|
// callback structure to support transactional rollback when possible
|
||||||
const secretRotation = await rotationFactory.issueCredentials(async (newCredentials) => {
|
const secretRotation = await rotationFactory.issueCredentials(async (newCredentials) => {
|
||||||
const encryptedGeneratedCredentials = await encryptSecretRotationCredentials({
|
const encryptedGeneratedCredentials = await encryptSecretRotationCredentials({
|
||||||
generatedCredentials: [newCredentials],
|
generatedCredentials: [newCredentials] as TSecretRotationV2GeneratedCredentials,
|
||||||
projectId,
|
projectId,
|
||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
@ -740,7 +754,8 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
message: `Secret Rotation with ID "${rotationId}" is not configured for ${SECRET_ROTATION_NAME_MAP[type]}`
|
message: `Secret Rotation with ID "${rotationId}" is not configured for ${SECRET_ROTATION_NAME_MAP[type]}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteTransaction = secretRotationV2DAL.transaction(async (tx) => {
|
const deleteTransaction = async () =>
|
||||||
|
secretRotationV2DAL.transaction(async (tx) => {
|
||||||
if (deleteSecrets) {
|
if (deleteSecrets) {
|
||||||
await fnSecretBulkDelete({
|
await fnSecretBulkDelete({
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
@ -762,10 +777,14 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
if (revokeGeneratedCredentials) {
|
if (revokeGeneratedCredentials) {
|
||||||
const appConnection = await decryptAppConnection(connection, kmsService);
|
const appConnection = await decryptAppConnection(connection, kmsService);
|
||||||
|
|
||||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type]({
|
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type](
|
||||||
|
{
|
||||||
...secretRotation,
|
...secretRotation,
|
||||||
connection: appConnection
|
connection: appConnection
|
||||||
} as TSecretRotationV2WithConnection);
|
} as TSecretRotationV2WithConnection,
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
);
|
||||||
|
|
||||||
const generatedCredentials = await decryptSecretRotationCredentials({
|
const generatedCredentials = await decryptSecretRotationCredentials({
|
||||||
encryptedGeneratedCredentials,
|
encryptedGeneratedCredentials,
|
||||||
@ -773,9 +792,9 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
await rotationFactory.revokeCredentials(generatedCredentials, async () => deleteTransaction);
|
await rotationFactory.revokeCredentials(generatedCredentials, deleteTransaction);
|
||||||
} else {
|
} else {
|
||||||
await deleteTransaction;
|
await deleteTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deleteSecrets) {
|
if (deleteSecrets) {
|
||||||
@ -840,10 +859,14 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
|
|
||||||
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
||||||
|
|
||||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type as SecretRotation]({
|
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[type as SecretRotation](
|
||||||
|
{
|
||||||
...secretRotation,
|
...secretRotation,
|
||||||
connection: appConnection
|
connection: appConnection
|
||||||
} as TSecretRotationV2WithConnection);
|
} as TSecretRotationV2WithConnection,
|
||||||
|
appConnectionDAL,
|
||||||
|
kmsService
|
||||||
|
);
|
||||||
|
|
||||||
const updatedRotation = await rotationFactory.rotateCredentials(inactiveCredentials, async (newCredentials) => {
|
const updatedRotation = await rotationFactory.rotateCredentials(inactiveCredentials, async (newCredentials) => {
|
||||||
const updatedCredentials = [...generatedCredentials];
|
const updatedCredentials = [...generatedCredentials];
|
||||||
@ -851,7 +874,7 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
|
|
||||||
const encryptedUpdatedCredentials = await encryptSecretRotationCredentials({
|
const encryptedUpdatedCredentials = await encryptSecretRotationCredentials({
|
||||||
projectId,
|
projectId,
|
||||||
generatedCredentials: updatedCredentials,
|
generatedCredentials: updatedCredentials as TSecretRotationV2GeneratedCredentials,
|
||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
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 { TSqlCredentialsRotationGeneratedCredentials } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
|
||||||
import { OrderByDirection } from "@app/lib/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 { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TAuth0ClientSecretRotation,
|
||||||
|
TAuth0ClientSecretRotationGeneratedCredentials,
|
||||||
|
TAuth0ClientSecretRotationInput,
|
||||||
|
TAuth0ClientSecretRotationListItem,
|
||||||
|
TAuth0ClientSecretRotationWithConnection
|
||||||
|
} from "./auth0-client-secret";
|
||||||
import {
|
import {
|
||||||
TMsSqlCredentialsRotation,
|
TMsSqlCredentialsRotation,
|
||||||
TMsSqlCredentialsRotationInput,
|
TMsSqlCredentialsRotationInput,
|
||||||
@ -18,17 +27,26 @@ import {
|
|||||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||||
import { SecretRotation } from "./secret-rotation-v2-enums";
|
import { SecretRotation } from "./secret-rotation-v2-enums";
|
||||||
|
|
||||||
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation;
|
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation | TAuth0ClientSecretRotation;
|
||||||
|
|
||||||
export type TSecretRotationV2WithConnection =
|
export type TSecretRotationV2WithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection;
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
|
| TAuth0ClientSecretRotationWithConnection;
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials = TSqlCredentialsRotationGeneratedCredentials;
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
|
| TAuth0ClientSecretRotationGeneratedCredentials;
|
||||||
|
|
||||||
export type TSecretRotationV2Input = TPostgresCredentialsRotationInput | TMsSqlCredentialsRotationInput;
|
export type TSecretRotationV2Input =
|
||||||
|
| TPostgresCredentialsRotationInput
|
||||||
|
| TMsSqlCredentialsRotationInput
|
||||||
|
| TAuth0ClientSecretRotationInput;
|
||||||
|
|
||||||
export type TSecretRotationV2ListItem = TPostgresCredentialsRotationListItem | TMsSqlCredentialsRotationListItem;
|
export type TSecretRotationV2ListItem =
|
||||||
|
| TPostgresCredentialsRotationListItem
|
||||||
|
| TMsSqlCredentialsRotationListItem
|
||||||
|
| TAuth0ClientSecretRotationListItem;
|
||||||
|
|
||||||
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
||||||
|
|
||||||
@ -129,27 +147,34 @@ export type TSecretRotationSendNotificationJobPayload = {
|
|||||||
// transactional behavior. By passing in the rotation mutation, if this mutation fails we can roll back the
|
// 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
|
// third party credential changes (when supported), preventing credentials getting out of sync
|
||||||
|
|
||||||
export type TRotationFactoryIssueCredentials = (
|
export type TRotationFactoryIssueCredentials<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||||
callback: (newCredentials: TSecretRotationV2GeneratedCredentials[number]) => Promise<TSecretRotationV2Raw>
|
callback: (newCredentials: T[number]) => Promise<TSecretRotationV2Raw>
|
||||||
) => Promise<TSecretRotationV2Raw>;
|
) => Promise<TSecretRotationV2Raw>;
|
||||||
|
|
||||||
export type TRotationFactoryRevokeCredentials = (
|
export type TRotationFactoryRevokeCredentials<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||||
generatedCredentials: TSecretRotationV2GeneratedCredentials,
|
generatedCredentials: T,
|
||||||
callback: () => Promise<TSecretRotationV2Raw>
|
callback: () => Promise<TSecretRotationV2Raw>
|
||||||
) => Promise<TSecretRotationV2Raw>;
|
) => Promise<TSecretRotationV2Raw>;
|
||||||
|
|
||||||
export type TRotationFactoryRotateCredentials = (
|
export type TRotationFactoryRotateCredentials<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||||
credentialsToRevoke: TSecretRotationV2GeneratedCredentials[number] | undefined,
|
credentialsToRevoke: T[number] | undefined,
|
||||||
callback: (newCredentials: TSecretRotationV2GeneratedCredentials[number]) => Promise<TSecretRotationV2Raw>
|
callback: (newCredentials: T[number]) => Promise<TSecretRotationV2Raw>
|
||||||
) => Promise<TSecretRotationV2Raw>;
|
) => Promise<TSecretRotationV2Raw>;
|
||||||
|
|
||||||
export type TRotationFactoryGetSecretsPayload = (
|
export type TRotationFactoryGetSecretsPayload<T extends TSecretRotationV2GeneratedCredentials> = (
|
||||||
generatedCredentials: TSecretRotationV2GeneratedCredentials[number]
|
generatedCredentials: T[number]
|
||||||
) => { key: string; value: string }[];
|
) => { key: string; value: string }[];
|
||||||
|
|
||||||
export type TRotationFactory = (secretRotation: TSecretRotationV2WithConnection) => {
|
export type TRotationFactory<
|
||||||
issueCredentials: TRotationFactoryIssueCredentials;
|
T extends TSecretRotationV2WithConnection,
|
||||||
revokeCredentials: TRotationFactoryRevokeCredentials;
|
C extends TSecretRotationV2GeneratedCredentials
|
||||||
rotateCredentials: TRotationFactoryRotateCredentials;
|
> = (
|
||||||
getSecretsPayload: TRotationFactoryGetSecretsPayload;
|
secretRotation: T,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
||||||
|
revokeCredentials: TRotationFactoryRevokeCredentials<C>;
|
||||||
|
rotateCredentials: TRotationFactoryRotateCredentials<C>;
|
||||||
|
getSecretsPayload: TRotationFactoryGetSecretsPayload<C>;
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { 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 { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
|
|
||||||
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema
|
MsSqlCredentialsRotationSchema,
|
||||||
|
Auth0ClientSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { randomInt } from "crypto";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
TRotationFactory,
|
||||||
TRotationFactoryGetSecretsPayload,
|
TRotationFactoryGetSecretsPayload,
|
||||||
TRotationFactoryIssueCredentials,
|
TRotationFactoryIssueCredentials,
|
||||||
TRotationFactoryRevokeCredentials,
|
TRotationFactoryRevokeCredentials,
|
||||||
@ -8,94 +7,12 @@ import {
|
|||||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
} 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 { getSqlConnectionClient, SQL_CONNECTION_ALTER_LOGIN_STATEMENT } from "@app/services/app-connection/shared/sql";
|
||||||
|
|
||||||
|
import { generatePassword } from "../utils";
|
||||||
import {
|
import {
|
||||||
TSqlCredentialsRotationGeneratedCredentials,
|
TSqlCredentialsRotationGeneratedCredentials,
|
||||||
TSqlCredentialsRotationWithConnection
|
TSqlCredentialsRotationWithConnection
|
||||||
} from "./sql-credentials-rotation-types";
|
} from "./sql-credentials-rotation-types";
|
||||||
|
|
||||||
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
|
||||||
length: 48,
|
|
||||||
required: {
|
|
||||||
lowercase: 1,
|
|
||||||
uppercase: 1,
|
|
||||||
digits: 1,
|
|
||||||
symbols: 0
|
|
||||||
},
|
|
||||||
allowedSymbols: "-_.~!*"
|
|
||||||
};
|
|
||||||
|
|
||||||
const generatePassword = () => {
|
|
||||||
try {
|
|
||||||
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
|
||||||
|
|
||||||
const chars = {
|
|
||||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
||||||
digits: "0123456789",
|
|
||||||
symbols: allowedSymbols || "-_.~!*"
|
|
||||||
};
|
|
||||||
|
|
||||||
const parts: string[] = [];
|
|
||||||
|
|
||||||
if (required.lowercase > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.lowercase)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.lowercase[randomInt(chars.lowercase.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.uppercase > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.uppercase)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.uppercase[randomInt(chars.uppercase.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.digits > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.digits)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.digits[randomInt(chars.digits.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.symbols > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.symbols)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.symbols[randomInt(chars.symbols.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0);
|
|
||||||
const remainingLength = Math.max(length - requiredTotal, 0);
|
|
||||||
|
|
||||||
const allowedChars = Object.entries(chars)
|
|
||||||
.filter(([key]) => required[key as keyof typeof required] > 0)
|
|
||||||
.map(([, value]) => value)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
parts.push(
|
|
||||||
...Array(remainingLength)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => allowedChars[randomInt(allowedChars.length)])
|
|
||||||
);
|
|
||||||
|
|
||||||
// shuffle the array to mix up the characters
|
|
||||||
for (let i = parts.length - 1; i > 0; i -= 1) {
|
|
||||||
const j = randomInt(i + 1);
|
|
||||||
[parts[i], parts[j]] = [parts[j], parts[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join("");
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const message = error instanceof Error ? error.message : "Unknown error";
|
|
||||||
throw new Error(`Failed to generate password: ${message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGeneratedCredentials) => {
|
const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGeneratedCredentials) => {
|
||||||
const error = e as Error;
|
const error = e as Error;
|
||||||
|
|
||||||
@ -110,7 +27,10 @@ const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGenerat
|
|||||||
return redactedMessage;
|
return redactedMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRotationWithConnection) => {
|
export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||||
|
TSqlCredentialsRotationWithConnection,
|
||||||
|
TSqlCredentialsRotationGeneratedCredentials
|
||||||
|
> = (secretRotation) => {
|
||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
parameters: { username1, username2 },
|
parameters: { username1, username2 },
|
||||||
@ -118,7 +38,7 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
|||||||
secretsMapping
|
secretsMapping
|
||||||
} = secretRotation;
|
} = secretRotation;
|
||||||
|
|
||||||
const validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
||||||
const client = await getSqlConnectionClient({
|
const client = await getSqlConnectionClient({
|
||||||
...connection,
|
...connection,
|
||||||
credentials: {
|
credentials: {
|
||||||
@ -136,7 +56,9 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const issueCredentials: TRotationFactoryIssueCredentials = async (callback) => {
|
const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
const client = await getSqlConnectionClient(connection);
|
const client = await getSqlConnectionClient(connection);
|
||||||
|
|
||||||
// For SQL, since we get existing users, we change both their passwords
|
// 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) {
|
for await (const credentials of credentialsSet) {
|
||||||
await validateCredentials(credentials);
|
await $validateCredentials(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(credentialsSet[0]);
|
return callback(credentialsSet[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const revokeCredentials: TRotationFactoryRevokeCredentials = async (credentialsToRevoke, callback) => {
|
const revokeCredentials: TRotationFactoryRevokeCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||||
|
credentialsToRevoke,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
const client = await getSqlConnectionClient(connection);
|
const client = await getSqlConnectionClient(connection);
|
||||||
|
|
||||||
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() }));
|
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() }));
|
||||||
@ -186,7 +111,10 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
|||||||
return callback();
|
return callback();
|
||||||
};
|
};
|
||||||
|
|
||||||
const rotateCredentials: TRotationFactoryRotateCredentials = async (_, callback) => {
|
const rotateCredentials: TRotationFactoryRotateCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||||
|
_,
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
const client = await getSqlConnectionClient(connection);
|
const client = await getSqlConnectionClient(connection);
|
||||||
|
|
||||||
// generate new password for the next active user
|
// generate new password for the next active user
|
||||||
@ -200,12 +128,14 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
|||||||
await client.destroy();
|
await client.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
await validateCredentials(credentials);
|
await $validateCredentials(credentials);
|
||||||
|
|
||||||
return callback(credentials);
|
return callback(credentials);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload = (generatedCredentials) => {
|
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TSqlCredentialsRotationGeneratedCredentials> = (
|
||||||
|
generatedCredentials
|
||||||
|
) => {
|
||||||
const { username, password } = secretsMapping;
|
const { username, password } = secretsMapping;
|
||||||
|
|
||||||
const secrets = [
|
const secrets = [
|
||||||
@ -226,7 +156,6 @@ export const sqlCredentialsRotationFactory = (secretRotation: TSqlCredentialsRot
|
|||||||
issueCredentials,
|
issueCredentials,
|
||||||
revokeCredentials,
|
revokeCredentials,
|
||||||
rotateCredentials,
|
rotateCredentials,
|
||||||
getSecretsPayload,
|
getSecretsPayload
|
||||||
validateCredentials
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
import { randomInt } from "crypto";
|
||||||
|
|
||||||
|
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
||||||
|
length: 48,
|
||||||
|
required: {
|
||||||
|
lowercase: 1,
|
||||||
|
uppercase: 1,
|
||||||
|
digits: 1,
|
||||||
|
symbols: 0
|
||||||
|
},
|
||||||
|
allowedSymbols: "-_.~!*"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generatePassword = () => {
|
||||||
|
try {
|
||||||
|
const { length, required, allowedSymbols } = DEFAULT_PASSWORD_REQUIREMENTS;
|
||||||
|
|
||||||
|
const chars = {
|
||||||
|
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||||
|
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
digits: "0123456789",
|
||||||
|
symbols: allowedSymbols || "-_.~!*"
|
||||||
|
};
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
if (required.lowercase > 0) {
|
||||||
|
parts.push(
|
||||||
|
...Array(required.lowercase)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => chars.lowercase[randomInt(chars.lowercase.length)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required.uppercase > 0) {
|
||||||
|
parts.push(
|
||||||
|
...Array(required.uppercase)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => chars.uppercase[randomInt(chars.uppercase.length)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required.digits > 0) {
|
||||||
|
parts.push(
|
||||||
|
...Array(required.digits)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => chars.digits[randomInt(chars.digits.length)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required.symbols > 0) {
|
||||||
|
parts.push(
|
||||||
|
...Array(required.symbols)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => chars.symbols[randomInt(chars.symbols.length)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0);
|
||||||
|
const remainingLength = Math.max(length - requiredTotal, 0);
|
||||||
|
|
||||||
|
const allowedChars = Object.entries(chars)
|
||||||
|
.filter(([key]) => required[key as keyof typeof required] > 0)
|
||||||
|
.map(([, value]) => value)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
parts.push(
|
||||||
|
...Array(remainingLength)
|
||||||
|
.fill(0)
|
||||||
|
.map(() => allowedChars[randomInt(allowedChars.length)])
|
||||||
|
);
|
||||||
|
|
||||||
|
// shuffle the array to mix up the characters
|
||||||
|
for (let i = parts.length - 1; i > 0; i -= 1) {
|
||||||
|
const j = randomInt(i + 1);
|
||||||
|
[parts[i], parts[j]] = [parts[j], parts[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join("");
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
throw new Error(`Failed to generate password: ${message}`);
|
||||||
|
}
|
||||||
|
};
|
@ -1782,6 +1782,12 @@ export const AppConnections = {
|
|||||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to be deleted.`
|
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to be deleted.`
|
||||||
}),
|
}),
|
||||||
CREDENTIALS: {
|
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: {
|
SQL_CONNECTION: {
|
||||||
host: "The hostname of the database server.",
|
host: "The hostname of the database server.",
|
||||||
port: "The port number of the database.",
|
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.",
|
"The username of the first login to rotate passwords for. This user must already exists in your database.",
|
||||||
username2:
|
username2:
|
||||||
"The username of the second login to rotate passwords for. This user must already exists in your database."
|
"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: {
|
SECRETS_MAPPING: {
|
||||||
SQL_CREDENTIALS: {
|
SQL_CREDENTIALS: {
|
||||||
username: "The name of the secret that the active username will be mapped to.",
|
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."
|
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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1549,7 +1549,8 @@ export const registerRoutes = async (
|
|||||||
resourceMetadataDAL,
|
resourceMetadataDAL,
|
||||||
snapshotService,
|
snapshotService,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
queueService
|
queueService,
|
||||||
|
appConnectionDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
await secretRotationV2QueueServiceFactory({
|
await secretRotationV2QueueServiceFactory({
|
||||||
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
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 { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws";
|
||||||
import {
|
import {
|
||||||
AzureAppConfigurationConnectionListItemSchema,
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
@ -51,7 +52,8 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedVercelConnectionSchema.options,
|
...SanitizedVercelConnectionSchema.options,
|
||||||
...SanitizedPostgresConnectionSchema.options,
|
...SanitizedPostgresConnectionSchema.options,
|
||||||
...SanitizedMsSqlConnectionSchema.options,
|
...SanitizedMsSqlConnectionSchema.options,
|
||||||
...SanitizedCamundaConnectionSchema.options
|
...SanitizedCamundaConnectionSchema.options,
|
||||||
|
...SanitizedAuth0ConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
@ -66,7 +68,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
VercelConnectionListItemSchema,
|
VercelConnectionListItemSchema,
|
||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
MsSqlConnectionListItemSchema,
|
MsSqlConnectionListItemSchema,
|
||||||
CamundaConnectionListItemSchema
|
CamundaConnectionListItemSchema,
|
||||||
|
Auth0ConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -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 { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
@ -28,5 +29,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Vercel]: registerVercelConnectionRouter,
|
[AppConnection.Vercel]: registerVercelConnectionRouter,
|
||||||
[AppConnection.Postgres]: registerPostgresConnectionRouter,
|
[AppConnection.Postgres]: registerPostgresConnectionRouter,
|
||||||
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
||||||
[AppConnection.Camunda]: registerCamundaConnectionRouter
|
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
||||||
|
[AppConnection.Auth0]: registerAuth0ConnectionRouter
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,8 @@ export enum AppConnection {
|
|||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
Postgres = "postgres",
|
Postgres = "postgres",
|
||||||
MsSql = "mssql",
|
MsSql = "mssql",
|
||||||
Camunda = "camunda"
|
Camunda = "camunda",
|
||||||
|
Auth0 = "auth0"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
TAppConnectionCredentialsValidator,
|
TAppConnectionCredentialsValidator,
|
||||||
TAppConnectionTransitionCredentialsToPlatform
|
TAppConnectionTransitionCredentialsToPlatform
|
||||||
} from "./app-connection-types";
|
} from "./app-connection-types";
|
||||||
|
import { Auth0ConnectionMethod, getAuth0ConnectionListItem, validateAuth0ConnectionCredentials } from "./auth0";
|
||||||
import { AwsConnectionMethod, getAwsConnectionListItem, validateAwsConnectionCredentials } from "./aws";
|
import { AwsConnectionMethod, getAwsConnectionListItem, validateAwsConnectionCredentials } from "./aws";
|
||||||
import {
|
import {
|
||||||
AzureAppConfigurationConnectionMethod,
|
AzureAppConfigurationConnectionMethod,
|
||||||
@ -63,7 +64,8 @@ export const listAppConnectionOptions = () => {
|
|||||||
getVercelConnectionListItem(),
|
getVercelConnectionListItem(),
|
||||||
getPostgresConnectionListItem(),
|
getPostgresConnectionListItem(),
|
||||||
getMsSqlConnectionListItem(),
|
getMsSqlConnectionListItem(),
|
||||||
getCamundaConnectionListItem()
|
getCamundaConnectionListItem(),
|
||||||
|
getAuth0ConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,6 +111,9 @@ export const decryptAppConnectionCredentials = async ({
|
|||||||
return JSON.parse(decryptedPlainTextBlob.toString()) as TAppConnection["credentials"];
|
return JSON.parse(decryptedPlainTextBlob.toString()) as TAppConnection["credentials"];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateAppConnectionCredentials = async (
|
||||||
|
appConnection: TAppConnectionConfig
|
||||||
|
): Promise<TAppConnection["credentials"]> => {
|
||||||
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnectionCredentialsValidator> = {
|
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnectionCredentialsValidator> = {
|
||||||
[AppConnection.AWS]: validateAwsConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.AWS]: validateAwsConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Databricks]: validateDatabricksConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Databricks]: validateDatabricksConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
@ -120,14 +125,14 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnect
|
|||||||
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
|
||||||
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateAppConnectionCredentials = async (
|
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
||||||
appConnection: TAppConnectionConfig
|
};
|
||||||
): Promise<TAppConnection["credentials"]> => VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
|
||||||
|
|
||||||
export const getAppConnectionMethodName = (method: TAppConnection["method"]) => {
|
export const getAppConnectionMethodName = (method: TAppConnection["method"]) => {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
@ -154,6 +159,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case PostgresConnectionMethod.UsernameAndPassword:
|
case PostgresConnectionMethod.UsernameAndPassword:
|
||||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||||
return "Username & Password";
|
return "Username & Password";
|
||||||
|
case Auth0ConnectionMethod.ClientCredentials:
|
||||||
|
return "Client Credentials";
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||||
@ -196,5 +203,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported
|
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.Auth0]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
@ -12,5 +12,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Vercel]: "Vercel",
|
[AppConnection.Vercel]: "Vercel",
|
||||||
[AppConnection.Postgres]: "PostgreSQL",
|
[AppConnection.Postgres]: "PostgreSQL",
|
||||||
[AppConnection.MsSql]: "Microsoft SQL Server",
|
[AppConnection.MsSql]: "Microsoft SQL Server",
|
||||||
[AppConnection.Camunda]: "Camunda"
|
[AppConnection.Camunda]: "Camunda",
|
||||||
|
[AppConnection.Auth0]: "Auth0"
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
|
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
|
||||||
validateAppConnectionCredentials
|
validateAppConnectionCredentials
|
||||||
} from "@app/services/app-connection/app-connection-fns";
|
} 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 { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
||||||
@ -27,6 +28,7 @@ import {
|
|||||||
TUpdateAppConnectionDTO,
|
TUpdateAppConnectionDTO,
|
||||||
TValidateAppConnectionCredentialsSchema
|
TValidateAppConnectionCredentialsSchema
|
||||||
} from "./app-connection-types";
|
} from "./app-connection-types";
|
||||||
|
import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0";
|
||||||
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
|
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
|
||||||
import { awsConnectionService } from "./aws/aws-connection-service";
|
import { awsConnectionService } from "./aws/aws-connection-service";
|
||||||
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
||||||
@ -68,7 +70,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
|
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
|
||||||
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
||||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
||||||
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema
|
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
||||||
|
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
@ -442,6 +445,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
humanitec: humanitecConnectionService(connectAppConnectionById),
|
humanitec: humanitecConnectionService(connectAppConnectionById),
|
||||||
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
|
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
|
||||||
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
vercel: vercelConnectionService(connectAppConnectionById)
|
vercel: vercelConnectionService(connectAppConnectionById),
|
||||||
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,12 @@ import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sq
|
|||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
import { AWSRegion } from "./app-connection-enums";
|
import { AWSRegion } from "./app-connection-enums";
|
||||||
|
import {
|
||||||
|
TAuth0Connection,
|
||||||
|
TAuth0ConnectionConfig,
|
||||||
|
TAuth0ConnectionInput,
|
||||||
|
TValidateAuth0ConnectionCredentialsSchema
|
||||||
|
} from "./auth0";
|
||||||
import {
|
import {
|
||||||
TAwsConnection,
|
TAwsConnection,
|
||||||
TAwsConnectionConfig,
|
TAwsConnectionConfig,
|
||||||
@ -83,6 +89,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TPostgresConnection
|
| TPostgresConnection
|
||||||
| TMsSqlConnection
|
| TMsSqlConnection
|
||||||
| TCamundaConnection
|
| TCamundaConnection
|
||||||
|
| TAuth0Connection
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
@ -102,6 +109,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TPostgresConnectionInput
|
| TPostgresConnectionInput
|
||||||
| TMsSqlConnectionInput
|
| TMsSqlConnectionInput
|
||||||
| TCamundaConnectionInput
|
| TCamundaConnectionInput
|
||||||
|
| TAuth0ConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
||||||
@ -124,9 +132,10 @@ export type TAppConnectionConfig =
|
|||||||
| TDatabricksConnectionConfig
|
| TDatabricksConnectionConfig
|
||||||
| THumanitecConnectionConfig
|
| THumanitecConnectionConfig
|
||||||
| TTerraformCloudConnectionConfig
|
| TTerraformCloudConnectionConfig
|
||||||
| TVercelConnectionConfig
|
|
||||||
| TSqlConnectionConfig
|
| TSqlConnectionConfig
|
||||||
| TCamundaConnectionConfig;
|
| TCamundaConnectionConfig
|
||||||
|
| TVercelConnectionConfig
|
||||||
|
| TAuth0ConnectionConfig;
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentialsSchema =
|
export type TValidateAppConnectionCredentialsSchema =
|
||||||
| TValidateAwsConnectionCredentialsSchema
|
| TValidateAwsConnectionCredentialsSchema
|
||||||
@ -139,8 +148,9 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidatePostgresConnectionCredentialsSchema
|
| TValidatePostgresConnectionCredentialsSchema
|
||||||
| TValidateMsSqlConnectionCredentialsSchema
|
| TValidateMsSqlConnectionCredentialsSchema
|
||||||
| TValidateCamundaConnectionCredentialsSchema
|
| TValidateCamundaConnectionCredentialsSchema
|
||||||
|
| TValidateVercelConnectionCredentialsSchema
|
||||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||||
| TValidateVercelConnectionCredentialsSchema;
|
| TValidateAuth0ConnectionCredentialsSchema;
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export enum Auth0ConnectionMethod {
|
||||||
|
ClientCredentials = "client-credentials"
|
||||||
|
}
|
@ -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<TAuth0AccessTokenResponse>({
|
||||||
|
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<TAppConnectionDALFactory, "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
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`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -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()
|
||||||
|
});
|
@ -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<TAuth0Connection>;
|
||||||
|
|
||||||
|
const listAuth0Clients = async (
|
||||||
|
appConnection: TAuth0Connection,
|
||||||
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
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<TAuth0ListClientsResponse>(`${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<TAppConnectionDALFactory, "updateById">,
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
|
) => {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
@ -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<typeof Auth0ConnectionSchema>;
|
||||||
|
|
||||||
|
export type TAuth0ConnectionInput = z.infer<typeof CreateAuth0ConnectionSchema> & {
|
||||||
|
app: AppConnection.Auth0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateAuth0ConnectionCredentialsSchema = typeof ValidateAuth0ConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TAuth0ConnectionConfig = DiscriminativePick<TAuth0Connection, "method" | "app" | "credentials"> & {
|
||||||
|
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[];
|
||||||
|
};
|
4
backend/src/services/app-connection/auth0/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./auth0-connection-enums";
|
||||||
|
export * from "./auth0-connection-fns";
|
||||||
|
export * from "./auth0-connection-schemas";
|
||||||
|
export * from "./auth0-connection-types";
|
@ -1,6 +1,7 @@
|
|||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
|
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
|
||||||
@ -26,6 +27,8 @@ const authorizeDatabricksConnection = async ({
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
workspaceUrl
|
workspaceUrl
|
||||||
}: Pick<TDatabricksConnection["credentials"], "workspaceUrl" | "clientId" | "clientSecret">) => {
|
}: Pick<TDatabricksConnection["credentials"], "workspaceUrl" | "clientId" | "clientSecret">) => {
|
||||||
|
await blockLocalAndPrivateIpAddresses(workspaceUrl);
|
||||||
|
|
||||||
const { data } = await request.post<TAuthorizeDatabricksConnection>(
|
const { data } = await request.post<TAuthorizeDatabricksConnection>(
|
||||||
`${removeTrailingSlash(workspaceUrl)}/oidc/v1/token`,
|
`${removeTrailingSlash(workspaceUrl)}/oidc/v1/token`,
|
||||||
"grant_type=client_credentials&scope=all-apis",
|
"grant_type=client_credentials&scope=all-apis",
|
||||||
|
@ -16,7 +16,7 @@ export const DatabricksConnectionServicePrincipalInputCredentialsSchema = z.obje
|
|||||||
workspaceUrl: z.string().trim().url().min(1, "Workspace URL required")
|
workspaceUrl: z.string().trim().url().min(1, "Workspace URL required")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DatabricksConnectionServicePrincipalOutputCredentialsSchema = z
|
const DatabricksConnectionServicePrincipalOutputCredentialsSchema = z
|
||||||
.object({
|
.object({
|
||||||
accessToken: z.string(),
|
accessToken: z.string(),
|
||||||
expiresAt: z.number()
|
expiresAt: z.number()
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/auth0/available"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/auth0"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Auth0 Connections](/integrations/app-connections/auth0) to learn how to obtain the
|
||||||
|
required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/auth0/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/auth0/{connectionId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/auth0/connection-name/{connectionName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/auth0"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/auth0/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Auth0 Connections](/integrations/app-connections/auth0) to learn how to obtain the
|
||||||
|
required credentials.
|
||||||
|
</Note>
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/auth0-client-secret"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
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.
|
||||||
|
</Note>
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v2/secret-rotations/auth0-client-secret/{rotationId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/auth0-client-secret/{rotationId}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/auth0-client-secret/rotation-name/{rotationName}"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Credentials by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/auth0-client-secret/{rotationId}/generated-credentials"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/auth0-client-secret"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Rotate Secrets"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/auth0-client-secret/{rotationId}/rotate-secrets"
|
||||||
|
---
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v2/secret-rotations/auth0-client-secret/{rotationId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
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.
|
||||||
|
</Note>
|
@ -5,6 +5,6 @@ openapi: "POST /api/v2/secret-rotations/mssql-credentials"
|
|||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Check out the configuration docs for [Microsoft SQL Server
|
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.
|
required parameters.
|
||||||
</Note>
|
</Note>
|
||||||
|
@ -5,6 +5,6 @@ openapi: "PATCH /api/v2/secret-rotations/mssql-credentials/{rotationId}"
|
|||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Check out the configuration docs for [Microsoft SQL Server
|
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.
|
required parameters.
|
||||||
</Note>
|
</Note>
|
||||||
|
@ -5,6 +5,6 @@ openapi: "POST /api/v2/secret-rotations/postgres-credentials"
|
|||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Check out the configuration docs for [PostgreSQL
|
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.
|
required parameters.
|
||||||
</Note>
|
</Note>
|
@ -5,6 +5,6 @@ openapi: "PATCH /api/v2/secret-rotations/postgres-credentials/{rotationId}"
|
|||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Check out the configuration docs for [PostgreSQL
|
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.
|
required parameters.
|
||||||
</Note>
|
</Note>
|
@ -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
|
- 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.
|
- 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.
|
- Released secret reminders feature.
|
||||||
|
|
||||||
## Oct 2023
|
## Oct 2023
|
||||||
|
@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
title: "Auth0 Client Secret"
|
||||||
|
description: "Learn how to automatically rotate Auth0 Client Secrets."
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
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.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
1. Navigate to your Secret Manager Project's Dashboard and select **Add Secret Rotation** from the actions dropdown.
|
||||||
|

|
||||||
|
|
||||||
|
2. Select the **Auth0 Client Secret** option.
|
||||||
|

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

|
||||||
|
|
||||||
|
- **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.
|
||||||
|
<Note>
|
||||||
|
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.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
|
||||||
|
4. Select the Auth0 application whose Client Secret you want to rotate. Then click **Next**.
|
||||||
|

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

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

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

|
||||||
|
|
||||||
|
8. Your **Auth0 Client Secret** credentials are now available for use via the mapped secrets.
|
||||||
|

|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
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.
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 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"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
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.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
### 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": "...",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
@ -47,6 +47,11 @@ Each set of credentials transitions through three distinct states:
|
|||||||
|
|
||||||
- **Active**: The primary credentials that will be used for new connections
|
- **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
|
- **Inactive**: These credentials are still valid but are no longer issued for new connections
|
||||||
|
<Note>
|
||||||
|
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.
|
||||||
|
</Note>
|
||||||
- **Revoked**: Permanently invalidated and deleted from the system
|
- **Revoked**: Permanently invalidated and deleted from the system
|
||||||
|
|
||||||
### Rotation Cycle Example (30-Day Interval)
|
### 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)
|
- [PostgreSQL Credentials](./postgres)
|
||||||
- [Microsoft SQL Server Credentials](./mssql)
|
- [Microsoft SQL Server Credentials](./mssql)
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Why do certain rotations only use a single credential set?">
|
||||||
|
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.
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
@ -42,7 +42,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
|
|||||||
3.1. Back in Infisical, in the Organization settings > Security > OIDC, click **Connect**.
|
3.1. Back in Infisical, in the Organization settings > Security > OIDC, click **Connect**.
|
||||||

|

|
||||||
|
|
||||||
3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **Client ID**, and **Client Secret** from step 2.1 and 2.2.
|
3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **JWT Signature Algorithm**, **Client ID**, and **Client Secret** from step 2.1 and 2.2.
|
||||||

|

|
||||||
|
|
||||||
Once you've done that, press **Update** to complete the required configuration.
|
Once you've done that, press **Update** to complete the required configuration.
|
||||||
|
@ -69,7 +69,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
|
|||||||
3.1. Back in Infisical, in the Organization settings > Security > OIDC, click Connect.
|
3.1. Back in Infisical, in the Organization settings > Security > OIDC, click Connect.
|
||||||

|

|
||||||
|
|
||||||
3.2. For configuration type, select Discovery URL. Then, set the appropriate values for **Discovery Document URL**, **Client ID**, and **Client Secret**.
|
3.2. For configuration type, select Discovery URL. Then, set the appropriate values for **Discovery Document URL**, **JWT Signature Algorithm**, **Client ID**, and **Client Secret**.
|
||||||

|

|
||||||
|
|
||||||
Once you've done that, press **Update** to complete the required configuration.
|
Once you've done that, press **Update** to complete the required configuration.
|
||||||
|
BIN
docs/images/app-connections/auth0/auth0-audience.png
Normal file
After Width: | Height: | Size: 370 KiB |
BIN
docs/images/app-connections/auth0/auth0-client-credentials.png
Normal file
After Width: | Height: | Size: 445 KiB |
After Width: | Height: | Size: 373 KiB |
After Width: | Height: | Size: 394 KiB |
BIN
docs/images/app-connections/auth0/auth0-select-m2m.png
Normal file
After Width: | Height: | Size: 420 KiB |
BIN
docs/images/app-connections/auth0/client-credentials-create.png
Normal file
After Width: | Height: | Size: 781 KiB |
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/images/app-connections/auth0/select-auth0-connection.png
Normal file
After Width: | Height: | Size: 784 KiB |
After Width: | Height: | Size: 343 KiB |
After Width: | Height: | Size: 750 KiB |
After Width: | Height: | Size: 759 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 687 KiB |
After Width: | Height: | Size: 709 KiB |
After Width: | Height: | Size: 723 KiB |
After Width: | Height: | Size: 695 KiB |
Before Width: | Height: | Size: 474 KiB After Width: | Height: | Size: 588 KiB |
Before Width: | Height: | Size: 470 KiB After Width: | Height: | Size: 588 KiB |
101
docs/integrations/app-connections/auth0.mdx
Normal file
@ -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
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Auth0 Applications Dashboard">
|
||||||
|
Navigate to the **Applications** page in Auth0 via the sidebar and click **Create Application**.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Create a Machine-to-Machine Application">
|
||||||
|
Give your application a name and select **Machine-to-Machine** for the application type.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Authorization">
|
||||||
|
Depending on your connection use case, authorize your application for the applicable API and grant the relevant permissions. Once done, click **Authorize**.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Secret Rotation">
|
||||||
|
Select the **Auth0 Management API** option from the dropdown and grant the `update:client_keys` and `read:clients` permission.
|
||||||
|

|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Step>
|
||||||
|
<Step title="Application Client Credentials">
|
||||||
|
On your application page, select the **Settings** tab and copy the **Domain**, **Client ID** and **Client Secret** for later.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Application Audience">
|
||||||
|
Next, select the **APIs** tab and copy the **API Identifier**.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Setup Auth0 Connection in Infisical
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
1. Navigate to the App Connections tab on the Organization Settings page.
|
||||||
|

|
||||||
|
|
||||||
|
2. Select the **Auth0 Connection** option.
|
||||||
|

|
||||||
|
|
||||||
|
3. Select the **Client Credentials** method option and provide the details obtained from the previous section and press **Connect to Auth0**.
|
||||||
|

|
||||||
|
|
||||||
|
4. Your **Auth0 Connection** is now available for use.
|
||||||
|

|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
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/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
@ -178,8 +178,9 @@
|
|||||||
"group": "Secret Rotation",
|
"group": "Secret Rotation",
|
||||||
"pages": [
|
"pages": [
|
||||||
"documentation/platform/secret-rotation/overview",
|
"documentation/platform/secret-rotation/overview",
|
||||||
"documentation/platform/secret-rotation/postgres",
|
"documentation/platform/secret-rotation/auth0-client-secret",
|
||||||
"documentation/platform/secret-rotation/mssql"
|
"documentation/platform/secret-rotation/postgres-credentials",
|
||||||
|
"documentation/platform/secret-rotation/mssql-credentials"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -414,6 +415,7 @@
|
|||||||
{
|
{
|
||||||
"group": "Connections",
|
"group": "Connections",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
"integrations/app-connections/auth0",
|
||||||
"integrations/app-connections/aws",
|
"integrations/app-connections/aws",
|
||||||
"integrations/app-connections/azure-app-configuration",
|
"integrations/app-connections/azure-app-configuration",
|
||||||
"integrations/app-connections/azure-key-vault",
|
"integrations/app-connections/azure-key-vault",
|
||||||
@ -844,6 +846,19 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"api-reference/endpoints/secret-rotations/list",
|
"api-reference/endpoints/secret-rotations/list",
|
||||||
"api-reference/endpoints/secret-rotations/options",
|
"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",
|
"group": "Microsoft SQL Server Credentials",
|
||||||
"pages": [
|
"pages": [
|
||||||
@ -888,6 +903,18 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"api-reference/endpoints/app-connections/list",
|
"api-reference/endpoints/app-connections/list",
|
||||||
"api-reference/endpoints/app-connections/options",
|
"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",
|
"group": "AWS",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
BIN
frontend/public/images/integrations/Auth0.png
Normal file
After Width: | Height: | Size: 100 KiB |
@ -1,4 +1,6 @@
|
|||||||
import { useState } from "react";
|
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 { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms";
|
||||||
import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader";
|
import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader";
|
||||||
@ -54,7 +56,24 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
|
|||||||
selectedRotation ? (
|
selectedRotation ? (
|
||||||
<SecretRotationV2ModalHeader isConfigured={false} type={selectedRotation} />
|
<SecretRotationV2ModalHeader isConfigured={false} type={selectedRotation} />
|
||||||
) : (
|
) : (
|
||||||
"Add Secret Rotation"
|
<div className="flex items-center text-mineshaft-300">
|
||||||
|
Add Secret Rotation
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://infisical.com/docs/documentation/platform/secret-rotation/overview"
|
||||||
|
className="mb-1 ml-1"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
|
<FontAwesomeIcon icon={faBookOpen} className="mb-[0.03rem] mr-1 text-[12px]" />
|
||||||
|
<span>Docs</span>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1 text-[10px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onPointerDownOutside={(e) => e.preventDefault()}
|
onPointerDownOutside={(e) => e.preventDefault()}
|
||||||
|
@ -24,7 +24,7 @@ export const SecretRotationV2ModalHeader = ({ type, isConfigured }: Props) => {
|
|||||||
{destinationDetails.name} Rotation
|
{destinationDetails.name} Rotation
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={`https://infisical.com/docs/platform/secret-rotations/${type}`}
|
href={`https://infisical.com/docs/documentation/platform/secret-rotation/${type}`}
|
||||||
className="mb-1 ml-1"
|
className="mb-1 ml-1"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
@ -24,17 +24,7 @@ export const SecretRotationV2Select = ({ onSelect }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
{secretRotationOptions?.map(({ type }) => {
|
{secretRotationOptions?.map(({ type }) => {
|
||||||
const { image, name } = SECRET_ROTATION_MAP[type];
|
const { image, name, size } = SECRET_ROTATION_MAP[type];
|
||||||
|
|
||||||
let size: number;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case SecretRotation.MsSqlCredentials:
|
|
||||||
size = 50;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
size = 45;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import { CredentialDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/CredentialDisplay";
|
||||||
|
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
|
||||||
|
import { TAuth0ClientSecretRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/auth0-client-secret-rotation";
|
||||||
|
|
||||||
|
import { ViewRotationGeneratedCredentialsDisplay } from "./shared";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
generatedCredentialsResponse: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewAuth0ClientSecretRotationGeneratedCredentials = ({
|
||||||
|
generatedCredentialsResponse: { generatedCredentials, activeIndex }
|
||||||
|
}: Props) => {
|
||||||
|
const inactiveIndex = activeIndex === 0 ? 1 : 0;
|
||||||
|
|
||||||
|
const activeCredentials = generatedCredentials[activeIndex];
|
||||||
|
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ViewRotationGeneratedCredentialsDisplay
|
||||||
|
activeCredentials={
|
||||||
|
<>
|
||||||
|
<CredentialDisplay label="Client ID">{activeCredentials?.clientId}</CredentialDisplay>
|
||||||
|
<CredentialDisplay isSensitive label="Client Secret">
|
||||||
|
{activeCredentials?.clientSecret}
|
||||||
|
</CredentialDisplay>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
inactiveCredentials={
|
||||||
|
<>
|
||||||
|
<CredentialDisplay label="Client ID">{inactiveCredentials?.clientId}</CredentialDisplay>
|
||||||
|
<CredentialDisplay isSensitive label="Client Secret">
|
||||||
|
{inactiveCredentials?.clientSecret}
|
||||||
|
</CredentialDisplay>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<NoticeBannerV2 title="Auth0 Retired Credentials Behavior">
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
Due to how Auth0 client secrets are rotated, retired credentials will not be able to
|
||||||
|
authenticate with Auth0 during their{" "}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://infisical.com/docs/documentation/platform/secret-rotation/overview#how-rotation-works"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline decoration-primary underline-offset-2 hover:text-mineshaft-200"
|
||||||
|
>
|
||||||
|
inactive period
|
||||||
|
</a>
|
||||||
|
. This is a limitation of the Auth0 platform and cannot be rectified by Infisical.
|
||||||
|
</p>
|
||||||
|
</NoticeBannerV2>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -3,6 +3,7 @@ import { faRotate } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
|
import { ViewAuth0ClientSecretRotationGeneratedCredentials } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewAuth0ClientSecretRotationGeneratedCredentials";
|
||||||
import { Modal, ModalContent, Spinner } from "@app/components/v2";
|
import { Modal, ModalContent, Spinner } from "@app/components/v2";
|
||||||
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
||||||
import {
|
import {
|
||||||
@ -11,7 +12,7 @@ import {
|
|||||||
useViewSecretRotationV2GeneratedCredentials
|
useViewSecretRotationV2GeneratedCredentials
|
||||||
} from "@app/hooks/api/secretRotationsV2";
|
} from "@app/hooks/api/secretRotationsV2";
|
||||||
|
|
||||||
import { ViewSqlRotationGeneratedCredentials } from "./shared";
|
import { ViewSqlCredentialsRotationGeneratedCredentials } from "./shared";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretRotation?: TSecretRotationV2;
|
secretRotation?: TSecretRotationV2;
|
||||||
@ -54,7 +55,14 @@ const Content = ({ secretRotation }: ContentProps) => {
|
|||||||
case SecretRotation.PostgresCredentials:
|
case SecretRotation.PostgresCredentials:
|
||||||
case SecretRotation.MsSqlCredentials:
|
case SecretRotation.MsSqlCredentials:
|
||||||
Component = (
|
Component = (
|
||||||
<ViewSqlRotationGeneratedCredentials
|
<ViewSqlCredentialsRotationGeneratedCredentials
|
||||||
|
generatedCredentialsResponse={generatedCredentialsResponse}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case SecretRotation.Auth0ClientSecret:
|
||||||
|
Component = (
|
||||||
|
<ViewAuth0ClientSecretRotationGeneratedCredentials
|
||||||
generatedCredentialsResponse={generatedCredentialsResponse}
|
generatedCredentialsResponse={generatedCredentialsResponse}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { faCheck, faClockRotateLeft } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
activeCredentials: ReactNode;
|
||||||
|
inactiveCredentials?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewRotationGeneratedCredentialsDisplay = ({
|
||||||
|
activeCredentials,
|
||||||
|
inactiveCredentials
|
||||||
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="w-full border-b border-mineshaft-600">
|
||||||
|
<span className="text-sm text-mineshaft-100">
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="mr-1.5 text-green" />
|
||||||
|
Current Credentials
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
The active credential set currently mapped to the rotation secrets.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col gap-x-8 gap-y-2 rounded border border-mineshaft-600 bg-mineshaft-700 p-2">
|
||||||
|
{activeCredentials}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{inactiveCredentials && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="w-full border-b border-mineshaft-600">
|
||||||
|
<span className="text-sm text-mineshaft-100">
|
||||||
|
<FontAwesomeIcon icon={faClockRotateLeft} className="mr-1.5 text-yellow" />
|
||||||
|
Retired Credentials
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-mineshaft-300">
|
||||||
|
The retired credential set that will be revoked during the next rotation cycle.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col gap-x-8 gap-y-2 rounded border border-mineshaft-600 bg-mineshaft-700 p-2">
|
||||||
|
{inactiveCredentials}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
import { CredentialDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/CredentialDisplay";
|
||||||
|
import { ViewRotationGeneratedCredentialsDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/ViewRotationGeneratedCredentialsDisplay";
|
||||||
|
import { TMsSqlCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/mssql-credentials-rotation";
|
||||||
|
import { TPostgresCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/postgres-credentials-rotation";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
generatedCredentialsResponse:
|
||||||
|
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||||
|
| TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewSqlCredentialsRotationGeneratedCredentials = ({
|
||||||
|
generatedCredentialsResponse: { generatedCredentials, activeIndex }
|
||||||
|
}: Props) => {
|
||||||
|
const inactiveIndex = activeIndex === 0 ? 1 : 0;
|
||||||
|
|
||||||
|
const activeCredentials = generatedCredentials[activeIndex];
|
||||||
|
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewRotationGeneratedCredentialsDisplay
|
||||||
|
activeCredentials={
|
||||||
|
<>
|
||||||
|
<CredentialDisplay label="Username">{activeCredentials?.username}</CredentialDisplay>
|
||||||
|
<CredentialDisplay isSensitive label="Password">
|
||||||
|
{activeCredentials?.password}
|
||||||
|
</CredentialDisplay>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
inactiveCredentials={
|
||||||
|
<>
|
||||||
|
<CredentialDisplay label="Username">{inactiveCredentials?.username}</CredentialDisplay>
|
||||||
|
<CredentialDisplay isSensitive label="Password">
|
||||||
|
{inactiveCredentials?.password}
|
||||||
|
</CredentialDisplay>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,57 +0,0 @@
|
|||||||
import { faCheck, faClockRotateLeft } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { CredentialDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/CredentialDisplay";
|
|
||||||
import { TViewSecretRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
generatedCredentialsResponse: TViewSecretRotationGeneratedCredentialsResponse;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewSqlRotationGeneratedCredentials = ({
|
|
||||||
generatedCredentialsResponse: { generatedCredentials, activeIndex }
|
|
||||||
}: Props) => {
|
|
||||||
const inactiveIndex = activeIndex === 0 ? 1 : 0;
|
|
||||||
|
|
||||||
const activeCredentials = generatedCredentials[activeIndex];
|
|
||||||
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="w-full border-b border-mineshaft-600">
|
|
||||||
<span className="text-sm text-mineshaft-100">
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="mr-1.5 text-green" />
|
|
||||||
Current Credentials
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-mineshaft-300">
|
|
||||||
The active credential set currently mapped to the rotation secrets.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col gap-x-8 gap-y-2 rounded border border-mineshaft-600 bg-mineshaft-700 p-2">
|
|
||||||
<CredentialDisplay label="Username">{activeCredentials?.username}</CredentialDisplay>
|
|
||||||
<CredentialDisplay isSensitive label="Password">
|
|
||||||
{activeCredentials?.password}
|
|
||||||
</CredentialDisplay>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<div className="w-full border-b border-mineshaft-600">
|
|
||||||
<span className="text-sm text-mineshaft-100">
|
|
||||||
<FontAwesomeIcon icon={faClockRotateLeft} className="mr-1.5 text-yellow" />
|
|
||||||
Retired Credentials
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-mineshaft-300">
|
|
||||||
The retired credential set that will be revoked during the next rotation cycle.
|
|
||||||
</p>
|
|
||||||
<div className="flex flex-col gap-x-8 gap-y-2 rounded border border-mineshaft-600 bg-mineshaft-700 p-2">
|
|
||||||
<CredentialDisplay label="Username">{inactiveCredentials?.username}</CredentialDisplay>
|
|
||||||
<CredentialDisplay isSensitive label="Password">
|
|
||||||
{inactiveCredentials?.password}
|
|
||||||
</CredentialDisplay>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1 +1,3 @@
|
|||||||
export * from "./ViewSqlRotationGeneratedCredentials";
|
export * from "./CredentialDisplay";
|
||||||
|
export * from "./ViewRotationGeneratedCredentialsDisplay";
|
||||||
|
export * from "./ViewSqlCredentialsRotationGeneratedCredentials";
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format, setHours, setMinutes } from "date-fns";
|
import { format, setHours, setMinutes } from "date-fns";
|
||||||
|
|
||||||
import { FilterableSelect, FormControl, Input, Switch } from "@app/components/v2";
|
import { FilterableSelect, FormControl, Input, Switch } from "@app/components/v2";
|
||||||
import { getRotateAtLocal } from "@app/helpers/secretRotationsV2";
|
import {
|
||||||
|
getRotateAtLocal,
|
||||||
|
IS_ROTATION_DUAL_CREDENTIALS,
|
||||||
|
SECRET_ROTATION_MAP
|
||||||
|
} from "@app/helpers/secretRotationsV2";
|
||||||
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
import { WorkspaceEnv } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
import { TSecretRotationV2Form } from "./schemas";
|
import { TSecretRotationV2Form } from "./schemas";
|
||||||
@ -14,7 +20,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => {
|
export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => {
|
||||||
const { control } = useFormContext<TSecretRotationV2Form>();
|
const { control, watch } = useFormContext<TSecretRotationV2Form>();
|
||||||
|
|
||||||
|
const [type, isAutoRotationEnabled] = watch(["type", "isAutoRotationEnabled"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -117,6 +125,23 @@ export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }:
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!IS_ROTATION_DUAL_CREDENTIALS[type] && isAutoRotationEnabled && (
|
||||||
|
<div className="rounded border border-yellow bg-yellow/10 p-2 px-3 text-xs text-yellow">
|
||||||
|
<FontAwesomeIcon icon={faWarning} className="mr-1" /> Due to{" "}
|
||||||
|
{SECRET_ROTATION_MAP[type].name} 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. <br />
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/platform/secret-rotation/overview#how-rotation-works"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline decoration-yellow underline-offset-2 hover:text-mineshaft-200"
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,7 @@ import { SecretRotationV2ReviewFields } from "@app/components/secret-rotations-v
|
|||||||
import { SecretRotationV2SecretsMappingFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields";
|
import { SecretRotationV2SecretsMappingFields } from "@app/components/secret-rotations-v2/forms/SecretRotationV2SecretsMappingFields";
|
||||||
import { Button } from "@app/components/v2";
|
import { Button } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
import { IS_ROTATION_DUAL_CREDENTIALS, SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
|
||||||
import {
|
import {
|
||||||
SecretRotation,
|
SecretRotation,
|
||||||
TSecretRotationV2,
|
TSecretRotationV2,
|
||||||
@ -84,7 +84,7 @@ export const SecretRotationV2Form = ({
|
|||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type,
|
type,
|
||||||
isAutoRotationEnabled: true,
|
isAutoRotationEnabled: IS_ROTATION_DUAL_CREDENTIALS[type],
|
||||||
rotationInterval: DEFAULT_ROTATION_INTERVAL,
|
rotationInterval: DEFAULT_ROTATION_INTERVAL,
|
||||||
rotateAtUtc: {
|
rotateAtUtc: {
|
||||||
hours: 0,
|
hours: 0,
|
||||||
@ -92,7 +92,7 @@ export const SecretRotationV2Form = ({
|
|||||||
},
|
},
|
||||||
environment: currentWorkspace?.environments.find((env) => env.slug === envSlug),
|
environment: currentWorkspace?.environments.find((env) => env.slug === envSlug),
|
||||||
secretPath,
|
secretPath,
|
||||||
...rotationOption!.template
|
...(rotationOption!.template as object) // can't infer type since we don't know which specific type it is
|
||||||
},
|
},
|
||||||
reValidateMode: "onChange"
|
reValidateMode: "onChange"
|
||||||
});
|
});
|
||||||
@ -142,7 +142,11 @@ export const SecretRotationV2Form = ({
|
|||||||
setSelectedTabIndex((prev) => prev - 1);
|
setSelectedTabIndex((prev) => prev - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { handleSubmit, trigger } = formMethods;
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
trigger,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = formMethods;
|
||||||
|
|
||||||
const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields);
|
const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields);
|
||||||
|
|
||||||
@ -219,7 +223,12 @@ export const SecretRotationV2Form = ({
|
|||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
||||||
<Button onClick={handleNext} colorSchema="secondary">
|
<Button
|
||||||
|
onClick={handleNext}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
{isFinalStep ? `${secretRotation ? "Update" : "Create"} Secret Rotation` : "Next"}
|
{isFinalStep ? `${secretRotation ? "Update" : "Create"} Secret Rotation` : "Next"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handlePrev} colorSchema="secondary">
|
<Button onClick={handlePrev} colorSchema="secondary">
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
|
||||||
|
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
|
||||||
|
import { useAuth0ConnectionListClients } from "@app/hooks/api/appConnections/auth0";
|
||||||
|
import { TAuth0Client } from "@app/hooks/api/appConnections/auth0/types";
|
||||||
|
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||||
|
|
||||||
|
export const Auth0ClientSecretRotationParametersFields = () => {
|
||||||
|
const { control, watch } = useFormContext<
|
||||||
|
TSecretRotationV2Form & {
|
||||||
|
type: SecretRotation.Auth0ClientSecret;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = watch("connection.id");
|
||||||
|
|
||||||
|
const { data: clients, isPending: isClientsPending } = useAuth0ConnectionListClients(
|
||||||
|
connectionId,
|
||||||
|
{ enabled: Boolean(connectionId) }
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name="parameters.clientId"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Application"
|
||||||
|
helperText={
|
||||||
|
<Tooltip
|
||||||
|
className="max-w-md"
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
Ensure that your connection has the{" "}
|
||||||
|
<span className="font-semibold">read_clients</span> permission and the application
|
||||||
|
exists in the connection's audience.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span>Don't see the application you're looking for?</span>{" "}
|
||||||
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
menuPlacement="top"
|
||||||
|
isLoading={isClientsPending && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={clients?.find((client) => client.id === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
onChange((option as SingleValue<TAuth0Client>)?.id ?? null);
|
||||||
|
}}
|
||||||
|
options={clients}
|
||||||
|
placeholder="Select an application..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -3,11 +3,13 @@ import { useFormContext } from "react-hook-form";
|
|||||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||||
|
|
||||||
import { TSecretRotationV2Form } from "../schemas";
|
import { TSecretRotationV2Form } from "../schemas";
|
||||||
import { SqlRotationParametersFields } from "./shared";
|
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
|
||||||
|
import { SqlCredentialsRotationParametersFields } from "./shared";
|
||||||
|
|
||||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||||
[SecretRotation.PostgresCredentials]: SqlRotationParametersFields,
|
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
|
||||||
[SecretRotation.MsSqlCredentials]: SqlRotationParametersFields
|
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
|
||||||
|
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretRotationV2ParametersFields = () => {
|
export const SecretRotationV2ParametersFields = () => {
|
||||||
|