Dynamic variable replacements for dns_challenge_override_domain

1. Output of caddy version:

v2.5.2

2. How I run Caddy:

Installed with brew running on localhost

a. System environment:

Mac

b. Command:

caddy run

c. Service/unit/compose file:

d. My complete Caddy config:

# Catch all for custom domains, terminate SSL on demand and reverse proxy to app
{
        reverse_proxy 127.0.0.1:8090

        tls {
                on_demand
                dns route53 {
                        aws_profile "my_profile"
                        max_retries 1
                }
                dns_challenge_override_domain _acme-challenge.myappsite.com
        }

        log {
                output file /var/log/caddy/access.log
        }
}
   

3. The problem I’m having:

I’m trying to onboard custom domains.
The flow is:

  • client site creates cname record pointing to app site, and another cname record for _acme-challenge.app pointing to _acme-challenge.myappsite.com
  • app site currently has an A name record pointing to localhost 127…
  • locally I’m running caddy, which receives the request when app.clientsite.com is called, at this point it creates a certificate (on_demand), then reverse proxy to 127…:8090 where I have a go server running the application.

All working well.
The question is - is it possible to substitute the requested host name into the dns_challenge_override_domain at run time?
Something like:
dns_challenge_override_domain _acme-challenge.{env.DOMAIN}.myappsite.com
The challenge dns record needs to be unique per client (in case two are doing it at the same time), so ideally it would be something like _acme-challenge.app.clientsite.com.myappsite.com

Is this possible? Or would I need to push a config block per clientsite with the unique dns_challenge_override_domain each time?

For reference, I need to use dns challenges as I will need wildcard certificates (also, with my current setup I think that’s the only way it will work given letsencrypt doesn’t have access to verify an http challenge end-point).

4. Error messages and/or full log output:

5. What I already tried:

{env.DOMAIN}

6. Links to relevant resources:

Not at runtime, but you can replace it at config-adapt time with the Caddyfile style of environment variable replacements, i.e. {$DOMAIN}, instead.

I don’t think that’s true, you can have multiple TXT records with the same name, with different challenge values. ACME issuers will check if any of the configured TXT records match their expected challenge.

That should work as long as the route53 plugin supports not overwriting the whole record set when adding a new one, and only deleted the relevant one when the challenge is completed. This post domain name system - Multiple TXT fields for same subdomain - Server Fault seems to suggest it should be possible with route53, but the plugin may or may not support it.

2 Likes

Thanks for the quick response.

Not at runtime, but you can replace it at config-adapt time with the Caddyfile style of environment variable replacements, i.e. {$DOMAIN} , instead.

Config adapt happens at caddy run, is that correct? I need it to be dynamic per client site, so could app.client1.com, app.client2.com replaced into that variable, I’m guessing that’s not suitable here.

I don’t think that’s true, you can have multiple TXT records with the same name

That would be ideal, unfortunately it throws
Tried to create resource record set ... but it already exists so looks like route53 doesn’t support adding them one at a time. I see the same in the route53 ui, I can’t add them one at a time.

@francislavoie I saw your comment on a related thread “With On-Demand TLS enabled, you don’t update your config at all, when you want to allow a new domain.”

Is there an alternative approach to using dns_challenge_override_domain, or is it just a limitation if I’m using route53? I’m finding if I don’t define dns_challenge_override_domain then it tries to create the challenge in a new zone for the client site with the custom domain, rather than the target app site (which the client site is forwarding the challenge to with a cname).

Yeah, the config is adapted from Caddyfile to JSON before the server starts, or when you reload your config with caddy reload.

I think that’s simply a bug with the route53 caddy-dns plugin. You can open an issue there to ask to support this.

If you read the link I sent, they clarify that you can, but you need to use a newline to enter more than one value.

That’s correct, the point of on_demand is to dynamically issue certificates when you receive a TLS handshake (at runtime, synchronously) instead of doing it asynchronously any time after the server is started (usually as soon as possible when started).

Pairing on_demand with DNS challenge is essentially undefined behaviour, they were never intended to be used together. But I guess with the recent addition of dns_challenge_override_domain there is an argument for that functionality. Either way, you’re treading new ground here, I don’t think anyone else does it this way.

Are you absolutely sure you need wildcard certs? Why can’t you just use a single domain per customer and do routing based on path? Or can’t you let Caddy use the HTTP or TLS-ALPN challenges for individual domains (provided that you only need a few specific subdomains per customer)?

1 Like

Thanks for your help, I’ll look into route53 plugin further.

Are you absolutely sure you need wildcard certs?

The issue I have is that a customer will eventually also have 4th level domains for different staging sites, e.g. staging.app.clientsite.com, and they could have any number of these, and potentially also staging.app2.clientsite.com. Let’s encrypt has a 5 cert limit which is why I need the wildcard cert.

LetsEncrypt has a limit of 50/w per Registered Domain (Domain and all its subdomains)
See Rate Limits - Let's Encrypt an extended list and explainations.

And in case you do hit the LE rate limit, Caddy simply falls back to ZeroSSL.
See Automatic HTTPS — Caddy Documentation

Ah. Thanks @IndeedNotJames , I’ll review my options then.

@francislavoie If i switch off using dns challenges, what does on demand tls use by default - is it the http-challenge? If so is there a standard way to test this locally? I.e. my domain is resolving to 127.0.0.1 localhost, so if I want to test using the lets encrypt staging site (https://acme-staging-v02.api.letsencrypt.org/directory), presumably any http challenge will fail as it won’t have access to my local site to see that the http://<YOUR_DOMAIN>/.well-known/acme-challenge/ has been created. So would I be right in saying on localhost I would just use tls internal, and to test lets encrypt I would need to set up my app on a public staging site?

Either HTTP or TLS-ALPN, at random. It doesn’t matter though. As long as you have ports 80 and 443 open and publicly accessible, either will work. Nothing to configure.

Correct, your domain must be publicly accessible for HTTP or TLS-ALPN challenges to work. No way around that. it’s a hard requirement.

Yeah, you can do that.

Great, thanks for all your help

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.