Add Azure Client Secrets Rotation

This commit is contained in:
carlosmonastyrski
2025-04-16 05:04:41 -03:00
parent 7d97a76ecc
commit 1184ea1b11
36 changed files with 861 additions and 36 deletions

View File

@@ -0,0 +1,19 @@
import {
AzureClientSecretRotationGeneratedCredentialsSchema,
AzureClientSecretRotationSchema,
CreateAzureClientSecretRotationSchema,
UpdateAzureClientSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/azure-client-secret";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerAzureClientSecretRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.AzureClientSecret,
server,
responseSchema: AzureClientSecretRotationSchema,
createSchema: CreateAzureClientSecretRotationSchema,
updateSchema: UpdateAzureClientSecretRotationSchema,
generatedCredentialsSchema: AzureClientSecretRotationGeneratedCredentialsSchema
});

View File

@@ -1,6 +1,7 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
@@ -12,5 +13,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
> = {
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter
};

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
@@ -13,7 +14,8 @@ import { AuthMode } from "@app/services/auth/auth-type";
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationListItemSchema,
MsSqlCredentialsRotationListItemSchema,
Auth0ClientSecretRotationListItemSchema
Auth0ClientSecretRotationListItemSchema,
AzureClientSecretRotationListItemSchema
]);
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {

View File

@@ -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 AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
name: "Azure Client Secret",
type: SecretRotation.AzureClientSecret,
connection: AppConnection.AzureClientSecrets,
template: {
secretsMapping: {
clientId: "AZURE_CLIENT_ID",
clientSecret: "AZURE_CLIENT_SECRET"
}
}
};

View File

@@ -0,0 +1,155 @@
import {
AzureAddPasswordResponse,
TAzureClientSecretRotationGeneratedCredentials,
TAzureClientSecretRotationWithConnection
} from "@app/ee/services/secret-rotation-v2/azure-client-secret/azure-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 { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets";
const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
export const azureClientSecretRotationFactory: TRotationFactory<
TAzureClientSecretRotationWithConnection,
TAzureClientSecretRotationGeneratedCredentials
> = (secretRotation, appConnectionDAL, kmsService) => {
const {
connection,
parameters: { appId },
secretsMapping,
rotationInterval
} = secretRotation;
/**
* Creates a new client secret for the Azure app.
*/
const $rotateClientSecret = async () => {
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
const endpoint = `${GRAPH_API_BASE}/applications/${appId}/addPassword`;
await blockLocalAndPrivateIpAddresses(endpoint);
const endDateTime = new Date();
endDateTime.setDate(endDateTime.getDate() + rotationInterval);
try {
const { data } = await request.post<AzureAddPasswordResponse>(
endpoint,
{
passwordCredential: {
displayName: "Infisical Auto-Rotated Secret",
endDateTime: endDateTime.toISOString()
}
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
if (!data?.secretText || !data?.keyId) {
throw new Error("Invalid response from Azure: missing secretText or keyId.");
}
return {
clientSecret: data.secretText,
clientId: data.keyId
};
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to add client secret to Azure app ${appId}: ${message}`);
}
};
/**
* Revokes a client secret from the Azure app using its keyId.
*/
const revokeCredential = async (clientId: string) => {
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
const endpoint = `${GRAPH_API_BASE}/applications/${appId}/removePassword`;
await blockLocalAndPrivateIpAddresses(endpoint);
try {
await request.post(
endpoint,
{ keyId: clientId },
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to remove client secret with keyId ${clientId} from app ${appId}: ${message}`);
}
};
/**
* Issues a new set of credentials.
*/
const issueCredentials: TRotationFactoryIssueCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
callback
) => {
const credentials = await $rotateClientSecret();
return callback(credentials);
};
/**
* Revokes a list of credentials.
*/
const revokeCredentials: TRotationFactoryRevokeCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
credentials,
callback
) => {
if (!credentials?.length) return callback();
await Promise.all(credentials.map(({ clientId }) => revokeCredential(clientId)));
return callback();
};
/**
* Rotates credentials by issuing new ones and revoking the old.
*/
const rotateCredentials: TRotationFactoryRotateCredentials<TAzureClientSecretRotationGeneratedCredentials> = async (
oldCredentials,
callback
) => {
const newCredentials = await $rotateClientSecret();
if (oldCredentials?.clientId) {
await revokeCredential(oldCredentials.clientId);
}
return callback(newCredentials);
};
/**
* Maps the generated credentials into the secret payload format.
*/
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAzureClientSecretRotationGeneratedCredentials> = ({
clientSecret,
clientId
}) => [
{ key: secretsMapping.clientSecret, value: clientSecret },
{ key: secretsMapping.clientId, value: clientId }
];
return {
issueCredentials,
revokeCredentials,
rotateCredentials,
getSecretsPayload
};
};

View File

@@ -0,0 +1,68 @@
import { z } from "zod";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import {
BaseCreateSecretRotationSchema,
BaseSecretRotationSchema,
BaseUpdateSecretRotationSchema
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
import { SecretRotations } from "@app/lib/api-docs";
import { SecretNameSchema } from "@app/server/lib/schemas";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const AzureClientSecretRotationGeneratedCredentialsSchema = z
.object({
clientId: z.string(),
clientSecret: z.string()
})
.array()
.min(1)
.max(2);
const AzureClientSecretRotationParametersSchema = z.object({
appId: z.string().trim().min(1, "Client ID Required").describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appId),
appName: z
.string()
.trim()
.min(1, "App Name Required")
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName)
});
const AzureClientSecretRotationSecretsMappingSchema = z.object({
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AZURE_CLIENT_SECRET.clientId),
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.AZURE_CLIENT_SECRET.clientSecret)
});
export const AzureClientSecretRotationTemplateSchema = z.object({
secretsMapping: z.object({
clientId: z.string(),
clientSecret: z.string()
})
});
export const AzureClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.AzureClientSecret).extend({
type: z.literal(SecretRotation.AzureClientSecret),
parameters: AzureClientSecretRotationParametersSchema,
secretsMapping: AzureClientSecretRotationSecretsMappingSchema
});
export const CreateAzureClientSecretRotationSchema = BaseCreateSecretRotationSchema(
SecretRotation.AzureClientSecret
).extend({
parameters: AzureClientSecretRotationParametersSchema,
secretsMapping: AzureClientSecretRotationSecretsMappingSchema
});
export const UpdateAzureClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
SecretRotation.AzureClientSecret
).extend({
parameters: AzureClientSecretRotationParametersSchema.optional(),
secretsMapping: AzureClientSecretRotationSecretsMappingSchema.optional()
});
export const AzureClientSecretRotationListItemSchema = z.object({
name: z.literal("Azure Client Secret"),
connection: z.literal(AppConnection.AzureClientSecrets),
type: z.literal(SecretRotation.AzureClientSecret),
template: AzureClientSecretRotationTemplateSchema
});

View File

@@ -0,0 +1,41 @@
import { z } from "zod";
import { TAzureClientSecretsConnection } from "@app/services/app-connection/azure-client-secrets";
import {
AzureClientSecretRotationGeneratedCredentialsSchema,
AzureClientSecretRotationListItemSchema,
AzureClientSecretRotationSchema,
CreateAzureClientSecretRotationSchema
} from "./azure-client-secret-rotation-schemas";
export type TAzureClientSecretRotation = z.infer<typeof AzureClientSecretRotationSchema>;
export type TAzureClientSecretRotationInput = z.infer<typeof CreateAzureClientSecretRotationSchema>;
export type TAzureClientSecretRotationListItem = z.infer<typeof AzureClientSecretRotationListItemSchema>;
export type TAzureClientSecretRotationWithConnection = TAzureClientSecretRotation & {
connection: TAzureClientSecretsConnection;
};
export type TAzureClientSecretRotationGeneratedCredentials = z.infer<
typeof AzureClientSecretRotationGeneratedCredentialsSchema
>;
export interface TAzureClientSecretRotationParameters {
appId: string;
keyId?: string;
displayName?: string;
}
export interface TAzureClientSecretRotationSecretsMapping {
appId: string;
clientSecret: string;
keyId: string;
}
export interface AzureAddPasswordResponse {
secretText: string;
keyId: string;
}

View File

@@ -0,0 +1,3 @@
export * from "./azure-client-secret-rotation-constants";
export * from "./azure-client-secret-rotation-schemas";
export * from "./azure-client-secret-rotation-types";

View File

@@ -1,7 +1,8 @@
export enum SecretRotation {
PostgresCredentials = "postgres-credentials",
MsSqlCredentials = "mssql-credentials",
Auth0ClientSecret = "auth0-client-secret"
Auth0ClientSecret = "auth0-client-secret",
AzureClientSecret = "azure-client-secret"
}
export enum SecretRotationStatus {

View File

@@ -4,6 +4,7 @@ import { getConfig } from "@app/lib/config/env";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./auth0-client-secret";
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
@@ -18,7 +19,8 @@ import {
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION
};
export const listSecretRotationOptions = () => {

View File

@@ -4,11 +4,13 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials",
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret"
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
[SecretRotation.AzureClientSecret]: "Azure Client Secret"
};
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets
};

View File

@@ -14,6 +14,7 @@ import {
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { auth0ClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/auth0-client-secret/auth0-client-secret-rotation-fns";
import { azureClientSecretRotationFactory } from "@app/ee/services/secret-rotation-v2/azure-client-secret/azure-client-secret-rotation-fns";
import { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import {
calculateNextRotationAt,
@@ -100,7 +101,7 @@ export type TSecretRotationV2ServiceFactoryDep = {
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
queueService: Pick<TQueueServiceFactory, "queuePg">;
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
};
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
@@ -114,7 +115,8 @@ type TRotationFactoryImplementation = TRotationFactory<
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation
};
export const secretRotationV2ServiceFactory = ({

View File

@@ -12,6 +12,13 @@ import {
TAuth0ClientSecretRotationListItem,
TAuth0ClientSecretRotationWithConnection
} from "./auth0-client-secret";
import {
TAzureClientSecretRotation,
TAzureClientSecretRotationGeneratedCredentials,
TAzureClientSecretRotationInput,
TAzureClientSecretRotationListItem,
TAzureClientSecretRotationWithConnection
} from "./azure-client-secret";
import {
TMsSqlCredentialsRotation,
TMsSqlCredentialsRotationInput,
@@ -27,26 +34,34 @@ import {
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
import { SecretRotation } from "./secret-rotation-v2-enums";
export type TSecretRotationV2 = TPostgresCredentialsRotation | TMsSqlCredentialsRotation | TAuth0ClientSecretRotation;
export type TSecretRotationV2 =
| TPostgresCredentialsRotation
| TMsSqlCredentialsRotation
| TAuth0ClientSecretRotation
| TAzureClientSecretRotation;
export type TSecretRotationV2WithConnection =
| TPostgresCredentialsRotationWithConnection
| TMsSqlCredentialsRotationWithConnection
| TAuth0ClientSecretRotationWithConnection;
| TAuth0ClientSecretRotationWithConnection
| TAzureClientSecretRotationWithConnection;
export type TSecretRotationV2GeneratedCredentials =
| TSqlCredentialsRotationGeneratedCredentials
| TAuth0ClientSecretRotationGeneratedCredentials;
| TAuth0ClientSecretRotationGeneratedCredentials
| TAzureClientSecretRotationGeneratedCredentials;
export type TSecretRotationV2Input =
| TPostgresCredentialsRotationInput
| TMsSqlCredentialsRotationInput
| TAuth0ClientSecretRotationInput;
| TAuth0ClientSecretRotationInput
| TAzureClientSecretRotationInput;
export type TSecretRotationV2ListItem =
| TPostgresCredentialsRotationListItem
| TMsSqlCredentialsRotationListItem
| TAuth0ClientSecretRotationListItem;
| TAuth0ClientSecretRotationListItem
| TAzureClientSecretRotationListItem;
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
@@ -170,7 +185,7 @@ export type TRotationFactory<
C extends TSecretRotationV2GeneratedCredentials
> = (
secretRotation: T,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
issueCredentials: TRotationFactoryIssueCredentials<C>;

View File

@@ -1,11 +1,13 @@
import { z } from "zod";
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
PostgresCredentialsRotationSchema,
MsSqlCredentialsRotationSchema,
Auth0ClientSecretRotationSchema
Auth0ClientSecretRotationSchema,
AzureClientSecretRotationSchema
]);

View File

@@ -2006,6 +2006,10 @@ export const SecretRotations = {
},
AUTH0_CLIENT_SECRET: {
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
},
AZURE_CLIENT_SECRET: {
appId: "The ID of the Azure Application to rotate the client secret for.",
appName: "The name of the Azure Application to rotate the client secret for."
}
},
SECRETS_MAPPING: {
@@ -2016,6 +2020,10 @@ export const SecretRotations = {
AUTH0_CLIENT_SECRET: {
clientId: "The name of the secret that the client ID will be mapped to.",
clientSecret: "The name of the secret that the rotated client secret will be mapped to."
},
AZURE_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."
}
}
};

View File

@@ -1,9 +1,14 @@
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 {
CreateAzureClientSecretsConnectionSchema,
SanitizedAzureClientSecretsConnectionSchema,
UpdateAzureClientSecretsConnectionSchema
} from "@app/services/app-connection/azure-client-secrets";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
@@ -15,4 +20,30 @@ export const registerAzureClientSecretsConnectionRouter = async (server: Fastify
createSchema: CreateAzureClientSecretsConnectionSchema,
updateSchema: UpdateAzureClientSecretsConnectionSchema
});
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(), appId: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const clients = await server.services.appConnection.azureClientSecrets.listApps(connectionId, req.permission);
return { clients };
}
});
};

