From f6573c4c23a95ee1bd1c869123a5d98834991941 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 27 Oct 2025 19:17:01 -0700 Subject: [PATCH] Enroll acme config --- .../certificate-profile-service.ts | 56 +++++++++++++++++-- .../enrollment-config-types.ts | 2 +- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index f858a8d4f..d9b9565e1 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -13,10 +13,10 @@ import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; -import { isCertChainValid } from "../certificate/certificate-fns"; import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; +import { isCertChainValid } from "../certificate/certificate-fns"; import { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; -import { TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; +import { TAcmeConfigData, TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; import { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal"; import { TKmsServiceFactory } from "../kms/kms-service"; import { TProjectDALFactory } from "../project/project-dal"; @@ -30,6 +30,37 @@ import { TCertificateProfileUpdate, TCertificateProfileWithConfigs } from "./certificate-profile-types"; +import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; + +const generateAndEncryptAcmeEabSecret = async ( + projectId: string, + kmsService: Pick, + projectDAL: Pick +) => { + try { + const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ + projectId, + projectDAL, + kmsService + }); + + const kmsEncryptor = await kmsService.encryptWithKmsKey({ + kmsId: certificateManagerKmsId + }); + + const appCfg = getConfig(); + const secret = crypto.randomBytes(32).toString("hex"); + const secretHash = await crypto.hashing().createHash(secret, appCfg.SALT_ROUNDS); + + const { cipherTextBlob } = await kmsEncryptor({ + plainText: Buffer.from(secretHash) + }); + + return { encryptedEabSecret: cipherTextBlob }; + } catch (error) { + throw new BadRequestError({ message: `Failed to generate ACME EAB secret: ${(error as Error).message}` }); + } +}; const validateAndEncryptPemCaChain = async ( caChain: string, @@ -95,9 +126,13 @@ const decryptCaChain = async ( } }; -export type TCertificateProfileCreateData = Omit & { +export type TCertificateProfileCreateData = Omit< + TCertificateProfileInsert, + "estConfigId" | "apiConfigId" | "acmeConfigId" +> & { estConfig?: TEstConfigData; apiConfig?: TApiConfigData; + acmeConfig?: TAcmeConfigData; }; type TCertificateProfileServiceFactoryDep = { @@ -105,6 +140,7 @@ type TCertificateProfileServiceFactoryDep = { certificateTemplateV2DAL: TCertificateTemplateV2DALFactory; apiEnrollmentConfigDAL: TApiEnrollmentConfigDALFactory; estEnrollmentConfigDAL: TEstEnrollmentConfigDALFactory; + acmeEnrollmentConfigDAL: TAcmeEnrollmentConfigDALFactory; permissionService: Pick; kmsService: Pick; projectDAL: Pick; @@ -124,6 +160,7 @@ export const certificateProfileServiceFactory = ({ certificateTemplateV2DAL, apiEnrollmentConfigDAL, estEnrollmentConfigDAL, + acmeEnrollmentConfigDAL, permissionService, kmsService, projectDAL @@ -188,11 +225,17 @@ export const certificateProfileServiceFactory = ({ message: "API enrollment requires API configuration" }); } + if (data.enrollmentType === EnrollmentType.ACME && !data.acmeConfig) { + throw new ForbiddenRequestError({ + message: "ACME enrollment requires ACME configuration" + }); + } // Create enrollment configs and profile const profile = await certificateProfileDAL.transaction(async (tx) => { let estConfigId: string | null = null; let apiConfigId: string | null = null; + let acmeConfigId: string | null = null; if (data.enrollmentType === EnrollmentType.EST && data.estConfig) { const appCfg = getConfig(); @@ -228,6 +271,10 @@ export const certificateProfileServiceFactory = ({ tx ); apiConfigId = apiConfig.id; + } else if (data.enrollmentType === EnrollmentType.ACME && data.acmeConfig) { + const { encryptedEabSecret } = await generateAndEncryptAcmeEabSecret(projectId, kmsService, projectDAL); + const acmeConfig = await acmeEnrollmentConfigDAL.create({ encryptedEabSecret }, tx); + acmeConfigId = acmeConfig.id; } // Create the profile with the created config IDs @@ -237,7 +284,8 @@ export const certificateProfileServiceFactory = ({ ...profileData, projectId, estConfigId, - apiConfigId + apiConfigId, + acmeConfigId }, tx ); diff --git a/backend/src/services/enrollment-config/enrollment-config-types.ts b/backend/src/services/enrollment-config/enrollment-config-types.ts index 17fd0a633..ff017e409 100644 --- a/backend/src/services/enrollment-config/enrollment-config-types.ts +++ b/backend/src/services/enrollment-config/enrollment-config-types.ts @@ -38,5 +38,5 @@ export interface TApiConfigData { } export interface TAcmeConfigData { - // TODO: we don't provide any config for ACME right now, but maybe in the future + eabSecret: string; }