Add Azure Client Secrets Rotation

This commit is contained in:
carlosmonastyrski
2025-04-16 05:04:41 -03:00
parent 7d97a76ecc
commit 1184ea1b11
36 changed files with 861 additions and 36 deletions
@@ -0,0 +1,71 @@
import { Controller, useFormContext } from "react-hook-form";
import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
import { useAzureConnectionListClients } from "@app/hooks/api/appConnections/azure";
import { TAzureClient } from "@app/hooks/api/appConnections/azure/types";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
export const AzureClientSecretRotationParametersFields = () => {
const { control, watch, setValue } = useFormContext<
TSecretRotationV2Form & {
type: SecretRotation.AzureClientSecret;
}
>();
const connectionId = watch("connection.id");
const { data: clients, isPending: isClientsPending } = useAzureConnectionListClients(
connectionId,
{ enabled: Boolean(connectionId) }
);
return (
<Controller
name="parameters.appId"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Application"
helperText={
<Tooltip
className="max-w-md"
content={
<>
Ensure that your connection has the{" "}
<span className="font-semibold">read_clients</span> permission and the application
exists in the connection&#39;s audience.
</>
}
>
<div>
<span>Don&#39;t see the application you&#39;re looking for?</span>{" "}
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
</div>
</Tooltip>
}
>
<FilterableSelect
menuPlacement="top"
isLoading={isClientsPending && Boolean(connectionId)}
isDisabled={!connectionId}
value={clients?.find((client) => client.id === value) ?? null}
onChange={(option) => {
onChange((option as SingleValue<TAzureClient>)?.id ?? null);
setValue("parameters.appName", (option as SingleValue<TAzureClient>)?.name ?? "");
}}
options={clients}
placeholder="Select an application..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
);
};
@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { TSecretRotationV2Form } from "../schemas";
import { Auth0ClientSecretRotationParametersFields } from "./Auth0ClientSecretRotationParametersFields";
import { AzureClientSecretRotationParametersFields } from "./AzureClientSecretRotationParametersFields";
import { SqlCredentialsRotationParametersFields } from "./shared";
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationParametersFields,
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationParametersFields,
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationParametersFields,
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationParametersFields
};
export const SecretRotationV2ParametersFields = () => {
@@ -0,0 +1,30 @@
import { useFormContext } from "react-hook-form";
import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas";
import { GenericFieldLabel } from "@app/components/v2";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { SecretRotationReviewSection } from "./shared";
export const AzureClientSecretRotationReviewFields = () => {
const { watch } = useFormContext<
TSecretRotationV2Form & {
type: SecretRotation.AzureClientSecret;
}
>();
const [parameters, { clientId, clientSecret }] = watch(["parameters", "secretsMapping"]);
return (
<>
<SecretRotationReviewSection label="Parameters">
<GenericFieldLabel label="App Name">{parameters.appName}</GenericFieldLabel>
<GenericFieldLabel label="App ID">{parameters.appId}</GenericFieldLabel>
</SecretRotationReviewSection>
<SecretRotationReviewSection label="Secrets Mapping">
<GenericFieldLabel label="Client ID">{clientId}</GenericFieldLabel>
<GenericFieldLabel label="Client Secret">{clientSecret}</GenericFieldLabel>
</SecretRotationReviewSection>
</>
);
};
@@ -7,12 +7,14 @@ import { getRotateAtLocal } from "@app/helpers/secretRotationsV2";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { Auth0ClientSecretRotationReviewFields } from "./Auth0ClientSecretRotationReviewFields";
import { AzureClientSecretRotationReviewFields } from "./AzureClientSecretRotationReviewFields";
import { SqlCredentialsRotationReviewFields } from "./shared";
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationReviewFields,
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationReviewFields,
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationReviewFields,
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationReviewFields
};
export const SecretRotationV2ReviewFields = () => {
@@ -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} />;
};
@@ -4,12 +4,14 @@ import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import { TSecretRotationV2Form } from "../schemas";
import { Auth0ClientSecretRotationSecretsMappingFields } from "./Auth0ClientSecretRotationSecretsMappingFields";
import { AzureClientSecretRotationSecretsMappingFields } from "./AzureClientSecretRotationSecretsMappingFields";
import { SqlCredentialsRotationSecretsMappingFields } from "./shared";
const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.PostgresCredentials]: SqlCredentialsRotationSecretsMappingFields,
[SecretRotation.MsSqlCredentials]: SqlCredentialsRotationSecretsMappingFields,
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields
[SecretRotation.Auth0ClientSecret]: Auth0ClientSecretRotationSecretsMappingFields,
[SecretRotation.AzureClientSecret]: AzureClientSecretRotationSecretsMappingFields
};
export const SecretRotationV2SecretsMappingFields = () => {
@@ -0,0 +1,18 @@
import { z } from "zod";
import { BaseSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/base-secret-rotation-v2-schema";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
export const AzureClientSecretRotationSchema = z
.object({
type: z.literal(SecretRotation.AzureClientSecret),
parameters: z.object({
appId: z.string().trim().min(1, "App ID required"),
appName: z.string().trim().min(1, "App Name required")
}),
secretsMapping: z.object({
clientId: z.string().trim().min(1, "Client ID required"),
clientSecret: z.string().trim().min(1, "Client Secret required")
})
})
.merge(BaseSecretRotationSchema);
@@ -1,13 +1,15 @@
import { z } from "zod";
import { Auth0ClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/auth0-client-secret-rotation-schema";
import { AzureClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/azure-client-secret-rotation-schema";
import { MsSqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mssql-credentials-rotation-schema";
import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema";
const SecretRotationUnionSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationSchema,
MsSqlCredentialsRotationSchema,
Auth0ClientSecretRotationSchema
Auth0ClientSecretRotationSchema,
AzureClientSecretRotationSchema
]);
export const SecretRotationV2FormSchema = SecretRotationUnionSchema;