From c176268d06dd31870f72613f04dfd477da7c47bd Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Tue, 2 Dec 2025 12:25:34 +0530 Subject: [PATCH 1/9] chore: external kms api refactor --- .../src/ee/routes/v1/external-kms-router.ts | 70 +---- .../v1/external-kms-routers/aws-kms-router.ts | 12 + .../external-kms-endpoints.ts | 273 ++++++++++++++++++ .../v1/external-kms-routers/gcp-kms-router.ts | 88 ++++++ .../routes/v1/external-kms-routers/index.ts | 9 + backend/src/ee/routes/v1/index.ts | 18 +- 6 files changed, 398 insertions(+), 72 deletions(-) create mode 100644 backend/src/ee/routes/v1/external-kms-routers/aws-kms-router.ts create mode 100644 backend/src/ee/routes/v1/external-kms-routers/external-kms-endpoints.ts create mode 100644 backend/src/ee/routes/v1/external-kms-routers/gcp-kms-router.ts create mode 100644 backend/src/ee/routes/v1/external-kms-routers/index.ts diff --git a/backend/src/ee/routes/v1/external-kms-router.ts b/backend/src/ee/routes/v1/external-kms-router.ts index a48e28e3d..b46b525fe 100644 --- a/backend/src/ee/routes/v1/external-kms-router.ts +++ b/backend/src/ee/routes/v1/external-kms-router.ts @@ -4,15 +4,10 @@ import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ExternalKmsAwsSchema, - ExternalKmsGcpCredentialSchema, ExternalKmsGcpSchema, ExternalKmsInputSchema, - ExternalKmsInputUpdateSchema, - KmsGcpKeyFetchAuthType, - KmsProviders, - TExternalKmsGcpCredentialSchema + ExternalKmsInputUpdateSchema } from "@app/ee/services/external-kms/providers/model"; -import { NotFoundError } 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"; @@ -293,67 +288,4 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => { return { externalKms }; } }); - - server.route({ - method: "POST", - url: "/gcp/keys", - config: { - rateLimit: writeLimit - }, - schema: { - body: z.discriminatedUnion("authMethod", [ - z.object({ - authMethod: z.literal(KmsGcpKeyFetchAuthType.Credential), - region: z.string().trim().min(1), - credential: ExternalKmsGcpCredentialSchema - }), - z.object({ - authMethod: z.literal(KmsGcpKeyFetchAuthType.Kms), - region: z.string().trim().min(1), - kmsId: z.string().trim().min(1) - }) - ]), - response: { - 200: z.object({ - keys: z.string().array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), - handler: async (req) => { - const { region, authMethod } = req.body; - let credentialJson: TExternalKmsGcpCredentialSchema | undefined; - - if (authMethod === KmsGcpKeyFetchAuthType.Credential) { - credentialJson = req.body.credential; - } else if (authMethod === KmsGcpKeyFetchAuthType.Kms) { - const externalKms = await server.services.externalKms.findById({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - id: req.body.kmsId - }); - - if (!externalKms || externalKms.external.provider !== KmsProviders.Gcp) { - throw new NotFoundError({ message: "KMS not found or not of type GCP" }); - } - - credentialJson = externalKms.external.providerInput.credential as TExternalKmsGcpCredentialSchema; - } - - if (!credentialJson) { - throw new NotFoundError({ - message: "Something went wrong while fetching the GCP credential, please check inputs and try again" - }); - } - - const results = await server.services.externalKms.fetchGcpKeys({ - credential: credentialJson, - gcpRegion: region - }); - - return results; - } - }); }; diff --git a/backend/src/ee/routes/v1/external-kms-routers/aws-kms-router.ts b/backend/src/ee/routes/v1/external-kms-routers/aws-kms-router.ts new file mode 100644 index 000000000..518b7947e --- /dev/null +++ b/backend/src/ee/routes/v1/external-kms-routers/aws-kms-router.ts @@ -0,0 +1,12 @@ +import { ExternalKmsAwsSchema, KmsProviders } from "@app/ee/services/external-kms/providers/model"; + +import { registerExternalKmsEndpoints } from "./external-kms-endpoints"; + +export const registerAwsKmsRouter = async (server: FastifyZodProvider) => { + registerExternalKmsEndpoints({ + server, + provider: KmsProviders.Aws, + createSchema: ExternalKmsAwsSchema, + updateSchema: ExternalKmsAwsSchema.partial() + }); +}; 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 new file mode 100644 index 000000000..049523324 --- /dev/null +++ b/backend/src/ee/routes/v1/external-kms-routers/external-kms-endpoints.ts @@ -0,0 +1,273 @@ +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, + TExternalKmsInputSchema, + TExternalKmsInputUpdateSchema +} from "@app/ee/services/external-kms/providers/model"; +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({ + 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 })]) + }) +}); + +export const registerExternalKmsEndpoints = < + T extends { type: KmsProviders; inputs: TExternalKmsInputSchema["inputs"] } +>({ + server, + provider, + createSchema, + updateSchema +}: { + server: FastifyZodProvider; + provider: T["type"]; + createSchema: z.ZodType; + updateSchema: z.ZodType>; +}) => { + server.route({ + method: "GET", + url: "/:id", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + id: z.string().trim().min(1) + }), + response: { + 200: z.object({ + externalKms: sanitizedExternalSchemaForGetById + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const externalKms = await server.services.externalKms.findById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.id + }); + + // Validate that the KMS is of the expected provider type + if (externalKms.external.provider !== provider) { + throw new Error(`KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}`); + } + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.GET_KMS, + metadata: { + kmsId: externalKms.id, + name: externalKms.name + } + } + }); + + return { externalKms }; + } + }); + + server.route({ + method: "POST", + url: "/", + config: { + rateLimit: writeLimit + }, + schema: { + body: z.object({ + name: z.string().min(1).trim().toLowerCase(), + description: z.string().trim().optional(), + provider: createSchema + }), + response: { + 200: z.object({ + externalKms: sanitizedExternalSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { + name, + description, + provider: providerInputs + } = req.body as { + name: string; + description?: string; + provider: T["inputs"]; + }; + + const providerInput = { + type: provider, + inputs: providerInputs + } as TExternalKmsInputSchema; + + const externalKms = await server.services.externalKms.create({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + name, + provider: providerInput, + description + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.CREATE_KMS, + metadata: { + kmsId: externalKms.id, + provider, + name, + description + } + } + }); + + return { externalKms }; + } + }); + + server.route({ + method: "PATCH", + url: "/:id", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + id: z.string().trim().min(1) + }), + body: z.object({ + name: z.string().min(1).trim().toLowerCase().optional(), + description: z.string().trim().optional(), + provider: updateSchema + }), + response: { + 200: z.object({ + externalKms: sanitizedExternalSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { + name, + description, + provider: providerInputs + } = req.body as { + name?: string; + description?: string; + provider: Partial; + }; + + const providerInput = { + type: provider, + inputs: providerInputs + } as TExternalKmsInputUpdateSchema; + + const externalKms = await server.services.externalKms.updateById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + name, + provider: providerInput, + description, + id: req.params.id + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.UPDATE_KMS, + metadata: { + kmsId: externalKms.id, + provider, + name, + description + } + } + }); + + return { externalKms }; + } + }); + + server.route({ + method: "DELETE", + url: "/:id", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + id: z.string().trim().min(1) + }), + response: { + 200: z.object({ + externalKms: sanitizedExternalSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const externalKms = await server.services.externalKms.deleteById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.params.id + }); + + // Validate that the KMS is of the expected provider type + if (externalKms.external.provider !== provider) { + throw new Error(`KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}`); + } + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.DELETE_KMS, + metadata: { + kmsId: externalKms.id, + name: externalKms.name + } + } + }); + + return { externalKms }; + } + }); +}; diff --git a/backend/src/ee/routes/v1/external-kms-routers/gcp-kms-router.ts b/backend/src/ee/routes/v1/external-kms-routers/gcp-kms-router.ts new file mode 100644 index 000000000..97b600c10 --- /dev/null +++ b/backend/src/ee/routes/v1/external-kms-routers/gcp-kms-router.ts @@ -0,0 +1,88 @@ +import { z } from "zod"; + +import { + ExternalKmsGcpCredentialSchema, + ExternalKmsGcpSchema, + KmsGcpKeyFetchAuthType, + KmsProviders, + TExternalKmsGcpCredentialSchema +} from "@app/ee/services/external-kms/providers/model"; +import { NotFoundError } from "@app/lib/errors"; +import { writeLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +import { registerExternalKmsEndpoints } from "./external-kms-endpoints"; + +export const registerGcpKmsRouter = async (server: FastifyZodProvider) => { + registerExternalKmsEndpoints({ + server, + provider: KmsProviders.Gcp, + createSchema: ExternalKmsGcpSchema, + updateSchema: ExternalKmsGcpSchema.partial() + }); + + server.route({ + method: "POST", + url: "/keys", + config: { + rateLimit: writeLimit + }, + schema: { + body: z.discriminatedUnion("authMethod", [ + z.object({ + authMethod: z.literal(KmsGcpKeyFetchAuthType.Credential), + region: z.string().trim().min(1), + credential: ExternalKmsGcpCredentialSchema + }), + z.object({ + authMethod: z.literal(KmsGcpKeyFetchAuthType.Kms), + region: z.string().trim().min(1), + kmsId: z.string().trim().min(1) + }) + ]), + response: { + 200: z.object({ + keys: z.string().array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { region, authMethod } = req.body; + let credentialJson: TExternalKmsGcpCredentialSchema | undefined; + + if (authMethod === KmsGcpKeyFetchAuthType.Credential && "credential" in req.body) { + credentialJson = req.body.credential; + } else if (authMethod === KmsGcpKeyFetchAuthType.Kms && "kmsId" in req.body) { + const externalKms = await server.services.externalKms.findById({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + id: req.body.kmsId + }); + + if (!externalKms || externalKms.external.provider !== KmsProviders.Gcp) { + throw new NotFoundError({ message: "KMS not found or not of type GCP" }); + } + + const providerInput = externalKms.external.providerInput as { credential: TExternalKmsGcpCredentialSchema }; + credentialJson = providerInput.credential; + } + + if (!credentialJson) { + throw new NotFoundError({ + message: "Something went wrong while fetching the GCP credential, please check inputs and try again" + }); + } + + const results = await server.services.externalKms.fetchGcpKeys({ + credential: credentialJson, + gcpRegion: region + }); + + return results; + } + }); +}; diff --git a/backend/src/ee/routes/v1/external-kms-routers/index.ts b/backend/src/ee/routes/v1/external-kms-routers/index.ts new file mode 100644 index 000000000..da70b0f59 --- /dev/null +++ b/backend/src/ee/routes/v1/external-kms-routers/index.ts @@ -0,0 +1,9 @@ +import { KmsProviders } from "@app/ee/services/external-kms/providers/model"; + +import { registerAwsKmsRouter } from "./aws-kms-router"; +import { registerGcpKmsRouter } from "./gcp-kms-router"; + +export const EXTERNAL_KMS_REGISTER_ROUTER_MAP: Record Promise> = { + [KmsProviders.Aws]: registerAwsKmsRouter, + [KmsProviders.Gcp]: registerGcpKmsRouter +}; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index 367c2833c..bc3a4f602 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -12,6 +12,8 @@ import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router" import { registerKubernetesDynamicSecretLeaseRouter } from "./dynamic-secret-lease-routers/kubernetes-lease-router"; import { registerDynamicSecretRouter } from "./dynamic-secret-router"; import { registerExternalKmsRouter } from "./external-kms-router"; + +import { EXTERNAL_KMS_REGISTER_ROUTER_MAP } from "./external-kms-routers"; import { registerGatewayRouter } from "./gateway-router"; import { registerGithubOrgSyncRouter } from "./github-org-sync-router"; import { registerGroupRouter } from "./group-router"; @@ -162,9 +164,19 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { { prefix: "/additional-privilege" } ); - await server.register(registerExternalKmsRouter, { - prefix: "/external-kms" - }); + await server.register( + async (externalKmsRouter) => { + await externalKmsRouter.register(registerExternalKmsRouter); + + // Provider-specific endpoints + await Promise.all( + Object.entries(EXTERNAL_KMS_REGISTER_ROUTER_MAP).map(([provider, router]) => + externalKmsRouter.register(router, { prefix: `/${provider}` }) + ) + ); + }, + { prefix: "/external-kms" } + ); await server.register(registerIdentityTemplateRouter, { prefix: "/identity-templates" }); await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" }); From 172959d72a36e083e822e6dcfa64a1e2257980a4 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Tue, 2 Dec 2025 16:35:05 +0530 Subject: [PATCH 2/9] fix: used new apis on frontend --- frontend/src/hooks/api/kms/mutations.tsx | 33 ++++++++++++------- frontend/src/hooks/api/kms/queries.tsx | 12 +++++-- .../OrgEncryptionTab/ExternalKmsItem.tsx | 3 +- .../OrgEncryptionTab/OrgEncryptionTab.tsx | 7 ++-- .../UpdateExternalKmsForm.tsx | 5 +-- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/frontend/src/hooks/api/kms/mutations.tsx b/frontend/src/hooks/api/kms/mutations.tsx index 4fb0a5af5..c911c8f97 100644 --- a/frontend/src/hooks/api/kms/mutations.tsx +++ b/frontend/src/hooks/api/kms/mutations.tsx @@ -6,6 +6,7 @@ import { kmsKeys } from "./queries"; import { AddExternalKmsType, ExternalKmsGcpSchemaType, + ExternalKmsProvider, KmsGcpKeyFetchAuthType, KmsType, UpdateExternalKmsType @@ -15,10 +16,11 @@ 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", { + const providerPath = provider.type === ExternalKmsProvider.Aws ? "aws" : "gcp"; + const { data } = await apiRequest.post(`/api/v1/external-kms/${providerPath}`, { name, description, - provider + provider: provider.inputs }); return data; @@ -40,10 +42,11 @@ export const useUpdateExternalKms = (orgId: string) => { }: { kmsId: string; } & UpdateExternalKmsType) => { - const { data } = await apiRequest.patch(`/api/v1/external-kms/${kmsId}`, { + const providerPath = provider.type === ExternalKmsProvider.Aws ? "aws" : "gcp"; + const { data } = await apiRequest.patch(`/api/v1/external-kms/${providerPath}/${kmsId}`, { name, description, - provider + provider: provider.inputs }); return data; @@ -58,8 +61,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 +133,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; }, diff --git a/frontend/src/hooks/api/kms/queries.tsx b/frontend/src/hooks/api/kms/queries.tsx index 97d25376c..854e6199a 100644 --- a/frontend/src/hooks/api/kms/queries.tsx +++ b/frontend/src/hooks/api/kms/queries.tsx @@ -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,14 +23,20 @@ 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}`); + } = await apiRequest.get<{ externalKms: Kms }>(`/api/v1/external-kms/${provider}/${kmsId}`); return externalKms; } }); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx index 44fc22331..9e620d949 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/ExternalKmsItem.tsx @@ -119,7 +119,8 @@ export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) = } handlePopUpOpen("editExternalKms", { - kmsId: kms.id + kmsId: kms.id, + provider: kms.externalKms.provider }); }} > diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx index 81649b604..13968525c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx @@ -25,6 +25,7 @@ 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 { ExternalKmsItem } from "./ExternalKmsItem"; @@ -47,11 +48,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", @@ -131,6 +133,7 @@ export const OrgEncryptionTab = withPermission( handlePopUpToggle("editExternalKms", state)} /> void; }; -export const UpdateExternalKmsForm = ({ isOpen, kmsId, onOpenChange }: Props) => { - const { data: externalKms, isPending } = useGetExternalKmsById(kmsId); +export const UpdateExternalKmsForm = ({ isOpen, kmsId, provider, onOpenChange }: Props) => { + const { data: externalKms, isPending } = useGetExternalKmsById({ kmsId, provider }); return ( From 313fc3f76171d7f7e23af16e53780cd1897fe486 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 4 Dec 2025 02:16:58 +0530 Subject: [PATCH 3/9] fix: provider -> configuration --- .../external-kms-endpoints.ts | 33 +++++++++---------- frontend/src/hooks/api/kms/mutations.tsx | 4 +-- 2 files changed, 17 insertions(+), 20 deletions(-) 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 049523324..a8a418c0a 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 @@ -9,6 +9,7 @@ import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "@app/ee/services/external-kms/providers/model"; +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"; @@ -75,7 +76,9 @@ export const registerExternalKmsEndpoints = < // Validate that the KMS is of the expected provider type if (externalKms.external.provider !== provider) { - throw new Error(`KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}`); + throw new BadRequestError({ + message: `KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}` + }); } await server.services.auditLog.createAuditLog({ @@ -104,7 +107,7 @@ export const registerExternalKmsEndpoints = < body: z.object({ name: z.string().min(1).trim().toLowerCase(), description: z.string().trim().optional(), - provider: createSchema + configuration: createSchema }), response: { 200: z.object({ @@ -114,19 +117,15 @@ export const registerExternalKmsEndpoints = < }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { - name, - description, - provider: providerInputs - } = req.body as { + const { name, description, configuration } = req.body as { name: string; description?: string; - provider: T["inputs"]; + configuration: T["inputs"]; }; const providerInput = { type: provider, - inputs: providerInputs + inputs: configuration } as TExternalKmsInputSchema; const externalKms = await server.services.externalKms.create({ @@ -170,7 +169,7 @@ export const registerExternalKmsEndpoints = < body: z.object({ name: z.string().min(1).trim().toLowerCase().optional(), description: z.string().trim().optional(), - provider: updateSchema + configuration: updateSchema }), response: { 200: z.object({ @@ -180,19 +179,15 @@ export const registerExternalKmsEndpoints = < }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const { - name, - description, - provider: providerInputs - } = req.body as { + const { name, description, configuration } = req.body as { name?: string; description?: string; - provider: Partial; + configuration: Partial; }; const providerInput = { type: provider, - inputs: providerInputs + inputs: configuration } as TExternalKmsInputUpdateSchema; const externalKms = await server.services.externalKms.updateById({ @@ -252,7 +247,9 @@ export const registerExternalKmsEndpoints = < // Validate that the KMS is of the expected provider type if (externalKms.external.provider !== provider) { - throw new Error(`KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}`); + throw new BadRequestError({ + message: `KMS provider mismatch. Expected ${provider}, got ${externalKms.external.provider}` + }); } await server.services.auditLog.createAuditLog({ diff --git a/frontend/src/hooks/api/kms/mutations.tsx b/frontend/src/hooks/api/kms/mutations.tsx index c911c8f97..fada7ea9f 100644 --- a/frontend/src/hooks/api/kms/mutations.tsx +++ b/frontend/src/hooks/api/kms/mutations.tsx @@ -20,7 +20,7 @@ export const useAddExternalKms = (orgId: string) => { const { data } = await apiRequest.post(`/api/v1/external-kms/${providerPath}`, { name, description, - provider: provider.inputs + configuration: provider.inputs }); return data; @@ -46,7 +46,7 @@ export const useUpdateExternalKms = (orgId: string) => { const { data } = await apiRequest.patch(`/api/v1/external-kms/${providerPath}/${kmsId}`, { name, description, - provider: provider.inputs + configuration: provider.inputs }); return data; From fef8d534284654b301da4365fc83a167315d918e Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 4 Dec 2025 02:21:32 +0530 Subject: [PATCH 4/9] fix: error -> BadRequestError --- .../external-kms/providers/aws-kms.ts | 5 +-- backend/src/services/kms/kms-service.ts | 35 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/backend/src/ee/services/external-kms/providers/aws-kms.ts b/backend/src/ee/services/external-kms/providers/aws-kms.ts index 2c248992f..82e95f360 100644 --- a/backend/src/ee/services/external-kms/providers/aws-kms.ts +++ b/backend/src/ee/services/external-kms/providers/aws-kms.ts @@ -3,6 +3,7 @@ import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts"; import { CustomAWSHasher } from "@app/lib/aws/hashing"; import { crypto } from "@app/lib/crypto/cryptography"; +import { BadRequestError } from "@app/lib/errors"; import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model"; @@ -22,7 +23,7 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => { }); const response = await stsClient.send(command); if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey) - throw new Error("Failed to assume role"); + throw new BadRequestError({ message: "Failed to assume role" }); const kmsClient = new KMSClient({ region: providerInputs.awsRegion, @@ -67,7 +68,7 @@ export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Pro const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] }); const kmsKey = await awsClient.send(command); - if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key"); + if (!kmsKey.KeyMetadata?.KeyId) throw new BadRequestError({ message: "Failed to generate kms key" }); const updatedProviderInputs = await ExternalKmsAwsSchema.parseAsync({ ...providerInputs, diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 8f868978d..a63f0d41d 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -253,7 +253,7 @@ export const kmsServiceFactory = ({ } if (!org.kmsDefaultKeyId) { - throw new Error("Invalid organization KMS"); + throw new BadRequestError({ message: "Invalid organization KMS" }); } return org.kmsDefaultKeyId; @@ -292,7 +292,7 @@ export const kmsServiceFactory = ({ let externalKms: TExternalKmsProviderFns; if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) { - throw new Error("Invalid organization KMS"); + throw new BadRequestError({ message: "Invalid organization KMS" }); } // The idea is external kms connection info is encrypted by an org default KMS @@ -338,7 +338,7 @@ export const kmsServiceFactory = ({ break; } default: - throw new Error("Invalid KMS provider."); + throw new BadRequestError({ message: "Invalid KMS provider." }); } return async ({ cipherTextBlob }: Pick) => { @@ -509,7 +509,7 @@ export const kmsServiceFactory = ({ if (kmsDoc.externalKms) { let externalKms: TExternalKmsProviderFns; if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) { - throw new Error("Invalid organization KMS"); + throw new BadRequestError({ message: "Invalid organization KMS" }); } const orgKmsDecryptor = await decryptWithKmsKey({ @@ -550,7 +550,7 @@ export const kmsServiceFactory = ({ break; } default: - throw new Error("Invalid KMS provider."); + throw new BadRequestError({ message: "Invalid KMS provider." }); } return async ({ plainText }: Pick) => { @@ -651,7 +651,7 @@ export const kmsServiceFactory = ({ } if (!org.kmsEncryptedDataKey) { - throw new Error("Invalid organization KMS"); + throw new BadRequestError({ message: "Invalid organization KMS" }); } const kmsDecryptor = await decryptWithKmsKey({ @@ -723,7 +723,7 @@ export const kmsServiceFactory = ({ } if (!project.kmsSecretManagerKeyId) { - throw new Error("Missing project KMS key ID"); + throw new BadRequestError({ message: "Missing project KMS key ID" }); } return project.kmsSecretManagerKeyId; @@ -832,9 +832,10 @@ export const kmsServiceFactory = ({ const isBase64 = !envConfig.ENCRYPTION_KEY; if (!encryptionKey) - throw new Error( - "Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?" - ); + throw new BadRequestError({ + message: + "Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?" + }); const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8"); @@ -846,7 +847,9 @@ export const kmsServiceFactory = ({ if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.HSM) { const hsmIsActive = await hsmService.isActive(); if (!hsmIsActive) { - throw new Error("Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?"); + throw new BadRequestError({ + message: "Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?" + }); } const decryptedKey = await hsmService.decrypt(kmsRootConfig.encryptedRootKey); @@ -861,14 +864,16 @@ export const kmsServiceFactory = ({ return cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer); } - throw new Error(`Invalid root key encryption strategy: ${kmsRootConfig.encryptionStrategy}`); + throw new BadRequestError({ message: `Invalid root key encryption strategy: ${kmsRootConfig.encryptionStrategy}` }); }; const $encryptRootKey = async (plainKeyBuffer: Buffer, strategy: RootKeyEncryptionStrategy) => { if (strategy === RootKeyEncryptionStrategy.HSM) { const hsmIsActive = await hsmService.isActive(); if (!hsmIsActive) { - throw new Error("Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?"); + throw new BadRequestError({ + message: "Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?" + }); } const encrypted = await hsmService.encrypt(plainKeyBuffer); return encrypted; @@ -882,7 +887,7 @@ export const kmsServiceFactory = ({ } // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - throw new Error(`Invalid root key encryption strategy: ${strategy}`); + throw new BadRequestError({ message: `Invalid root key encryption strategy: ${strategy}` }); }; // by keeping the decrypted data key in inner scope @@ -1130,7 +1135,7 @@ export const kmsServiceFactory = ({ if (!encryptedRootKey) { logger.error("KMS: Failed to re-encrypt ROOT Key with selected strategy"); - throw new Error("Failed to re-encrypt ROOT Key with selected strategy"); + throw new BadRequestError({ message: "Failed to re-encrypt ROOT Key with selected strategy" }); } await kmsRootConfigDAL.updateById(KMS_ROOT_CONFIG_UUID, { From bd149940a51f79f9719e800a6e4cb8ba4d1d9939 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 4 Dec 2025 02:40:36 +0530 Subject: [PATCH 5/9] fix: provider error in external kms --- .../external-kms/external-kms-service.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) 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 9614f3298..828201ceb 100644 --- a/backend/src/ee/services/external-kms/external-kms-service.ts +++ b/backend/src/ee/services/external-kms/external-kms-service.ts @@ -91,6 +91,14 @@ export const externalKmsServiceFactory = ({ sanitizedProviderInput = JSON.stringify(newProviderInput); await externalKms.validateConnection(); + } catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + + throw new BadRequestError({ + message: error instanceof Error ? `AWS error: ${error.message}` : "Failed to validate AWS connection" + }); } finally { await externalKms.cleanup(); } @@ -102,6 +110,14 @@ export const externalKmsServiceFactory = ({ try { await externalKms.validateConnection(); sanitizedProviderInput = JSON.stringify(provider.inputs); + } catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + + throw new BadRequestError({ + message: error instanceof Error ? `GCP error: ${error.message}` : "Failed to validate GCP connection" + }); } finally { await externalKms.cleanup(); } @@ -200,6 +216,14 @@ export const externalKmsServiceFactory = ({ try { await externalKms.validateConnection(); sanitizedProviderInput = JSON.stringify(updatedProviderInput); + } catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + + throw new BadRequestError({ + message: error instanceof Error ? `AWS error: ${error.message}` : "Failed to validate AWS connection" + }); } finally { await externalKms.cleanup(); } @@ -215,6 +239,14 @@ export const externalKmsServiceFactory = ({ try { await externalKms.validateConnection(); sanitizedProviderInput = JSON.stringify(updatedProviderInput); + } catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + + throw new BadRequestError({ + message: error instanceof Error ? `GCP error: ${error.message}` : "Failed to validate GCP connection" + }); } finally { await externalKms.cleanup(); } @@ -393,6 +425,14 @@ export const externalKmsServiceFactory = ({ const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } }); try { return await externalKms.getKeysList(); + } catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + + throw new BadRequestError({ + message: error instanceof Error ? `GCP error: ${error.message}` : "Failed to fetch GCP keys" + }); } finally { await externalKms.cleanup(); } From f14b03b6acf30b1ab74bab2f1ad032766c9dab91 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Fri, 5 Dec 2025 02:49:20 +0530 Subject: [PATCH 6/9] 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" && ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + )} { )}
- - - -
- - - ); -}; - export const EditExternalKmsDetailsModal = ({ isOpen, onOpenChange, kmsId, provider }: Props) => { - const { data: kms, isPending } = useGetExternalKmsById({ kmsId, provider }); + const { data: kms } = useGetExternalKmsById({ kmsId, provider }); return ( - {isPending && } - {kms && onOpenChange(false)} />} + {kms?.externalKms?.provider === ExternalKmsProvider.Aws && ( + onOpenChange(false)} + onCompleted={() => onOpenChange(false)} + /> + )} + {kms?.externalKms?.provider === ExternalKmsProvider.Gcp && ( + onOpenChange(false)} + onCompleted={() => onOpenChange(false)} + /> + )} ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx index d120bb658..7b80a33d2 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx @@ -24,7 +24,7 @@ type Props = { onCompleted: () => void; onCancel: () => void; kms?: Kms; - mode?: "full" | "credentials"; + mode?: "full" | "credentials" | "details"; }; const GCP_REGIONS = [ @@ -145,53 +145,70 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: 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; - } try { if (kms) { - if (mode === "credentials") { - await updateGcpExternalKms({ - kmsId: kms.id, - name: kms.name, - description: kms.description, - configuration: { - type: ExternalKmsProvider.Gcp, - inputs: { - gcpRegion, - keyName: keyObject?.value - } - } - }); - } else { + if (mode === "details") { await updateGcpExternalKms({ kmsId: kms.id, name, - description, + description + }); + + createNotification({ + text: "Successfully updated GCP External KMS Details", + type: "success" + }); + } else if (mode === "credentials") { + const gcpRegion = gcpRegionObject?.value; + if (!gcpRegion) { + setError("gcpRegion", { + message: "Please select a GCP region." + }); + return; + } + + 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, configuration: { type: ExternalKmsProvider.Gcp, inputs: { gcpRegion, - keyName: keyObject?.value + keyName: keyObject?.value ?? kms.externalKms.configuration.keyName } } }); + + createNotification({ + text: "Successfully updated GCP External KMS configuration", + type: "success" + }); + } + } else { + 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; } - createNotification({ - text: - mode === "credentials" - ? "Successfully updated GCP External KMS configuration" - : "Successfully updated GCP External KMS", - type: "success" - }); - } else { const credentialJson = await getCredentialFileJson(); if (!credentialJson) { return; @@ -230,8 +247,9 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: 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." }); @@ -284,7 +302,7 @@ export const GcpKmsForm = ({ onCompleted, onCancel, kms, mode = "full" }: Props) return (
- {mode === "full" && ( + {(mode === "full" || mode === "details") && ( <> )} - ( - - { - resetField("keyObject"); - field.onChange(e); - fetchGCPKeys(); - }} - formatOptionLabel={formatOptionLabel} + {(mode === "full" || mode === "credentials") && ( + <> + ( + + { + resetField("keyObject"); + field.onChange(e); + fetchGCPKeys(); + }} + formatOptionLabel={formatOptionLabel} + /> + + )} + /> + {!kms && ( + ( + + { + onChange(e.target.files); + fetchGCPKeys(); + }} + /> + + )} /> - - )} - /> - {!kms && ( - ( - - { - onChange(e.target.files); - fetchGCPKeys(); - }} - /> - )} - /> - )} - ( - - - - )} - /> - {kms && ( - - To change your GCP credentials, create a new external KMS and assign it to project you - want to use it with. - + ( + + + + )} + /> + {kms && ( + + To change your GCP credentials, create a new external KMS and assign it to project you + want to use it with. + + )} + )}