Merge pull request #4976 from Infisical/chore/external-kms-api-refactor

chore: external-kms API refactor
This commit is contained in:
Piyush Gupta
2025-12-06 01:23:05 +05:30
committed by GitHub
19 changed files with 1093 additions and 420 deletions
+24 -14
View File
@@ -6,6 +6,7 @@ import { kmsKeys } from "./queries";
import {
AddExternalKmsType,
ExternalKmsGcpSchemaType,
ExternalKmsProvider,
KmsGcpKeyFetchAuthType,
KmsType,
UpdateExternalKmsType
@@ -14,11 +15,12 @@ import {
export const useAddExternalKms = (orgId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ name, description, provider }: AddExternalKmsType) => {
const { data } = await apiRequest.post("/api/v1/external-kms", {
mutationFn: async ({ name, description, configuration }: AddExternalKmsType) => {
const providerPath = configuration.type === ExternalKmsProvider.Aws ? "aws" : "gcp";
const { data } = await apiRequest.post(`/api/v1/external-kms/${providerPath}`, {
name,
description,
provider
configuration: configuration.inputs
});
return data;
@@ -29,21 +31,21 @@ export const useAddExternalKms = (orgId: string) => {
});
};
export const useUpdateExternalKms = (orgId: string) => {
export const useUpdateExternalKms = (orgId: string, provider: ExternalKmsProvider) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
kmsId,
name,
description,
provider
configuration
}: {
kmsId: string;
} & UpdateExternalKmsType) => {
const { data } = await apiRequest.patch(`/api/v1/external-kms/${kmsId}`, {
const { data } = await apiRequest.patch(`/api/v1/external-kms/${provider}/${kmsId}`, {
name,
description,
provider
configuration: configuration?.inputs
});
return data;
@@ -58,8 +60,8 @@ export const useUpdateExternalKms = (orgId: string) => {
export const useRemoveExternalKms = (orgId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (kmsId: string) => {
const { data } = await apiRequest.delete(`/api/v1/external-kms/${kmsId}`);
mutationFn: async ({ kmsId, provider }: { kmsId: string; provider: ExternalKmsProvider }) => {
const { data } = await apiRequest.delete(`/api/v1/external-kms/${provider}/${kmsId}`);
return data;
},
@@ -130,11 +132,19 @@ export const useExternalKmsFetchGcpKeys = (orgId: string) => {
);
}
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", {
authMethod: credential ? KmsGcpKeyFetchAuthType.Credential : KmsGcpKeyFetchAuthType.Kms,
region: gcpRegion,
...rest
});
const requestBody = credential
? {
authMethod: KmsGcpKeyFetchAuthType.Credential,
region: gcpRegion,
credential
}
: {
authMethod: KmsGcpKeyFetchAuthType.Kms,
region: gcpRegion,
kmsId
};
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", requestBody);
return data;
},
+10 -6
View File
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { Kms, KmsListEntry } from "./types";
import { ExternalKmsProvider, Kms, KmsListEntry } from "./types";
export const kmsKeys = {
getExternalKmsList: (orgId: string) => ["get-all-external-kms", { orgId }],
@@ -23,15 +23,19 @@ export const useGetExternalKmsList = (orgId: string, { enabled }: { enabled?: bo
});
};
export const useGetExternalKmsById = (kmsId: string) => {
export const useGetExternalKmsById = ({
kmsId,
provider
}: {
kmsId: string;
provider: ExternalKmsProvider;
}) => {
return useQuery({
queryKey: kmsKeys.getExternalKmsById(kmsId),
enabled: Boolean(kmsId),
queryFn: async () => {
const {
data: { externalKms }
} = await apiRequest.get<{ externalKms: Kms }>(`/api/v1/external-kms/${kmsId}`);
return externalKms;
const { data } = await apiRequest.get<Kms>(`/api/v1/external-kms/${provider}/${kmsId}`);
return data;
}
});
};
+8 -6
View File
@@ -8,12 +8,13 @@ export type Kms = {
description: string;
orgId: string;
name: string;
external: {
externalKms: {
id: string;
status: string;
statusDetails: string;
provider: string;
providerInput: Record<string, any>;
configuration: Record<string, any>;
credentialsHash?: string;
};
};
@@ -123,14 +124,14 @@ export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
export const AddExternalKmsSchema = z.object({
name: slugSchema({ min: 1, field: "Alias" }),
description: z.string().trim().optional(),
provider: ExternalKmsInputSchema
configuration: ExternalKmsInputSchema
});
export type AddExternalKmsType = z.infer<typeof AddExternalKmsSchema>;
// we need separate schema for update because the credential field is not required on GCP
export const ExternalKmsUpdateInputSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema }),
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema.partial() }),
z.object({
type: z.literal(ExternalKmsProvider.Gcp),
inputs: ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })
@@ -144,9 +145,10 @@ export const UpdateExternalKmsSchema = z.object({
.min(1)
.refine((v) => slugify(v) === v, {
message: "Alias must be a valid slug"
}),
})
.optional(),
description: z.string().trim().optional(),
provider: ExternalKmsUpdateInputSchema
configuration: ExternalKmsUpdateInputSchema.optional()
});
export type UpdateExternalKmsType = z.infer<typeof UpdateExternalKmsSchema>;
@@ -11,7 +11,8 @@ import {
AddExternalKmsType,
ExternalKmsProvider,
Kms,
KmsAwsCredentialType
KmsAwsCredentialType,
UpdateExternalKmsSchema
} from "@app/hooks/api/kms/types";
const AWS_REGIONS = [
@@ -50,9 +51,12 @@ type Props = {
onCompleted: () => void;
onCancel: () => void;
kms?: Kms;
mode?: "full" | "credentials" | "details";
};
export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
export const AwsKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) => {
const validationSchema = kms ? UpdateExternalKmsSchema : AddExternalKmsSchema;
const {
control,
handleSubmit,
@@ -60,24 +64,35 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
setValue,
formState: { isSubmitting }
} = useForm<AddExternalKmsType>({
resolver: zodResolver(AddExternalKmsSchema),
resolver: zodResolver(validationSchema),
defaultValues: {
name: kms?.name,
description: kms?.description ?? "",
provider: {
configuration: {
type: ExternalKmsProvider.Aws,
inputs: {
credential: {
type: kms?.external?.providerInput?.credential?.type,
data: {
accessKey: kms?.external?.providerInput?.credential?.data?.accessKey,
secretKey: kms?.external?.providerInput?.credential?.data?.secretKey,
assumeRoleArn: kms?.external?.providerInput?.credential?.data?.assumeRoleArn,
externalId: kms?.external?.providerInput?.credential?.data?.externalId
}
},
awsRegion: kms?.external?.providerInput?.awsRegion,
kmsKeyId: kms?.external?.providerInput?.kmsKeyId
...(mode !== "details" &&
kms?.externalKms?.configuration?.credential?.type &&
kms.externalKms.configuration.credential.data
? {
credential: {
type: kms.externalKms.configuration.credential.type,
data: {
accessKey: kms.externalKms.configuration.credential.data?.accessKey ?? "",
secretKey: kms.externalKms.configuration.credential.data?.secretKey ?? "",
assumeRoleArn:
kms.externalKms.configuration.credential.data?.assumeRoleArn ?? "",
externalId: kms.externalKms.configuration.credential.data?.externalId ?? ""
}
}
}
: {}),
...(mode !== "credentials"
? {
awsRegion: kms?.externalKms?.configuration?.awsRegion ?? "",
kmsKeyId: kms?.externalKms?.configuration?.kmsKeyId ?? ""
}
: {})
}
}
}
@@ -85,30 +100,59 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
const { currentOrg } = useOrganization();
const { mutateAsync: addAwsExternalKms } = useAddExternalKms(currentOrg.id);
const { mutateAsync: updateAwsExternalKms } = useUpdateExternalKms(currentOrg.id);
const { mutateAsync: updateAwsExternalKms } = useUpdateExternalKms(
currentOrg.id,
ExternalKmsProvider.Aws
);
const selectedAwsAuthType = watch("provider.inputs.credential.type");
const selectedAwsAuthType = watch("configuration.inputs.credential.type");
const handleAwsKmsFormSubmit = async (data: AddExternalKmsType) => {
const { name, description, provider } = data;
const { name, description, configuration } = data;
try {
if (kms) {
await updateAwsExternalKms({
kmsId: kms.id,
name,
description,
provider
});
if (configuration.type !== ExternalKmsProvider.Aws) {
throw new Error("Invalid configuration type");
}
const awsInputs = configuration.inputs;
if (mode === "credentials") {
await updateAwsExternalKms({
kmsId: kms.id,
configuration: {
type: ExternalKmsProvider.Aws,
inputs: {
credential: { ...awsInputs.credential }
}
}
});
} else {
await updateAwsExternalKms({
kmsId: kms.id,
name,
description,
configuration: {
type: ExternalKmsProvider.Aws,
inputs: {
awsRegion: awsInputs.awsRegion,
kmsKeyId: awsInputs.kmsKeyId
}
}
});
}
createNotification({
text: "Successfully updated AWS External KMS",
text:
mode === "credentials"
? "Successfully updated AWS External KMS credentials"
: "Successfully updated AWS External KMS Details",
type: "success"
});
} else {
await addAwsExternalKms({
name,
description,
provider
configuration
});
createNotification({
@@ -125,104 +169,149 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
return (
<form onSubmit={handleSubmit(handleAwsKmsFormSubmit)} autoComplete="off">
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl label="Alias" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="description"
render={({ field, fieldState: { error } }) => (
<FormControl label="Description" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.inputs.credential.type"
defaultValue={KmsAwsCredentialType.AssumeRole}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Authentication Mode"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => {
setValue("provider.inputs.credential.data.accessKey", "");
setValue("provider.inputs.credential.data.secretKey", "");
setValue("provider.inputs.credential.data.assumeRoleArn", "");
setValue("provider.inputs.credential.data.externalId", "");
onChange(e);
}}
className="w-full"
>
<SelectItem value={KmsAwsCredentialType.AssumeRole}>AWS Assume Role</SelectItem>
<SelectItem value={KmsAwsCredentialType.AccessKey}>Access Key</SelectItem>
</Select>
</FormControl>
)}
/>
{selectedAwsAuthType === KmsAwsCredentialType.AccessKey ? (
{(mode === "full" || mode === "details") && (
<>
<Controller
control={control}
name="provider.inputs.credential.data.accessKey"
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Key ID"
errorText={error?.message}
isError={Boolean(error)}
>
<FormControl label="Alias" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.inputs.credential.data.secretKey"
name="description"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Access Key"
errorText={error?.message}
isError={Boolean(error)}
>
<Input type="password" autoComplete="new-password" placeholder="" {...field} />
<FormControl label="Description" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
</>
) : (
)}
{(mode === "full" || mode === "credentials") && (
<>
<Controller
control={control}
name="provider.inputs.credential.data.assumeRoleArn"
render={({ field, fieldState: { error } }) => (
name="configuration.inputs.credential.type"
defaultValue={KmsAwsCredentialType.AssumeRole}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="IAM Role ARN For Role Assumption"
label="Authentication Mode"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="" {...field} />
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => {
setValue("configuration.inputs.credential.data.accessKey", "");
setValue("configuration.inputs.credential.data.secretKey", "");
setValue("configuration.inputs.credential.data.assumeRoleArn", "");
setValue("configuration.inputs.credential.data.externalId", "");
onChange(e);
}}
className="w-full"
>
<SelectItem value={KmsAwsCredentialType.AssumeRole}>AWS Assume Role</SelectItem>
<SelectItem value={KmsAwsCredentialType.AccessKey}>Access Key</SelectItem>
</Select>
</FormControl>
)}
/>
{selectedAwsAuthType === KmsAwsCredentialType.AccessKey ? (
<>
<Controller
control={control}
name="configuration.inputs.credential.data.accessKey"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Key ID"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="configuration.inputs.credential.data.secretKey"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secret Access Key"
errorText={error?.message}
isError={Boolean(error)}
>
<Input type="password" autoComplete="new-password" placeholder="" {...field} />
</FormControl>
)}
/>
</>
) : (
<>
<Controller
control={control}
name="configuration.inputs.credential.data.assumeRoleArn"
render={({ field, fieldState: { error } }) => (
<FormControl
label="IAM Role ARN For Role Assumption"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="configuration.inputs.credential.data.externalId"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Assume Role External ID"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
</>
)}
</>
)}
{(mode === "full" || mode === "details") && (
<>
<Controller
control={control}
name="configuration.inputs.awsRegion"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="AWS Region" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full border border-mineshaft-500"
>
{AWS_REGIONS.map((awsRegion) => (
<SelectItem value={awsRegion.slug} key={`kms-aws-region-${awsRegion.slug}`}>
{awsRegion.name} <Badge variant="neutral">{awsRegion.slug}</Badge>
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.inputs.credential.data.externalId"
name="configuration.inputs.kmsKeyId"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Assume Role External ID"
label="AWS KMS Key ID"
errorText={error?.message}
isError={Boolean(error)}
>
@@ -232,38 +321,9 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
/>
</>
)}
<Controller
control={control}
name="provider.inputs.awsRegion"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="AWS Region" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full border border-mineshaft-500"
>
{AWS_REGIONS.map((awsRegion) => (
<SelectItem value={awsRegion.slug} key={`kms-aws-region-${awsRegion.slug}`}>
{awsRegion.name} <Badge variant="neutral">{awsRegion.slug}</Badge>
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.inputs.kmsKeyId"
render={({ field, fieldState: { error } }) => (
<FormControl label="AWS KMS Key ID" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<div className="mt-6 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting}>
Save
{mode === "credentials" ? "Update Credentials" : "Save"}
</Button>
<Button variant="outline_bg" onClick={onCancel}>
Cancel
@@ -1,4 +1,4 @@
import { ContentLoader, Modal, ModalContent } from "@app/components/v2";
import { Modal, ModalContent } from "@app/components/v2";
import { useGetExternalKmsById } from "@app/hooks/api";
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
@@ -8,25 +8,36 @@ import { GcpKmsForm } from "./GcpKmsForm";
type Props = {
isOpen: boolean;
kmsId: string;
provider: ExternalKmsProvider;
onOpenChange: (state: boolean) => void;
};
export const UpdateExternalKmsForm = ({ isOpen, kmsId, onOpenChange }: Props) => {
const { data: externalKms, isPending } = useGetExternalKmsById(kmsId);
export const EditExternalKmsCredentialsModal = ({
isOpen,
kmsId,
provider,
onOpenChange
}: Props) => {
const { data: kms } = useGetExternalKmsById({ kmsId, provider });
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Edit configuration" bodyClassName="overflow-visible">
{isPending && <ContentLoader />}
{externalKms?.external?.provider === ExternalKmsProvider.Aws && (
<ModalContent
title="Edit Credentials"
subTitle="Update the credentials for this KMS."
bodyClassName="overflow-visible"
>
{kms?.externalKms?.provider === ExternalKmsProvider.Aws && (
<AwsKmsForm
kms={externalKms}
kms={kms}
mode="credentials"
onCancel={() => onOpenChange(false)}
onCompleted={() => onOpenChange(false)}
/>
)}
{externalKms?.external?.provider === ExternalKmsProvider.Gcp && (
{kms?.externalKms?.provider === ExternalKmsProvider.Gcp && (
<GcpKmsForm
kms={externalKms}
kms={kms}
mode="credentials"
onCancel={() => onOpenChange(false)}
onCompleted={() => onOpenChange(false)}
/>
@@ -0,0 +1,44 @@
import { Modal, ModalContent } from "@app/components/v2";
import { useGetExternalKmsById } from "@app/hooks/api";
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
import { AwsKmsForm } from "./AwsKmsForm";
import { GcpKmsForm } from "./GcpKmsForm";
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
kmsId: string;
provider: ExternalKmsProvider;
};
export const EditExternalKmsDetailsModal = ({ isOpen, onOpenChange, kmsId, provider }: Props) => {
const { data: kms } = useGetExternalKmsById({ kmsId, provider });
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Edit KMS Details"
subTitle="Update the name and description for this KMS."
bodyClassName="overflow-visible"
>
{kms?.externalKms?.provider === ExternalKmsProvider.Aws && (
<AwsKmsForm
kms={kms}
mode="details"
onCancel={() => onOpenChange(false)}
onCompleted={() => onOpenChange(false)}
/>
)}
{kms?.externalKms?.provider === ExternalKmsProvider.Gcp && (
<GcpKmsForm
kms={kms}
mode="details"
onCancel={() => onOpenChange(false)}
onCompleted={() => onOpenChange(false)}
/>
)}
</ModalContent>
</Modal>
);
};
@@ -22,7 +22,9 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
kms: KmsListEntry;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["editExternalKms", "removeExternalKms", "upgradePlan"]>,
popUpName: keyof UsePopUpState<
["editExternalKmsDetails", "editExternalKmsCredentials", "removeExternalKms", "upgradePlan"]
>,
data?: {
kmsId?: string;
name?: string;
@@ -104,27 +106,52 @@ export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) =
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Kms}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true
});
return;
}
<>
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true
});
return;
}
handlePopUpOpen("editExternalKms", {
kmsId: kms.id
});
}}
>
Edit
</DropdownMenuItem>
handlePopUpOpen("editExternalKmsDetails", {
kmsId: kms.id,
provider: kms.externalKms.provider
});
}}
>
Edit Details
</DropdownMenuItem>
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true
});
return;
}
handlePopUpOpen("editExternalKmsCredentials", {
kmsId: kms.id,
provider: kms.externalKms.provider
});
}}
>
Edit Credentials
</DropdownMenuItem>
</>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Kms}>
@@ -24,6 +24,7 @@ type Props = {
onCompleted: () => void;
onCancel: () => void;
kms?: Kms;
mode?: "full" | "credentials" | "details";
};
const GCP_REGIONS = [
@@ -76,7 +77,7 @@ const formatOptionLabel = ({ value, label }: { value: string; label: string }) =
</div>
);
export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
export const GcpKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) => {
const [isCredentialValid, setIsCredentialValid] = useState<boolean>(false);
const [keys, setKeys] = useState<{ value: string; label: string }[]>([]);
@@ -88,7 +89,7 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
getValues,
resetField,
setValue,
formState: { isSubmitting }
formState: { isSubmitting, isDirty, isValid }
} = useForm<AddExternalKmsGcpFormSchemaType>({
resolver: zodResolver(AddExternalKmsGcpFormSchema),
defaultValues: {
@@ -98,9 +99,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
gcpRegion: kms
? {
label:
GCP_REGIONS.find((r) => r.value === kms.external.providerInput.gcpRegion)?.label ??
GCP_REGIONS.find((r) => r.value === kms.externalKms.configuration.gcpRegion)?.label ??
"",
value: kms.external.providerInput.gcpRegion
value: kms.externalKms.configuration.gcpRegion
}
: undefined,
keyObject: undefined
@@ -109,7 +110,11 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
const { currentOrg } = useOrganization();
const { mutateAsync: addGcpExternalKms } = useAddExternalKms(currentOrg.id);
const { mutateAsync: updateGcpExternalKms } = useUpdateExternalKms(currentOrg.id);
const { mutateAsync: updateGcpExternalKms } = useUpdateExternalKms(
currentOrg.id,
ExternalKmsProvider.Gcp
);
const { mutateAsync: fetchGcpKeys, isPending: isFetchGcpKeysLoading } =
useExternalKmsFetchGcpKeys(currentOrg?.id);
@@ -140,36 +145,62 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
// handles the form submission
const handleGcpKmsFormSubmit = async (data: AddExternalKmsGcpFormSchemaType) => {
const { name, description, gcpRegion: gcpRegionObject, keyObject } = data;
const gcpRegion = gcpRegionObject.value;
if (!keys.find((k) => k.value === keyObject?.value)) {
setError("keyObject", {
message: "Please select a valid key."
});
resetField("keyObject");
return;
}
const { name, description, formType, gcpRegion: gcpRegionObject, keyObject } = data;
try {
if (kms) {
await updateGcpExternalKms({
kmsId: kms.id,
name,
description,
provider: {
type: ExternalKmsProvider.Gcp,
inputs: {
gcpRegion,
keyName: keyObject?.value
}
if (formType === "updateGcpKms") {
const gcpRegion = gcpRegionObject?.value;
if (!gcpRegion) {
setError("gcpRegion", {
message: "Please select a GCP region."
});
return;
}
});
createNotification({
text: "Successfully updated GCP External KMS",
type: "success"
});
} else {
if (keyObject && !keys.find((k) => k.value === keyObject.value)) {
setError("keyObject", {
message: "Please select a valid key."
});
resetField("keyObject");
return;
}
await updateGcpExternalKms({
kmsId: kms.id,
name,
description,
configuration: {
type: ExternalKmsProvider.Gcp,
inputs: {
gcpRegion,
keyName: keyObject?.value ?? kms.externalKms.configuration.keyName
}
}
});
createNotification({
text: "Successfully updated GCP External KMS Details",
type: "success"
});
}
} else if (formType === "newGcpKms") {
const gcpRegion = gcpRegionObject?.value;
if (!gcpRegion) {
setError("gcpRegion", {
message: "Please select a GCP region."
});
return;
}
if (!keys.find((k) => k.value === keyObject?.value)) {
setError("keyObject", {
message: "Please select a valid key."
});
resetField("keyObject");
return;
}
const credentialJson = await getCredentialFileJson();
if (!credentialJson) {
return;
@@ -177,11 +208,11 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
await addGcpExternalKms({
name,
description,
provider: {
configuration: {
type: ExternalKmsProvider.Gcp,
inputs: {
gcpRegion,
keyName: keyObject?.value,
keyName: keyObject?.value ?? "",
credential: credentialJson
}
}
@@ -208,8 +239,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
if (!kms && !credentialJson) {
return;
}
const gcpRegion = getValues("gcpRegion").value;
if (!gcpRegion.length) {
const gcpRegionObject = getValues("gcpRegion");
const gcpRegion = gcpRegionObject?.value;
if (!gcpRegion) {
setError("gcpRegion", {
message: "Please select a GCP region to fetch GCP Keys."
});
@@ -231,7 +263,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
setKeys(returnedKeys);
if (kms) {
const existingKey = returnedKeys.find((k) => k.value === kms.external.providerInput.keyName);
const existingKey = returnedKeys.find(
(k) => k.value === kms.externalKms.configuration.keyName
);
if (existingKey) {
setValue("keyObject", existingKey);
}
@@ -260,96 +294,104 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => {
return (
<form onSubmit={handleSubmit(handleGcpKmsFormSubmit)} autoComplete="off">
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl label="Alias" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="description"
render={({ field, fieldState: { error } }) => (
<FormControl label="Description" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="gcpRegion"
render={({ field, fieldState: { error } }) => (
<FormControl label="GCP Region" errorText={error?.message} isError={Boolean(error)}>
<FilterableSelect
className="w-full"
placeholder="Select a GCP region"
name="gcpRegion"
options={GCP_REGIONS}
value={field.value}
onChange={(e) => {
resetField("keyObject");
field.onChange(e);
fetchGCPKeys();
}}
formatOptionLabel={formatOptionLabel}
{(mode === "full" || mode === "details") && (
<>
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl label="Alias" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="description"
render={({ field, fieldState: { error } }) => (
<FormControl label="Description" errorText={error?.message} isError={Boolean(error)}>
<Input placeholder="" {...field} />
</FormControl>
)}
/>
<Controller
control={control}
name="gcpRegion"
render={({ field, fieldState: { error } }) => (
<FormControl label="GCP Region" errorText={error?.message} isError={Boolean(error)}>
<FilterableSelect
className="w-full"
placeholder="Select a GCP region"
name="gcpRegion"
options={GCP_REGIONS}
value={field.value}
onChange={(e) => {
resetField("keyObject");
field.onChange(e);
fetchGCPKeys();
}}
formatOptionLabel={formatOptionLabel}
/>
</FormControl>
)}
/>
{!kms && (
<Controller
control={control}
name="credentialFile"
render={({ field: { value, onChange, ref, ...rest }, fieldState: { error } }) => (
<FormControl
label="Service Account Credential JSON"
errorText={error?.message}
isError={Boolean(error)}
>
<Input
{...rest}
ref={ref}
type="file"
accept=".json"
placeholder=""
value={value?.filename}
onChange={(e) => {
onChange(e.target.files);
fetchGCPKeys();
}}
/>
</FormControl>
)}
/>
</FormControl>
)}
/>
{!kms && (
<Controller
control={control}
name="credentialFile"
render={({ field: { value, onChange, ref, ...rest }, fieldState: { error } }) => (
<FormControl
label="Service Account Credential JSON"
errorText={error?.message}
isError={Boolean(error)}
>
<Input
{...rest}
ref={ref}
type="file"
accept=".json"
placeholder=""
value={value?.filename}
onChange={(e) => {
onChange(e.target.files);
fetchGCPKeys();
}}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="keyObject"
render={({ field, fieldState: { error } }) => (
<FormControl label="GCP Key Name" errorText={error?.message} isError={Boolean(error)}>
<FilterableSelect
className="w-full"
placeholder={getPlaceholderText()}
isDisabled={!isCredentialValid || !keys.length}
name="key"
options={keys}
value={field.value}
onChange={field.onChange}
/>
</FormControl>
)}
/>
</>
)}
<Controller
control={control}
name="keyObject"
render={({ field, fieldState: { error } }) => (
<FormControl label="GCP Key Name" errorText={error?.message} isError={Boolean(error)}>
<FilterableSelect
className="w-full"
placeholder={getPlaceholderText()}
isDisabled={!isCredentialValid || !keys.length}
name="key"
options={keys}
value={field.value}
onChange={field.onChange}
/>
</FormControl>
)}
/>
{kms && (
{kms && mode === "credentials" && (
<span className="text-xs text-mineshaft-300">
To change your GCP credentials, create a new external KMS and assign it to project you
want to use it with.
</span>
)}
<div className="mt-6 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting}>
<Button
type="submit"
isLoading={isSubmitting}
isDisabled={!isDirty || !isValid || mode === "credentials"}
>
Save
</Button>
<Button variant="outline_bg" onClick={onCancel}>
@@ -25,10 +25,12 @@ import {
import { withPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks";
import { useGetExternalKmsList, useRemoveExternalKms } from "@app/hooks/api";
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
import { AddExternalKmsForm } from "./AddExternalKmsForm";
import { EditExternalKmsCredentialsModal } from "./EditExternalKmsCredentialsModal";
import { EditExternalKmsDetailsModal } from "./EditExternalKmsDetailsModal";
import { ExternalKmsItem } from "./ExternalKmsItem";
import { UpdateExternalKmsForm } from "./UpdateExternalKmsForm";
export const OrgEncryptionTab = withPermission(
() => {
@@ -38,7 +40,8 @@ export const OrgEncryptionTab = withPermission(
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"upgradePlan",
"addExternalKms",
"editExternalKms",
"editExternalKmsDetails",
"editExternalKmsCredentials",
"removeExternalKms"
] as const);
const { data: externalKmsList, isPending: isExternalKmsListLoading } =
@@ -47,11 +50,12 @@ export const OrgEncryptionTab = withPermission(
const { mutateAsync: removeExternalKms } = useRemoveExternalKms(currentOrg.id);
const handleRemoveExternalKms = async () => {
const { kmsId } = popUp?.removeExternalKms?.data as {
const { kmsId, provider } = popUp?.removeExternalKms?.data as {
kmsId: string;
provider: ExternalKmsProvider;
};
await removeExternalKms(kmsId);
await removeExternalKms({ kmsId, provider });
createNotification({
text: "Successfully deleted external KMS",
@@ -128,10 +132,21 @@ export const OrgEncryptionTab = withPermission(
isOpen={popUp.addExternalKms.isOpen}
onToggle={(state) => handlePopUpToggle("addExternalKms", state)}
/>
<UpdateExternalKmsForm
isOpen={popUp.editExternalKms.isOpen}
kmsId={(popUp.editExternalKms.data as { kmsId: string })?.kmsId}
onOpenChange={(state) => handlePopUpToggle("editExternalKms", state)}
<EditExternalKmsDetailsModal
isOpen={popUp.editExternalKmsDetails.isOpen}
kmsId={(popUp.editExternalKmsDetails.data as { kmsId: string })?.kmsId}
provider={
(popUp.editExternalKmsDetails.data as { provider: ExternalKmsProvider })?.provider
}
onOpenChange={(state) => handlePopUpToggle("editExternalKmsDetails", state)}
/>
<EditExternalKmsCredentialsModal
isOpen={popUp.editExternalKmsCredentials.isOpen}
kmsId={(popUp.editExternalKmsCredentials.data as { kmsId: string })?.kmsId}
provider={
(popUp.editExternalKmsCredentials.data as { provider: ExternalKmsProvider })?.provider
}
onOpenChange={(state) => handlePopUpToggle("editExternalKmsCredentials", state)}
/>
<DeleteActionModal
isOpen={popUp.removeExternalKms.isOpen}