From 167466a1591b544622fd04281bae489b3b3e0f98 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 27 Oct 2025 16:12:27 -0700 Subject: [PATCH] Add ACME stuff --- .../bdd/features/pki/acme/new-account.feature | 2 +- backend/src/@types/knex.d.ts | 8 +++ backend/src/db/schemas/models.ts | 1 + .../db/schemas/pki-acme-enrollment-configs.ts | 20 ++++++ backend/src/ee/routes/v1/pki-acme-router.ts | 16 +++++ .../acme-enrollment-config-dal.ts | 61 +++++++++++++++++++ .../enrollment-config-types.ts | 13 ++++ 7 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 backend/src/db/schemas/pki-acme-enrollment-configs.ts create mode 100644 backend/src/services/enrollment-config/acme-enrollment-config-dal.ts diff --git a/backend/bdd/features/pki/acme/new-account.feature b/backend/bdd/features/pki/acme/new-account.feature index 8c0a9e78b..09e616412 100644 --- a/backend/bdd/features/pki/acme/new-account.feature +++ b/backend/bdd/features/pki/acme/new-account.feature @@ -2,4 +2,4 @@ Feature: New Account Scenario: Create a new account Given I have an ACME cert profile as "acme_profile" When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory - Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + Then I register a new ACME account with email fangpen@infisical.com and EAB key id {acme_profile.eab_kid} with secret {acme_profile.eab_secret} as acme_account diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index 7ff31ed99..2be2af84c 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -287,6 +287,9 @@ import { TPkiEstEnrollmentConfigs, TPkiEstEnrollmentConfigsInsert, TPkiEstEnrollmentConfigsUpdate, + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate, TPkiSubscribers, TPkiSubscribersInsert, TPkiSubscribersUpdate, @@ -709,6 +712,11 @@ declare module "knex/types/tables" { TPkiApiEnrollmentConfigsInsert, TPkiApiEnrollmentConfigsUpdate >; + [TableName.PkiAcmeEnrollmentConfig]: KnexOriginal.CompositeTableType< + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate + >; [TableName.CertificateTemplateEstConfig]: KnexOriginal.CompositeTableType< TCertificateTemplateEstConfigs, TCertificateTemplateEstConfigsInsert, diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index e10c6dcbe..6012247ac 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -27,6 +27,7 @@ export enum TableName { PkiCertificateProfile = "pki_certificate_profiles", PkiEstEnrollmentConfig = "pki_est_enrollment_configs", PkiApiEnrollmentConfig = "pki_api_enrollment_configs", + PkiAcmeEnrollmentConfig = "pki_acme_enrollment_configs", PkiSubscriber = "pki_subscribers", PkiAlert = "pki_alerts", PkiCollection = "pki_collections", diff --git a/backend/src/db/schemas/pki-acme-enrollment-configs.ts b/backend/src/db/schemas/pki-acme-enrollment-configs.ts new file mode 100644 index 000000000..19058b00b --- /dev/null +++ b/backend/src/db/schemas/pki-acme-enrollment-configs.ts @@ -0,0 +1,20 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const PkiAcmeEnrollmentConfigsSchema = z.object({ + id: z.string().uuid(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeEnrollmentConfigs = z.infer; +export type TPkiAcmeEnrollmentConfigsInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeEnrollmentConfigsUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/ee/routes/v1/pki-acme-router.ts b/backend/src/ee/routes/v1/pki-acme-router.ts index 49cc81021..5f19359a6 100644 --- a/backend/src/ee/routes/v1/pki-acme-router.ts +++ b/backend/src/ee/routes/v1/pki-acme-router.ts @@ -146,6 +146,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 201: CreateAcmeOrderResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req, res) => { const order = await server.services.pkiAcme.createAcmeOrder(req.params.profileId, req.body); res.code(201); @@ -170,6 +172,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: DeactivateAcmeAccountResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const result = await server.services.pkiAcme.deactivateAcmeAccount(req.params.profileId, req.params.accountId); return result; @@ -193,6 +197,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: ListAcmeOrdersResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const orders = await server.services.pkiAcme.listAcmeOrders(req.params.profileId, req.params.accountId); return orders; @@ -216,6 +222,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: GetAcmeOrderResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const order = await server.services.pkiAcme.getAcmeOrder(req.params.profileId, req.params.orderId); return order; @@ -239,6 +247,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: FinalizeAcmeOrderResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const order = await server.services.pkiAcme.finalizeAcmeOrder(req.params.profileId, req.params.orderId, req.body); return order; @@ -262,6 +272,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: z.string() } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req, res) => { const certificate = await server.services.pkiAcme.downloadAcmeCertificate( req.params.profileId, @@ -289,6 +301,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: GetAcmeAuthorizationResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const authz = await server.services.pkiAcme.getAcmeAuthorization(req.params.profileId, req.params.authzId); return authz; @@ -312,6 +326,8 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { 200: RespondToAcmeChallengeResponseSchema } }, + // TODO: replace with verify ACME signature here instead + // onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const challenge = await server.services.pkiAcme.respondToAcmeChallenge(req.params.profileId, req.params.authzId); return challenge; diff --git a/backend/src/services/enrollment-config/acme-enrollment-config-dal.ts b/backend/src/services/enrollment-config/acme-enrollment-config-dal.ts new file mode 100644 index 000000000..afa8f17ef --- /dev/null +++ b/backend/src/services/enrollment-config/acme-enrollment-config-dal.ts @@ -0,0 +1,61 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify } from "@app/lib/knex"; + +import { TAcmeEnrollmentConfigInsert, TAcmeEnrollmentConfigUpdate } from "./enrollment-config-types"; + +export type TAcmeEnrollmentConfigDALFactory = ReturnType; + +export const acmeEnrollmentConfigDALFactory = (db: TDbClient) => { + const acmeEnrollmentConfigOrm = ormify(db, TableName.PkiAcmeEnrollmentConfig); + + const create = async (data: TAcmeEnrollmentConfigInsert, tx?: Knex) => { + try { + const result = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).insert(data).returning("*"); + const [acmeConfig] = result; + + if (!acmeConfig) { + throw new Error("Failed to create ACME enrollment config"); + } + + return acmeConfig; + } catch (error) { + throw new DatabaseError({ error, name: "Create ACME enrollment config" }); + } + }; + + const updateById = async (id: string, data: TAcmeEnrollmentConfigUpdate, tx?: Knex) => { + try { + const result = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).where({ id }).update(data).returning("*"); + const [acmeConfig] = result; + + if (!acmeConfig) { + return null; + } + + return acmeConfig; + } catch (error) { + throw new DatabaseError({ error, name: "Update ACME enrollment config" }); + } + }; + + const findById = async (id: string, tx?: Knex) => { + try { + const acmeConfig = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).where({ id }).first(); + + return acmeConfig || null; + } catch (error) { + throw new DatabaseError({ error, name: "Find ACME enrollment config by id" }); + } + }; + + return { + ...acmeEnrollmentConfigOrm, + create, + updateById, + findById + }; +}; diff --git a/backend/src/services/enrollment-config/enrollment-config-types.ts b/backend/src/services/enrollment-config/enrollment-config-types.ts index d2e03e4da..17fd0a633 100644 --- a/backend/src/services/enrollment-config/enrollment-config-types.ts +++ b/backend/src/services/enrollment-config/enrollment-config-types.ts @@ -8,6 +8,11 @@ import { TPkiEstEnrollmentConfigsInsert, TPkiEstEnrollmentConfigsUpdate } from "@app/db/schemas/pki-est-enrollment-configs"; +import { + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate +} from "@app/db/schemas/pki-acme-enrollment-configs"; export type TEstEnrollmentConfig = TPkiEstEnrollmentConfigs; export type TEstEnrollmentConfigInsert = TPkiEstEnrollmentConfigsInsert; @@ -17,6 +22,10 @@ export type TApiEnrollmentConfig = TPkiApiEnrollmentConfigs; export type TApiEnrollmentConfigInsert = TPkiApiEnrollmentConfigsInsert; export type TApiEnrollmentConfigUpdate = TPkiApiEnrollmentConfigsUpdate; +export type TAcmeEnrollmentConfig = TPkiAcmeEnrollmentConfigs; +export type TAcmeEnrollmentConfigInsert = TPkiAcmeEnrollmentConfigsInsert; +export type TAcmeEnrollmentConfigUpdate = TPkiAcmeEnrollmentConfigsUpdate; + export interface TEstConfigData { disableBootstrapCaValidation: boolean; passphrase: string; @@ -27,3 +36,7 @@ export interface TApiConfigData { autoRenew: boolean; renewBeforeDays?: number; } + +export interface TAcmeConfigData { + // TODO: we don't provide any config for ACME right now, but maybe in the future +}