Skip to content

await auto() exits prematurely when dns-01 challenge failed for wildcard certificate #75

@frenzzy

Description

@frenzzy

await auto() prematurely exits when the dns-01 challenge fails while obtaining a wildcard certificate, without waiting for the http-01 challenge to complete or be canceled. This leads to background code execution, which unpredictably affects the behavior of multiple auto() function calls (for example, when challengeCreateFn manipulates files).

Let's look at:

import acme from 'acme-client'

async function updateCert(domain: string) {
  try {
    const client = new acme.Client({
      directoryUrl: acme.directory.letsencrypt.staging,
      accountKey: await acme.crypto.createPrivateKey(),
    })

    const [key, csr] = await acme.crypto.createCsr({
      commonName: `*.${domain}`,
      altNames: [domain],
    })

    const cert = await client.auto({
      csr,
      email: 'test@example.com',
      termsOfServiceAgreed: true,
      challengeCreateFn: async (authz, challenge, keyAuthorization) => {
        if (challenge.type === 'http-01') {
          const filePath = `/.well-known/acme-challenge/${challenge.token}`
          const fileContents = keyAuthorization
          console.log(`Creating challenge response for ${authz.identifier.value} at path: ${filePath}`)
        } else if (challenge.type === 'dns-01') {
          const domainName = authz.identifier.value
          const dnsRecord = `_acme-challenge.${domainName}`
          console.log(`Creating TXT record for ${domainName}: ${dnsRecord}`)
          throw new Error('OOPS!') // <----------------------------------------------------------------
        }
      },
      challengeRemoveFn: async (authz, challenge, keyAuthorization) => {
        if (challenge.type === 'http-01') {
          const filePath = `/.well-known/acme-challenge/${challenge.token}`
          console.log(`Removing challenge response for ${authz.identifier.value} at path: ${filePath}`)
        } else if (challenge.type === 'dns-01') {
          const domainName = authz.identifier.value
          const dnsRecord = `_acme-challenge.${domainName}`
          console.log(`Removing TXT record for ${authz.identifier.value}: ${dnsRecord}`)
        }
      },
    })

    console.log('Success')
  } catch (err) {
    console.log(`Failed to refresh TLS certificate for ${domain}`)
    throw err
  }
}

updateCert('example.com')

it logs OOPS! error but continues doing something in background after that:

[auto] Checking account
[auto] Registering account
HTTP request: get https://acme-staging-v02.api.letsencrypt.org/directory
RESP 200 get https://acme-staging-v02.api.letsencrypt.org/directory
HTTP request: head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
RESP 200 head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
Using nonce: 54K1CK85xaffeP4djlJrEVw673ADQ1nAYupeL2on4GvZYdONtsI
HTTP request: post https://acme-staging-v02.api.letsencrypt.org/acme/new-acct
RESP 201 post https://acme-staging-v02.api.letsencrypt.org/acme/new-acct
[auto] Parsing domains from Certificate Signing Request
[auto] Resolved 2 unique domains from parsing the Certificate Signing Request
[auto] Placing new certificate order with ACME provider
HTTP request: head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
RESP 200 head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
Using nonce: 54K1CK85VnCktCqmrES2lo61EY7iYBhwrkEoM7VJlhnFHkbRDYg
HTTP request: post https://acme-staging-v02.api.letsencrypt.org/acme/new-order
RESP 201 post https://acme-staging-v02.api.letsencrypt.org/acme/new-order
HTTP request: head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
HTTP request: head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
RESP 200 head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
Using nonce: 54K1CK854pZkxtPGJfZe6NxC4-zJNkz9NkDdELmTCC0MJIoy_5E
HTTP request: post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760414
RESP 200 head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
Using nonce: 34mwA6roheyeT68YFin8zRd3jpAN780m4Hc6nf1TX_NNe9byKHg
HTTP request: post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760424
RESP 200 post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760414
RESP 200 post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760424
[auto] Placed certificate order successfully, received 2 identity authorizations
[auto] Resolving and satisfying authorization challenges
[auto] [example.com] Found 1 challenges, selected type: dns-01
[auto] [example.com] Trigger challengeCreateFn()
[auto] [example.com] Found 3 challenges, selected type: http-01
[auto] [example.com] Trigger challengeCreateFn()
[auto] Waiting for challenge valid status
Triggered challengeCreateFn()
Creating TXT record for example.com: _acme-challenge.example.com
Triggered challengeCreateFn()
Creating challenge response for example.com at path: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0
[auto] [example.com] Trigger challengeRemoveFn()
Triggered challengeRemoveFn()
Removing TXT record for example.com: _acme-challenge.example.com
[auto] [example.com] challengeRemoveFn threw error: Required environment variable SELECTEL_TOKEN is not set
[auto] [example.com] Unable to complete challenge: Required environment variable SELECTEL_TOKEN is not set
[auto] [example.com] Deactivating failed authorization
HTTP request: head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
[auto] [example.com] Running challenge verification
Waiting for ACME challenge verification
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
RESP 200 head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
Using nonce: 54K1CK85vznMQ8eRKhpxXHaGgHawILZjv0k_YU8RY4PHVlXujF4
HTTP request: post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760414
Promise rejected attempt #1, retrying in 5000ms: Request failed with status code 404
RESP 200 post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760414
Failed to refresh TLS certificate for example.com
OOPS! <---------------------------------------------------------------------------------------------------------------------
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #2, retrying in 10000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #3, retrying in 20000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #4, retrying in 30000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #5, retrying in 30000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #6, retrying in 30000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #7, retrying in 30000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #8, retrying in 30000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
Promise rejected attempt #9, retrying in 30000ms: Request failed with status code 404
Sending HTTP query to example.com, suffix: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0, port: 80
[auto] [example.com] Trigger challengeRemoveFn()
Triggered challengeRemoveFn()
Removing challenge response for example.com at path: /.well-known/acme-challenge/L_ySSYQ5PGwruQTo10DkFph30LAYEBLi0lqa_BWhlN0
[auto] [example.com] Unable to complete challenge: Request failed with status code 404
[auto] [example.com] Deactivating failed authorization
HTTP request: head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
RESP 200 head https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
Using nonce: 54K1CK855u0Gsc0GGn1uPWAXFeNQicZgUskAUlFM2fb2QtSqfrY
HTTP request: post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760424
RESP 200 post https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/8167760424

I would expect that any background work is stopped in case of exception thrown or exception is thrown when all background work is finished to eliminate any possible side effects after re-running the auto() function.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions