diff --git a/backend/src/db/migrations/20251209191101_add-acme-order-id-for-cert-requests.ts b/backend/src/db/migrations/20251209191101_add-acme-order-id-for-cert-requests.ts new file mode 100644 index 000000000..3a87e75a4 --- /dev/null +++ b/backend/src/db/migrations/20251209191101_add-acme-order-id-for-cert-requests.ts @@ -0,0 +1,38 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; +import { dropConstraintIfExists } from "./utils/dropConstraintIfExists"; + +const FOREIGN_KEY_CONSTRAINT_NAME = "certificate_requests_acme_order_id_fkey"; +const INDEX_NAME = "certificate_requests_acme_order_id_idx"; + +export async function up(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.CertificateRequests)) { + const hasAcmeOrderId = await knex.schema.hasColumn(TableName.CertificateRequests, "acmeOrderId"); + + if (!hasAcmeOrderId) { + await knex.schema.alterTable(TableName.CertificateRequests, (t) => { + t.uuid("acmeOrderId").nullable(); + t.foreign("acmeOrderId", FOREIGN_KEY_CONSTRAINT_NAME) + .references("id") + .inTable(TableName.PkiAcmeOrder) + .onDelete("SET NULL"); + t.index("acmeOrderId", INDEX_NAME); + }); + } + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.CertificateRequests)) { + const hasAcmeOrderId = await knex.schema.hasColumn(TableName.CertificateRequests, "acmeOrderId"); + + if (hasAcmeOrderId) { + await dropConstraintIfExists(TableName.CertificateRequests, FOREIGN_KEY_CONSTRAINT_NAME, knex); + await knex.schema.alterTable(TableName.CertificateRequests, (t) => { + t.dropIndex("acmeOrderId", INDEX_NAME); + t.dropColumn("acmeOrderId"); + }); + } + } +} diff --git a/backend/src/db/schemas/certificate-requests.ts b/backend/src/db/schemas/certificate-requests.ts index e01e08bbd..4013ea5bb 100644 --- a/backend/src/db/schemas/certificate-requests.ts +++ b/backend/src/db/schemas/certificate-requests.ts @@ -26,7 +26,8 @@ export const CertificateRequestsSchema = z.object({ keyAlgorithm: z.string().nullable().optional(), signatureAlgorithm: z.string().nullable().optional(), errorMessage: z.string().nullable().optional(), - metadata: z.string().nullable().optional() + metadata: z.string().nullable().optional(), + acmeOrderId: z.string().uuid().nullable().optional() }); export type TCertificateRequests = z.infer; diff --git a/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts index 5aab0be63..cf7d96e87 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts @@ -4,6 +4,7 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; +import { CertificateRequestStatus } from "@app/services/certificate-request/certificate-request-types"; export type TPkiAcmeOrderDALFactory = ReturnType; @@ -19,6 +20,43 @@ export const pkiAcmeOrderDALFactory = (db: TDbClient) => { } }; + const findWithCertificateRequestForSync = async (id: string, tx?: Knex) => { + try { + const order = await (tx || db)(TableName.PkiAcmeOrder) + .leftJoin( + TableName.CertificateRequests, + `${TableName.PkiAcmeOrder}.id`, + `${TableName.CertificateRequests}.acmeOrderId` + ) + .select( + selectAllTableCols(TableName.PkiAcmeOrder), + db.ref("id").withSchema(TableName.CertificateRequests).as("certificateRequestId"), + db.ref("status").withSchema(TableName.CertificateRequests).as("certificateRequestStatus"), + db.ref("certificateId").withSchema(TableName.CertificateRequests).as("certificateId") + ) + .forUpdate(TableName.PkiAcmeOrder) + .where(`${TableName.PkiAcmeOrder}.id`, id) + .first(); + if (!order) { + return null; + } + const { certificateRequestId, certificateRequestStatus, certificateId, ...details } = order; + return { + ...details, + certificateRequest: + certificateRequestId && certificateRequestStatus + ? { + id: certificateRequestId, + status: certificateRequestStatus as CertificateRequestStatus, + certificateId + } + : undefined + }; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME order by id with certificate request" }); + } + }; + const findByAccountAndOrderIdWithAuthorizations = async (accountId: string, orderId: string, tx?: Knex) => { try { const rows = await (tx || db)(TableName.PkiAcmeOrder) @@ -72,6 +110,7 @@ export const pkiAcmeOrderDALFactory = (db: TDbClient) => { return { ...pkiAcmeOrderOrm, findByIdForFinalization, + findWithCertificateRequestForSync, findByAccountAndOrderIdWithAuthorizations, listByAccountId }; 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 23b86d172..ebd17c26a 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts @@ -6,8 +6,8 @@ export enum AcmeIdentifierType { export enum AcmeOrderStatus { Pending = "pending", - Processing = "processing", Ready = "ready", + Processing = "processing", Valid = "valid", Invalid = "invalid" } 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 57582655a..705cd6c39 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -7,8 +7,10 @@ import { importJWK, JWSHeaderParameters } from "jose"; +import { Knex } from "knex"; import { z, ZodError } from "zod"; +import { TPkiAcmeOrders } from "@app/db/schemas"; import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; @@ -17,20 +19,15 @@ import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { isPrivateIp } from "@app/lib/ip/ipRange"; import { logger } from "@app/lib/logger"; -import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { ActorType } from "@app/services/auth/auth-type"; import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; -import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; -import { - CertExtendedKeyUsage, - CertKeyUsage, - CertSubjectAlternativeNameType -} from "@app/services/certificate/certificate-types"; -import { orderCertificate } from "@app/services/certificate-authority/acme/acme-certificate-authority-fns"; +import { CertSubjectAlternativeNameType } from "@app/services/certificate/certificate-types"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; -import { TExternalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/external-certificate-authority-dal"; +import { + TCertificateIssuanceQueueFactory, + TIssueCertificateFromProfileJobData +} from "@app/services/certificate-authority/certificate-issuance-queue"; import { extractAlgorithmsFromCSR, extractCertificateRequestFromCSR @@ -40,6 +37,8 @@ import { EnrollmentType, TCertificateProfileWithConfigs } from "@app/services/certificate-profile/certificate-profile-types"; +import { TCertificateRequestServiceFactory } from "@app/services/certificate-request/certificate-request-service"; +import { CertificateRequestStatus } from "@app/services/certificate-request/certificate-request-types"; import { TCertificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; @@ -100,13 +99,9 @@ import { type TPkiAcmeServiceFactoryDep = { projectDAL: Pick; - appConnectionDAL: Pick; - certificateDAL: Pick; certificateAuthorityDAL: Pick; - externalCertificateAuthorityDAL: Pick; certificateProfileDAL: Pick; certificateBodyDAL: Pick; - certificateSecretDAL: Pick; certificateTemplateV2DAL: Pick; acmeAccountDAL: Pick< TPkiAcmeAccountDALFactory, @@ -114,11 +109,13 @@ type TPkiAcmeServiceFactoryDep = { >; acmeOrderDAL: Pick< TPkiAcmeOrderDALFactory, + | "findById" | "create" | "transaction" | "updateById" | "findByAccountAndOrderIdWithAuthorizations" | "findByIdForFinalization" + | "findWithCertificateRequestForSync" | "listByAccountId" >; acmeAuthDAL: Pick; @@ -135,6 +132,8 @@ type TPkiAcmeServiceFactoryDep = { licenseService: Pick; certificateV3Service: Pick; certificateTemplateV2Service: Pick; + certificateRequestService: Pick; + certificateIssuanceQueue: Pick; acmeChallengeService: Pick; pkiAcmeQueueService: Pick; auditLogService: Pick; @@ -142,13 +141,9 @@ type TPkiAcmeServiceFactoryDep = { export const pkiAcmeServiceFactory = ({ projectDAL, - appConnectionDAL, - certificateDAL, certificateAuthorityDAL, - externalCertificateAuthorityDAL, certificateProfileDAL, certificateBodyDAL, - certificateSecretDAL, certificateTemplateV2DAL, acmeAccountDAL, acmeOrderDAL, @@ -160,6 +155,8 @@ export const pkiAcmeServiceFactory = ({ licenseService, certificateV3Service, certificateTemplateV2Service, + certificateRequestService, + certificateIssuanceQueue, acmeChallengeService, pkiAcmeQueueService, auditLogService @@ -367,6 +364,52 @@ export const pkiAcmeServiceFactory = ({ }; }; + const checkAndSyncAcmeOrderStatus = async ({ orderId }: { orderId: string }): Promise => { + const order = await acmeOrderDAL.findById(orderId); + if (!order) { + throw new NotFoundError({ message: "ACME order not found" }); + } + if (order.status !== AcmeOrderStatus.Processing) { + // We only care about processing orders, as they are the ones that have async certificate requests + return order; + } + return acmeOrderDAL.transaction(async (tx) => { + // Lock the order for syncing with async cert request + const orderWithCertificateRequest = await acmeOrderDAL.findWithCertificateRequestForSync(orderId, tx); + if (!orderWithCertificateRequest) { + throw new NotFoundError({ message: "ACME order not found" }); + } + // Check the status again after we have acquired the lock, as things may have changed since we last checked + if ( + orderWithCertificateRequest.status !== AcmeOrderStatus.Processing || + !orderWithCertificateRequest.certificateRequest + ) { + return orderWithCertificateRequest; + } + let newStatus: AcmeOrderStatus | undefined; + let newCertificateId: string | undefined; + switch (orderWithCertificateRequest.certificateRequest.status) { + case CertificateRequestStatus.PENDING: + break; + case CertificateRequestStatus.ISSUED: + newStatus = AcmeOrderStatus.Valid; + newCertificateId = orderWithCertificateRequest.certificateRequest.certificateId ?? undefined; + break; + case CertificateRequestStatus.FAILED: + newStatus = AcmeOrderStatus.Invalid; + break; + default: + throw new AcmeServerInternalError({ + message: `Invalid certificate request status: ${orderWithCertificateRequest.certificateRequest.status as string}` + }); + } + if (newStatus) { + return acmeOrderDAL.updateById(orderId, { status: newStatus, certificateId: newCertificateId }, tx); + } + return orderWithCertificateRequest; + }); + }; + const getAcmeDirectory = async (profileId: string): Promise => { const profile = await validateAcmeProfile(profileId); return { @@ -734,9 +777,12 @@ export const pkiAcmeServiceFactory = ({ if (!order) { throw new NotFoundError({ message: "ACME order not found" }); } + // Sync order first in case if there is a certificate request that needs to be processed + await checkAndSyncAcmeOrderStatus({ orderId }); + const updatedOrder = (await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId))!; return { status: 200, - body: buildAcmeOrderResource({ profileId, order }), + body: buildAcmeOrderResource({ profileId, order: updatedOrder }), headers: { Location: buildUrl(profileId, `/orders/${orderId}`), Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` @@ -744,6 +790,129 @@ export const pkiAcmeServiceFactory = ({ }; }; + const processCertificateIssuanceForOrder = async ({ + caType, + accountId, + actorOrgId, + profileId, + orderId, + csr, + finalizingOrder, + certificateRequest, + profile, + ca, + tx + }: { + caType: CaType; + accountId: string; + actorOrgId: string; + profileId: string; + orderId: string; + csr: string; + finalizingOrder: { + notBefore?: Date | null; + notAfter?: Date | null; + }; + certificateRequest: ReturnType; + profile: TCertificateProfileWithConfigs; + ca: Awaited>; + tx?: Knex; + }): Promise<{ certificateId?: string; certIssuanceJobData?: TIssueCertificateFromProfileJobData }> => { + if (caType === CaType.INTERNAL) { + const result = await certificateV3Service.signCertificateFromProfile({ + actor: ActorType.ACME_ACCOUNT, + actorId: accountId, + actorAuthMethod: null, + actorOrgId, + profileId, + csr, + notBefore: finalizingOrder.notBefore ? new Date(finalizingOrder.notBefore) : undefined, + notAfter: finalizingOrder.notAfter ? new Date(finalizingOrder.notAfter) : undefined, + validity: !finalizingOrder.notAfter + ? { + // 47 days, the default TTL comes with Let's Encrypt + // TODO: read config from the profile to get the expiration time instead + ttl: `${47}d` + } + : // ttl is not used if notAfter is provided + ({ ttl: "0d" } as const), + enrollmentType: EnrollmentType.ACME + }); + return { + certificateId: result.certificateId + }; + } + + const { keyAlgorithm: extractedKeyAlgorithm, signatureAlgorithm: extractedSignatureAlgorithm } = + extractAlgorithmsFromCSR(csr); + const updatedCertificateRequest = { + ...certificateRequest, + keyAlgorithm: extractedKeyAlgorithm, + signatureAlgorithm: extractedSignatureAlgorithm, + validity: finalizingOrder.notAfter + ? (() => { + const notBefore = finalizingOrder.notBefore ? new Date(finalizingOrder.notBefore) : new Date(); + const notAfter = new Date(finalizingOrder.notAfter); + const diffMs = notAfter.getTime() - notBefore.getTime(); + const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24)); + return { ttl: `${diffDays}d` }; + })() + : certificateRequest.validity + }; + + const template = await certificateTemplateV2DAL.findById(profile.certificateTemplateId); + if (!template) { + throw new NotFoundError({ message: "Certificate template not found" }); + } + const validationResult = await certificateTemplateV2Service.validateCertificateRequest( + template.id, + updatedCertificateRequest + ); + if (!validationResult.isValid) { + throw new AcmeBadCSRError({ message: `Invalid CSR: ${validationResult.errors.join(", ")}` }); + } + + const certRequest = await certificateRequestService.createCertificateRequest({ + actor: ActorType.ACME_ACCOUNT, + actorId: accountId, + actorAuthMethod: null, + actorOrgId, + projectId: profile.projectId, + caId: ca.id, + profileId: profile.id, + commonName: updatedCertificateRequest.commonName ?? "", + keyUsages: updatedCertificateRequest.keyUsages?.map((usage) => usage.toString()) ?? [], + extendedKeyUsages: updatedCertificateRequest.extendedKeyUsages?.map((usage) => usage.toString()) ?? [], + keyAlgorithm: updatedCertificateRequest.keyAlgorithm || "", + signatureAlgorithm: updatedCertificateRequest.signatureAlgorithm || "", + altNames: updatedCertificateRequest.subjectAlternativeNames?.map((san) => san.value).join(","), + notBefore: updatedCertificateRequest.notBefore, + notAfter: updatedCertificateRequest.notAfter, + status: CertificateRequestStatus.PENDING, + acmeOrderId: orderId, + csr, + tx + }); + const csrObj = new x509.Pkcs10CertificateRequest(csr); + const csrPem = csrObj.toString("pem"); + return { + certIssuanceJobData: { + certificateId: orderId, + profileId: profile.id, + caId: profile.caId || "", + ttl: updatedCertificateRequest.validity?.ttl || "1y", + signatureAlgorithm: updatedCertificateRequest.signatureAlgorithm || "", + keyAlgorithm: updatedCertificateRequest.keyAlgorithm || "", + commonName: updatedCertificateRequest.commonName || "", + altNames: updatedCertificateRequest.subjectAlternativeNames?.map((san) => san.value) || [], + keyUsages: updatedCertificateRequest.keyUsages?.map((usage) => usage.toString()) ?? [], + extendedKeyUsages: updatedCertificateRequest.extendedKeyUsages?.map((usage) => usage.toString()) ?? [], + certificateRequestId: certRequest.id, + csr: csrPem + } + }; + }; + const finalizeAcmeOrder = async ({ profileId, accountId, @@ -768,7 +937,11 @@ export const pkiAcmeServiceFactory = ({ throw new NotFoundError({ message: "ACME order not found" }); } if (order.status === AcmeOrderStatus.Ready) { - const { order: updatedOrder, error } = await acmeOrderDAL.transaction(async (tx) => { + const { + order: updatedOrder, + error, + certIssuanceJobData + } = await acmeOrderDAL.transaction(async (tx) => { const finalizingOrder = (await acmeOrderDAL.findByIdForFinalization(orderId, tx))!; // TODO: ideally, this should be doen with onRequest: verifyAuth([AuthMode.ACME_JWS_SIGNATURE]), instead? const { ownerOrgId: actorOrgId } = (await certificateProfileDAL.findByIdWithOwnerOrgId(profileId, tx))!; @@ -815,94 +988,31 @@ export const pkiAcmeServiceFactory = ({ } const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL; let errorToReturn: Error | undefined; + let certIssuanceJobDataToReturn: TIssueCertificateFromProfileJobData | undefined; try { - const { certificateId } = await (async () => { - if (caType === CaType.INTERNAL) { - const result = await certificateV3Service.signCertificateFromProfile({ - actor: ActorType.ACME_ACCOUNT, - actorId: accountId, - actorAuthMethod: null, - actorOrgId, - profileId, - csr, - notBefore: finalizingOrder.notBefore ? new Date(finalizingOrder.notBefore) : undefined, - notAfter: finalizingOrder.notAfter ? new Date(finalizingOrder.notAfter) : undefined, - validity: !finalizingOrder.notAfter - ? { - // 47 days, the default TTL comes with Let's Encrypt - // TODO: read config from the profile to get the expiration time instead - ttl: `${47}d` - } - : // ttl is not used if notAfter is provided - ({ ttl: "0d" } as const), - enrollmentType: EnrollmentType.ACME - }); - return { certificateId: result.certificateId }; - } - const { certificateAuthority } = (await certificateProfileDAL.findByIdWithConfigs(profileId, tx))!; - const csrObj = new x509.Pkcs10CertificateRequest(csr); - const csrPem = csrObj.toString("pem"); - - const { keyAlgorithm: extractedKeyAlgorithm, signatureAlgorithm: extractedSignatureAlgorithm } = - extractAlgorithmsFromCSR(csr); - - certificateRequest.keyAlgorithm = extractedKeyAlgorithm; - certificateRequest.signatureAlgorithm = extractedSignatureAlgorithm; - if (finalizingOrder.notAfter) { - const notBefore = finalizingOrder.notBefore ? new Date(finalizingOrder.notBefore) : new Date(); - const notAfter = new Date(finalizingOrder.notAfter); - const diffMs = notAfter.getTime() - notBefore.getTime(); - const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24)); - certificateRequest.validity = { ttl: `${diffDays}d` }; - } - - const template = await certificateTemplateV2DAL.findById(profile.certificateTemplateId); - if (!template) { - throw new NotFoundError({ message: "Certificate template not found" }); - } - const validationResult = await certificateTemplateV2Service.validateCertificateRequest( - template.id, - certificateRequest - ); - if (!validationResult.isValid) { - throw new AcmeBadCSRError({ message: `Invalid CSR: ${validationResult.errors.join(", ")}` }); - } - // TODO: this is pretty slow, and we are holding the transaction open for a long time, - // we should queue the certificate issuance to a background job instead - const cert = await orderCertificate( - { - caId: certificateAuthority!.id, - // It is possible that the CSR does not have a common name, in which case we use an empty string - // (more likely than not for a CSR from a modern ACME client like certbot, cert-manager, etc.) - commonName: certificateRequest.commonName ?? "", - altNames: certificateRequest.subjectAlternativeNames?.map((san) => san.value), - csr: Buffer.from(csrPem), - // TODO: not 100% sure what are these columns for, but let's put the values for common website SSL certs for now - keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT, CertKeyUsage.KEY_AGREEMENT], - extendedKeyUsages: [CertExtendedKeyUsage.SERVER_AUTH] - }, - { - appConnectionDAL, - certificateAuthorityDAL, - externalCertificateAuthorityDAL, - certificateDAL, - certificateBodyDAL, - certificateSecretDAL, - kmsService, - projectDAL - } - ); - return { certificateId: cert.id }; - })(); + const result = await processCertificateIssuanceForOrder({ + caType, + accountId, + actorOrgId, + profileId, + orderId, + csr, + finalizingOrder, + certificateRequest, + profile, + ca, + tx + }); await acmeOrderDAL.updateById( orderId, { - status: AcmeOrderStatus.Valid, + status: result.certificateId ? AcmeOrderStatus.Valid : AcmeOrderStatus.Processing, csr, - certificateId + certificateId: result.certificateId }, tx ); + certIssuanceJobDataToReturn = result.certIssuanceJobData; } catch (exp) { await acmeOrderDAL.updateById( orderId, @@ -920,17 +1030,25 @@ export const pkiAcmeServiceFactory = ({ } else if (exp instanceof AcmeError) { errorToReturn = exp; } else { - errorToReturn = new AcmeServerInternalError({ message: "Failed to sign certificate with internal error" }); + errorToReturn = new AcmeServerInternalError({ + message: "Failed to sign certificate with internal error" + }); } } return { order: (await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId, tx))!, - error: errorToReturn + error: errorToReturn, + certIssuanceJobData: certIssuanceJobDataToReturn }; }); if (error) { throw error; } + if (certIssuanceJobData) { + // TODO: ideally, this should be done inside the transaction, but the pg-boss queue doesn't support external transactions + // as it seems to be. we need to commit the transaction before queuing the job, otherwise the job will fail (not found error). + await certificateIssuanceQueue.queueCertificateIssuance(certIssuanceJobData); + } order = updatedOrder; await auditLogService.createAuditLog({ projectId: profile.projectId, @@ -976,14 +1094,16 @@ export const pkiAcmeServiceFactory = ({ if (!order) { throw new NotFoundError({ message: "ACME order not found" }); } - if (order.status !== AcmeOrderStatus.Valid) { + // Sync order first in case if there is a certificate request that needs to be processed + const syncedOrder = await checkAndSyncAcmeOrderStatus({ orderId }); + if (syncedOrder.status !== AcmeOrderStatus.Valid) { throw new AcmeOrderNotReadyError({ message: "ACME order is not valid" }); } - if (!order.certificateId) { + if (!syncedOrder.certificateId) { throw new NotFoundError({ message: "The certificate for this ACME order no longer exists" }); } - const certBody = await certificateBodyDAL.findOne({ certId: order.certificateId }); + const certBody = await certificateBodyDAL.findOne({ certId: syncedOrder.certificateId }); const certificateManagerKeyId = await getProjectKmsCertificateKeyId({ projectId: profile.projectId, projectDAL, diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 3bc6b22e7..cd4f75e7c 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -2314,13 +2314,9 @@ export const registerRoutes = async ( const pkiAcmeService = pkiAcmeServiceFactory({ projectDAL, - appConnectionDAL, - certificateDAL, certificateAuthorityDAL, - externalCertificateAuthorityDAL, certificateProfileDAL, certificateBodyDAL, - certificateSecretDAL, certificateTemplateV2DAL, acmeAccountDAL, acmeOrderDAL, @@ -2332,6 +2328,8 @@ export const registerRoutes = async ( licenseService, certificateV3Service, certificateTemplateV2Service, + certificateRequestService, + certificateIssuanceQueue, acmeChallengeService, pkiAcmeQueueService, auditLogService diff --git a/backend/src/services/certificate-request/certificate-request-service.ts b/backend/src/services/certificate-request/certificate-request-service.ts index 3bce23de8..78bde276b 100644 --- a/backend/src/services/certificate-request/certificate-request-service.ts +++ b/backend/src/services/certificate-request/certificate-request-service.ts @@ -91,6 +91,7 @@ export const certificateRequestServiceFactory = ({ permissionService }: TCertificateRequestServiceFactoryDep) => { const createCertificateRequest = async ({ + acmeOrderId, actor, actorId, actorAuthMethod, @@ -123,6 +124,7 @@ export const certificateRequestServiceFactory = ({ { status, projectId, + acmeOrderId, ...validatedData }, tx diff --git a/backend/src/services/certificate-request/certificate-request-types.ts b/backend/src/services/certificate-request/certificate-request-types.ts index 0c62973b2..9ccf6fbae 100644 --- a/backend/src/services/certificate-request/certificate-request-types.ts +++ b/backend/src/services/certificate-request/certificate-request-types.ts @@ -21,6 +21,7 @@ export type TCreateCertificateRequestDTO = TProjectPermission & { metadata?: string; status: CertificateRequestStatus; certificateId?: string; + acmeOrderId?: string; }; export type TGetCertificateRequestDTO = TProjectPermission & {