Delegating deployment of DNS-01 challenge records to a subdomain using DigitalOcean and Caddy

Hi,

I am preparing a proof of concept for a large infrastructure change at my company, and as part of it I am proposing we put majority of our web-based services behind a Caddy reverse proxy, using Let's Encrypt for SSL. The primary constraint here is that we would prefer using DNS-01 challenge for Let's Encrypt, as we have a lot of internal services that are not accessible from the internet, and we would like to avoid opening them up just for the sake of Let's Encrypt.

The problem is that we won't be able to move our domain name to a new registrar and the current registrar does not support adding/updating DNS records via API, so I want to create a subdomain, say acme-challenge-delegate.mycompany.com, and use DigitalOcean to serve records for this zone only (as we can automate DNS records there via the caddy-dns/digitalocean plugin). Then, every time we would add a new service behind the reverse proxy, I would create a new CNAME _acme-challenge record for the service's domain (i.e. _acme-challenge.blog.mycompany.com) pointing to the separated zone, and Caddy would somehow understand that it should deploy a TXT record for the challenge to the delegated record: _acme-challenge.blog.acme-challenge-delegate.mycompany.com.

I have tested this with my own jnko.cz domain and I cannot get it to work (just for context, this domain is on Cloudflare, but I need to simulate the production environment, hence delegating a subdomain to DigitalOcean for deploying the challenge records - I am well aware that if this was my personal use-case I could just use caddy-dns/cloudflare plugin):

  1. I have created acme-challenge-delegate.jnko.cz domain in DigitalOcean and pointed NS records to DigitalOcean's nameservers in Cloudflare.

  2. I have created _acme-challenge.subdom.jnko.cz CNAME record pointing to _acme-challenge.subdom.acme-challenge-delegate.jnko.cz in Cloudflare.

  3. Just for testing, I have created a TXT record for _acme-challenge.subdom.acme-challenge-delegate.jnko.cz with just string abc

  4. I have tested that by querying a TXT record for _acme-challenge.subdom.jnko.cz -> I do get the abc string.

  5. In my caddy file, I have added the following snippet:


(digitalocean-tls) {

    tls {

        dns digitalocean {env.DIGITALOCEAN_API_TOKEN}

        resolvers 1.1.1.1

    }

}

{$DOMAINS} {

    reverse_proxy {$UPSTREAM}

    import digitalocean-tls

    import secure-headers

}


The variables resolve to the following values:

  • {env.DIGITALOCEAN_API_TOKEN}: my-digitalocean-api-token (obviously this is not the real token)

  • {$DOMAINS}: subdom.jnko.cz

  • {$UPSTREAM}: http://localhost:8080

Having checked the Docker container logs, I can see that the caddy container si attempting to add a temporary record to zone jnko.cz, which obviously doesn't exist in DigitalOcean, as it is managed in Cloudflare:


2024-08-21 13:14:37 {"level":"info","ts":1724242477.8642247,"logger":"tls.obtain","msg":"obtaining certificate","identifier":"subdom.jnko.cz"}

2024-08-21 13:14:37 {"level":"info","ts":1724242477.8646028,"logger":"tls.issuance.acme","msg":"using ACME account","account_id":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/160238903","account_contact":["mailto:<redacted>"]}

2024-08-21 13:14:38 {"level":"info","ts":1724242478.3440325,"logger":"tls.issuance.acme.acme_client","msg":"trying to solve challenge","identifier":"subdom.jnko.cz","challenge_type":"dns-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}

2024-08-21 13:14:38 {"level":"error","ts":1724242478.8003442,"logger":"tls.issuance.acme.acme_client","msg":"cleaning up solver","identifier":"subdom.jnko.cz","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.subdom.jnko.cz\" (usually OK if presenting also failed)"}

2024-08-21 13:14:38 {"level":"error","ts":1724242478.9615953,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"subdom.jnko.cz","issuer":"acme-v02.api.letsencrypt.org-directory","error":"[subdom.jnko.cz] solving challenges: presenting for challenge: adding temporary record for zone \"jnko.cz.\": POST https://api.digitalocean.com/v2/domains/jnko.cz/records: 404 (request \"bdc12acf-d815-4714-b1eb-f247ec5b9f94\") domain not found (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/160238903/18549605053) (ca=https://acme-staging-v02.api.letsencrypt.org/directory)"}

2024-08-21 13:14:38 {"level":"error","ts":1724242478.9617307,"logger":"tls.obtain","msg":"will retry","error":"[subdom.jnko.cz] Obtain: [subdom.jnko.cz] solving challenges: presenting for challenge: adding temporary record for zone \"jnko.cz.\": POST https://api.digitalocean.com/v2/domains/jnko.cz/records: 404 (request \"bdc12acf-d815-4714-b1eb-f247ec5b9f94\") domain not found (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/160238903/18549605053) (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":3,"retrying_in":120,"elapsed":185.132033446,"max_duration":2592000}

I am hoping someone here might be able to help? I have not tried delegating deployment of challenge records to a subdomain before... Is there a different/better approach, if this isn't suitable?

Many thanks in advance!

3 Likes

Your general idea looks fine and you have a good understanding of the issues.

I do not know Caddy well but you may need to use dns_challenge_override_domain

In general, some ACME clients have DNS challenge plugins that automatically follow any kind of delegation (NS / CNAME) but others require you to set an "alias" in the ACME Client too that describes the DNS delegation. I think the above setting is how Caddy does it.

This is probably a better discussion on the Caddy forum but I think there will be other volunteers here with good thoughts about your overall strategy. And, perhaps @mholt will want to add comment?

PS: Oh, and welcome to the community @jnko266

6 Likes

Yep I think dns_challenge_override_domain is what you want.

Feel free to join us over at the Caddy community if you have more Caddy questions!

5 Likes

Can confirm, dns_challenge_override_domain did the trick!

Many thanks to both @mholt and @MikeMcQ

4 Likes