mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
Add Azure Client Secrets Rotation
This commit is contained in:
+19
@@ -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
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
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 { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
||||||
|
import { registerAzureClientSecretRotationRouter } from "./azure-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";
|
||||||
|
|
||||||
@@ -12,5 +13,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
> = {
|
> = {
|
||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,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 { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
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 { 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";
|
||||||
@@ -13,7 +14,8 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
|
AzureClientSecretRotationListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
+155
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
+68
@@ -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
|
||||||
|
});
|
||||||
+41
@@ -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;
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
export enum SecretRotation {
|
export enum SecretRotation {
|
||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret"
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
|
AzureClientSecret = "azure-client-secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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 { 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 { 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";
|
||||||
@@ -18,7 +19,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
|
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretRotationOptions = () => {
|
export const listSecretRotationOptions = () => {
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ 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"
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
|
[SecretRotation.AzureClientSecret]: "Azure 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
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||||
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
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 { 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 { SecretRotation, SecretRotationStatus } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
import {
|
import {
|
||||||
calculateNextRotationAt,
|
calculateNextRotationAt,
|
||||||
@@ -100,7 +101,7 @@ 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">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||||
@@ -114,7 +115,8 @@ type TRotationFactoryImplementation = TRotationFactory<
|
|||||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MsSqlCredentials]: 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 = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ import {
|
|||||||
TAuth0ClientSecretRotationListItem,
|
TAuth0ClientSecretRotationListItem,
|
||||||
TAuth0ClientSecretRotationWithConnection
|
TAuth0ClientSecretRotationWithConnection
|
||||||
} from "./auth0-client-secret";
|
} from "./auth0-client-secret";
|
||||||
|
import {
|
||||||
|
TAzureClientSecretRotation,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
|
TAzureClientSecretRotationInput,
|
||||||
|
TAzureClientSecretRotationListItem,
|
||||||
|
TAzureClientSecretRotationWithConnection
|
||||||
|
} from "./azure-client-secret";
|
||||||
import {
|
import {
|
||||||
TMsSqlCredentialsRotation,
|
TMsSqlCredentialsRotation,
|
||||||
TMsSqlCredentialsRotationInput,
|
TMsSqlCredentialsRotationInput,
|
||||||
@@ -27,26 +34,34 @@ 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 | TAuth0ClientSecretRotation;
|
export type TSecretRotationV2 =
|
||||||
|
| TPostgresCredentialsRotation
|
||||||
|
| TMsSqlCredentialsRotation
|
||||||
|
| TAuth0ClientSecretRotation
|
||||||
|
| TAzureClientSecretRotation;
|
||||||
|
|
||||||
export type TSecretRotationV2WithConnection =
|
export type TSecretRotationV2WithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection;
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
|
| TAzureClientSecretRotationWithConnection;
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials =
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
| TSqlCredentialsRotationGeneratedCredentials
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentials;
|
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||||
|
| TAzureClientSecretRotationGeneratedCredentials;
|
||||||
|
|
||||||
export type TSecretRotationV2Input =
|
export type TSecretRotationV2Input =
|
||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput;
|
| TAuth0ClientSecretRotationInput
|
||||||
|
| TAzureClientSecretRotationInput;
|
||||||
|
|
||||||
export type TSecretRotationV2ListItem =
|
export type TSecretRotationV2ListItem =
|
||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem;
|
| TAuth0ClientSecretRotationListItem
|
||||||
|
| TAzureClientSecretRotationListItem;
|
||||||
|
|
||||||
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
export type TSecretRotationV2Raw = NonNullable<Awaited<ReturnType<TSecretRotationV2DALFactory["findById"]>>>;
|
||||||
|
|
||||||
@@ -170,7 +185,7 @@ export type TRotationFactory<
|
|||||||
C extends TSecretRotationV2GeneratedCredentials
|
C extends TSecretRotationV2GeneratedCredentials
|
||||||
> = (
|
> = (
|
||||||
secretRotation: T,
|
secretRotation: T,
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
) => {
|
) => {
|
||||||
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
issueCredentials: TRotationFactoryIssueCredentials<C>;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
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 { 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
|
Auth0ClientSecretRotationSchema,
|
||||||
|
AzureClientSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2006,6 +2006,10 @@ export const SecretRotations = {
|
|||||||
},
|
},
|
||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientId: "The client ID of the Auth0 Application to rotate the client secret for."
|
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: {
|
SECRETS_MAPPING: {
|
||||||
@@ -2016,6 +2020,10 @@ export const SecretRotations = {
|
|||||||
AUTH0_CLIENT_SECRET: {
|
AUTH0_CLIENT_SECRET: {
|
||||||
clientId: "The name of the secret that the client ID will be mapped to.",
|
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."
|
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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+31
@@ -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 { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import {
|
import {
|
||||||
CreateAzureClientSecretsConnectionSchema,
|
CreateAzureClientSecretsConnectionSchema,
|
||||||
SanitizedAzureClientSecretsConnectionSchema,
|
SanitizedAzureClientSecretsConnectionSchema,
|
||||||
UpdateAzureClientSecretsConnectionSchema
|
UpdateAzureClientSecretsConnectionSchema
|
||||||
} from "@app/services/app-connection/azure-client-secrets";
|
} from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
@@ -15,4 +20,30 @@ export const registerAzureClientSecretsConnectionRouter = async (server: Fastify
|
|||||||
createSchema: CreateAzureClientSecretsConnectionSchema,
|
createSchema: CreateAzureClientSecretsConnectionSchema,
|
||||||
updateSchema: UpdateAzureClientSecretsConnectionSchema
|
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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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";
|
||||||
import { ValidateAzureClientSecretsConnectionCredentialsSchema } from "./azure-client-secrets";
|
import { ValidateAzureClientSecretsConnectionCredentialsSchema } from "./azure-client-secrets";
|
||||||
|
import { azureClientSecretsConnectionService } from "./azure-client-secrets/azure-client-secrets-service";
|
||||||
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
|
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
|
||||||
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
|
||||||
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
import { camundaConnectionService } from "./camunda/camunda-connection-service";
|
||||||
@@ -448,6 +449,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
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)
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
|
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+78
-5
@@ -2,15 +2,22 @@ import { AxiosError, AxiosResponse } from "axios";
|
|||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
import {
|
||||||
|
decryptAppConnectionCredentials,
|
||||||
|
encryptAppConnectionCredentials,
|
||||||
|
getAppConnectionMethodName
|
||||||
|
} from "@app/services/app-connection/app-connection-fns";
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
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 { AppConnection } from "../app-connection-enums";
|
||||||
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
|
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
|
||||||
import {
|
import {
|
||||||
ExchangeCodeAzureResponse,
|
ExchangeCodeAzureResponse,
|
||||||
TAzureClientSecretsConnectionConfig
|
TAzureClientSecretsConnectionConfig,
|
||||||
|
TAzureClientSecretsConnectionCredentials
|
||||||
} from "./azure-client-secrets-connection-types";
|
} from "./azure-client-secrets-connection-types";
|
||||||
|
|
||||||
export const getAzureClientSecretsConnectionListItem = () => {
|
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) => {
|
export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => {
|
||||||
const { credentials: inputCredentials, method } = config;
|
const { credentials: inputCredentials, method } = config;
|
||||||
|
|
||||||
@@ -44,10 +117,10 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA
|
|||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: inputCredentials.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_id: INF_APP_CONNECTION_AZURE_CLIENT_ID,
|
||||||
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
|
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) {
|
} catch (e: unknown) {
|
||||||
|
|||||||
+30
-5
@@ -26,7 +26,11 @@ export type TAzureClientSecretsConnectionConfig = DiscriminativePick<
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExchangeCodeAzureResponse = {
|
export type TAzureClientSecretsConnectionCredentials = z.infer<
|
||||||
|
typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ExchangeCodeAzureResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
scope: string;
|
scope: string;
|
||||||
expires_in: number;
|
expires_in: number;
|
||||||
@@ -34,8 +38,29 @@ export type ExchangeCodeAzureResponse = {
|
|||||||
access_token: string;
|
access_token: string;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
id_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
|
|
||||||
>;
|
|
||||||
|
|||||||
+70
@@ -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
|
||||||
|
};
|
||||||
|
};
|
||||||
+5
-1
@@ -38,7 +38,11 @@ export const getAzureConnectionAccessToken = async (
|
|||||||
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
|
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` });
|
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not an Azure Key Vault connection` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+71
@@ -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'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<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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
+3
-1
@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
|||||||
|
|
||||||
import { TSecretRotationV2Form } from "../schemas";
|
import { TSecretRotationV2Form } from "../schemas";
|
||||||
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
|
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
|
||||||
|
import { AzureClientSecretRotationParametersFields } from "./AzureClientSecretRotationParametersFields";
|
||||||
import { SqlCredentialsRotationParametersFields } from "./shared";
|
import { SqlCredentialsRotationParametersFields } from "./shared";
|
||||||
|
|
||||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
|
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
|
||||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
|
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
|
||||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields
|
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields,
|
||||||
|
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationParametersFields
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretRotationV2ParametersFields = () => {
|
export const SecretRotationV2ParametersFields = () => {
|
||||||
|
|||||||
+30
@@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+3
-1
@@ -7,12 +7,14 @@ import { getRotateAtLocal } from "@app/helpers/secretRotationsV2";
|
|||||||
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
|
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
|
||||||
|
import { AzureClientSecretRotationReviewFields } from "./AzureClientSecretRotationReviewFields";
|
||||||
import { SqlCredentialsRotationReviewFields } from "./shared";
|
import { SqlCredentialsRotationReviewFields } from "./shared";
|
||||||
|
|
||||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationReviewFields,
|
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationReviewFields,
|
||||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationReviewFields,
|
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationReviewFields,
|
||||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields
|
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields,
|
||||||
|
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationReviewFields
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretRotationV2ReviewFields = () => {
|
export const SecretRotationV2ReviewFields = () => {
|
||||||
|
|||||||
+58
@@ -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} />;
|
||||||
|
};
|
||||||
+3
-1
@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
|||||||
|
|
||||||
import { TSecretRotationV2Form } from "../schemas";
|
import { TSecretRotationV2Form } from "../schemas";
|
||||||
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
|
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
|
||||||
|
import { AzureClientSecretRotationSecretsMappingFields } from "./AzureClientSecretRotationSecretsMappingFields";
|
||||||
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
|
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
|
||||||
|
|
||||||
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
|
||||||
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||||
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
|
||||||
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields
|
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields,
|
||||||
|
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationSecretsMappingFields
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretRotationV2SecretsMappingFields = () => {
|
export const SecretRotationV2SecretsMappingFields = () => {
|
||||||
|
|||||||
+18
@@ -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);
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Auth0ClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/auth0-client-secret-rotation-schema";
|
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 { 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";
|
import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema";
|
||||||
|
|
||||||
const SecretRotationUnionSchema = z.discriminatedUnion("type", [
|
const SecretRotationUnionSchema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema
|
Auth0ClientSecretRotationSchema,
|
||||||
|
AzureClientSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const SecretRotationV2FormSchema = SecretRotationUnionSchema;
|
export const SecretRotationV2FormSchema = SecretRotationUnionSchema;
|
||||||
|
|||||||
@@ -19,20 +19,27 @@ export const SECRET_ROTATION_MAP: Record<
|
|||||||
name: "Auth0 Client Secret",
|
name: "Auth0 Client Secret",
|
||||||
image: "Auth0.png",
|
image: "Auth0.png",
|
||||||
size: 35
|
size: 35
|
||||||
|
},
|
||||||
|
[SecretRotation.AzureClientSecret]: {
|
||||||
|
name: "Azure Client Secret",
|
||||||
|
image: "Microsoft Azure.png",
|
||||||
|
size: 35
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
[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
|
// 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> = {
|
export const IS_ROTATION_DUAL_CREDENTIALS: Record<SecretRotation, boolean> = {
|
||||||
[SecretRotation.PostgresCredentials]: true,
|
[SecretRotation.PostgresCredentials]: true,
|
||||||
[SecretRotation.MsSqlCredentials]: true,
|
[SecretRotation.MsSqlCredentials]: true,
|
||||||
[SecretRotation.Auth0ClientSecret]: false
|
[SecretRotation.Auth0ClientSecret]: false,
|
||||||
|
[SecretRotation.AzureClientSecret]: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
|
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./queries";
|
||||||
@@ -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
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type TAzureClient = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
export enum SecretRotation {
|
export enum SecretRotation {
|
||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret"
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
|
AzureClientSecret = "azure-client-secret"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
|||||||
@@ -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"];
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -4,6 +4,11 @@ import {
|
|||||||
TAuth0ClientSecretRotationGeneratedCredentialsResponse,
|
TAuth0ClientSecretRotationGeneratedCredentialsResponse,
|
||||||
TAuth0ClientSecretRotationOption
|
TAuth0ClientSecretRotationOption
|
||||||
} from "@app/hooks/api/secretRotationsV2/types/auth0-client-secret-rotation";
|
} 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 {
|
import {
|
||||||
TMsSqlCredentialsRotation,
|
TMsSqlCredentialsRotation,
|
||||||
TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||||
@@ -20,13 +25,15 @@ export type TSecretRotationV2 = (
|
|||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
|
| TAzureClientSecretRotation
|
||||||
) & {
|
) & {
|
||||||
secrets: (SecretV3RawSanitized | null)[];
|
secrets: (SecretV3RawSanitized | null)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationV2Option =
|
export type TSecretRotationV2Option =
|
||||||
| TSqlCredentialsRotationOption
|
| TSqlCredentialsRotationOption
|
||||||
| TAuth0ClientSecretRotationOption;
|
| TAuth0ClientSecretRotationOption
|
||||||
|
| TAzureClientSecretRotationOption;
|
||||||
|
|
||||||
export type TListSecretRotationV2Options = { secretRotationOptions: TSecretRotationV2Option[] };
|
export type TListSecretRotationV2Options = { secretRotationOptions: TSecretRotationV2Option[] };
|
||||||
|
|
||||||
@@ -35,7 +42,8 @@ export type TSecretRotationV2Response = { secretRotation: TSecretRotationV2 };
|
|||||||
export type TViewSecretRotationGeneratedCredentialsResponse =
|
export type TViewSecretRotationGeneratedCredentialsResponse =
|
||||||
| TPostgresCredentialsRotationGeneratedCredentialsResponse
|
| TPostgresCredentialsRotationGeneratedCredentialsResponse
|
||||||
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
| TAuth0ClientSecretRotationGeneratedCredentialsResponse
|
||||||
|
| TAzureClientSecretRotationGeneratedCredentialsResponse;
|
||||||
|
|
||||||
export type TCreateSecretRotationV2DTO = DiscriminativePick<
|
export type TCreateSecretRotationV2DTO = DiscriminativePick<
|
||||||
TSecretRotationV2,
|
TSecretRotationV2,
|
||||||
@@ -82,10 +90,12 @@ export type TSecretRotationOptionMap = {
|
|||||||
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
|
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
|
||||||
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
|
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
|
||||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
|
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
|
||||||
|
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationGeneratedCredentialsResponseMap = {
|
export type TSecretRotationGeneratedCredentialsResponseMap = {
|
||||||
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
||||||
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
|
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
|
||||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
||||||
|
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationGeneratedCredentialsResponse;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user