View File

@@ -33,6 +33,7 @@ import { ValidateAwsConnectionCredentialsSchema } from "./aws";
import { awsConnectionService } from "./aws/aws-connection-service";
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
import { ValidateAzureClientSecretsConnectionCredentialsSchema } from "./azure-client-secrets";
import { azureClientSecretsConnectionService } from "./azure-client-secrets/azure-client-secrets-service";
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
import { camundaConnectionService } from "./camunda/camunda-connection-service";
@@ -448,6 +449,7 @@ export const appConnectionServiceFactory = ({
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
vercel: vercelConnectionService(connectAppConnectionById),
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
};
};

View File

@@ -2,15 +2,22 @@ import { AxiosError, AxiosResponse } from "axios";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import {
decryptAppConnectionCredentials,
encryptAppConnectionCredentials,
getAppConnectionMethodName
} from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TAppConnectionDALFactory } from "../app-connection-dal";
import { AppConnection } from "../app-connection-enums";
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
import {
ExchangeCodeAzureResponse,
TAzureClientSecretsConnectionConfig
TAzureClientSecretsConnectionConfig,
TAzureClientSecretsConnectionCredentials
} from "./azure-client-secrets-connection-types";
export const getAzureClientSecretsConnectionListItem = () => {
@@ -24,6 +31,72 @@ export const getAzureClientSecretsConnectionListItem = () => {
};
};
export const getAzureConnectionAccessToken = async (
connectionId: string,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const appCfg = getConfig();
if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
throw new BadRequestError({
message: `Azure environment variables have not been configured`
});
}
const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) {
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
}
if (appConnection.app !== AppConnection.AzureClientSecrets) {
throw new BadRequestError({
message: `Connection with ID '${connectionId}' is not an Azure Client Secrets connection`
});
}
const credentials = (await decryptAppConnectionCredentials({
orgId: appConnection.orgId,
kmsService,
encryptedCredentials: appConnection.encryptedCredentials
})) as TAzureClientSecretsConnectionCredentials;
const { expiresAt, refreshToken } = credentials;
// get new token if expired or less than 5 minutes until expiry
if (Date.now() < expiresAt - 300000) {
return credentials.accessToken;
}
const { data } = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"),
new URLSearchParams({
grant_type: "refresh_token",
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID,
client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
refresh_token: refreshToken
})
);
const updatedCredentials = {
...credentials,
accessToken: data.access_token,
expiresAt: Date.now() + data.expires_in * 1000,
refreshToken: data.refresh_token
};
const encryptedCredentials = await encryptAppConnectionCredentials({
credentials: updatedCredentials,
orgId: appConnection.orgId,
kmsService
});
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials });
return data.access_token;
};
export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => {
const { credentials: inputCredentials, method } = config;
@@ -44,10 +117,10 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA
new URLSearchParams({
grant_type: "authorization_code",
code: inputCredentials.code,
scope: `openid offline_access https://azconfig.io/.default`,
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID,
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
redirect_uri: `${SITE_URL}/organization/app-connections/azure-client-secrets/oauth/callback`
})
);
} catch (e: unknown) {

View File

@@ -26,7 +26,11 @@ export type TAzureClientSecretsConnectionConfig = DiscriminativePick<
orgId: string;
};
export type ExchangeCodeAzureResponse = {
export type TAzureClientSecretsConnectionCredentials = z.infer<
typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema
>;
export interface ExchangeCodeAzureResponse {
token_type: string;
scope: string;
expires_in: number;
@@ -34,8 +38,29 @@ export type ExchangeCodeAzureResponse = {
access_token: string;
refresh_token: string;
id_token: string;
};
}
export interface TAzureRegisteredApp {
id: string;
appId: string;
displayName: string;
description?: string;
createdDateTime: string;
identifierUris?: string[];
signInAudience?: string;
}
export interface TAzureListRegisteredAppsResponse {
"@odata.context": string;
"@odata.nextLink"?: string;
value: TAzureRegisteredApp[];
}
export interface TAzureClientSecret {
keyId: string;
displayName?: string;
startDateTime: string;
endDateTime: string;
secretText?: string;
}
export type TAzureClientSecretsConnectionCredentials = z.infer<
typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema
>;

View File

@@ -0,0 +1,70 @@
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 { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import {
TAzureClientSecretsConnection,
TAzureListRegisteredAppsResponse,
TAzureRegisteredApp
} from "./azure-client-secrets-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TAzureClientSecretsConnection>;
const listAzureRegisteredApps = async (
appConnection: TAzureClientSecretsConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const accessToken = await getAzureConnectionAccessToken(appConnection.id, appConnectionDAL, kmsService);
const graphEndpoint = `https://graph.microsoft.com/v1.0/applications`;
await blockLocalAndPrivateIpAddresses(graphEndpoint);
const apps: TAzureRegisteredApp[] = [];
let nextLink = graphEndpoint;
while (nextLink) {
// eslint-disable-next-line no-await-in-loop
const { data: appsPage } = await request.get<TAzureListRegisteredAppsResponse>(nextLink, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
});
apps.push(...appsPage.value);
nextLink = appsPage["@odata.nextLink"] || "";
}
return apps;
};
export const azureClientSecretsConnectionService = (
getAppConnection: TGetAppConnectionFunc,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.AzureClientSecrets, connectionId, actor);
const apps = await listAzureRegisteredApps(appConnection, appConnectionDAL, kmsService);
return apps.map((app) => ({
id: app.id,
name: app.displayName,
appId: app.appId
}));
};
return {
listApps
};
};

