Merge pull request #3440 from Infisical/feat/azureClientSecretsRotation
Feat/azure client secrets rotation
@@ -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
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotat
|
|||||||
|
|
||||||
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
import { registerAuth0ClientSecretRotationRouter } from "./auth0-client-secret-rotation-router";
|
||||||
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-rotation-router";
|
||||||
|
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
|
||||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
import { registerLdapPasswordRotationRouter } from "./ldap-password-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";
|
||||||
@@ -15,6 +16,7 @@ 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.LdapPassword]: registerLdapPasswordRotationRouter,
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||||
|
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
import { Auth0ClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/auth0-client-secret";
|
||||||
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/aws-iam-user-secret";
|
||||||
|
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
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";
|
||||||
@@ -16,8 +17,9 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema,
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
LdapPasswordRotationListItemSchema,
|
AzureClientSecretRotationListItemSchema,
|
||||||
AwsIamUserSecretRotationListItemSchema
|
AwsIamUserSecretRotationListItemSchema,
|
||||||
|
LdapPasswordRotationListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
|
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 { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets";
|
||||||
|
|
||||||
|
const GRAPH_API_BASE = "https://graph.microsoft.com/v1.0";
|
||||||
|
|
||||||
|
type AzureErrorResponse = { error: { message: string } };
|
||||||
|
|
||||||
|
const sleep = async () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const azureClientSecretRotationFactory: TRotationFactory<
|
||||||
|
TAzureClientSecretRotationWithConnection,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials
|
||||||
|
> = (secretRotation, appConnectionDAL, kmsService) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
parameters: { objectId, clientId: clientIdParam },
|
||||||
|
secretsMapping
|
||||||
|
} = 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/${objectId}/addPassword`;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const formattedDate = `${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(
|
||||||
|
2,
|
||||||
|
"0"
|
||||||
|
)}-${now.getFullYear()}`;
|
||||||
|
|
||||||
|
const endDateTime = new Date();
|
||||||
|
endDateTime.setFullYear(now.getFullYear() + 5);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await request.post<AzureAddPasswordResponse>(
|
||||||
|
endpoint,
|
||||||
|
{
|
||||||
|
passwordCredential: {
|
||||||
|
displayName: `Infisical Rotated Secret (${formattedDate})`,
|
||||||
|
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,
|
||||||
|
keyId: data.keyId,
|
||||||
|
clientId: clientIdParam
|
||||||
|
};
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to add client secret to Azure app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes a client secret from the Azure app using its keyId.
|
||||||
|
*/
|
||||||
|
const revokeCredential = async (keyId: string) => {
|
||||||
|
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
|
||||||
|
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/removePassword`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request.post(
|
||||||
|
endpoint,
|
||||||
|
{ keyId },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof AxiosError) {
|
||||||
|
let message;
|
||||||
|
if (
|
||||||
|
error.response?.data &&
|
||||||
|
typeof error.response.data === "object" &&
|
||||||
|
"error" in error.response.data &&
|
||||||
|
typeof (error.response.data as AzureErrorResponse).error.message === "string"
|
||||||
|
) {
|
||||||
|
message = (error.response.data as AzureErrorResponse).error.message;
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to remove client secret with keyId ${keyId} from app ${objectId}: ${
|
||||||
|
message || error.message || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate connection: verify credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
for (const { keyId } of credentials) {
|
||||||
|
await revokeCredential(keyId);
|
||||||
|
await sleep();
|
||||||
|
}
|
||||||
|
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?.keyId) {
|
||||||
|
await revokeCredential(oldCredentials.keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(newCredentials);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps the generated credentials into the secret payload format.
|
||||||
|
*/
|
||||||
|
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TAzureClientSecretRotationGeneratedCredentials> = ({
|
||||||
|
clientSecret
|
||||||
|
}) => [
|
||||||
|
{ key: secretsMapping.clientSecret, value: clientSecret },
|
||||||
|
{ key: secretsMapping.clientId, value: clientIdParam }
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
issueCredentials,
|
||||||
|
revokeCredentials,
|
||||||
|
rotateCredentials,
|
||||||
|
getSecretsPayload
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
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(),
|
||||||
|
keyId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.max(2);
|
||||||
|
|
||||||
|
const AzureClientSecretRotationParametersSchema = z.object({
|
||||||
|
objectId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Object ID Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.objectId),
|
||||||
|
appName: z.string().trim().describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.appName).optional(),
|
||||||
|
clientId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Client ID Required")
|
||||||
|
.describe(SecretRotations.PARAMETERS.AZURE_CLIENT_SECRET.clientId)
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
@@ -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";
|
||||||
@@ -2,8 +2,9 @@ export enum SecretRotation {
|
|||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret",
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
LdapPassword = "ldap-password",
|
AzureClientSecret = "azure-client-secret",
|
||||||
AwsIamUserSecret = "aws-iam-user-secret"
|
AwsIamUserSecret = "aws-iam-user-secret",
|
||||||
|
LdapPassword = "ldap-password"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretRotationStatus {
|
export enum SecretRotationStatus {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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 { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret";
|
||||||
|
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
|
||||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION } from "./ldap-password";
|
import { LDAP_PASSWORD_ROTATION_LIST_OPTION } from "./ldap-password";
|
||||||
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";
|
||||||
@@ -21,8 +22,9 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
|
|||||||
[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.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION,
|
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION
|
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretRotationOptions = () => {
|
export const listSecretRotationOptions = () => {
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
|||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
[SecretRotation.LdapPassword]: "LDAP Password",
|
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret"
|
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||||
|
[SecretRotation.LdapPassword]: "LDAP Password"
|
||||||
};
|
};
|
||||||
|
|
||||||
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.LdapPassword]: AppConnection.LDAP,
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||||
|
[SecretRotation.LdapPassword]: AppConnection.LDAP
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 { ldapPasswordRotationFactory } from "@app/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-fns";
|
import { ldapPasswordRotationFactory } from "@app/ee/services/secret-rotation-v2/ldap-password/ldap-password-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 {
|
||||||
@@ -102,7 +103,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>;
|
||||||
@@ -117,8 +118,9 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
|
|||||||
[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.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation
|
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secretRotationV2ServiceFactory = ({
|
export const secretRotationV2ServiceFactory = ({
|
||||||
@@ -447,7 +449,8 @@ export const secretRotationV2ServiceFactory = ({
|
|||||||
{
|
{
|
||||||
parameters: payload.parameters,
|
parameters: payload.parameters,
|
||||||
secretsMapping,
|
secretsMapping,
|
||||||
connection
|
connection,
|
||||||
|
rotationInterval: payload.rotationInterval
|
||||||
} as TSecretRotationV2WithConnection,
|
} as TSecretRotationV2WithConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService
|
kmsService
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ import {
|
|||||||
TAwsIamUserSecretRotationListItem,
|
TAwsIamUserSecretRotationListItem,
|
||||||
TAwsIamUserSecretRotationWithConnection
|
TAwsIamUserSecretRotationWithConnection
|
||||||
} from "./aws-iam-user-secret";
|
} from "./aws-iam-user-secret";
|
||||||
|
import {
|
||||||
|
TAzureClientSecretRotation,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentials,
|
||||||
|
TAzureClientSecretRotationInput,
|
||||||
|
TAzureClientSecretRotationListItem,
|
||||||
|
TAzureClientSecretRotationWithConnection
|
||||||
|
} from "./azure-client-secret";
|
||||||
import {
|
import {
|
||||||
TLdapPasswordRotation,
|
TLdapPasswordRotation,
|
||||||
TLdapPasswordRotationGeneratedCredentials,
|
TLdapPasswordRotationGeneratedCredentials,
|
||||||
@@ -45,6 +52,7 @@ export type TSecretRotationV2 =
|
|||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
|
| TAzureClientSecretRotation
|
||||||
| TLdapPasswordRotation
|
| TLdapPasswordRotation
|
||||||
| TAwsIamUserSecretRotation;
|
| TAwsIamUserSecretRotation;
|
||||||
|
|
||||||
@@ -52,12 +60,14 @@ export type TSecretRotationV2WithConnection =
|
|||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
|
| TAzureClientSecretRotationWithConnection
|
||||||
| TLdapPasswordRotationWithConnection
|
| TLdapPasswordRotationWithConnection
|
||||||
| TAwsIamUserSecretRotationWithConnection;
|
| TAwsIamUserSecretRotationWithConnection;
|
||||||
|
|
||||||
export type TSecretRotationV2GeneratedCredentials =
|
export type TSecretRotationV2GeneratedCredentials =
|
||||||
| TSqlCredentialsRotationGeneratedCredentials
|
| TSqlCredentialsRotationGeneratedCredentials
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentials
|
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||||
|
| TAzureClientSecretRotationGeneratedCredentials
|
||||||
| TLdapPasswordRotationGeneratedCredentials
|
| TLdapPasswordRotationGeneratedCredentials
|
||||||
| TAwsIamUserSecretRotationGeneratedCredentials;
|
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||||
|
|
||||||
@@ -65,6 +75,7 @@ export type TSecretRotationV2Input =
|
|||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput
|
| TAuth0ClientSecretRotationInput
|
||||||
|
| TAzureClientSecretRotationInput
|
||||||
| TLdapPasswordRotationInput
|
| TLdapPasswordRotationInput
|
||||||
| TAwsIamUserSecretRotationInput;
|
| TAwsIamUserSecretRotationInput;
|
||||||
|
|
||||||
@@ -72,6 +83,7 @@ export type TSecretRotationV2ListItem =
|
|||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem
|
| TAuth0ClientSecretRotationListItem
|
||||||
|
| TAzureClientSecretRotationListItem
|
||||||
| TLdapPasswordRotationListItem
|
| TLdapPasswordRotationListItem
|
||||||
| TAwsIamUserSecretRotationListItem;
|
| TAwsIamUserSecretRotationListItem;
|
||||||
|
|
||||||
@@ -197,7 +209,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,6 +1,7 @@
|
|||||||
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 { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
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";
|
||||||
@@ -11,6 +12,7 @@ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
|||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema,
|
Auth0ClientSecretRotationSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
LdapPasswordRotationSchema,
|
LdapPasswordRotationSchema,
|
||||||
AwsIamUserSecretRotationSchema
|
AwsIamUserSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1875,6 +1875,10 @@ export const AppConnections = {
|
|||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
instanceUrl: "The TeamCity instance URL to connect with.",
|
instanceUrl: "The TeamCity instance URL to connect with.",
|
||||||
accessToken: "The access token to use to connect with TeamCity."
|
accessToken: "The access token to use to connect with TeamCity."
|
||||||
|
},
|
||||||
|
AZURE_CLIENT_SECRETS: {
|
||||||
|
code: "The OAuth code to use to connect with Azure Client Secrets.",
|
||||||
|
tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -2083,6 +2087,11 @@ 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: {
|
||||||
|
objectId: "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.",
|
||||||
|
clientId: "The client ID of the Azure Application to rotate the client secret for."
|
||||||
|
},
|
||||||
LDAP_PASSWORD: {
|
LDAP_PASSWORD: {
|
||||||
dn: "The Distinguished Name (DN) of the principal to rotate the password for."
|
dn: "The Distinguished Name (DN) of the principal to rotate the password for."
|
||||||
},
|
},
|
||||||
@@ -2113,6 +2122,10 @@ export const SecretRotations = {
|
|||||||
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."
|
||||||
|
},
|
||||||
LDAP_PASSWORD: {
|
LDAP_PASSWORD: {
|
||||||
dn: "The name of the secret that the Distinguished Name (DN) of the principal will be mapped to.",
|
dn: "The name of the secret that the Distinguished Name (DN) of the principal will be mapped to.",
|
||||||
password: "The name of the secret that the rotated password will be mapped to."
|
password: "The name of the secret that the rotated password will be mapped to."
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import {
|
|||||||
AzureAppConfigurationConnectionListItemSchema,
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
SanitizedAzureAppConfigurationConnectionSchema
|
SanitizedAzureAppConfigurationConnectionSchema
|
||||||
} from "@app/services/app-connection/azure-app-configuration";
|
} from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
import {
|
||||||
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
|
SanitizedAzureClientSecretsConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-client-secrets";
|
||||||
import {
|
import {
|
||||||
AzureKeyVaultConnectionListItemSchema,
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
SanitizedAzureKeyVaultConnectionSchema
|
SanitizedAzureKeyVaultConnectionSchema
|
||||||
@@ -63,8 +67,9 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedPostgresConnectionSchema.options,
|
...SanitizedPostgresConnectionSchema.options,
|
||||||
...SanitizedMsSqlConnectionSchema.options,
|
...SanitizedMsSqlConnectionSchema.options,
|
||||||
...SanitizedCamundaConnectionSchema.options,
|
...SanitizedCamundaConnectionSchema.options,
|
||||||
...SanitizedWindmillConnectionSchema.options,
|
|
||||||
...SanitizedAuth0ConnectionSchema.options,
|
...SanitizedAuth0ConnectionSchema.options,
|
||||||
|
...SanitizedAzureClientSecretsConnectionSchema.options,
|
||||||
|
...SanitizedWindmillConnectionSchema.options,
|
||||||
...SanitizedLdapConnectionSchema.options,
|
...SanitizedLdapConnectionSchema.options,
|
||||||
...SanitizedTeamCityConnectionSchema.options
|
...SanitizedTeamCityConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
@@ -82,8 +87,9 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
MsSqlConnectionListItemSchema,
|
MsSqlConnectionListItemSchema,
|
||||||
CamundaConnectionListItemSchema,
|
CamundaConnectionListItemSchema,
|
||||||
WindmillConnectionListItemSchema,
|
|
||||||
Auth0ConnectionListItemSchema,
|
Auth0ConnectionListItemSchema,
|
||||||
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
|
WindmillConnectionListItemSchema,
|
||||||
LdapConnectionListItemSchema,
|
LdapConnectionListItemSchema,
|
||||||
TeamCityConnectionListItemSchema
|
TeamCityConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export const registerAzureClientSecretsConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureClientSecrets,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureClientSecretsConnectionSchema,
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
|
|||||||
import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
|
import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
|
||||||
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
||||||
|
import { registerAzureClientSecretsConnectionRouter } from "./azure-client-secrets-connection-router";
|
||||||
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
@@ -26,6 +27,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.GCP]: registerGcpConnectionRouter,
|
[AppConnection.GCP]: registerGcpConnectionRouter,
|
||||||
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||||
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
||||||
|
[AppConnection.AzureClientSecrets]: registerAzureClientSecretsConnectionRouter,
|
||||||
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
||||||
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
[AppConnection.Humanitec]: registerHumanitecConnectionRouter,
|
||||||
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
|
[AppConnection.TerraformCloud]: registerTerraformCloudConnectionRouter,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export enum AppConnection {
|
|||||||
GCP = "gcp",
|
GCP = "gcp",
|
||||||
AzureKeyVault = "azure-key-vault",
|
AzureKeyVault = "azure-key-vault",
|
||||||
AzureAppConfiguration = "azure-app-configuration",
|
AzureAppConfiguration = "azure-app-configuration",
|
||||||
|
AzureClientSecrets = "azure-client-secrets",
|
||||||
Humanitec = "humanitec",
|
Humanitec = "humanitec",
|
||||||
TerraformCloud = "terraform-cloud",
|
TerraformCloud = "terraform-cloud",
|
||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import {
|
|||||||
getAzureAppConfigurationConnectionListItem,
|
getAzureAppConfigurationConnectionListItem,
|
||||||
validateAzureAppConfigurationConnectionCredentials
|
validateAzureAppConfigurationConnectionCredentials
|
||||||
} from "./azure-app-configuration";
|
} from "./azure-app-configuration";
|
||||||
|
import {
|
||||||
|
AzureClientSecretsConnectionMethod,
|
||||||
|
getAzureClientSecretsConnectionListItem,
|
||||||
|
validateAzureClientSecretsConnectionCredentials
|
||||||
|
} from "./azure-client-secrets";
|
||||||
import {
|
import {
|
||||||
AzureKeyVaultConnectionMethod,
|
AzureKeyVaultConnectionMethod,
|
||||||
getAzureKeyVaultConnectionListItem,
|
getAzureKeyVaultConnectionListItem,
|
||||||
@@ -76,6 +81,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getPostgresConnectionListItem(),
|
getPostgresConnectionListItem(),
|
||||||
getMsSqlConnectionListItem(),
|
getMsSqlConnectionListItem(),
|
||||||
getCamundaConnectionListItem(),
|
getCamundaConnectionListItem(),
|
||||||
|
getAzureClientSecretsConnectionListItem(),
|
||||||
getWindmillConnectionListItem(),
|
getWindmillConnectionListItem(),
|
||||||
getAuth0ConnectionListItem(),
|
getAuth0ConnectionListItem(),
|
||||||
getLdapConnectionListItem(),
|
getLdapConnectionListItem(),
|
||||||
@@ -136,6 +142,8 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.AzureKeyVault]: validateAzureKeyVaultConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.AzureKeyVault]: validateAzureKeyVaultConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.AzureAppConfiguration]:
|
[AppConnection.AzureAppConfiguration]:
|
||||||
validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator,
|
validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.AzureClientSecrets]:
|
||||||
|
validateAzureClientSecretsConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
@@ -157,6 +165,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
return "GitHub App";
|
return "GitHub App";
|
||||||
case AzureKeyVaultConnectionMethod.OAuth:
|
case AzureKeyVaultConnectionMethod.OAuth:
|
||||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||||
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
return "OAuth";
|
return "OAuth";
|
||||||
case AwsConnectionMethod.AccessKey:
|
case AwsConnectionMethod.AccessKey:
|
||||||
@@ -226,6 +235,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.AzureClientSecrets]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.GCP]: "GCP",
|
[AppConnection.GCP]: "GCP",
|
||||||
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
||||||
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
|
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
|
||||||
|
[AppConnection.AzureClientSecrets]: "Azure Client Secrets",
|
||||||
[AppConnection.Databricks]: "Databricks",
|
[AppConnection.Databricks]: "Databricks",
|
||||||
[AppConnection.Humanitec]: "Humanitec",
|
[AppConnection.Humanitec]: "Humanitec",
|
||||||
[AppConnection.TerraformCloud]: "Terraform Cloud",
|
[AppConnection.TerraformCloud]: "Terraform Cloud",
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0";
|
|||||||
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
|
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
|
||||||
import { awsConnectionService } from "./aws/aws-connection-service";
|
import { awsConnectionService } from "./aws/aws-connection-service";
|
||||||
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
|
||||||
|
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";
|
||||||
@@ -76,6 +78,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
||||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
||||||
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
||||||
|
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
|
||||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
||||||
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
||||||
@@ -454,8 +457,9 @@ 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),
|
||||||
windmill: windmillConnectionService(connectAppConnectionById),
|
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
|
windmill: windmillConnectionService(connectAppConnectionById),
|
||||||
teamcity: teamcityConnectionService(connectAppConnectionById)
|
teamcity: teamcityConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ import {
|
|||||||
TAzureAppConfigurationConnectionInput,
|
TAzureAppConfigurationConnectionInput,
|
||||||
TValidateAzureAppConfigurationConnectionCredentialsSchema
|
TValidateAzureAppConfigurationConnectionCredentialsSchema
|
||||||
} from "./azure-app-configuration";
|
} from "./azure-app-configuration";
|
||||||
|
import {
|
||||||
|
TAzureClientSecretsConnection,
|
||||||
|
TAzureClientSecretsConnectionConfig,
|
||||||
|
TAzureClientSecretsConnectionInput,
|
||||||
|
TValidateAzureClientSecretsConnectionCredentialsSchema
|
||||||
|
} from "./azure-client-secrets";
|
||||||
import {
|
import {
|
||||||
TAzureKeyVaultConnection,
|
TAzureKeyVaultConnection,
|
||||||
TAzureKeyVaultConnectionConfig,
|
TAzureKeyVaultConnectionConfig,
|
||||||
@@ -107,6 +113,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TPostgresConnection
|
| TPostgresConnection
|
||||||
| TMsSqlConnection
|
| TMsSqlConnection
|
||||||
| TCamundaConnection
|
| TCamundaConnection
|
||||||
|
| TAzureClientSecretsConnection
|
||||||
| TWindmillConnection
|
| TWindmillConnection
|
||||||
| TAuth0Connection
|
| TAuth0Connection
|
||||||
| TLdapConnection
|
| TLdapConnection
|
||||||
@@ -130,6 +137,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TPostgresConnectionInput
|
| TPostgresConnectionInput
|
||||||
| TMsSqlConnectionInput
|
| TMsSqlConnectionInput
|
||||||
| TCamundaConnectionInput
|
| TCamundaConnectionInput
|
||||||
|
| TAzureClientSecretsConnectionInput
|
||||||
| TWindmillConnectionInput
|
| TWindmillConnectionInput
|
||||||
| TAuth0ConnectionInput
|
| TAuth0ConnectionInput
|
||||||
| TLdapConnectionInput
|
| TLdapConnectionInput
|
||||||
@@ -153,12 +161,13 @@ export type TAppConnectionConfig =
|
|||||||
| TGcpConnectionConfig
|
| TGcpConnectionConfig
|
||||||
| TAzureKeyVaultConnectionConfig
|
| TAzureKeyVaultConnectionConfig
|
||||||
| TAzureAppConfigurationConnectionConfig
|
| TAzureAppConfigurationConnectionConfig
|
||||||
|
| TAzureClientSecretsConnectionConfig
|
||||||
| TDatabricksConnectionConfig
|
| TDatabricksConnectionConfig
|
||||||
| THumanitecConnectionConfig
|
| THumanitecConnectionConfig
|
||||||
| TTerraformCloudConnectionConfig
|
| TTerraformCloudConnectionConfig
|
||||||
| TVercelConnectionConfig
|
|
||||||
| TSqlConnectionConfig
|
| TSqlConnectionConfig
|
||||||
| TCamundaConnectionConfig
|
| TCamundaConnectionConfig
|
||||||
|
| TVercelConnectionConfig
|
||||||
| TWindmillConnectionConfig
|
| TWindmillConnectionConfig
|
||||||
| TAuth0ConnectionConfig
|
| TAuth0ConnectionConfig
|
||||||
| TLdapConnectionConfig
|
| TLdapConnectionConfig
|
||||||
@@ -170,13 +179,14 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateGcpConnectionCredentialsSchema
|
| TValidateGcpConnectionCredentialsSchema
|
||||||
| TValidateAzureKeyVaultConnectionCredentialsSchema
|
| TValidateAzureKeyVaultConnectionCredentialsSchema
|
||||||
| TValidateAzureAppConfigurationConnectionCredentialsSchema
|
| TValidateAzureAppConfigurationConnectionCredentialsSchema
|
||||||
|
| TValidateAzureClientSecretsConnectionCredentialsSchema
|
||||||
| TValidateDatabricksConnectionCredentialsSchema
|
| TValidateDatabricksConnectionCredentialsSchema
|
||||||
| TValidateHumanitecConnectionCredentialsSchema
|
| TValidateHumanitecConnectionCredentialsSchema
|
||||||
| TValidatePostgresConnectionCredentialsSchema
|
| TValidatePostgresConnectionCredentialsSchema
|
||||||
| TValidateMsSqlConnectionCredentialsSchema
|
| TValidateMsSqlConnectionCredentialsSchema
|
||||||
| TValidateCamundaConnectionCredentialsSchema
|
| TValidateCamundaConnectionCredentialsSchema
|
||||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
|
||||||
| TValidateVercelConnectionCredentialsSchema
|
| TValidateVercelConnectionCredentialsSchema
|
||||||
|
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||||
| TValidateWindmillConnectionCredentialsSchema
|
| TValidateWindmillConnectionCredentialsSchema
|
||||||
| TValidateAuth0ConnectionCredentialsSchema
|
| TValidateAuth0ConnectionCredentialsSchema
|
||||||
| TValidateLdapConnectionCredentialsSchema
|
| TValidateLdapConnectionCredentialsSchema
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export enum AzureClientSecretsConnectionMethod {
|
||||||
|
OAuth = "oauth"
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import { AxiosError, AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
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,
|
||||||
|
TAzureClientSecretsConnectionCredentials
|
||||||
|
} from "./azure-client-secrets-connection-types";
|
||||||
|
|
||||||
|
export const getAzureClientSecretsConnectionListItem = () => {
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig();
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Azure Client Secrets" as const,
|
||||||
|
app: AppConnection.AzureClientSecrets as const,
|
||||||
|
methods: Object.values(AzureClientSecretsConnectionMethod) as [AzureClientSecretsConnectionMethod.OAuth],
|
||||||
|
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 { refreshToken } = credentials;
|
||||||
|
const currentTime = Date.now();
|
||||||
|
|
||||||
|
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: currentTime + 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;
|
||||||
|
|
||||||
|
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig();
|
||||||
|
|
||||||
|
if (!SITE_URL) {
|
||||||
|
throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
|
||||||
|
let tokenError: AxiosError | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
tokenResp = await request.post<ExchangeCodeAzureResponse>(
|
||||||
|
IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"),
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code: inputCredentials.code,
|
||||||
|
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`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
if (e instanceof AxiosError) {
|
||||||
|
tokenError = e;
|
||||||
|
} else {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Unable to validate connection: verify credentials`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenError) {
|
||||||
|
if (tokenError instanceof AxiosError) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to get access token: ${
|
||||||
|
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
|
||||||
|
}`
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: "Failed to get access token"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenResp) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Failed to get access token: Token was empty with no error`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
|
return {
|
||||||
|
tenantId: inputCredentials.tenantId,
|
||||||
|
accessToken: tokenResp.data.access_token,
|
||||||
|
refreshToken: tokenResp.data.refresh_token,
|
||||||
|
expiresAt: Date.now() + tokenResp.data.expires_in * 1000
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: `Unhandled Azure connection method: ${method as AzureClientSecretsConnectionMethod}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
|
||||||
|
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
|
||||||
|
|
||||||
|
export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({
|
||||||
|
code: z.string().trim().min(1, "OAuth code required").describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.code),
|
||||||
|
tenantId: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Tenant ID required")
|
||||||
|
.describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object({
|
||||||
|
tenantId: z.string(),
|
||||||
|
accessToken: z.string(),
|
||||||
|
refreshToken: z.string(),
|
||||||
|
expiresAt: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(AzureClientSecretsConnectionMethod.OAuth)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.AzureClientSecrets).method),
|
||||||
|
credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateAzureClientSecretsConnectionSchema = ValidateAzureClientSecretsConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateAzureClientSecretsConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets));
|
||||||
|
|
||||||
|
const BaseAzureClientSecretsConnectionSchema = BaseAppConnectionSchema.extend({
|
||||||
|
app: z.literal(AppConnection.AzureClientSecrets)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AzureClientSecretsConnectionSchema = z.intersection(
|
||||||
|
BaseAzureClientSecretsConnectionSchema,
|
||||||
|
z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z.literal(AzureClientSecretsConnectionMethod.OAuth),
|
||||||
|
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseAzureClientSecretsConnectionSchema.extend({
|
||||||
|
method: z.literal(AzureClientSecretsConnectionMethod.OAuth),
|
||||||
|
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({
|
||||||
|
tenantId: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const AzureClientSecretsConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("Azure Client Secrets"),
|
||||||
|
app: z.literal(AppConnection.AzureClientSecrets),
|
||||||
|
methods: z.nativeEnum(AzureClientSecretsConnectionMethod).array(),
|
||||||
|
oauthClientId: z.string().optional()
|
||||||
|
});
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
AzureClientSecretsConnectionOAuthOutputCredentialsSchema,
|
||||||
|
AzureClientSecretsConnectionSchema,
|
||||||
|
CreateAzureClientSecretsConnectionSchema,
|
||||||
|
ValidateAzureClientSecretsConnectionCredentialsSchema
|
||||||
|
} from "./azure-client-secrets-connection-schemas";
|
||||||
|
|
||||||
|
export type TAzureClientSecretsConnection = z.infer<typeof AzureClientSecretsConnectionSchema>;
|
||||||
|
|
||||||
|
export type TAzureClientSecretsConnectionInput = z.infer<typeof CreateAzureClientSecretsConnectionSchema> & {
|
||||||
|
app: AppConnection.AzureClientSecrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateAzureClientSecretsConnectionCredentialsSchema =
|
||||||
|
typeof ValidateAzureClientSecretsConnectionCredentialsSchema;
|
||||||
|
|
||||||
|
export type TAzureClientSecretsConnectionConfig = DiscriminativePick<
|
||||||
|
TAzureClientSecretsConnectionInput,
|
||||||
|
"method" | "app" | "credentials"
|
||||||
|
> & {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAzureClientSecretsConnectionCredentials = z.infer<
|
||||||
|
typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export interface ExchangeCodeAzureResponse {
|
||||||
|
token_type: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
ext_expires_in: number;
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
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`;
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./azure-client-secrets-connection-enums";
|
||||||
|
export * from "./azure-client-secrets-connection-fns";
|
||||||
|
export * from "./azure-client-secrets-connection-schemas";
|
||||||
|
export * from "./azure-client-secrets-connection-types";
|
||||||
@@ -38,8 +38,12 @@ 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 (
|
||||||
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not an Azure Key Vault connection` });
|
appConnection.app !== AppConnection.AzureKeyVault &&
|
||||||
|
appConnection.app !== AppConnection.AzureAppConfiguration &&
|
||||||
|
appConnection.app !== AppConnection.AzureClientSecrets
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not a valid Azure connection` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = (await decryptAppConnectionCredentials({
|
const credentials = (await decryptAppConnectionCredentials({
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-client-secrets/available"
|
||||||
|
---
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/azure-client-secrets"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Azure Client Secret Connections must be created through the Infisical UI.
|
||||||
|
Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) for a step-by-step
|
||||||
|
guide.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/azure-client-secrets/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-client-secrets/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-client-secrets/connection-name/{connectionName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/azure-client-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/azure-client-secrets/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Azure Client Secret Connections must be updated through the Infisical UI.
|
||||||
|
Check out the configuration docs for [Azure Client Secret Connections](/integrations/app-connections/azure-client-secrets) for a step-by-step
|
||||||
|
guide.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/azure-client-secret"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Azure Client Secret Rotations](/documentation/platform/secret-rotation/azure-client-secret) to learn how to obtain the
|
||||||
|
required parameters.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v2/secret-rotations/azure-client-secret/{rotationId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/azure-client-secret/{rotationId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/azure-client-secret/rotation-name/{rotationName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get Credentials by ID"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/azure-client-secret/{rotationId}/generated-credentials"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v2/secret-rotations/azure-client-secret"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Rotate Secrets"
|
||||||
|
openapi: "POST /api/v2/secret-rotations/azure-client-secret/{rotationId}/rotate-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v2/secret-rotations/azure-client-secret/{rotationId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Azure Client Secret Rotations](/documentation/platform/secret-rotation/azure-client-secret) to learn how to obtain the
|
||||||
|
required parameters.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
title: "Azure Client Secret"
|
||||||
|
description: "Learn how to automatically rotate Azure Client Secrets."
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Create an [Azure Client Secret Connection](/integrations/app-connections/azure-client-secrets).
|
||||||
|
|
||||||
|
## Create an Azure Client Secret Rotation in Infisical
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
1. Navigate to your Secret Manager Project's Dashboard and select **Add Secret Rotation** from the actions dropdown.
|
||||||
|

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

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

|
||||||
|
|
||||||
|
- **Azure Connection** - the connection that will perform the rotation of the specified application's Client Secret.
|
||||||
|
- **Rotation Interval** - the interval, in days, that once elapsed will trigger a rotation.
|
||||||
|
- **Rotate At** - the local time of day when rotation should occur once the interval has elapsed.
|
||||||
|
- **Auto-Rotation Enabled** - whether secrets should automatically be rotated once the rotation interval has elapsed. Disable this option to manually rotate secrets or pause secret rotation.
|
||||||
|
|
||||||
|
4. Select the Azure application whose Client Secret you want to rotate. Then click **Next**.
|
||||||
|

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

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

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

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

|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create an Azure Client Secret Rotation, make an API request to the [Create Azure
|
||||||
|
Client Secret Rotation](/api-reference/endpoints/secret-rotations/azure-client-secret/create) API endpoint.
|
||||||
|
|
||||||
|
You will first need the **Client ID** and **Object ID** of the Azure application you want to rotate the secret for. This can be obtained from the Applications dashboard.
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://us.infisical.com/api/v2/secret-rotations/azure-client-secret \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-azure-rotation",
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"description": "my client secret rotation",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"environment": "dev",
|
||||||
|
"secretPath": "/",
|
||||||
|
"isAutoRotationEnabled": true,
|
||||||
|
"rotationInterval": 30,
|
||||||
|
"rotateAtUtc": {
|
||||||
|
"hours": 0,
|
||||||
|
"minutes": 0
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"objectId": "...",
|
||||||
|
"clientId": "...",
|
||||||
|
"appName": "..."
|
||||||
|
},
|
||||||
|
"secretsMapping": {
|
||||||
|
"clientId": "AZURE_CLIENT_ID",
|
||||||
|
"clientSecret": "AZURE_CLIENT_SECRET"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretRotation": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-azure-rotation",
|
||||||
|
"description": "my client secret rotation",
|
||||||
|
"secretsMapping": {
|
||||||
|
"clientId": "AZURE_CLIENT_ID",
|
||||||
|
"clientSecret": "AZURE_CLIENT_SECRET"
|
||||||
|
},
|
||||||
|
"isAutoRotationEnabled": true,
|
||||||
|
"activeIndex": 0,
|
||||||
|
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"createdAt": "2023-11-07T05:31:56Z",
|
||||||
|
"updatedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"rotationInterval": 30,
|
||||||
|
"rotationStatus": "success",
|
||||||
|
"lastRotationAttemptedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"lastRotatedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"lastRotationJobId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"nextRotationAt": "2023-11-07T05:31:56Z",
|
||||||
|
"connection": {
|
||||||
|
"app": "azure",
|
||||||
|
"name": "my-azure-connection",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"slug": "dev",
|
||||||
|
"name": "Development",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"folder": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"path": "/"
|
||||||
|
},
|
||||||
|
"rotateAtUtc": {
|
||||||
|
"hours": 0,
|
||||||
|
"minutes": 0
|
||||||
|
},
|
||||||
|
"lastRotationMessage": null,
|
||||||
|
"type": "azure-client-secret",
|
||||||
|
"parameters": {
|
||||||
|
"objectId": "...",
|
||||||
|
"appName": "...",
|
||||||
|
"clientId": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
After Width: | Height: | Size: 566 KiB |
|
After Width: | Height: | Size: 578 KiB |
|
After Width: | Height: | Size: 857 KiB |
|
After Width: | Height: | Size: 580 KiB |
|
After Width: | Height: | Size: 600 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 531 KiB |
|
After Width: | Height: | Size: 866 KiB |
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 509 KiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 497 KiB |
|
After Width: | Height: | Size: 539 KiB |
103
docs/integrations/app-connections/azure-client-secrets.mdx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
title: "Azure Client Secrets Connection"
|
||||||
|
description: "Learn how to configure an Azure Client Secrets Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
Infisical currently only supports one method for connecting to Azure, which is OAuth.
|
||||||
|
|
||||||
|
<Accordion title="Self-Hosted Instance">
|
||||||
|
Using the Azure Client Secrets connection on a self-hosted instance of Infisical requires configuring an application in Azure
|
||||||
|
and registering your instance with it.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Set up Azure.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create an application in Azure">
|
||||||
|
Navigate to Azure Active Directory > App registrations to create a new application.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
Azure Active Directory is now Microsoft Entra ID.
|
||||||
|
</Info>
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Create the application. As part of the form, set the **Redirect URI** to `https://your-domain.com/organization/app-connections/azure/oauth/callback`.
|
||||||
|
<Tip>
|
||||||
|
The domain you defined in the Redirect URI should be equivalent to the `SITE_URL` configured in your Infisical instance.
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Assign API permissions to the application">
|
||||||
|
|
||||||
|
For the Azure Connection to work with Client Secrets, you need to assign the following permission to the application.
|
||||||
|
|
||||||
|
#### Azure Client Secrets permissions
|
||||||
|
|
||||||
|
Set the API permissions of the Azure application to include the following permissions:
|
||||||
|
- Microsoft Graph
|
||||||
|
- `Application.ReadWrite.All`
|
||||||
|
- `Application.ReadWrite.OwnedBy`
|
||||||
|
- `Application.ReadWrite.All` (Delegated)
|
||||||
|
- `Directory.ReadWrite.All` (Delegated)
|
||||||
|
- `User.Read` (Delegated)
|
||||||
|
- Azure App Configuration
|
||||||
|
- `KeyValue.Delete` (Delegated)
|
||||||
|
- `KeyValue.Read` (Delegated)
|
||||||
|
- `KeyValue.Write` (Delegated)
|
||||||
|
- Access Key Vault
|
||||||
|
- `user_impersonation` (Delegated)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Add your application credentials to Infisical">
|
||||||
|
Obtain the **Application (Client) ID** and **Directory (Tenant) ID** (this will be used later in the Infisical connection) in Overview and generate a **Client Secret** in Certificate & secrets for your Azure application.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Back in your Infisical instance, add two new environment variables for the credentials of your Azure application.
|
||||||
|
|
||||||
|
- `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application.
|
||||||
|
- `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application.
|
||||||
|
|
||||||
|
Once added, restart your Infisical instance and use the Azure Client Secrets connection.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
## Setup Azure Connection in Infisical
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to App Connections">
|
||||||
|
Navigate to the **App Connections** tab on the **Organization Settings** page. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Add Connection">
|
||||||
|
Select the **Azure Connection** option from the connection options modal. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Authorize Connection">
|
||||||
|
Fill in the **Tenant ID** field with the Directory (Tenant) ID you obtained in the previous step.
|
||||||
|
|
||||||
|
Now select the **OAuth** method and click **Connect to Azure**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Grant Access">
|
||||||
|
You will then be redirected to Azure to grant Infisical access to your Azure account. Once granted,
|
||||||
|
you will be redirected back to Infisical's App Connections page. 
|
||||||
|
</Step>
|
||||||
|
<Step title="Connection Created">
|
||||||
|
Your **Azure Client Secrets Connection** is now available for use. 
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
@@ -181,6 +181,7 @@
|
|||||||
"documentation/platform/secret-rotation/overview",
|
"documentation/platform/secret-rotation/overview",
|
||||||
"documentation/platform/secret-rotation/auth0-client-secret",
|
"documentation/platform/secret-rotation/auth0-client-secret",
|
||||||
"documentation/platform/secret-rotation/aws-iam-user-secret",
|
"documentation/platform/secret-rotation/aws-iam-user-secret",
|
||||||
|
"documentation/platform/secret-rotation/azure-client-secret",
|
||||||
"documentation/platform/secret-rotation/ldap-password",
|
"documentation/platform/secret-rotation/ldap-password",
|
||||||
"documentation/platform/secret-rotation/mssql-credentials",
|
"documentation/platform/secret-rotation/mssql-credentials",
|
||||||
"documentation/platform/secret-rotation/postgres-credentials"
|
"documentation/platform/secret-rotation/postgres-credentials"
|
||||||
@@ -430,6 +431,7 @@
|
|||||||
"integrations/app-connections/auth0",
|
"integrations/app-connections/auth0",
|
||||||
"integrations/app-connections/aws",
|
"integrations/app-connections/aws",
|
||||||
"integrations/app-connections/azure-app-configuration",
|
"integrations/app-connections/azure-app-configuration",
|
||||||
|
"integrations/app-connections/azure-client-secrets",
|
||||||
"integrations/app-connections/azure-key-vault",
|
"integrations/app-connections/azure-key-vault",
|
||||||
"integrations/app-connections/camunda",
|
"integrations/app-connections/camunda",
|
||||||
"integrations/app-connections/databricks",
|
"integrations/app-connections/databricks",
|
||||||
@@ -887,6 +889,19 @@
|
|||||||
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/update"
|
"api-reference/endpoints/secret-rotations/aws-iam-user-secret/update"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Azure Client Secret",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/create",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/delete",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/get-generated-credentials-by-id",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/list",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/rotate-secrets",
|
||||||
|
"api-reference/endpoints/secret-rotations/azure-client-secret/update"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "LDAP Password",
|
"group": "LDAP Password",
|
||||||
"pages": [
|
"pages": [
|
||||||
@@ -980,6 +995,18 @@
|
|||||||
"api-reference/endpoints/app-connections/azure-app-configuration/delete"
|
"api-reference/endpoints/app-connections/azure-app-configuration/delete"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Azure Client Secret",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/list",
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/available",
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/get-by-id",
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/get-by-name",
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/create",
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/update",
|
||||||
|
"api-reference/endpoints/app-connections/azure-client-secret/delete"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Azure Key Vault",
|
"group": "Azure Key Vault",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { CredentialDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/CredentialDisplay";
|
||||||
|
import { TAzureClientSecretRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/azure-client-secret-rotation";
|
||||||
|
|
||||||
|
import { ViewRotationGeneratedCredentialsDisplay } from "./shared";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
generatedCredentialsResponse: TAzureClientSecretRotationGeneratedCredentialsResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewAzureClientSecretRotationGeneratedCredentials = ({
|
||||||
|
generatedCredentialsResponse: { generatedCredentials, activeIndex }
|
||||||
|
}: Props) => {
|
||||||
|
const inactiveIndex = activeIndex === 0 ? 1 : 0;
|
||||||
|
|
||||||
|
const activeCredentials = generatedCredentials[activeIndex];
|
||||||
|
const inactiveCredentials = generatedCredentials[inactiveIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewRotationGeneratedCredentialsDisplay
|
||||||
|
activeCredentials={
|
||||||
|
<>
|
||||||
|
<CredentialDisplay label="Client ID">{activeCredentials?.clientId}</CredentialDisplay>
|
||||||
|
<CredentialDisplay isSensitive label="Client Secret">
|
||||||
|
{activeCredentials?.clientSecret}
|
||||||
|
</CredentialDisplay>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
inactiveCredentials={
|
||||||
|
<>
|
||||||
|
<CredentialDisplay label="Client ID">{inactiveCredentials?.clientId}</CredentialDisplay>
|
||||||
|
<CredentialDisplay isSensitive label="Client Secret">
|
||||||
|
{inactiveCredentials?.clientSecret}
|
||||||
|
</CredentialDisplay>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { ViewAuth0ClientSecretRotationGeneratedCredentials } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewAuth0ClientSecretRotationGeneratedCredentials";
|
import { ViewAuth0ClientSecretRotationGeneratedCredentials } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewAuth0ClientSecretRotationGeneratedCredentials";
|
||||||
|
import { ViewAzureClientSecretRotationGeneratedCredentials } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewAzureClientSecretRotationGeneratedCredentials";
|
||||||
import { ViewLdapPasswordRotationGeneratedCredentials } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewLdapPasswordRotationGeneratedCredentials";
|
import { ViewLdapPasswordRotationGeneratedCredentials } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/ViewLdapPasswordRotationGeneratedCredentials";
|
||||||
import { Modal, ModalContent, Spinner } from "@app/components/v2";
|
import { Modal, ModalContent, Spinner } from "@app/components/v2";
|
||||||
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
|
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
|
||||||
@@ -75,6 +76,13 @@ const Content = ({ secretRotation }: ContentProps) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case SecretRotation.AzureClientSecret:
|
||||||
|
Component = (
|
||||||
|
<ViewAzureClientSecretRotationGeneratedCredentials
|
||||||
|
generatedCredentialsResponse={generatedCredentialsResponse}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
case SecretRotation.LdapPassword:
|
case SecretRotation.LdapPassword:
|
||||||
Component = (
|
Component = (
|
||||||
<ViewLdapPasswordRotationGeneratedCredentials
|
<ViewLdapPasswordRotationGeneratedCredentials
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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.objectId"
|
||||||
|
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">
|
||||||
|
Application.ReadWrite.All, Directory.ReadWrite.All,
|
||||||
|
Application.ReadWrite.OwnedBy, user_impersonation and User.Read
|
||||||
|
</span>{" "}
|
||||||
|
permissions and the application exists in Azure.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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 ?? "");
|
||||||
|
setValue("parameters.clientId", (option as SingleValue<TAzureClient>)?.appId ?? "");
|
||||||
|
}}
|
||||||
|
options={clients}
|
||||||
|
placeholder="Select an application..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
|||||||
import { TSecretRotationV2Form } from "../schemas";
|
import { TSecretRotationV2Form } from "../schemas";
|
||||||
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
|
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
|
||||||
import { AwsIamUserSecretRotationParametersFields } from "./AwsIamUserSecretRotationParametersFields";
|
import { AwsIamUserSecretRotationParametersFields } from "./AwsIamUserSecretRotationParametersFields";
|
||||||
|
import { AzureClientSecretRotationParametersFields } from "./AzureClientSecretRotationParametersFields";
|
||||||
import { LdapPasswordRotationParametersFields } from "./LdapPasswordRotationParametersFields";
|
import { LdapPasswordRotationParametersFields } from "./LdapPasswordRotationParametersFields";
|
||||||
import { SqlCredentialsRotationParametersFields } from "./shared";
|
import { SqlCredentialsRotationParametersFields } from "./shared";
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ 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,
|
||||||
[SecretRotation.LdapPassword]: LdapPasswordRotationParametersFields,
|
[SecretRotation.LdapPassword]: LdapPasswordRotationParametersFields,
|
||||||
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationParametersFields
|
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationParametersFields
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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.objectId}</GenericFieldLabel>
|
||||||
|
</SecretRotationReviewSection>
|
||||||
|
<SecretRotationReviewSection label="Secrets Mapping">
|
||||||
|
<GenericFieldLabel label="Client ID">{clientId}</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Client Secret">{clientSecret}</GenericFieldLabel>
|
||||||
|
</SecretRotationReviewSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
|||||||
|
|
||||||
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
|
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
|
||||||
import { AwsIamUserSecretRotationReviewFields } from "./AwsIamUserSecretRotationReviewFields";
|
import { AwsIamUserSecretRotationReviewFields } from "./AwsIamUserSecretRotationReviewFields";
|
||||||
|
import { AzureClientSecretRotationReviewFields } from "./AzureClientSecretRotationReviewFields";
|
||||||
import { LdapPasswordRotationReviewFields } from "./LdapPasswordRotationReviewFields";
|
import { LdapPasswordRotationReviewFields } from "./LdapPasswordRotationReviewFields";
|
||||||
import { SqlCredentialsRotationReviewFields } from "./shared";
|
import { SqlCredentialsRotationReviewFields } from "./shared";
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ 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,
|
||||||
[SecretRotation.LdapPassword]: LdapPasswordRotationReviewFields,
|
[SecretRotation.LdapPassword]: LdapPasswordRotationReviewFields,
|
||||||
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationReviewFields
|
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationReviewFields
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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} />;
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
|
|||||||
import { TSecretRotationV2Form } from "../schemas";
|
import { TSecretRotationV2Form } from "../schemas";
|
||||||
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
|
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
|
||||||
import { AwsIamUserSecretRotationSecretsMappingFields } from "./AwsIamUserSecretRotationSecretsMappingFields";
|
import { AwsIamUserSecretRotationSecretsMappingFields } from "./AwsIamUserSecretRotationSecretsMappingFields";
|
||||||
|
import { AzureClientSecretRotationSecretsMappingFields } from "./AzureClientSecretRotationSecretsMappingFields";
|
||||||
import { LdapPasswordRotationSecretsMappingFields } from "./LdapPasswordRotationSecretsMappingFields";
|
import { LdapPasswordRotationSecretsMappingFields } from "./LdapPasswordRotationSecretsMappingFields";
|
||||||
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
|
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ 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,
|
||||||
[SecretRotation.LdapPassword]: LdapPasswordRotationSecretsMappingFields,
|
[SecretRotation.LdapPassword]: LdapPasswordRotationSecretsMappingFields,
|
||||||
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationSecretsMappingFields
|
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationSecretsMappingFields
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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({
|
||||||
|
objectId: z.string().trim().min(1, "Object ID required"),
|
||||||
|
appName: z.string().trim().min(1, "App Name required"),
|
||||||
|
clientId: z.string().trim().min(1, "Client ID required")
|
||||||
|
}),
|
||||||
|
secretsMapping: z.object({
|
||||||
|
clientId: z.string().trim().min(1, "Client ID required"),
|
||||||
|
clientSecret: z.string().trim().min(1, "Client Secret required")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.merge(BaseSecretRotationSchema);
|
||||||
@@ -2,14 +2,16 @@ 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 { AwsIamUserSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/aws-iam-user-secret-rotation-schema";
|
import { AwsIamUserSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/aws-iam-user-secret-rotation-schema";
|
||||||
|
import { AzureClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/azure-client-secret-rotation-schema";
|
||||||
import { LdapPasswordRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/ldap-password-rotation-schema";
|
import { LdapPasswordRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/ldap-password-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", [
|
||||||
|
Auth0ClientSecretRotationSchema,
|
||||||
|
AzureClientSecretRotationSchema,
|
||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema,
|
|
||||||
LdapPasswordRotationSchema,
|
LdapPasswordRotationSchema,
|
||||||
AwsIamUserSecretRotationSchema
|
AwsIamUserSecretRotationSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Auth0ConnectionMethod,
|
Auth0ConnectionMethod,
|
||||||
AwsConnectionMethod,
|
AwsConnectionMethod,
|
||||||
AzureAppConfigurationConnectionMethod,
|
AzureAppConfigurationConnectionMethod,
|
||||||
|
AzureClientSecretsConnectionMethod,
|
||||||
AzureKeyVaultConnectionMethod,
|
AzureKeyVaultConnectionMethod,
|
||||||
CamundaConnectionMethod,
|
CamundaConnectionMethod,
|
||||||
DatabricksConnectionMethod,
|
DatabricksConnectionMethod,
|
||||||
@@ -44,6 +45,10 @@ export const APP_CONNECTION_MAP: Record<
|
|||||||
name: "Azure App Configuration",
|
name: "Azure App Configuration",
|
||||||
image: "Microsoft Azure.png"
|
image: "Microsoft Azure.png"
|
||||||
},
|
},
|
||||||
|
[AppConnection.AzureClientSecrets]: {
|
||||||
|
name: "Azure Client Secrets",
|
||||||
|
image: "Microsoft Azure.png"
|
||||||
|
},
|
||||||
[AppConnection.Databricks]: { name: "Databricks", image: "Databricks.png" },
|
[AppConnection.Databricks]: { name: "Databricks", image: "Databricks.png" },
|
||||||
[AppConnection.Humanitec]: { name: "Humanitec", image: "Humanitec.png" },
|
[AppConnection.Humanitec]: { name: "Humanitec", image: "Humanitec.png" },
|
||||||
[AppConnection.TerraformCloud]: { name: "Terraform Cloud", image: "Terraform Cloud.png" },
|
[AppConnection.TerraformCloud]: { name: "Terraform Cloud", image: "Terraform Cloud.png" },
|
||||||
@@ -63,6 +68,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
|||||||
return { name: "GitHub App", icon: faGithub };
|
return { name: "GitHub App", icon: faGithub };
|
||||||
case AzureKeyVaultConnectionMethod.OAuth:
|
case AzureKeyVaultConnectionMethod.OAuth:
|
||||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||||
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
return { name: "OAuth", icon: faPassport };
|
return { name: "OAuth", icon: faPassport };
|
||||||
case AwsConnectionMethod.AccessKey:
|
case AwsConnectionMethod.AccessKey:
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ export const SECRET_ROTATION_MAP: Record<
|
|||||||
image: "Auth0.png",
|
image: "Auth0.png",
|
||||||
size: 35
|
size: 35
|
||||||
},
|
},
|
||||||
|
[SecretRotation.AzureClientSecret]: {
|
||||||
|
name: "Azure Client Secret",
|
||||||
|
image: "Microsoft Azure.png",
|
||||||
|
size: 65
|
||||||
|
},
|
||||||
[SecretRotation.LdapPassword]: {
|
[SecretRotation.LdapPassword]: {
|
||||||
name: "LDAP Password",
|
name: "LDAP Password",
|
||||||
image: "LDAP.png",
|
image: "LDAP.png",
|
||||||
@@ -36,6 +41,7 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
|
|||||||
[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,
|
||||||
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS
|
||||||
};
|
};
|
||||||
@@ -45,6 +51,7 @@ 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]: true,
|
||||||
[SecretRotation.LdapPassword]: false,
|
[SecretRotation.LdapPassword]: false,
|
||||||
[SecretRotation.AwsIamUserSecret]: true
|
[SecretRotation.AwsIamUserSecret]: true
|
||||||
};
|
};
|
||||||
|
|||||||
1
frontend/src/hooks/api/appConnections/azure/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./queries";
|
||||||
37
frontend/src/hooks/api/appConnections/azure/queries.tsx
Normal 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
|
||||||
|
});
|
||||||
|
};
|
||||||
5
frontend/src/hooks/api/appConnections/azure/types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type TAzureClient = {
|
||||||
|
name: string;
|
||||||
|
appId: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ export enum AppConnection {
|
|||||||
GCP = "gcp",
|
GCP = "gcp",
|
||||||
AzureKeyVault = "azure-key-vault",
|
AzureKeyVault = "azure-key-vault",
|
||||||
AzureAppConfiguration = "azure-app-configuration",
|
AzureAppConfiguration = "azure-app-configuration",
|
||||||
|
AzureClientSecrets = "azure-client-secrets",
|
||||||
Databricks = "databricks",
|
Databricks = "databricks",
|
||||||
Humanitec = "humanitec",
|
Humanitec = "humanitec",
|
||||||
TerraformCloud = "terraform-cloud",
|
TerraformCloud = "terraform-cloud",
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export type TAzureAppConfigurationConnectionOption = TAppConnectionOptionBase &
|
|||||||
oauthClientId?: string;
|
oauthClientId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAzureClientSecretsConnectionOption = TAppConnectionOptionBase & {
|
||||||
|
app: AppConnection.AzureClientSecrets;
|
||||||
|
oauthClientId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TDatabricksConnectionOption = TAppConnectionOptionBase & {
|
export type TDatabricksConnectionOption = TAppConnectionOptionBase & {
|
||||||
app: AppConnection.Databricks;
|
app: AppConnection.Databricks;
|
||||||
};
|
};
|
||||||
@@ -81,6 +86,7 @@ export type TAppConnectionOption =
|
|||||||
| TGcpConnectionOption
|
| TGcpConnectionOption
|
||||||
| TAzureAppConfigurationConnectionOption
|
| TAzureAppConfigurationConnectionOption
|
||||||
| TAzureKeyVaultConnectionOption
|
| TAzureKeyVaultConnectionOption
|
||||||
|
| TAzureClientSecretsConnectionOption
|
||||||
| TDatabricksConnectionOption
|
| TDatabricksConnectionOption
|
||||||
| THumanitecConnectionOption
|
| THumanitecConnectionOption
|
||||||
| TTerraformCloudConnectionOption
|
| TTerraformCloudConnectionOption
|
||||||
@@ -98,6 +104,7 @@ export type TAppConnectionOptionMap = {
|
|||||||
[AppConnection.GCP]: TGcpConnectionOption;
|
[AppConnection.GCP]: TGcpConnectionOption;
|
||||||
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnectionOption;
|
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnectionOption;
|
||||||
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnectionOption;
|
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnectionOption;
|
||||||
|
[AppConnection.AzureClientSecrets]: TAzureClientSecretsConnectionOption;
|
||||||
[AppConnection.Databricks]: TDatabricksConnectionOption;
|
[AppConnection.Databricks]: TDatabricksConnectionOption;
|
||||||
[AppConnection.Humanitec]: THumanitecConnectionOption;
|
[AppConnection.Humanitec]: THumanitecConnectionOption;
|
||||||
[AppConnection.TerraformCloud]: TTerraformCloudConnectionOption;
|
[AppConnection.TerraformCloud]: TTerraformCloudConnectionOption;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||||
|
|
||||||
|
export enum AzureClientSecretsConnectionMethod {
|
||||||
|
OAuth = "oauth"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TAzureClientSecretsConnection = TRootAppConnection & {
|
||||||
|
app: AppConnection.AzureClientSecrets;
|
||||||
|
} & {
|
||||||
|
method: AzureClientSecretsConnectionMethod.OAuth;
|
||||||
|
credentials: {
|
||||||
|
code: string;
|
||||||
|
tenantId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { TAppConnectionOption } from "./app-options";
|
|||||||
import { TAuth0Connection } from "./auth0-connection";
|
import { TAuth0Connection } from "./auth0-connection";
|
||||||
import { TAwsConnection } from "./aws-connection";
|
import { TAwsConnection } from "./aws-connection";
|
||||||
import { TAzureAppConfigurationConnection } from "./azure-app-configuration-connection";
|
import { TAzureAppConfigurationConnection } from "./azure-app-configuration-connection";
|
||||||
|
import { TAzureClientSecretsConnection } from "./azure-client-secrets-connection";
|
||||||
import { TAzureKeyVaultConnection } from "./azure-key-vault-connection";
|
import { TAzureKeyVaultConnection } from "./azure-key-vault-connection";
|
||||||
import { TCamundaConnection } from "./camunda-connection";
|
import { TCamundaConnection } from "./camunda-connection";
|
||||||
import { TDatabricksConnection } from "./databricks-connection";
|
import { TDatabricksConnection } from "./databricks-connection";
|
||||||
@@ -20,6 +21,7 @@ import { TWindmillConnection } from "./windmill-connection";
|
|||||||
export * from "./auth0-connection";
|
export * from "./auth0-connection";
|
||||||
export * from "./aws-connection";
|
export * from "./aws-connection";
|
||||||
export * from "./azure-app-configuration-connection";
|
export * from "./azure-app-configuration-connection";
|
||||||
|
export * from "./azure-client-secrets-connection";
|
||||||
export * from "./azure-key-vault-connection";
|
export * from "./azure-key-vault-connection";
|
||||||
export * from "./camunda-connection";
|
export * from "./camunda-connection";
|
||||||
export * from "./databricks-connection";
|
export * from "./databricks-connection";
|
||||||
@@ -40,6 +42,7 @@ export type TAppConnection =
|
|||||||
| TGcpConnection
|
| TGcpConnection
|
||||||
| TAzureKeyVaultConnection
|
| TAzureKeyVaultConnection
|
||||||
| TAzureAppConfigurationConnection
|
| TAzureAppConfigurationConnection
|
||||||
|
| TAzureClientSecretsConnection
|
||||||
| TDatabricksConnection
|
| TDatabricksConnection
|
||||||
| THumanitecConnection
|
| THumanitecConnection
|
||||||
| TTerraformCloudConnection
|
| TTerraformCloudConnection
|
||||||
@@ -83,6 +86,7 @@ export type TAppConnectionMap = {
|
|||||||
[AppConnection.GCP]: TGcpConnection;
|
[AppConnection.GCP]: TGcpConnection;
|
||||||
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
|
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
|
||||||
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
|
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
|
||||||
|
[AppConnection.AzureClientSecrets]: TAzureClientSecretsConnection;
|
||||||
[AppConnection.Databricks]: TDatabricksConnection;
|
[AppConnection.Databricks]: TDatabricksConnection;
|
||||||
[AppConnection.Humanitec]: THumanitecConnection;
|
[AppConnection.Humanitec]: THumanitecConnection;
|
||||||
[AppConnection.TerraformCloud]: TTerraformCloudConnection;
|
[AppConnection.TerraformCloud]: TTerraformCloudConnection;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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",
|
||||||
LdapPassword = "ldap-password",
|
LdapPassword = "ldap-password",
|
||||||
AwsIamUserSecret = "aws-iam-user-secret"
|
AwsIamUserSecret = "aws-iam-user-secret"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
objectId: 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"];
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -9,6 +9,11 @@ import {
|
|||||||
TAwsIamUserSecretRotationGeneratedCredentialsResponse,
|
TAwsIamUserSecretRotationGeneratedCredentialsResponse,
|
||||||
TAwsIamUserSecretRotationOption
|
TAwsIamUserSecretRotationOption
|
||||||
} from "@app/hooks/api/secretRotationsV2/types/aws-iam-user-secret-rotation";
|
} from "@app/hooks/api/secretRotationsV2/types/aws-iam-user-secret-rotation";
|
||||||
|
import {
|
||||||
|
TAzureClientSecretRotation,
|
||||||
|
TAzureClientSecretRotationGeneratedCredentialsResponse,
|
||||||
|
TAzureClientSecretRotationOption
|
||||||
|
} from "@app/hooks/api/secretRotationsV2/types/azure-client-secret-rotation";
|
||||||
import {
|
import {
|
||||||
TLdapPasswordRotation,
|
TLdapPasswordRotation,
|
||||||
TLdapPasswordRotationGeneratedCredentialsResponse,
|
TLdapPasswordRotationGeneratedCredentialsResponse,
|
||||||
@@ -30,6 +35,7 @@ export type TSecretRotationV2 = (
|
|||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
|
| TAzureClientSecretRotation
|
||||||
| TLdapPasswordRotation
|
| TLdapPasswordRotation
|
||||||
| TAwsIamUserSecretRotation
|
| TAwsIamUserSecretRotation
|
||||||
) & {
|
) & {
|
||||||
@@ -39,6 +45,7 @@ export type TSecretRotationV2 = (
|
|||||||
export type TSecretRotationV2Option =
|
export type TSecretRotationV2Option =
|
||||||
| TSqlCredentialsRotationOption
|
| TSqlCredentialsRotationOption
|
||||||
| TAuth0ClientSecretRotationOption
|
| TAuth0ClientSecretRotationOption
|
||||||
|
| TAzureClientSecretRotationOption
|
||||||
| TLdapPasswordRotationOption
|
| TLdapPasswordRotationOption
|
||||||
| TAwsIamUserSecretRotationOption;
|
| TAwsIamUserSecretRotationOption;
|
||||||
|
|
||||||
@@ -50,6 +57,7 @@ export type TViewSecretRotationGeneratedCredentialsResponse =
|
|||||||
| TPostgresCredentialsRotationGeneratedCredentialsResponse
|
| TPostgresCredentialsRotationGeneratedCredentialsResponse
|
||||||
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
|
||||||
| TAuth0ClientSecretRotationGeneratedCredentialsResponse
|
| TAuth0ClientSecretRotationGeneratedCredentialsResponse
|
||||||
|
| TAzureClientSecretRotationGeneratedCredentialsResponse
|
||||||
| TLdapPasswordRotationGeneratedCredentialsResponse
|
| TLdapPasswordRotationGeneratedCredentialsResponse
|
||||||
| TAwsIamUserSecretRotationGeneratedCredentialsResponse;
|
| TAwsIamUserSecretRotationGeneratedCredentialsResponse;
|
||||||
|
|
||||||
@@ -98,6 +106,7 @@ export type TSecretRotationOptionMap = {
|
|||||||
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
|
[SecretRotation.PostgresCredentials]: TSqlCredentialsRotationOption;
|
||||||
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
|
[SecretRotation.MsSqlCredentials]: TSqlCredentialsRotationOption;
|
||||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
|
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationOption;
|
||||||
|
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationOption;
|
||||||
[SecretRotation.LdapPassword]: TLdapPasswordRotationOption;
|
[SecretRotation.LdapPassword]: TLdapPasswordRotationOption;
|
||||||
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationOption;
|
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationOption;
|
||||||
};
|
};
|
||||||
@@ -106,6 +115,7 @@ export type TSecretRotationGeneratedCredentialsResponseMap = {
|
|||||||
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
[SecretRotation.PostgresCredentials]: TPostgresCredentialsRotationGeneratedCredentialsResponse;
|
||||||
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
|
[SecretRotation.MsSqlCredentials]: TMsSqlCredentialsRotationGeneratedCredentialsResponse;
|
||||||
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
[SecretRotation.Auth0ClientSecret]: TAuth0ClientSecretRotationGeneratedCredentialsResponse;
|
||||||
|
[SecretRotation.AzureClientSecret]: TAzureClientSecretRotationGeneratedCredentialsResponse;
|
||||||
[SecretRotation.LdapPassword]: TLdapPasswordRotationGeneratedCredentialsResponse;
|
[SecretRotation.LdapPassword]: TLdapPasswordRotationGeneratedCredentialsResponse;
|
||||||
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationGeneratedCredentialsResponse;
|
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationGeneratedCredentialsResponse;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AppConnectionHeader } from "../AppConnectionHeader";
|
|||||||
import { Auth0ConnectionForm } from "./Auth0ConnectionForm";
|
import { Auth0ConnectionForm } from "./Auth0ConnectionForm";
|
||||||
import { AwsConnectionForm } from "./AwsConnectionForm";
|
import { AwsConnectionForm } from "./AwsConnectionForm";
|
||||||
import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConnectionForm";
|
import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConnectionForm";
|
||||||
|
import { AzureClientSecretsConnectionForm } from "./AzureClientSecretsConnectionForm";
|
||||||
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
|
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
|
||||||
import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
import { CamundaConnectionForm } from "./CamundaConnectionForm";
|
||||||
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
|
||||||
@@ -87,6 +88,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
|||||||
return <MsSqlConnectionForm onSubmit={onSubmit} />;
|
return <MsSqlConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.Camunda:
|
case AppConnection.Camunda:
|
||||||
return <CamundaConnectionForm onSubmit={onSubmit} />;
|
return <CamundaConnectionForm onSubmit={onSubmit} />;
|
||||||
|
case AppConnection.AzureClientSecrets:
|
||||||
|
return <AzureClientSecretsConnectionForm />;
|
||||||
case AppConnection.Windmill:
|
case AppConnection.Windmill:
|
||||||
return <WindmillConnectionForm onSubmit={onSubmit} />;
|
return <WindmillConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.Auth0:
|
case AppConnection.Auth0:
|
||||||
@@ -155,6 +158,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
|||||||
return <MsSqlConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <MsSqlConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
case AppConnection.Camunda:
|
case AppConnection.Camunda:
|
||||||
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
|
case AppConnection.AzureClientSecrets:
|
||||||
|
return <AzureClientSecretsConnectionForm appConnection={appConnection} />;
|
||||||
case AppConnection.Windmill:
|
case AppConnection.Windmill:
|
||||||
return <WindmillConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
return <WindmillConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||||
case AppConnection.Auth0:
|
case AppConnection.Auth0:
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||||
|
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||||
|
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||||
|
import {
|
||||||
|
AzureClientSecretsConnectionMethod,
|
||||||
|
TAzureClientSecretsConnection,
|
||||||
|
useGetAppConnectionOption
|
||||||
|
} from "@app/hooks/api/appConnections";
|
||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
|
||||||
|
import {
|
||||||
|
genericAppConnectionFieldsSchema,
|
||||||
|
GenericAppConnectionsFields
|
||||||
|
} from "./GenericAppConnectionFields";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
appConnection?: TAzureClientSecretsConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||||
|
app: z.literal(AppConnection.AzureClientSecrets),
|
||||||
|
method: z.nativeEnum(AzureClientSecretsConnectionMethod),
|
||||||
|
tenantId: z.string().trim().min(1, "Tenant ID is required")
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
|
||||||
|
const isUpdate = Boolean(appConnection);
|
||||||
|
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
option: { oauthClientId },
|
||||||
|
isLoading
|
||||||
|
} = useGetAppConnectionOption(AppConnection.AzureClientSecrets);
|
||||||
|
|
||||||
|
const form = useForm<FormData>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: appConnection
|
||||||
|
? {
|
||||||
|
...appConnection,
|
||||||
|
tenantId: appConnection.credentials.tenantId
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
app: AppConnection.AzureClientSecrets,
|
||||||
|
method: AzureClientSecretsConnectionMethod.OAuth
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
control,
|
||||||
|
watch,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
const selectedMethod = watch("method");
|
||||||
|
|
||||||
|
const onSubmit = (formData: FormData) => {
|
||||||
|
setIsRedirecting(true);
|
||||||
|
const state = crypto.randomBytes(16).toString("hex");
|
||||||
|
localStorage.setItem("latestCSRFToken", state);
|
||||||
|
localStorage.setItem(
|
||||||
|
"azureClientSecretsConnectionFormData",
|
||||||
|
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (formData.method) {
|
||||||
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
|
window.location.assign(
|
||||||
|
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMissingConfig = !oauthClientId;
|
||||||
|
|
||||||
|
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{!isUpdate && <GenericAppConnectionsFields />}
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="tenantId"
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
tooltipText="The Directory (tenant) ID."
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Tenant ID"
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="method"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
tooltipText={`The method you would like to use to connect with ${
|
||||||
|
APP_CONNECTION_MAP[AppConnection.AzureClientSecrets].name
|
||||||
|
}. This field cannot be changed after creation.`}
|
||||||
|
errorText={
|
||||||
|
!isLoading && isMissingConfig
|
||||||
|
? `Environment variables have not been configured. ${
|
||||||
|
isInfisicalCloud()
|
||||||
|
? "Please contact Infisical."
|
||||||
|
: `See documentation to configure Azure ${methodDetails.name} Connections.`
|
||||||
|
}`
|
||||||
|
: error?.message
|
||||||
|
}
|
||||||
|
isError={Boolean(error?.message) || isMissingConfig}
|
||||||
|
label="Method"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
isDisabled={isUpdate}
|
||||||
|
value={value}
|
||||||
|
onValueChange={(val) => onChange(val)}
|
||||||
|
className="w-full border border-mineshaft-500"
|
||||||
|
position="popper"
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
>
|
||||||
|
{Object.values(AzureClientSecretsConnectionMethod).map((method) => {
|
||||||
|
return (
|
||||||
|
<SelectItem value={method} key={method}>
|
||||||
|
{getAppConnectionMethodDetails(method).name}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
colorSchema="secondary"
|
||||||
|
isLoading={isSubmitting || isRedirecting}
|
||||||
|
isDisabled={isSubmitting || (!isUpdate && !isDirty) || isMissingConfig || isRedirecting}
|
||||||
|
>
|
||||||
|
{isUpdate ? "Reconnect to Azure" : "Connect to Azure"}
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,9 +7,11 @@ import { ROUTE_PATHS } from "@app/const/routes";
|
|||||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||||
import {
|
import {
|
||||||
AzureAppConfigurationConnectionMethod,
|
AzureAppConfigurationConnectionMethod,
|
||||||
|
AzureClientSecretsConnectionMethod,
|
||||||
AzureKeyVaultConnectionMethod,
|
AzureKeyVaultConnectionMethod,
|
||||||
GitHubConnectionMethod,
|
GitHubConnectionMethod,
|
||||||
TAzureAppConfigurationConnection,
|
TAzureAppConfigurationConnection,
|
||||||
|
TAzureClientSecretsConnection,
|
||||||
TAzureKeyVaultConnection,
|
TAzureKeyVaultConnection,
|
||||||
TGitHubConnection,
|
TGitHubConnection,
|
||||||
useCreateAppConnection,
|
useCreateAppConnection,
|
||||||
@@ -32,18 +34,26 @@ type AzureAppConfigurationFormData = BaseFormData &
|
|||||||
Pick<TAzureAppConfigurationConnection, "name" | "method" | "description"> &
|
Pick<TAzureAppConfigurationConnection, "name" | "method" | "description"> &
|
||||||
Pick<TAzureAppConfigurationConnection["credentials"], "tenantId">;
|
Pick<TAzureAppConfigurationConnection["credentials"], "tenantId">;
|
||||||
|
|
||||||
|
type AzureClientSecretsFormData = BaseFormData &
|
||||||
|
Pick<TAzureClientSecretsConnection, "name" | "method" | "description"> &
|
||||||
|
Pick<TAzureClientSecretsConnection["credentials"], "tenantId">;
|
||||||
|
|
||||||
type FormDataMap = {
|
type FormDataMap = {
|
||||||
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
||||||
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
||||||
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
||||||
app: AppConnection.AzureAppConfiguration;
|
app: AppConnection.AzureAppConfiguration;
|
||||||
};
|
};
|
||||||
|
[AppConnection.AzureClientSecrets]: AzureClientSecretsFormData & {
|
||||||
|
app: AppConnection.AzureClientSecrets;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
||||||
[AppConnection.GitHub]: "githubConnectionFormData",
|
[AppConnection.GitHub]: "githubConnectionFormData",
|
||||||
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
|
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
|
||||||
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData"
|
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData",
|
||||||
|
[AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OAuthCallbackPage = () => {
|
export const OAuthCallbackPage = () => {
|
||||||
@@ -194,6 +204,54 @@ export const OAuthCallbackPage = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleAzureClientSecrets = useCallback(async () => {
|
||||||
|
const formData = getFormData(AppConnection.AzureClientSecrets);
|
||||||
|
if (formData === null) return null;
|
||||||
|
|
||||||
|
clearState(AppConnection.AzureClientSecrets);
|
||||||
|
|
||||||
|
const { connectionId, name, description, returnUrl } = formData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (connectionId) {
|
||||||
|
await updateAppConnection.mutateAsync({
|
||||||
|
app: AppConnection.AzureClientSecrets,
|
||||||
|
connectionId,
|
||||||
|
credentials: {
|
||||||
|
code: code as string,
|
||||||
|
tenantId: formData.tenantId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createAppConnection.mutateAsync({
|
||||||
|
app: AppConnection.AzureClientSecrets,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
method: AzureClientSecretsConnectionMethod.OAuth,
|
||||||
|
credentials: {
|
||||||
|
code: code as string,
|
||||||
|
tenantId: formData.tenantId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
createNotification({
|
||||||
|
title: `Failed to ${connectionId ? "update" : "add"} Azure Client Secrets Connection`,
|
||||||
|
text: err?.message,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
navigate({
|
||||||
|
to: returnUrl ?? "/organization/app-connections"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
connectionId,
|
||||||
|
returnUrl,
|
||||||
|
appConnectionName: formData.app
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleGithub = useCallback(async () => {
|
const handleGithub = useCallback(async () => {
|
||||||
const formData = getFormData(AppConnection.GitHub);
|
const formData = getFormData(AppConnection.GitHub);
|
||||||
if (formData === null) return null;
|
if (formData === null) return null;
|
||||||
@@ -280,6 +338,8 @@ export const OAuthCallbackPage = () => {
|
|||||||
data = await handleAzureKeyVault();
|
data = await handleAzureKeyVault();
|
||||||
} else if (appConnection === AppConnection.AzureAppConfiguration) {
|
} else if (appConnection === AppConnection.AzureAppConfiguration) {
|
||||||
data = await handleAzureAppConfiguration();
|
data = await handleAzureAppConfiguration();
|
||||||
|
} else if (appConnection === AppConnection.AzureClientSecrets) {
|
||||||
|
data = await handleAzureClientSecrets();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|||||||