diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index f38410f0d..9a84b0dc8 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -92,4 +92,4 @@ def step_impl(context: Context): def step_impl(context: Context, email: str, kid: str, secret: str, account_var: str): # TODO: add EAB info here registration = messages.NewRegistration.from_data(email=email) - context.var[account_var] = context.acme_client.new_account(registration) + context.vars[account_var] = context.acme_client.new_account(registration) diff --git a/backend/src/ee/routes/v1/pki-acme-router.ts b/backend/src/ee/routes/v1/pki-acme-router.ts index f9342a825..2a82bfeb3 100644 --- a/backend/src/ee/routes/v1/pki-acme-router.ts +++ b/backend/src/ee/routes/v1/pki-acme-router.ts @@ -27,6 +27,20 @@ import { } from "@app/ee/services/pki-acme/pki-acme-schemas"; export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { + server.addContentTypeParser("application/jose+json", { parseAs: "string" }, (_, body, done) => { + try { + const strBody = body instanceof Buffer ? body.toString() : body; + if (!strBody) { + done(null, undefined); + } + const json: unknown = JSON.parse(strBody as string); + // TODO: deal with JWS payload here + done(null, json); + } catch (err) { + const error = err as Error; + done(error, undefined); + } + }); // GET /api/v1/pki/acme/profiles//directory // Directory (RFC 8555 Section 7.1.1) server.route({ @@ -94,7 +108,10 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { }, handler: async (req, res) => { const account = await server.services.pkiAcme.createAcmeAccount(req.params.profileId, req.body); + // TODO: deal with existing account case here res.code(201); + const nonce = await server.services.pkiAcme.getAcmeNewNonce(req.params.profileId); + res.header("Replay-Nonce", nonce); return account; } }); diff --git a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts index 6cfee8ecc..2f470f834 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts @@ -11,7 +11,7 @@ export const GetAcmeDirectoryResponseSchema = z.object({ newNonce: z.string(), newAccount: z.string(), newOrder: z.string(), - revokeCert: z.string() + revokeCert: z.string().optional() }); // New Nonce endpoint diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts index 1ece387e0..1148dda3d 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -1,3 +1,4 @@ +import { getConfig } from "@app/lib/config/env"; import { NotFoundError } from "@app/lib/errors"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; @@ -21,20 +22,24 @@ type TPkiAcmeServiceFactoryDep = { }; export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => { + const appCfg = getConfig(); + const getAcmeDirectory = async (profileId: string): Promise => { // FIXME: Implement ACME directory endpoint // Validate profile exists and is for ACME enrollment - const profile = await certificateProfileDAL.findById(profileId); - if (!profile) { - throw new NotFoundError({ message: "Certificate profile not found" }); - } + // const profile = await certificateProfileDAL.findById(profileId); + // if (!profile) { + // throw new NotFoundError({ message: "Certificate profile not found" }); + // } // FIXME: Validate profile is configured for ACME enrollment + + // Return absolute URLs using SITE_URL + const baseUrl = appCfg.SITE_URL ?? ""; return { - newNonce: `/api/v1/pki/acme/profiles/${profileId}/new-nonce`, - newAccount: `/api/v1/pki/acme/profiles/${profileId}/new-account`, - newOrder: `/api/v1/pki/acme/profiles/${profileId}/new-order`, - revokeCert: `/api/v1/pki/acme/profiles/${profileId}/revoke-cert` + newNonce: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/new-nonce`, + newAccount: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/new-account`, + newOrder: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/new-order` }; }; @@ -48,23 +53,26 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService // FIXME: Implement ACME new account registration // Use EAB authentication to find corresponding Infisical machine identity // Check permissions and return account information + const baseUrl = appCfg.SITE_URL || ""; + const accountId = "FIXME-account-id"; return { status: "valid", - accountUrl: `/api/v1/pki/acme/profiles/${profileId}/accounts/FIXME-account-id`, + accountUrl: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/accounts/${accountId}`, contact: [], - orders: `/api/v1/pki/acme/profiles/${profileId}/accounts/FIXME-account-id/orders` + orders: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/accounts/${accountId}/orders` }; }; const createAcmeOrder = async (profileId: string, body: unknown): Promise => { // FIXME: Implement ACME new order creation const orderId = "FIXME-order-id"; + const baseUrl = appCfg.SITE_URL || ""; return { status: "pending", expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), identifiers: [], authorizations: [], - finalize: `/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize` + finalize: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize` }; }; @@ -87,12 +95,13 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService const getAcmeOrder = async (profileId: string, orderId: string): Promise => { // FIXME: Implement ACME get order + const baseUrl = appCfg.SITE_URL || ""; return { status: "pending", expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), identifiers: [], authorizations: [], - finalize: `/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize` + finalize: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize` }; }; @@ -102,13 +111,14 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService csr: string ): Promise => { // FIXME: Implement ACME finalize order + const baseUrl = appCfg.SITE_URL || ""; return { status: "processing", expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), identifiers: [], authorizations: [], - finalize: `/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize`, - certificate: `/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/certificate` + finalize: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize`, + certificate: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/certificate` }; }; @@ -120,6 +130,7 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService const getAcmeAuthorization = async (profileId: string, authzId: string): Promise => { // FIXME: Implement ACME authorization retrieval + const baseUrl = appCfg.SITE_URL || ""; return { status: "pending", expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), @@ -130,7 +141,7 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService challenges: [ { type: "http-01", - url: `/api/v1/pki/acme/profiles/${profileId}/authorizations/${authzId}/challenges/http-01`, + url: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/authorizations/${authzId}/challenges/http-01`, status: "pending", token: "FIXME-challenge-token" } @@ -144,9 +155,10 @@ export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeService ): Promise => { // FIXME: Implement ACME challenge response // Trigger verification process + const baseUrl = appCfg.SITE_URL || ""; return { type: "http-01", - url: `/api/v1/pki/acme/profiles/${profileId}/authorizations/${authzId}/challenges/http-01`, + url: `${baseUrl}/api/v1/pki/acme/profiles/${profileId}/authorizations/${authzId}/challenges/http-01`, status: "pending", token: "FIXME-challenge-token" };