View File

@@ -38,7 +38,11 @@ export const getAzureConnectionAccessToken = async (
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
}
if (appConnection.app !== AppConnection.AzureKeyVault && appConnection.app !== AppConnection.AzureAppConfiguration) {
if (
appConnection.app !== AppConnection.AzureKeyVault &&
appConnection.app !== AppConnection.AzureAppConfiguration &&
appConnection.app !== AppConnection.AzureClientSecrets
) {
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not an Azure Key Vault connection` });
}

View File

@@ -0,0 +1,71 @@
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 { useAzureConnectionListClients } from "@app/hooks/api/appConnections/azure";
import { TAzureClient } from "@app/hooks/api/appConnections/azure/types";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
export const AzureClientSecretRotationParametersFields = () => {
const { control, watch, setValue } = useFormContext<
TSecretRotationV2Form & {
type: SecretRotation.AzureClientSecret;
}
>();
const connectionId = watch("connection.id");
const { data: clients, isPending: isClientsPending } = useAzureConnectionListClients(
connectionId,
{ enabled: Boolean(connectionId) }
);
return (
<Controller
name="parameters.appId"
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&#39;s audience.
</>
}
>
<div>
<span>Don&#39;t see the application you&#39;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<TAzureClient>)?.id ?? null);
setValue("parameters.appName", (option as SingleValue<TAzureClient>)?.name ?? "");
}}
options={clients}
placeholder="Select an application..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
);
};

View File

@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { TSecretRotationV2Form } from "../schemas";
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
import { AzureClientSecretRotationParametersFields } from "./AzureClientSecretRotationParametersFields";
import { SqlCredentialsRotationParametersFields } from "./shared";
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields,
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationParametersFields
};
export const SecretRotationV2ParametersFields = () => {

View File

@@ -0,0 +1,30 @@
import { useFormContext } from "react-hook-form";
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
import { GenericFieldLabel } from "@app/components/v2";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { SecretRotationReviewSection } from "./shared";
export const AzureClientSecretRotationReviewFields = () => {
const { watch } = useFormContext<
TSecretRotationV2Form & {
type: SecretRotation.AzureClientSecret;
}
>();
const [parameters, { clientId, clientSecret }] = watch(["parameters", "secretsMapping"]);
return (
<>
<SecretRotationReviewSection label="Parameters">
<GenericFieldLabel label="App Name">{parameters.appName}</GenericFieldLabel>
<GenericFieldLabel label="App ID">{parameters.appId}</GenericFieldLabel>
</SecretRotationReviewSection>
<SecretRotationReviewSection label="Secrets Mapping">
<GenericFieldLabel label="Client ID">{clientId}</GenericFieldLabel>
<GenericFieldLabel label="Client Secret">{clientSecret}</GenericFieldLabel>
</SecretRotationReviewSection>
</>
);
};

View File

@@ -7,12 +7,14 @@ import { getRotateAtLocal } from "@app/helpers/secretRotationsV2";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
import { AzureClientSecretRotationReviewFields } from "./AzureClientSecretRotationReviewFields";
import { SqlCredentialsRotationReviewFields } from "./shared";
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationReviewFields,
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationReviewFields,
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields,
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationReviewFields
};
export const SecretRotationV2ReviewFields = () => {

View File

@@ -0,0 +1,58 @@
import { Controller, useFormContext } from "react-hook-form";
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
import { FormControl, Input } from "@app/components/v2";
import { SecretRotation, useSecretRotationV2Option } from "@app/hooks/api/secretRotationsV2";
import { SecretsMappingTable } from "./shared";
export const AzureClientSecretRotationSecretsMappingFields = () => {
const { control } = useFormContext<
TSecretRotationV2Form & {
type: SecretRotation.AzureClientSecret;
}
>();
const { rotationOption } = useSecretRotationV2Option(SecretRotation.AzureClientSecret);
const items = [
{
name: "Client ID",
input: (
<Controller
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input
value={value}
onChange={onChange}
placeholder={rotationOption?.template.secretsMapping.clientId}
/>
</FormControl>
)}
control={control}
name="secretsMapping.clientId"
/>
)
},
{
name: "Client Secret",
input: (
<Controller
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input
value={value}
onChange={onChange}
placeholder={rotationOption?.template.secretsMapping.clientSecret}
/>
</FormControl>
)}
control={control}
name="secretsMapping.clientSecret"
/>
)
}
];
return <SecretsMappingTable items={items} />;
};

View File

@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { TSecretRotationV2Form } from "../schemas";
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
import { AzureClientSecretRotationSecretsMappingFields } from "./AzureClientSecretRotationSecretsMappingFields";
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationSecretsMappingFields,
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields,
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationSecretsMappingFields
};
export const SecretRotationV2SecretsMappingFields = () => {

View File

@@ -0,0 +1,18 @@
import { z } from "zod";
import { BaseSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/base-secret-rotation-v2-schema";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
export const AzureClientSecretRotationSchema = z
.object({
type: z.literal(SecretRotation.AzureClientSecret),
parameters: z.object({
appId: z.string().trim().min(1, "App ID required"),
appName: z.string().trim().min(1, "App Name required")
}),
secretsMapping: z.object({
clientId: z.string().trim().min(1, "Client ID required"),
clientSecret: z.string().trim().min(1, "Client Secret required")
})
})
.merge(BaseSecretRotationSchema);

View File

@@ -1,13 +1,15 @@
import { z } from "zod";
import { Auth0ClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/auth0-client-secret-rotation-schema";
import { AzureClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/azure-client-secret-rotation-schema";
import { MsSqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mssql-credentials-rotation-schema";
import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema";
const SecretRotationUnionSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationSchema,
MsSqlCredentialsRotationSchema,
Auth0ClientSecretRotationSchema
Auth0ClientSecretRotationSchema,
AzureClientSecretRotationSchema
]);
export const SecretRotationV2FormSchema = SecretRotationUnionSchema;

View File

@@ -19,20 +19,27 @@ export const SECRET_ROTATION_MAP: Record<
name: "Auth0 Client Secret",
image: "Auth0.png",
size: 35
},
[SecretRotation.AzureClientSecret]: {
name: "Azure Client Secret",
image: "Microsoft Azure.png",
size: 35
}
};
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets
};
// if a rotation can potentially have downtime due to rotating a single credential set this to false
export const IS_ROTATION_DUAL_CREDENTIALS: Record<SecretRotation, boolean> = {
[SecretRotation.PostgresCredentials]: true,
[SecretRotation.MsSqlCredentials]: true,
[SecretRotation.Auth0ClientSecret]: false
[SecretRotation.Auth0ClientSecret]: false,
[SecretRotation.AzureClientSecret]: false
};
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {

View File

@@ -0,0 +1 @@
export * from "./queries";

View File

@@ -0,0 +1,37 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "../queries";
import { TAzureClient } from "./types";
const azureConnectionKeys = {
all: [...appConnectionKeys.all, "azure"] as const,
listClients: (connectionId: string) =>
[...azureConnectionKeys.all, "clients", connectionId] as const
};
export const useAzureConnectionListClients = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TAzureClient[],
unknown,
TAzureClient[],
ReturnType<typeof azureConnectionKeys.listClients>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: azureConnectionKeys.listClients(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<{ clients: TAzureClient[] }>(
`/api/v1/app-connections/azure-client-secrets/${connectionId}/clients`
);
return data.clients;
},
...options
});
};

View File

@@ -0,0 +1,4 @@
export type TAzureClient = {
name: string;
id: string;
};

View File

@@ -1,7 +1,8 @@
export enum SecretRotation {
PostgresCredentials = "postgres-credentials",
MsSqlCredentials = "mssql-credentials",
Auth0ClientSecret = "auth0-client-secret"
Auth0ClientSecret = "auth0-client-secret",
AzureClientSecret = "azure-client-secret"
}
export enum SecretRotationStatus {

View File

@@ -0,0 +1,38 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import {
TSecretRotationV2Base,
TSecretRotationV2GeneratedCredentialsResponseBase
} from "@app/hooks/api/secretRotationsV2/types/shared";
export type TAzureClientSecretRotation = TSecretRotationV2Base & {
type: SecretRotation.AzureClientSecret;
parameters: {
appId: string;
appName: string;
};
secretsMapping: {
clientId: string;
clientSecret: string;
};
};
export type TAzureClientSecretRotationGeneratedCredentials = {
clientId: string;
clientSecret: string;
};
export type TAzureClientSecretRotationGeneratedCredentialsResponse =
TSecretRotationV2GeneratedCredentialsResponseBase<
SecretRotation.AzureClientSecret,
TAzureClientSecretRotationGeneratedCredentials
>;
export type TAzureClientSecretRotationOption = {
name: string;
type: SecretRotation.AzureClientSecret;
connection: AppConnection.AzureClientSecrets;
template: {
secretsMapping: TAzureClientSecretRotation["secretsMapping"];
};
};

View File

@@ -4,6 +4,11 @@ import {
TAuth0ClientSecretRotationGeneratedCredentialsResponse,
TAuth0ClientSecretRotationOption
} from "@app/hooks/api/secretRotationsV2/types/auth0-client-secret-rotation";
import {
TAzureClientSecretRotation,
TAzureClientSecretRotationGeneratedCredentialsResponse,
TAzureClientSecretRotationOption
} from "@app/hooks/api/secretRotationsV2/types/azure-client-secret-rotation";
import {
TMsSqlCredentialsRotation,
TMsSqlCredentialsRotationGeneratedCredentialsResponse
@@ -20,13 +25,15 @@ export type TSecretRotationV2 = (
| TPostgresCredentialsRotation
| TMsSqlCredentialsRotation
| TAuth0ClientSecretRotation
| TAzureClientSecretRotation
) & {
secrets: (SecretV3RawSanitized | null)[];
};
export type TSecretRotationV2Option =
| TSqlCredentialsRotationOption
| TAuth0ClientSecretRotationOption;
| TAuth0ClientSecretRotationOption
| TAzureClientSecretRotationOption;
export type TListSecretRotationV2Options = { secretRotationOptions: TSecretRotationV2Option[] };
@@ -35,7 +42,8 @@ export type TSecretRotationV2Response = { secretRotation: TSecretRotationV2 };
export type TViewSecretRotationGeneratedCredentialsResponse =
| TPostgresCredentialsRotationGeneratedCredentialsResponse
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
| TAuth0ClientSecretRotationGeneratedCredentialsResponse;
| TAuth0ClientSecretRotationGeneratedCredentialsResponse
| TAzureClientSecretRotationGeneratedCredentialsResponse;
export type TCreateSecretRotationV2DTO = DiscriminativePick<
TSecretRotationV2,
@@ -82,10 +90,12 @@ export type TSecretRotationOptionMap = {
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationOption;
};
export type TSecretRotationGeneratedCredentialsResponseMap = {
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationGeneratedCredentialsResponse;
};