diff --git a/backend/bdd/features/pki/acme/new-order.feature b/backend/bdd/features/pki/acme/new-order.feature index 5ec9620e4..ebc0961ee 100644 --- a/backend/bdd/features/pki/acme/new-order.feature +++ b/backend/bdd/features/pki/acme/new-order.feature @@ -1,10 +1,10 @@ Feature: New Order Scenario: Create a new order -# 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 + 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 # # TODO: make it I have an account already instead? -# 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 When I create certificate signing request as csr Then I add names to certificate signing request csr """ @@ -15,3 +15,4 @@ Feature: New Order """ Then I create a RSA private key pair as cert_key Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index d3f0c109e..254ea4af4 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -98,6 +98,13 @@ def step_impl(context: Context, email: str, kid: str, secret: str, account_var: context.vars[account_var] = context.acme_client.new_account(registration) +@then( + "I submit the certificate signing request PEM {pem_var} certificate order to the ACME server" +) +def step_impl(context: Context, pem_var: str): + context.acme_order = context.acme_client.new_order(context.vars[pem_var]) + + @when("I create certificate signing request as {csr_var}") def step_impl(context: Context, csr_var: str): context.vars[csr_var] = x509.CertificateSigningRequestBuilder() @@ -144,5 +151,4 @@ def step_impl(context: Context, csr_var: str, pk_var: str, pem_var: str): context.vars[csr_var] .sign(context.vars[pk_var], hashes.SHA256()) .public_bytes(serialization.Encoding.PEM) - .decode("utf-8") ) 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 e8967a9ac..098046ffc 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -54,7 +54,7 @@ import { type TPkiAcmeServiceFactoryDep = { certificateProfileDAL: Pick; acmeAccountDAL: Pick; - acmeOrderDAL: Pick; + acmeOrderDAL: Pick; acmeAuthDAL: Pick; acmeOrderAuthDAL: Pick; }; @@ -256,52 +256,67 @@ export const pkiAcmeServiceFactory = ({ accountId: string; payload: TCreateAcmeOrderPayload; }): Promise> => { - const account = await acmeAccountDAL.findById(profileId, accountId)!; // TODO: check and see if we have existing orders for this account that meet the criteria // if we do, return the existing order - const order = await acmeOrderDAL.create({ - accountId: account.id, - status: AcmeOrderStatus.Pending + const order = await acmeOrderDAL.transaction(async (tx) => { + const account = await acmeAccountDAL.findById(profileId, accountId)!; + const createdOrder = await acmeOrderDAL.create( + { + accountId: account.id, + status: AcmeOrderStatus.Pending + }, + tx + ); + const authorizations: TPkiAcmeAuths[] = await Promise.all( + payload.identifiers.map(async (identifier) => { + if (identifier.type === AcmeIdentifierType.DNS) { + // TODO: reuse existing authorizations for this identifier if they exist + return await acmeAuthDAL.create({ + accountId: account.id, + status: AcmeAuthStatus.Pending, + identifierType: identifier.type, + identifierValue: identifier.value, + // TODO: read config from the profile to get the expiration time instead + expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) + }); + } else { + throw new AcmeMalformedError({ detail: "Only DNS identifiers are supported" }); + } + }) + ); + + await acmeOrderAuthDAL.insertMany( + authorizations.map((auth) => ({ + orderId: order.id, + authId: auth.id + })) + ); + return { ...createdOrder, authorizations, account }; }); - const authorizations: TPkiAcmeAuths[] = await Promise.all( - payload.identifiers.map(async (identifier) => { - if (identifier.type === AcmeIdentifierType.DNS) { - // TODO: reuse existing authorizations for this identifier if they exist - return await acmeAuthDAL.create({ - accountId: account.id, - status: AcmeAuthStatus.Pending, - identifierType: identifier.type, - identifierValue: identifier.value, - // TODO: read config from the profile to get the expiration time instead - expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) - }); - } else { - throw new AcmeMalformedError({ detail: "Only DNS identifiers are supported" }); - } - }) - ); - await acmeOrderAuthDAL.insertMany( - authorizations.map((auth) => ({ - orderId: order.id, - authId: auth.id - })) - ); - - // FIXME: Implement ACME new order creation - const orderId = "FIXME-order-id"; return { status: 201, body: { status: "pending", + // TODO: read config from the profile to get the expiration time instead expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), - identifiers: [], - authorizations: [], - finalize: buildUrl(`/api/v1/pki/acme/profiles/${account.profileId}/orders/${orderId}/finalize`) + identifiers: order.authorizations.map((auth) => ({ + type: auth.identifierType, + value: auth.identifierValue + })), + authorizations: order.authorizations.map((auth) => ({ + id: auth.id, + status: auth.status, + identifier: { + type: auth.identifierType, + value: auth.identifierValue + } + })), + finalize: buildUrl(`/api/v1/pki/acme/profiles/${order.account.profileId}/orders/${order.id}/finalize`) }, headers: { - Location: buildUrl(`/api/v1/pki/acme/profiles/${account.profileId}/orders/${orderId}`) + Location: buildUrl(`/api/v1/pki/acme/profiles/${order.account.profileId}/orders/${order.id}`) } }; };