From f14b03b6acf30b1ab74bab2f1ad032766c9dab91 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Fri, 5 Dec 2025 02:49:20 +0530 Subject: [PATCH] fix: kms endpoints to rerutn credentialsHash --- .../external-kms-endpoints.ts | 80 +++++++----- .../external-kms/external-kms-service.ts | 75 +++++++++-- .../services/external-kms/providers/model.ts | 45 +++++-- frontend/src/hooks/api/kms/mutations.tsx | 15 ++- frontend/src/hooks/api/kms/queries.tsx | 6 +- frontend/src/hooks/api/kms/types.ts | 12 +- .../OrgEncryptionTab/AwsKmsForm.tsx | 117 ++++++++++-------- ...sx => EditExternalKmsCredentialsModal.tsx} | 19 ++- .../EditExternalKmsDetailsModal.tsx | 108 ++++++++++++++++ .../OrgEncryptionTab/ExternalKmsItem.tsx | 70 +++++++---- .../OrgEncryptionTab/GcpKmsForm.tsx | 102 +++++++++------ .../OrgEncryptionTab/OrgEncryptionTab.tsx | 26 ++-- 12 files changed, 481 insertions(+), 194 deletions(-) rename frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/{UpdateExternalKmsForm.tsx => EditExternalKmsCredentialsModal.tsx} (67%) create mode 100644 frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/EditExternalKmsDetailsModal.tsx diff --git a/backend/src/ee/routes/v1/external-kms-routers/external-kms-endpoints.ts b/backend/src/ee/routes/v1/external-kms-routers/external-kms-endpoints.ts index a8a418c0a..47b4947f2 100644 --- a/backend/src/ee/routes/v1/external-kms-routers/external-kms-endpoints.ts +++ b/backend/src/ee/routes/v1/external-kms-routers/external-kms-endpoints.ts @@ -3,35 +3,27 @@ import { z } from "zod"; import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { - ExternalKmsAwsSchema, - ExternalKmsGcpSchema, KmsProviders, + SanitizedExternalKmsAwsSchema, + SanitizedExternalKmsGcpSchema, TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "@app/ee/services/external-kms/providers/model"; +import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError } from "@app/lib/errors"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; const sanitizedExternalSchema = KmsKeysSchema.extend({ - external: ExternalKmsSchema.pick({ - id: true, - status: true, - statusDetails: true, - provider: true - }) -}); - -const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({ - external: ExternalKmsSchema.pick({ + externalKms: ExternalKmsSchema.pick({ id: true, status: true, statusDetails: true, provider: true }).extend({ - // for GCP, we don't return the credential object as it is sensitive data that should not be exposed - providerInput: z.union([ExternalKmsAwsSchema, ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })]) + configuration: z.union([SanitizedExternalKmsAwsSchema, SanitizedExternalKmsGcpSchema]), + credentialsHash: z.string().optional() }) }); @@ -59,9 +51,7 @@ export const registerExternalKmsEndpoints = < id: z.string().trim().min(1) }), response: { - 200: z.object({ - externalKms: sanitizedExternalSchemaForGetById - }) + 200: sanitizedExternalSchema } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), @@ -93,7 +83,16 @@ export const registerExternalKmsEndpoints = < } }); - return { externalKms }; + const { + external: { providerInput: configuration, ...externalKmsData }, + ...rest + } = externalKms; + + const credentialsHash = crypto.nativeCrypto + .createHash("sha256") + .update(externalKmsData.encryptedProviderInputs) + .digest("hex"); + return { ...rest, externalKms: { ...externalKmsData, configuration, credentialsHash } }; } }); @@ -110,9 +109,7 @@ export const registerExternalKmsEndpoints = < configuration: createSchema }), response: { - 200: z.object({ - externalKms: sanitizedExternalSchema - }) + 200: sanitizedExternalSchema } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), @@ -152,7 +149,15 @@ export const registerExternalKmsEndpoints = < } }); - return { externalKms }; + const { + external: { providerInput: externalKmsConfiguration, ...externalKmsData }, + ...rest + } = externalKms; + const credentialsHash = crypto.nativeCrypto + .createHash("sha256") + .update(externalKmsData.encryptedProviderInputs) + .digest("hex"); + return { ...rest, externalKms: { ...externalKmsData, configuration: externalKmsConfiguration, credentialsHash } }; } }); @@ -169,12 +174,10 @@ export const registerExternalKmsEndpoints = < body: z.object({ name: z.string().min(1).trim().toLowerCase().optional(), description: z.string().trim().optional(), - configuration: updateSchema + configuration: updateSchema.optional() }), response: { - 200: z.object({ - externalKms: sanitizedExternalSchema - }) + 200: sanitizedExternalSchema } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), @@ -215,7 +218,15 @@ export const registerExternalKmsEndpoints = < } }); - return { externalKms }; + const { + external: { providerInput: externalKmsConfiguration, ...externalKmsData }, + ...rest + } = externalKms; + const credentialsHash = crypto.nativeCrypto + .createHash("sha256") + .update(externalKmsData.encryptedProviderInputs) + .digest("hex"); + return { ...rest, externalKms: { ...externalKmsData, configuration: externalKmsConfiguration, credentialsHash } }; } }); @@ -230,9 +241,7 @@ export const registerExternalKmsEndpoints = < id: z.string().trim().min(1) }), response: { - 200: z.object({ - externalKms: sanitizedExternalSchema - }) + 200: sanitizedExternalSchema } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), @@ -264,7 +273,16 @@ export const registerExternalKmsEndpoints = < } }); - return { externalKms }; + const { + external: { providerInput: configuration, ...externalKmsData }, + ...rest + } = externalKms; + const credentialsHash = crypto.nativeCrypto + .createHash("sha256") + .update(externalKmsData.encryptedProviderInputs) + .digest("hex"); + + return { ...rest, externalKms: { ...externalKmsData, configuration, credentialsHash } }; } }); }; diff --git a/backend/src/ee/services/external-kms/external-kms-service.ts b/backend/src/ee/services/external-kms/external-kms-service.ts index 828201ceb..eb595ee02 100644 --- a/backend/src/ee/services/external-kms/external-kms-service.ts +++ b/backend/src/ee/services/external-kms/external-kms-service.ts @@ -24,7 +24,13 @@ import { } from "./external-kms-types"; import { AwsKmsProviderFactory } from "./providers/aws-kms"; import { GcpKmsProviderFactory } from "./providers/gcp-kms"; -import { ExternalKmsAwsSchema, ExternalKmsGcpSchema, KmsProviders, TExternalKmsGcpSchema } from "./providers/model"; +import { + ExternalKmsAwsSchema, + ExternalKmsGcpSchema, + KmsProviders, + TExternalKmsAwsSchema, + TExternalKmsGcpSchema +} from "./providers/model"; type TExternalKmsServiceFactoryDep = { externalKmsDAL: TExternalKmsDALFactory; @@ -72,6 +78,7 @@ export const externalKmsServiceFactory = ({ const kmsName = name ? slugify(name) : slugify(alphaNumericNanoId(8).toLowerCase()); let sanitizedProviderInput = ""; + let sanitizedProviderInputObject: TExternalKmsAwsSchema | TExternalKmsGcpSchema; switch (provider.type) { case KmsProviders.Aws: { @@ -88,6 +95,7 @@ export const externalKmsServiceFactory = ({ try { // if missing kms key this generate a new kms key id and returns new provider input const newProviderInput = await externalKms.generateInputKmsKey(); + sanitizedProviderInputObject = newProviderInput; sanitizedProviderInput = JSON.stringify(newProviderInput); await externalKms.validateConnection(); @@ -109,6 +117,7 @@ export const externalKmsServiceFactory = ({ const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs }); try { await externalKms.validateConnection(); + sanitizedProviderInputObject = provider.inputs; sanitizedProviderInput = JSON.stringify(provider.inputs); } catch (error) { if (error instanceof BadRequestError) { @@ -155,7 +164,10 @@ export const externalKmsServiceFactory = ({ }, tx ); - return { ...kms, external: externalKmsCfg }; + return { + ...kms, + external: { ...externalKmsCfg, providerInput: sanitizedProviderInputObject } + }; }); return externalKms; @@ -195,6 +207,7 @@ export const externalKmsServiceFactory = ({ if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` }); let sanitizedProviderInput = ""; + let sanitizedProviderInputObject: TExternalKmsAwsSchema | TExternalKmsGcpSchema; const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, @@ -215,6 +228,7 @@ export const externalKmsServiceFactory = ({ const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput }); try { await externalKms.validateConnection(); + sanitizedProviderInputObject = updatedProviderInput; sanitizedProviderInput = JSON.stringify(updatedProviderInput); } catch (error) { if (error instanceof BadRequestError) { @@ -238,6 +252,7 @@ export const externalKmsServiceFactory = ({ const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput }); try { await externalKms.validateConnection(); + sanitizedProviderInputObject = updatedProviderInput; sanitizedProviderInput = JSON.stringify(updatedProviderInput); } catch (error) { if (error instanceof BadRequestError) { @@ -266,14 +281,17 @@ export const externalKmsServiceFactory = ({ } const externalKms = await externalKmsDAL.transaction(async (tx) => { - const kms = await kmsDAL.updateById( - kmsDoc.id, - { - description, - name: kmsName - }, - tx - ); + let kms = kmsDoc; + if (kmsName || description) { + kms = await kmsDAL.updateById( + kmsDoc.id, + { + description, + name: kmsName + }, + tx + ); + } if (encryptedProviderInputs) { const externalKmsCfg = await externalKmsDAL.updateById( externalKmsDoc.id, @@ -282,9 +300,9 @@ export const externalKmsServiceFactory = ({ }, tx ); - return { ...kms, external: externalKmsCfg }; + return { ...kms, external: { ...externalKmsCfg, providerInput: sanitizedProviderInputObject } }; } - return { ...kms, external: externalKmsDoc }; + return { ...kms, external: { ...externalKmsDoc, providerInput: sanitizedProviderInputObject } }; }); return externalKms; @@ -305,9 +323,40 @@ export const externalKmsServiceFactory = ({ const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` }); + let decryptedProviderInputObject: TExternalKmsAwsSchema | TExternalKmsGcpSchema; + + const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: actorOrgId + }); + + const decryptedProviderInputBlob = orgDataKeyDecryptor({ + cipherTextBlob: externalKmsDoc.encryptedProviderInputs + }); + + switch (externalKmsDoc.provider) { + case KmsProviders.Aws: { + const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync( + JSON.parse(decryptedProviderInputBlob.toString()) + ); + decryptedProviderInputObject = decryptedProviderInput; + break; + } + case KmsProviders.Gcp: { + const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync( + JSON.parse(decryptedProviderInputBlob.toString()) + ); + + decryptedProviderInputObject = decryptedProviderInput; + break; + } + default: + break; + } + const externalKms = await externalKmsDAL.transaction(async (tx) => { const kms = await kmsDAL.deleteById(kmsDoc.id, tx); - return { ...kms, external: externalKmsDoc }; + return { ...kms, external: { ...externalKmsDoc, providerInput: decryptedProviderInputObject } }; }); return externalKms; diff --git a/backend/src/ee/services/external-kms/providers/model.ts b/backend/src/ee/services/external-kms/providers/model.ts index 6cb78a34e..13d374b26 100644 --- a/backend/src/ee/services/external-kms/providers/model.ts +++ b/backend/src/ee/services/external-kms/providers/model.ts @@ -19,27 +19,31 @@ export enum KmsGcpKeyFetchAuthType { Kms = "kmsId" } +const AwsConnectionAssumeRoleCredentialsSchema = z.object({ + assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"), + externalId: z + .string() + .trim() + .min(1) + .optional() + .describe("AWS assume role external id for furthur security in authentication") +}); + +const AwsConnectionAccessTokenCredentialsSchema = z.object({ + accessKey: z.string().trim().min(1).describe("AWS user account access key"), + secretKey: z.string().trim().min(1).describe("AWS user account secret key") +}); + export const ExternalKmsAwsSchema = z.object({ credential: z .discriminatedUnion("type", [ z.object({ type: z.literal(KmsAwsCredentialType.AccessKey), - data: z.object({ - accessKey: z.string().trim().min(1).describe("AWS user account access key"), - secretKey: z.string().trim().min(1).describe("AWS user account secret key") - }) + data: AwsConnectionAccessTokenCredentialsSchema }), z.object({ type: z.literal(KmsAwsCredentialType.AssumeRole), - data: z.object({ - assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"), - externalId: z - .string() - .trim() - .min(1) - .optional() - .describe("AWS assume role external id for furthur security in authentication") - }) + data: AwsConnectionAssumeRoleCredentialsSchema }) ]) .describe("AWS credential information to connect"), @@ -52,6 +56,19 @@ export const ExternalKmsAwsSchema = z.object({ }); export type TExternalKmsAwsSchema = z.infer; +export const SanitizedExternalKmsAwsSchema = ExternalKmsAwsSchema.extend({ + credential: z.discriminatedUnion("type", [ + z.object({ + type: z.literal(KmsAwsCredentialType.AccessKey), + data: AwsConnectionAccessTokenCredentialsSchema.pick({ accessKey: true }) + }), + z.object({ + type: z.literal(KmsAwsCredentialType.AssumeRole), + data: AwsConnectionAssumeRoleCredentialsSchema.pick({}) + }) + ]) +}); + export const ExternalKmsGcpCredentialSchema = z.object({ type: z.literal(KmsGcpCredentialType.ServiceAccount), project_id: z.string().min(1), @@ -75,6 +92,8 @@ export const ExternalKmsGcpSchema = z.object({ }); export type TExternalKmsGcpSchema = z.infer; +export const SanitizedExternalKmsGcpSchema = ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true }); + const ExternalKmsGcpClientSchema = ExternalKmsGcpSchema.pick({ gcpRegion: true }).extend({ credential: ExternalKmsGcpCredentialSchema }); diff --git a/frontend/src/hooks/api/kms/mutations.tsx b/frontend/src/hooks/api/kms/mutations.tsx index fada7ea9f..534e9fd5d 100644 --- a/frontend/src/hooks/api/kms/mutations.tsx +++ b/frontend/src/hooks/api/kms/mutations.tsx @@ -15,12 +15,12 @@ import { export const useAddExternalKms = (orgId: string) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async ({ name, description, provider }: AddExternalKmsType) => { - const providerPath = provider.type === ExternalKmsProvider.Aws ? "aws" : "gcp"; + 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, - configuration: provider.inputs + configuration: configuration.inputs }); return data; @@ -31,22 +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 providerPath = provider.type === ExternalKmsProvider.Aws ? "aws" : "gcp"; - const { data } = await apiRequest.patch(`/api/v1/external-kms/${providerPath}/${kmsId}`, { + const { data } = await apiRequest.patch(`/api/v1/external-kms/${provider}/${kmsId}`, { name, description, - configuration: provider.inputs + configuration: configuration?.inputs }); return data; diff --git a/frontend/src/hooks/api/kms/queries.tsx b/frontend/src/hooks/api/kms/queries.tsx index 854e6199a..4342c1881 100644 --- a/frontend/src/hooks/api/kms/queries.tsx +++ b/frontend/src/hooks/api/kms/queries.tsx @@ -34,10 +34,8 @@ export const useGetExternalKmsById = ({ queryKey: kmsKeys.getExternalKmsById(kmsId), enabled: Boolean(kmsId), queryFn: async () => { - const { - data: { externalKms } - } = await apiRequest.get<{ externalKms: Kms }>(`/api/v1/external-kms/${provider}/${kmsId}`); - return externalKms; + const { data } = await apiRequest.get(`/api/v1/external-kms/${provider}/${kmsId}`); + return data; } }); }; diff --git a/frontend/src/hooks/api/kms/types.ts b/frontend/src/hooks/api/kms/types.ts index 73b821b1a..a5b514bfe 100644 --- a/frontend/src/hooks/api/kms/types.ts +++ b/frontend/src/hooks/api/kms/types.ts @@ -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; + configuration: Record; + credentialsHash?: string; }; }; @@ -123,7 +124,7 @@ 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; @@ -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; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx index 251cebcbe..980e098cd 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx @@ -50,9 +50,10 @@ type Props = { onCompleted: () => void; onCancel: () => void; kms?: Kms; + mode?: "full" | "credentials"; }; -export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => { +export const AwsKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) => { const { control, handleSubmit, @@ -64,20 +65,20 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => { defaultValues: { name: kms?.name, description: kms?.description ?? "", - provider: { + configuration: { type: ExternalKmsProvider.Aws, inputs: { credential: { - type: kms?.external?.providerInput?.credential?.type, + type: kms?.externalKms?.configuration?.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 + 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 } }, - awsRegion: kms?.external?.providerInput?.awsRegion, - kmsKeyId: kms?.external?.providerInput?.kmsKeyId + awsRegion: kms?.externalKms?.configuration?.awsRegion, + kmsKeyId: kms?.externalKms?.configuration?.kmsKeyId } } } @@ -85,30 +86,42 @@ 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 (mode === "credentials") { + await updateAwsExternalKms({ + kmsId: kms.id, + configuration + }); + } else { + await updateAwsExternalKms({ + kmsId: kms.id, + name, + description + }); + } createNotification({ - text: "Successfully updated AWS External KMS", + text: + mode === "credentials" + ? "Successfully updated AWS External KMS credentials" + : "Successfully updated AWS External KMS", type: "success" }); } else { await addAwsExternalKms({ name, description, - provider + configuration }); createNotification({ @@ -125,27 +138,31 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => { return (
+ {mode === "full" && ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + )} ( - - - - )} - /> - ( - - - - )} - /> - ( { 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", ""); + 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); }} @@ -177,7 +194,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => { <> ( { /> ( { <> ( { /> ( { )} ( @@ -263,7 +280,7 @@ export const AwsKmsForm = ({ onCompleted, onCancel, kms }: Props) => { />
+ + + +
+ + + ); +}; + +export const EditExternalKmsDetailsModal = ({ isOpen, onOpenChange, kmsId, provider }: Props) => { + const { data: kms, isPending } = useGetExternalKmsById({ kmsId, provider }); + + return ( + + + {isPending && } + {kms && onOpenChange(false)} />} + + + ); +}; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx index 9e620d949..734a8e0ab 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx @@ -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,28 +106,52 @@ export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) = {(isAllowed) => ( - { - e.stopPropagation(); - if (subscription && !subscription?.externalKms) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true - }); - return; - } + <> + { + e.stopPropagation(); + if (subscription && !subscription?.externalKms) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); + return; + } - handlePopUpOpen("editExternalKms", { - kmsId: kms.id, - provider: kms.externalKms.provider - }); - }} - > - Edit - + handlePopUpOpen("editExternalKmsDetails", { + kmsId: kms.id, + provider: kms.externalKms.provider + }); + }} + > + Edit Details + + { + e.stopPropagation(); + if (subscription && !subscription?.externalKms) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true + }); + return; + } + + handlePopUpOpen("editExternalKmsCredentials", { + kmsId: kms.id, + provider: kms.externalKms.provider + }); + }} + > + Edit Credentials + + )} diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx index 610ac823e..d120bb658 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx @@ -24,6 +24,7 @@ type Props = { onCompleted: () => void; onCancel: () => void; kms?: Kms; + mode?: "full" | "credentials"; }; const GCP_REGIONS = [ @@ -76,7 +77,7 @@ const formatOptionLabel = ({ value, label }: { value: string; label: string }) = ); -export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => { +export const GcpKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) => { const [isCredentialValid, setIsCredentialValid] = useState(false); const [keys, setKeys] = useState<{ value: string; label: string }[]>([]); @@ -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,10 @@ 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); @@ -152,21 +156,39 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => { try { if (kms) { - await updateGcpExternalKms({ - kmsId: kms.id, - name, - description, - provider: { - type: ExternalKmsProvider.Gcp, - inputs: { - gcpRegion, - keyName: keyObject?.value + if (mode === "credentials") { + await updateGcpExternalKms({ + kmsId: kms.id, + name: kms.name, + description: kms.description, + configuration: { + type: ExternalKmsProvider.Gcp, + inputs: { + gcpRegion, + keyName: keyObject?.value + } } - } - }); + }); + } else { + await updateGcpExternalKms({ + kmsId: kms.id, + name, + description, + configuration: { + type: ExternalKmsProvider.Gcp, + inputs: { + gcpRegion, + keyName: keyObject?.value + } + } + }); + } createNotification({ - text: "Successfully updated GCP External KMS", + text: + mode === "credentials" + ? "Successfully updated GCP External KMS configuration" + : "Successfully updated GCP External KMS", type: "success" }); } else { @@ -177,7 +199,7 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => { await addGcpExternalKms({ name, description, - provider: { + configuration: { type: ExternalKmsProvider.Gcp, inputs: { gcpRegion, @@ -231,7 +253,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,24 +284,28 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms }: Props) => { return (
- ( - - - - )} - /> - ( - - - - )} - /> + {mode === "full" && ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + )} { )}