diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index a2bc332f8..be484b5fd 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -105,6 +105,7 @@ import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-c import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service"; import { TPkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { TPkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; +import { TPkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-types"; import { TProjectServiceFactory } from "@app/services/project/project-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service"; @@ -294,6 +295,7 @@ declare module "fastify" { certificateAuthority: TCertificateAuthorityServiceFactory; certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory; certificateEst: TCertificateEstServiceFactory; + pkiAcme: TPkiAcmeServiceFactory; certificateEstV3: TCertificateEstV3ServiceFactory; pkiCollection: TPkiCollectionServiceFactory; pkiSubscriber: TPkiSubscriberServiceFactory; diff --git a/backend/src/ee/routes/v1/pki-acme-router.ts b/backend/src/ee/routes/v1/pki-acme-router.ts index b308c8d58..f9342a825 100644 --- a/backend/src/ee/routes/v1/pki-acme-router.ts +++ b/backend/src/ee/routes/v1/pki-acme-router.ts @@ -3,6 +3,28 @@ import { z } from "zod"; import { ApiDocsTags } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; +import { + CreateAcmeAccountResponseSchema, + CreateAcmeAccountSchema, + CreateAcmeOrderResponseSchema, + CreateAcmeOrderSchema, + DeactivateAcmeAccountResponseSchema, + DeactivateAcmeAccountSchema, + DownloadAcmeCertificateSchema, + FinalizeAcmeOrderResponseSchema, + FinalizeAcmeOrderSchema, + GetAcmeAuthorizationResponseSchema, + GetAcmeAuthorizationSchema, + GetAcmeDirectoryResponseSchema, + GetAcmeDirectorySchema, + GetAcmeNewNonceSchema, + GetAcmeOrderResponseSchema, + GetAcmeOrderSchema, + ListAcmeOrdersResponseSchema, + ListAcmeOrdersSchema, + RespondToAcmeChallengeResponseSchema, + RespondToAcmeChallengeSchema +} from "@app/ee/services/pki-acme/pki-acme-schemas"; export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { // GET /api/v1/pki/acme/profiles//directory @@ -17,27 +39,14 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Directory - provides URLs for the client to make API calls to", - params: z.object({ - profileId: z.string().uuid() - }), + ...GetAcmeDirectorySchema.shape, response: { - 200: z.object({ - newNonce: z.string(), - newAccount: z.string(), - newOrder: z.string(), - revokeCert: z.string() - }) + 200: GetAcmeDirectoryResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME directory endpoint - // This endpoint should return the base URLs for ACME operations - return { - newNonce: `/api/v1/pki/acme/profiles/${req.params.profileId}/new-nonce`, - newAccount: `/api/v1/pki/acme/profiles/${req.params.profileId}/new-account`, - newOrder: `/api/v1/pki/acme/profiles/${req.params.profileId}/new-order`, - revokeCert: `/api/v1/pki/acme/profiles/${req.params.profileId}/revoke-cert` - }; + const directory = await server.services.pkiAcme.getAcmeDirectory(req.params.profileId); + return directory; } }); @@ -54,17 +63,13 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME New Nonce - generate a new nonce and return in Replay-Nonce header", - params: z.object({ - profileId: z.string().uuid() - }), + ...GetAcmeNewNonceSchema.shape, response: { 200: z.object({}) } }, handler: async (req, res) => { - // FIXME: Implement ACME new nonce generation - // Generate a new nonce, store it, and return it in the Replay-Nonce header - const nonce = "FIXME-generate-nonce"; + const nonce = await server.services.pkiAcme.getAcmeNewNonce(req.params.profileId); res.header("Replay-Nonce", nonce); return {}; } @@ -82,40 +87,15 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME New Account - register a new account or find existing one", - params: z.object({ - profileId: z.string().uuid() - }), - body: z.object({ - contact: z.array(z.string()).optional(), - termsOfServiceAgreed: z.boolean().optional(), - onlyReturnExisting: z.boolean().optional(), - externalAccountBinding: z - .object({ - protected: z.string(), - payload: z.string(), - signature: z.string() - }) - .optional() - }), + ...CreateAcmeAccountSchema.shape, response: { - 201: z.object({ - status: z.string(), - contact: z.array(z.string()).optional(), - orders: z.string().optional(), - accountUrl: z.string() - }) + 201: CreateAcmeAccountResponseSchema } }, - handler: async (req) => { - // FIXME: Implement ACME new account registration - // Use EAB authentication to find corresponding Infisical machine identity - // Check permissions and return account information - return { - status: "valid", - accountUrl: `/api/v1/pki/acme/profiles/${req.params.profileId}/accounts/FIXME-account-id`, - contact: req.body.contact, - orders: `/api/v1/pki/acme/profiles/${req.params.profileId}/accounts/FIXME-account-id/orders` - }; + handler: async (req, res) => { + const account = await server.services.pkiAcme.createAcmeAccount(req.params.profileId, req.body); + res.code(201); + return account; } }); @@ -131,47 +111,15 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME New Order - apply for a new certificate", - params: z.object({ - profileId: z.string().uuid() - }), - body: z.object({ - identifiers: z.array( - z.object({ - type: z.string(), - value: z.string() - }) - ), - notBefore: z.string().optional(), - notAfter: z.string().optional() - }), + ...CreateAcmeOrderSchema.shape, response: { - 201: z.object({ - status: z.string(), - expires: z.string(), - identifiers: z.array( - z.object({ - type: z.string(), - value: z.string() - }) - ), - authorizations: z.array(z.string()), - finalize: z.string(), - certificate: z.string().optional() - }) + 201: CreateAcmeOrderResponseSchema } }, - handler: async (req) => { - // FIXME: Implement ACME new order creation - const orderId = "FIXME-order-id"; - return { - status: "pending", - expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), - identifiers: req.body.identifiers, - authorizations: req.body.identifiers.map( - (id) => `/api/v1/pki/acme/profiles/${req.params.profileId}/authorizations/FIXME-authz-${id.value}` - ), - finalize: `/api/v1/pki/acme/profiles/${req.params.profileId}/orders/${orderId}/finalize` - }; + handler: async (req, res) => { + const order = await server.services.pkiAcme.createAcmeOrder(req.params.profileId, req.body); + res.code(201); + return order; } }); @@ -187,24 +135,14 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Account Deactivation", - params: z.object({ - profileId: z.string().uuid(), - accountId: z.string() - }), - body: z.object({ - status: z.literal("deactivated") - }), + ...DeactivateAcmeAccountSchema.shape, response: { - 200: z.object({ - status: z.string() - }) + 200: DeactivateAcmeAccountResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME account deactivation - return { - status: "deactivated" - }; + const result = await server.services.pkiAcme.deactivateAcmeAccount(req.params.profileId, req.params.accountId); + return result; } }); @@ -220,21 +158,14 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME List Orders - get existing orders from current account", - params: z.object({ - profileId: z.string().uuid(), - accountId: z.string() - }), + ...ListAcmeOrdersSchema.shape, response: { - 200: z.object({ - orders: z.array(z.string()) - }) + 200: ListAcmeOrdersResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME list orders - return { - orders: [] - }; + const orders = await server.services.pkiAcme.listAcmeOrders(req.params.profileId, req.params.accountId); + return orders; } }); @@ -250,35 +181,14 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Get Order - return status and details of the order", - params: z.object({ - profileId: z.string().uuid(), - orderId: z.string() - }), + ...GetAcmeOrderSchema.shape, response: { - 200: z.object({ - status: z.string(), - expires: z.string().optional(), - identifiers: z.array( - z.object({ - type: z.string(), - value: z.string() - }) - ), - authorizations: z.array(z.string()), - finalize: z.string(), - certificate: z.string().optional() - }) + 200: GetAcmeOrderResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME get order - return { - status: "pending", - expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), - identifiers: [], - authorizations: [], - finalize: `/api/v1/pki/acme/profiles/${req.params.profileId}/orders/${req.params.orderId}/finalize` - }; + const order = await server.services.pkiAcme.getAcmeOrder(req.params.profileId, req.params.orderId); + return order; } }); @@ -294,39 +204,18 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Finalize Order - finalize cert order by providing CSR", - params: z.object({ - profileId: z.string().uuid(), - orderId: z.string() - }), - body: z.object({ - csr: z.string() - }), + ...FinalizeAcmeOrderSchema.shape, response: { - 200: z.object({ - status: z.string(), - expires: z.string().optional(), - identifiers: z.array( - z.object({ - type: z.string(), - value: z.string() - }) - ), - authorizations: z.array(z.string()), - finalize: z.string(), - certificate: z.string().optional() - }) + 200: FinalizeAcmeOrderResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME finalize order - return { - status: "processing", - expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), - identifiers: [], - authorizations: [], - finalize: `/api/v1/pki/acme/profiles/${req.params.profileId}/orders/${req.params.orderId}/finalize`, - certificate: `/api/v1/pki/acme/profiles/${req.params.profileId}/orders/${req.params.orderId}/certificate` - }; + const order = await server.services.pkiAcme.finalizeAcmeOrder( + req.params.profileId, + req.params.orderId, + req.body.csr + ); + return order; } }); @@ -342,18 +231,16 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Download Certificate - download certificate when ready", - params: z.object({ - profileId: z.string().uuid(), - orderId: z.string() - }), + ...DownloadAcmeCertificateSchema.shape, response: { 200: z.string() } }, handler: async (req, res) => { - // FIXME: Implement ACME certificate download - // Return the certificate in PEM format - const certificate = "FIXME-certificate-pem"; + const certificate = await server.services.pkiAcme.downloadAcmeCertificate( + req.params.profileId, + req.params.orderId + ); res.header("Content-Type", "application/pem-certificate-chain"); return certificate; } @@ -371,48 +258,14 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Identifier Authorization - get authorization info (challenges)", - params: z.object({ - profileId: z.string().uuid(), - authzId: z.string() - }), + ...GetAcmeAuthorizationSchema.shape, response: { - 200: z.object({ - status: z.string(), - expires: z.string().optional(), - identifier: z.object({ - type: z.string(), - value: z.string() - }), - challenges: z.array( - z.object({ - type: z.string(), - url: z.string(), - status: z.string(), - token: z.string(), - validated: z.string().optional() - }) - ) - }) + 200: GetAcmeAuthorizationResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME authorization retrieval - return { - status: "pending", - expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), - identifier: { - type: "dns", - value: "FIXME-domain-name" - }, - challenges: [ - { - type: "http-01", - url: `/api/v1/pki/acme/profiles/${req.params.profileId}/authorizations/${req.params.authzId}/challenges/http-01`, - status: "pending", - token: "FIXME-challenge-token" - } - ] - }; + const authz = await server.services.pkiAcme.getAcmeAuthorization(req.params.profileId, req.params.authzId); + return authz; } }); @@ -428,36 +281,14 @@ export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { hide: false, tags: [ApiDocsTags.PkiAcme], description: "ACME Respond to Challenge - let ACME server know challenge is ready", - params: z.object({ - profileId: z.string().uuid(), - authzId: z.string() - }), + ...RespondToAcmeChallengeSchema.shape, response: { - 200: z.object({ - type: z.string(), - url: z.string(), - status: z.string(), - token: z.string(), - validated: z.string().optional(), - error: z - .object({ - type: z.string(), - detail: z.string(), - status: z.number() - }) - .optional() - }) + 200: RespondToAcmeChallengeResponseSchema } }, handler: async (req) => { - // FIXME: Implement ACME challenge response - // Trigger verification process - return { - type: "http-01", - url: `/api/v1/pki/acme/profiles/${req.params.profileId}/authorizations/${req.params.authzId}/challenges/http-01`, - status: "pending", - token: "FIXME-challenge-token" - }; + const challenge = await server.services.pkiAcme.respondToAcmeChallenge(req.params.profileId, req.params.authzId); + return challenge; } }); }; diff --git a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts new file mode 100644 index 000000000..6cfee8ecc --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts @@ -0,0 +1,210 @@ +import { z } from "zod"; + +// Directory endpoint +export const GetAcmeDirectorySchema = z.object({ + params: z.object({ + profileId: z.string().uuid() + }) +}); + +export const GetAcmeDirectoryResponseSchema = z.object({ + newNonce: z.string(), + newAccount: z.string(), + newOrder: z.string(), + revokeCert: z.string() +}); + +// New Nonce endpoint +export const GetAcmeNewNonceSchema = z.object({ + params: z.object({ + profileId: z.string().uuid() + }) +}); + +// New Account endpoint +export const CreateAcmeAccountSchema = z.object({ + params: z.object({ + profileId: z.string().uuid() + }), + body: z.object({ + contact: z.array(z.string()).optional(), + termsOfServiceAgreed: z.boolean().optional(), + onlyReturnExisting: z.boolean().optional(), + externalAccountBinding: z + .object({ + protected: z.string(), + payload: z.string(), + signature: z.string() + }) + .optional() + }) +}); + +export const CreateAcmeAccountResponseSchema = z.object({ + status: z.string(), + contact: z.array(z.string()).optional(), + orders: z.string().optional(), + accountUrl: z.string() +}); + +// New Order endpoint +export const CreateAcmeOrderSchema = z.object({ + params: z.object({ + profileId: z.string().uuid() + }), + body: z.object({ + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string() + }) + ), + notBefore: z.string().optional(), + notAfter: z.string().optional() + }) +}); + +export const CreateAcmeOrderResponseSchema = z.object({ + status: z.string(), + expires: z.string(), + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string() + }) + ), + authorizations: z.array(z.string()), + finalize: z.string(), + certificate: z.string().optional() +}); + +// Account Deactivation endpoint +export const DeactivateAcmeAccountSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + accountId: z.string() + }), + body: z.object({ + status: z.literal("deactivated") + }) +}); + +export const DeactivateAcmeAccountResponseSchema = z.object({ + status: z.string() +}); + +// List Orders endpoint +export const ListAcmeOrdersSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + accountId: z.string() + }) +}); + +export const ListAcmeOrdersResponseSchema = z.object({ + orders: z.array(z.string()) +}); + +// Get Order endpoint +export const GetAcmeOrderSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + orderId: z.string() + }) +}); + +export const GetAcmeOrderResponseSchema = z.object({ + status: z.string(), + expires: z.string().optional(), + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string() + }) + ), + authorizations: z.array(z.string()), + finalize: z.string(), + certificate: z.string().optional() +}); + +// Finalize Order endpoint +export const FinalizeAcmeOrderSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + orderId: z.string() + }), + body: z.object({ + csr: z.string() + }) +}); + +export const FinalizeAcmeOrderResponseSchema = z.object({ + status: z.string(), + expires: z.string().optional(), + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string() + }) + ), + authorizations: z.array(z.string()), + finalize: z.string(), + certificate: z.string().optional() +}); + +// Download Certificate endpoint +export const DownloadAcmeCertificateSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + orderId: z.string() + }) +}); + +// Get Authorization endpoint +export const GetAcmeAuthorizationSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + authzId: z.string() + }) +}); + +export const GetAcmeAuthorizationResponseSchema = z.object({ + status: z.string(), + expires: z.string().optional(), + identifier: z.object({ + type: z.string(), + value: z.string() + }), + challenges: z.array( + z.object({ + type: z.string(), + url: z.string(), + status: z.string(), + token: z.string(), + validated: z.string().optional() + }) + ) +}); + +// Respond to Challenge endpoint +export const RespondToAcmeChallengeSchema = z.object({ + params: z.object({ + profileId: z.string().uuid(), + authzId: z.string() + }) +}); + +export const RespondToAcmeChallengeResponseSchema = z.object({ + type: z.string(), + url: z.string(), + status: z.string(), + token: z.string(), + validated: z.string().optional(), + error: z + .object({ + type: z.string(), + detail: z.string(), + status: z.number() + }) + .optional() +}); diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts new file mode 100644 index 000000000..1ece387e0 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -0,0 +1,168 @@ +import { NotFoundError } from "@app/lib/errors"; + +import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; + +import { + TCreateAcmeAccountResponse, + TCreateAcmeOrderResponse, + TDeactivateAcmeAccountResponse, + TDownloadAcmeCertificateDTO, + TFinalizeAcmeOrderResponse, + TGetAcmeAuthorizationResponse, + TGetAcmeDirectoryResponse, + TGetAcmeOrderResponse, + TListAcmeOrdersResponse, + TPkiAcmeServiceFactory, + TRespondToAcmeChallengeResponse +} from "./pki-acme-types"; + +type TPkiAcmeServiceFactoryDep = { + certificateProfileDAL: Pick; +}; + +export const pkiAcmeServiceFactory = ({ certificateProfileDAL }: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => { + 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" }); + } + + // FIXME: Validate profile is configured for ACME enrollment + 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` + }; + }; + + const getAcmeNewNonce = async (profileId: string): Promise => { + // FIXME: Implement ACME new nonce generation + // Generate a new nonce, store it, and return it + return "FIXME-generate-nonce"; + }; + + const createAcmeAccount = async (profileId: string, body: unknown): Promise => { + // FIXME: Implement ACME new account registration + // Use EAB authentication to find corresponding Infisical machine identity + // Check permissions and return account information + return { + status: "valid", + accountUrl: `/api/v1/pki/acme/profiles/${profileId}/accounts/FIXME-account-id`, + contact: [], + orders: `/api/v1/pki/acme/profiles/${profileId}/accounts/FIXME-account-id/orders` + }; + }; + + const createAcmeOrder = async (profileId: string, body: unknown): Promise => { + // FIXME: Implement ACME new order creation + const orderId = "FIXME-order-id"; + 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` + }; + }; + + const deactivateAcmeAccount = async ( + profileId: string, + accountId: string + ): Promise => { + // FIXME: Implement ACME account deactivation + return { + status: "deactivated" + }; + }; + + const listAcmeOrders = async (profileId: string, accountId: string): Promise => { + // FIXME: Implement ACME list orders + return { + orders: [] + }; + }; + + const getAcmeOrder = async (profileId: string, orderId: string): Promise => { + // FIXME: Implement ACME get order + 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` + }; + }; + + const finalizeAcmeOrder = async ( + profileId: string, + orderId: string, + csr: string + ): Promise => { + // FIXME: Implement ACME finalize order + 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` + }; + }; + + const downloadAcmeCertificate = async (profileId: string, orderId: string): Promise => { + // FIXME: Implement ACME certificate download + // Return the certificate in PEM format + return "FIXME-certificate-pem"; + }; + + const getAcmeAuthorization = async (profileId: string, authzId: string): Promise => { + // FIXME: Implement ACME authorization retrieval + return { + status: "pending", + expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + identifier: { + type: "dns", + value: "FIXME-domain-name" + }, + challenges: [ + { + type: "http-01", + url: `/api/v1/pki/acme/profiles/${profileId}/authorizations/${authzId}/challenges/http-01`, + status: "pending", + token: "FIXME-challenge-token" + } + ] + }; + }; + + const respondToAcmeChallenge = async ( + profileId: string, + authzId: string + ): Promise => { + // FIXME: Implement ACME challenge response + // Trigger verification process + return { + type: "http-01", + url: `/api/v1/pki/acme/profiles/${profileId}/authorizations/${authzId}/challenges/http-01`, + status: "pending", + token: "FIXME-challenge-token" + }; + }; + + return { + getAcmeDirectory, + getAcmeNewNonce, + createAcmeAccount, + createAcmeOrder, + deactivateAcmeAccount, + listAcmeOrders, + getAcmeOrder, + finalizeAcmeOrder, + downloadAcmeCertificate, + getAcmeAuthorization, + respondToAcmeChallenge + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-types.ts b/backend/src/ee/services/pki-acme/pki-acme-types.ts new file mode 100644 index 000000000..4a80ab0f6 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-types.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; + +import { + CreateAcmeAccountResponseSchema, + CreateAcmeOrderResponseSchema, + DeactivateAcmeAccountResponseSchema, + FinalizeAcmeOrderResponseSchema, + GetAcmeAuthorizationResponseSchema, + GetAcmeDirectoryResponseSchema, + GetAcmeOrderResponseSchema, + ListAcmeOrdersResponseSchema, + RespondToAcmeChallengeResponseSchema +} from "./pki-acme-schemas"; + +export type TGetAcmeDirectoryResponse = z.infer; +export type TCreateAcmeAccountResponse = z.infer; +export type TCreateAcmeOrderResponse = z.infer; +export type TDeactivateAcmeAccountResponse = z.infer; +export type TListAcmeOrdersResponse = z.infer; +export type TGetAcmeOrderResponse = z.infer; +export type TFinalizeAcmeOrderResponse = z.infer; +export type TDownloadAcmeCertificateDTO = string; +export type TGetAcmeAuthorizationResponse = z.infer; +export type TRespondToAcmeChallengeResponse = z.infer; + +export type TPkiAcmeServiceFactory = { + getAcmeDirectory: (profileId: string) => Promise; + getAcmeNewNonce: (profileId: string) => Promise; + createAcmeAccount: (profileId: string, body: unknown) => Promise; + createAcmeOrder: (profileId: string, body: unknown) => Promise; + deactivateAcmeAccount: (profileId: string, accountId: string) => Promise; + listAcmeOrders: (profileId: string, accountId: string) => Promise; + getAcmeOrder: (profileId: string, orderId: string) => Promise; + finalizeAcmeOrder: (profileId: string, orderId: string, csr: string) => Promise; + downloadAcmeCertificate: (profileId: string, orderId: string) => Promise; + getAcmeAuthorization: (profileId: string, authzId: string) => Promise; + respondToAcmeChallenge: (profileId: string, authzId: string) => Promise; +}; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e2009e7fa..0e25e9aee 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -25,6 +25,7 @@ import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/ import { certificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { certificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service"; import { certificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service"; +import { pkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-service"; import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; @@ -1161,6 +1162,10 @@ export const registerRoutes = async ( projectDAL }); + const pkiAcmeService = pkiAcmeServiceFactory({ + certificateProfileDAL + }); + const pkiAlertService = pkiAlertServiceFactory({ pkiAlertDAL, pkiCollectionDAL, @@ -2436,6 +2441,7 @@ export const registerRoutes = async ( certificateProfile: certificateProfileService, certificateAuthorityCrl: certificateAuthorityCrlService, certificateEst: certificateEstService, + pkiAcme: pkiAcmeService, pit: pitService, pkiAlert: pkiAlertService, pkiCollection: pkiCollectionService,