From 0269f57768471aaf3d9f49b8509c18a5e5c7c532 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 6 Feb 2025 22:36:31 +0800 Subject: [PATCH] feat: completed kmip server cert config --- .../db/migrations/20250203141127_add-kmip.ts | 1 - .../kmip-instance-server-certificates.ts | 3 +- backend/src/lib/ip/index.ts | 4 + backend/src/server/routes/v1/admin-router.ts | 7 +- .../certificate-authority-validators.ts | 4 +- .../super-admin/super-admin-service.ts | 45 +-- frontend/src/hooks/api/admin/mutation.ts | 13 + frontend/src/hooks/api/admin/types.ts | 15 + .../OverviewPage/components/KmipPanel.tsx | 298 +++++++++++++++--- 9 files changed, 320 insertions(+), 70 deletions(-) diff --git a/backend/src/db/migrations/20250203141127_add-kmip.ts b/backend/src/db/migrations/20250203141127_add-kmip.ts index 24b806a9d..d4dba9011 100644 --- a/backend/src/db/migrations/20250203141127_add-kmip.ts +++ b/backend/src/db/migrations/20250203141127_add-kmip.ts @@ -61,7 +61,6 @@ export async function up(knex: Knex): Promise { t.datetime("expiration").notNullable(); t.binary("encryptedCertificate").notNullable(); t.binary("encryptedChain").notNullable(); - t.binary("encryptedPrivateKey").notNullable(); }); } } diff --git a/backend/src/db/schemas/kmip-instance-server-certificates.ts b/backend/src/db/schemas/kmip-instance-server-certificates.ts index 916e0b131..a13188b06 100644 --- a/backend/src/db/schemas/kmip-instance-server-certificates.ts +++ b/backend/src/db/schemas/kmip-instance-server-certificates.ts @@ -18,8 +18,7 @@ export const KmipInstanceServerCertificatesSchema = z.object({ issuedAt: z.date(), expiration: z.date(), encryptedCertificate: zodBuffer, - encryptedChain: zodBuffer, - encryptedPrivateKey: zodBuffer + encryptedChain: zodBuffer }); export type TKmipInstanceServerCertificates = z.infer; diff --git a/backend/src/lib/ip/index.ts b/backend/src/lib/ip/index.ts index 6503165f6..0b35a2759 100644 --- a/backend/src/lib/ip/index.ts +++ b/backend/src/lib/ip/index.ts @@ -103,6 +103,10 @@ export const isValidIpOrCidr = (ip: string): boolean => { return false; }; +export const isValidIp = (ip: string) => { + return net.isIPv4(ip) || net.isIPv6(ip); +}; + export type TIp = { ipAddress: string; type: IPType; diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index 9783af31b..2ca79974b 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -332,7 +332,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - serverCertificateChain: z.string() + serverCertificateChain: z.string(), + clientCertificateChain: z.string() }) } }, @@ -357,7 +358,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { schema: { response: { 200: z.object({ - serverCertificateChain: z.string() + serverCertificateChain: z.string(), + clientCertificateChain: z.string() }) } }, @@ -386,6 +388,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ + serialNumber: z.string(), certificateChain: z.string(), certificate: z.string(), privateKey: z.string() diff --git a/backend/src/services/certificate-authority/certificate-authority-validators.ts b/backend/src/services/certificate-authority/certificate-authority-validators.ts index 16e7dcf49..1840652ca 100644 --- a/backend/src/services/certificate-authority/certificate-authority-validators.ts +++ b/backend/src/services/certificate-authority/certificate-authority-validators.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { isValidIp } from "@app/lib/ip"; + const isValidDate = (dateString: string) => { const date = new Date(dateString); return !Number.isNaN(date.getTime()); @@ -25,7 +27,7 @@ export const validateAltNamesField = z if (data === "") return true; // Split and validate each alt name return data.split(", ").every((name) => { - return hostnameRegex.test(name) || z.string().email().safeParse(name).success; + return hostnameRegex.test(name) || z.string().email().safeParse(name).success || isValidIp(name); }); }, { diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 33220119d..467ecb325 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -2,7 +2,6 @@ import * as x509 from "@peculiar/x509"; import bcrypt from "bcrypt"; import crypto, { KeyObject } from "crypto"; import ms from "ms"; -import z from "zod"; import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TKmipInstanceConfigDALFactory } from "@app/ee/services/kmip/kmip-instance-config-dal"; @@ -13,6 +12,7 @@ import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { isValidIp } from "@app/lib/ip"; import { TAuthLoginFactory } from "../auth/auth-login-service"; import { AuthMethod } from "../auth/auth-type"; @@ -526,8 +526,8 @@ export const superAdminServiceFactory = ({ }); return { - // the order of the cert is intentional - for client chains, ordering should be from intermediate to root - serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim() + serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim(), + clientCertificateChain: `${clientIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim() }; }; @@ -544,10 +544,13 @@ export const superAdminServiceFactory = ({ const serverIntermediateCaCert = new x509.X509Certificate( decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaCertificate) ); + const clientIntermediateCaCert = new x509.X509Certificate( + decryptWithRoot(kmipInstanceConfig.encryptedClientIntermediateCaCertificate) + ); return { - // the order of the cert is intentional - for client chains, ordering should be from intermediate to root - serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim() + serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim(), + clientCertificateChain: `${clientIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim() }; }; @@ -604,20 +607,12 @@ export const superAdminServiceFactory = ({ ]; const altNamesArray: { - type: "email" | "dns"; + type: "email" | "dns" | "ip"; value: string; }[] = altNames .split(",") .map((name) => name.trim()) .map((altName) => { - // check if the altName is a valid email - if (z.string().email().safeParse(altName).success) { - return { - type: "email", - value: altName - }; - } - // check if the altName is a valid hostname if (hostnameRegex.test(altName)) { return { @@ -626,7 +621,14 @@ export const superAdminServiceFactory = ({ }; } - // If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly + // check if the altName is a valid IP + if (isValidIp(altName)) { + return { + type: "ip", + value: altName + }; + } + throw new Error(`Invalid altName: ${altName}`); }); @@ -668,8 +670,9 @@ export const superAdminServiceFactory = ({ const encryptWithRoot = kmsService.encryptWithRootKey(); const skLeafObj = KeyObject.from(leafKeys.privateKey); + const certificateChain = `${caCertObj.toString("pem")}\n${decryptedCaCertChain}`.trim(); - const serverCert = await kmipInstanceServerCertificateDAL.create({ + await kmipInstanceServerCertificateDAL.create({ keyAlgorithm, issuedAt: notBeforeDate, expiration: notAfterDate, @@ -677,11 +680,15 @@ export const superAdminServiceFactory = ({ commonName, altNames, encryptedCertificate: encryptWithRoot(Buffer.from(new Uint8Array(leafCert.rawData))), - encryptedPrivateKey: encryptWithRoot(skLeafObj.export({ format: "der", type: "pkcs8" })), - encryptedChain: encryptWithRoot(Buffer.from(`${decryptedCaCertChain}\n${caCertObj.toString("pem")}`.trim())) + encryptedChain: encryptWithRoot(Buffer.from(certificateChain)) }); - return serverCert; + return { + serialNumber, + privateKey: skLeafObj.export({ format: "pem", type: "pkcs8" }) as string, + certificate: leafCert.toString("pem"), + certificateChain + }; }; return { diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts index 57cc586e7..4ad411de4 100644 --- a/frontend/src/hooks/api/admin/mutation.ts +++ b/frontend/src/hooks/api/admin/mutation.ts @@ -7,8 +7,10 @@ import { User } from "../users/types"; import { adminQueryKeys, adminStandaloneKeys } from "./queries"; import { AdminSlackConfig, + InstanceKmipServerCert, RootKeyEncryptionStrategy, TCreateAdminUserDTO, + TGenerateInstanceKmipServerCertDTO, TServerConfig, TSetupInstanceKmipDTO, TUpdateAdminSlackConfigDTO @@ -111,3 +113,14 @@ export const useSetupInstanceKmip = () => { } }); }; + +export const useGenerateInstanceKmipServerCert = () => { + return useMutation({ + mutationFn: async (payload: TGenerateInstanceKmipServerCertDTO) => { + return apiRequest.post( + "/api/v1/admin/kmip/server-certificates", + payload + ); + } + }); +}; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 979f5f445..809397497 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -66,6 +66,7 @@ export type TGetServerRootKmsEncryptionDetails = { export type InstanceKmipConfig = { serverCertificateChain: string; + clientCertificateChain: string; }; export enum RootKeyEncryptionStrategy { @@ -76,3 +77,17 @@ export enum RootKeyEncryptionStrategy { export type TSetupInstanceKmipDTO = { caKeyAlgorithm: CertKeyAlgorithm; }; + +export type TGenerateInstanceKmipServerCertDTO = { + commonName: string; + keyAlgorithm: CertKeyAlgorithm; + altNames: string; + ttl: string; +}; + +export type InstanceKmipServerCert = { + serialNumber: string; + certificate: string; + certificateChain: string; + privateKey: string; +}; diff --git a/frontend/src/pages/admin/OverviewPage/components/KmipPanel.tsx b/frontend/src/pages/admin/OverviewPage/components/KmipPanel.tsx index 1c597168f..e22aa3626 100644 --- a/frontend/src/pages/admin/OverviewPage/components/KmipPanel.tsx +++ b/frontend/src/pages/admin/OverviewPage/components/KmipPanel.tsx @@ -9,6 +9,7 @@ import { Button, FormControl, IconButton, + Input, Modal, ModalContent, Select, @@ -20,9 +21,11 @@ import { import { downloadTxtFile } from "@app/helpers/download"; import { usePopUp, useTimedReset } from "@app/hooks"; import { useGetInstanceKmipConfig, useSetupInstanceKmip } from "@app/hooks/api"; +import { useGenerateInstanceKmipServerCert } from "@app/hooks/api/admin/mutation"; import { InstanceKmipConfig } from "@app/hooks/api/admin/types"; import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants"; import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums"; +import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificatesTab/components/CertificateContent"; const kmipInstanceConfigFormSchema = z.object({ caKeyAlgorithm: z.nativeEnum(CertKeyAlgorithm) @@ -59,11 +62,15 @@ const KmipInstanceConfigSection = ({ handlePopUpClose("configureKmip"); }; - const [copyTextCertificate, isCopyingCertificate, setCopyTextCertificate] = useTimedReset( - { + const [copyTextClientCertificate, isCopyingClientCertificate, setCopyTextClientCertificate] = + useTimedReset({ initialState: "Copy to clipboard" - } - ); + }); + + const [copyTextServerCertificate, isCopyingServerCertificate, setCopyTextServerCertificate] = + useTimedReset({ + initialState: "Copy to clipboard" + }); return ( <> @@ -75,48 +82,92 @@ const KmipInstanceConfigSection = ({ )} {!isKmipConfigLoading && kmipConfig && ( -
-
KMIP CA Certificate for Clients
-
- This certificate chain should be used by KMIP clients to verify the identity of the - KMIP servers and establish a secure TLS connection for encrypted communication. -
-
-
- - { - navigator.clipboard.writeText(kmipConfig.serverCertificateChain); - setCopyTextCertificate("Copied"); - }} - > - - - - - { - downloadTxtFile("ca-chain.pem", kmipConfig.serverCertificateChain); - }} - > - - - + <> +
+
Certificate Chain for KMIP Clients
+
+ This certificate chain is used by KMIP clients to verify the identity of the KMIP + server. It should be presented by the server during TLS authentication to establish + a secure and encrypted connection.
+
+
+ + { + navigator.clipboard.writeText(kmipConfig.serverCertificateChain); + setCopyTextClientCertificate("Copied"); + }} + > + + + + + { + downloadTxtFile("ca-chain.pem", kmipConfig.serverCertificateChain); + }} + > + + + +
+
+