Override domain in TLS request

1. Caddy version:

caddy 2.6.2, Linux

2. How I installed, and run Caddy:

Running Caddy with the caddy-docker-proxy - extension in a Docker container

a. System environment:

Ubuntu 22.04

b. Command:

docker-compose up

c. Service/unit/compose file:

version: "3.7"
services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
      # image: pk/caddy:2.6.2
    ports:
      - 80:80
      - 443:443
    environment:
      - CADDY_INGRESS_NETWORKS=caddy
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - caddy_data:/data
      - caddy_config:/config
      - ./www:/www:ro
    restart: unless-stopped
    labels:
      caddy_0.debug: ""
      caddy_0.email: ptrxyz@gmx.de
      caddy_0.on_demand_tls.interval: 2m
      caddy_0.on_demand_tls.burst: 5
      caddy_0.acme_ca: https://acme-staging-v02.api.letsencrypt.org/directory
      caddy_20: domain1.net, (www.)?domain2.net
      caddy_20.tls.on_demand: ""
      caddy_20.redir: "https://www.domain1.net{uri}"
      caddy_10: www.domain1.net
      caddy_10.root: "* /www"
      caddy_10.file_server: ""
      caddy_10.encode: zstd gzip
networks:
  caddy:
    external: true

volumes:
  caddy_data:
  caddy_config:

d. My complete Caddy config:

// not applicable, auto-generated from docker labels, but it boils down to this:

domain1.net, (www.)?domain2.net {
    redir https://www.domain1.net${uri}
}

www.domain1.net {
    file_server
}

3. The problem I’m having:

Let’s say I have two domains, domain1.net and domain2.net, Let’s Encrypt should be enabled for both, DNS-Challenge is not an option for me, so I can’t use wildcard certs.
I would like to redirect domain1.net, domain2.net, and www.domain2.net to www.domain1.net.

What I basically do is this:

domain1.net, domain2.net, www.domain2.net {
    redir https://www.domain1.net${uri}
}

www.domain1.net {
    file_server
}

This works well, however, in fact I do not have just two domains, I have around 20. So listing all the domains one by one would not scale well. So I changed to do this instead, to not having to specify www.domain.net and domain.net all the time:

domain1.net, (www.)?domain2.net {
    redir https://www.domain1.net${uri}
}

www.domain1.net {
    file_server
}

My problem is, that this breaks Let’s Encrypts since the “real” domain names can’t be derived from the wildcard matcher. I somehow would have to tell Let’s encrypt to always request the Cert for the actual domain as soon as a request matches my host matcher:

domain1.net --> matches, so request cert for "domain1.net"
domain2.net --> matches, so request cert for "domain2.net"
www.domain2.net --> matches, so request cert for "www.domain2.net"
domain3.net --> does not match, do nothing

I figure that there is dns_challenge_override_domain yet it seems to only work for the DNS challenge.
I wonder if there is something like :

domain1.net, (www.)?domain2.net {
    redir https://www.domain1.net${uri}

    tls {
         override_request_domain ${host}     // this does not work, but this is what i probably want to do :D
    }
}

www.domain1.net {
    file_server
}

4. Error messages and/or full log output:

[ERROR]  Removing invalid block: subject does not qualify for certificate: 
domain1.net, (www.)?domain2.net {
    redir https://www.domain1.net{uri}
}

5. What I already tried:

see above.

6. Links to relevant resources:

Are the 20 domains a fixed number it will they increase?

Maybe On-Demand TLS is what you need, instead of reading the domain from config it dynamically obtains a new cert during the first TLS handshake.

If the list wont increase I would just add all to the Caddyfile

This on its own is unsafe. You must use an ask endpoint to avoid denial of service attacks. Someone could point a wildcard domain to your server then make requests infinitely making your server issue garbage TLS certs until you hit rate limits or run out of storage space.

That’s not a valid site address (obviously, as the error tells you). You can’t use regexp in the host matcher, which is what his maps to (plus extra logic to enable TLS automation for the domains in the site address).

What I’d suggest you do is something like this:

{
	on_demand_tls {
		ask http://localhost:9000/ask
	}
}

https:// {
	map {host}                {should_redir}  {target} {
		domain1.net           true            www.domain1.net
		~(www\.)?domain2.net  true            www.domain1.net
		default               false           -
	}

	@redirect `{should_redir}`
	handle @redirect {
		redir https://{target}{uri}
	}

	handle {
		# do whatever for other domains
	}
}

www.domain1.net {
	# serve your site
}

Using https:// as the site address is important to allow matching all domains.

The ask endpoint is important to only allow issuing certs for the domains you trust. You can implement that many different ways, how you do it is up to you. It could even be an internal site in Caddy (running on another port or w/e) which does a map like above to give a :+1: or :-1: to the hostname.

The map can have multiple outputs, one of them a boolean to indicate whether you should redirect. The @redirect matcher uses a CEL expression to match on the {should_redir} boolean output of the map, and the associated handle performs the redirect if so. And the last handle can do whatever fallback logic you want.

2 Likes

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