caddyserver / caddy

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
https://caddyserver.com
Apache License 2.0
58.65k stars 4.05k forks source link

How to use both custom certificates and wildcard auto-generated ones? #5216

Closed pedrolamas closed 2 months ago

pedrolamas commented 2 years ago

I want to caddy to generate a wildcard certificate *.example.com, and then use that for multiple hosts (like homeassistant.example.com and other.example.com) on port 443.

I also want to have homeassistant-external.example.com on port 21443 so I can use a manually set certificate.

Basically, I want that when I access https://homeassistant.example.com:443 the certificate used is the auto-generated wildcard one, and when I access https://homeassistant-external.example.com:21443 it uses the supplied certificate instead.

The problem is the moment I add the :21443 block, it will always pick up that certificate for both :443 and :21443, and ignore the auto-generated one!

I have this setup working fine under nginx, but I haven’t been able to do it with caddy…

(Note: this is a follow up on https://caddy.community/t/how-to-use-custom-certificates-with-wildcard-generated-ones/17808/1)

docker-compose.yml

version: '3.7'

services:
  caddy:
    build: ./caddy
    restart: unless-stopped
    networks:
      backbone:
        ipv4_address: 10.0.0.12
    ports:
      - "80:80"
      - "443:443"
      - "21443:21443"
    volumes:
      - "./caddy/Caddyfile:/etc/caddy/Caddyfile"
      - "./caddy/data:/data"
      - "./caddy/cf-certs/certificate.pem:/etc/ssl/certs/cf-certificate.pem"
      - "./caddy/cf-certs/origin-pull-ca.pem:/etc/ssl/certs/cf-origin-pull-ca.pem"
      - "./caddy/cf-certs/privatekey.pem:/etc/ssl/private/cf-key.pem"
    env_file:
      - ./common.env
      - ./caddy/secrets.env
    dns: 10.0.0.3

networks:
  backbone:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: backbone
    ipam:
      config:
        - subnet: 10.0.0.0/27

Dockerfile

FROM caddy:builder-alpine AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare

FROM caddy:alpine

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Caddyfile

*.example.com:443 {
    tls {
        dns cloudflare {env.CADDY_CLOUDFLARE_TOKEN}
    }

    @homeassistant host homeassistant.example.com
    handle @homeassistant {
        encode zstd gzip
        reverse_proxy homeassistant:8123
    }

    @other host other.example.com
    handle @other {
        encode zstd gzip
        reverse_proxy other
    }
}

:21443 {
     tls /etc/ssl/certs/cf-certificate.pem /etc/ssl/private/cf-key.pem {
         client_auth {
             mode require_and_verify
             trusted_ca_cert_file /etc/ssl/certs/cf-origin-pull-ca.pem
         }
     }

     @homeassistant host homeassistant-external.example.com
     handle @homeassistant {
         encode zstd gzip
         reverse_proxy homeassistant:8123
     }
}

Log entries

{"level":"info","ts":1669049465.481152,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
{"level":"warn","ts":1669049465.4910066,"msg":"Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
{"level":"info","ts":1669049465.4954476,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1669049465.4981682,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x4000440310"}
{"level":"info","ts":1669049465.50363,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
{"level":"info","ts":1669049465.5037131,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1669049465.51021,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"info","ts":1669049465.5101776,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/data/caddy"}
{"level":"info","ts":1669049465.510438,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details."}
{"level":"debug","ts":1669049465.5105987,"logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":true}
{"level":"info","ts":1669049465.5106263,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"debug","ts":1669049465.5107412,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
{"level":"info","ts":1669049465.5107656,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"info","ts":1669049465.5107765,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["*.example.com"]}
{"level":"debug","ts":1669049465.5117052,"logger":"tls","msg":"loading managed certificate","domain":"*.example.com","expiration":1676811013,"issuer_key":"acme-v02.api.letsencrypt.org-directory","storage":"FileStorage:/data/caddy"}
{"level":"debug","ts":1669049465.5124776,"logger":"tls.cache","msg":"added certificate to cache","subjects":["*.example.com"],"expiration":1676811013,"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"73ca35f4146d93539ede788e63f664049395110f58b0fbd301b215124fb07479","cache_size":1,"cache_capacity":10000}
{"level":"debug","ts":1669049465.5125697,"logger":"events","msg":"event","name":"cached_managed_cert","id":"48c0b6ea-a061-4272-b97d-0564946e6fac","origin":"tls","data":{"sans":["*.example.com"]}}
{"level":"info","ts":1669049465.5134115,"logger":"tls","msg":"finished cleaning storage units"}
{"level":"info","ts":1669049465.5136263,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1669049465.5136645,"msg":"serving initial configuration"}
mholt commented 1 year ago

Thanks for opening an issue, I or someone will look at this soon!

pedrolamas commented 1 year ago

For the record, I managed to bypass this issue by running two separate Caddy Docker containers, one for the internal stuff (using the auto-generated wildcard certificate), and the other for the external (with the Cloudflare certificate)

amy1337 commented 6 months ago

Thanks for opening an issue, I or someone will look at this soon!

Any news on this? I current plan to host services on port 443 with a, lets say letsencrypt wildcard cert, and other services (also on port 443) with my own certs so hosting 2 caddy instances sadly isn't an option.

mholt commented 6 months ago

@amy1337 This issue is pretty old by now, could you try with the latest Caddy version (ideally the latest 2.8 beta) and show us your config and corresponding curl -v commands that demonstrate what is not working as expected.

vehlwn commented 2 months ago

I think I have a similar problem: tls directive from one site block leaks to another blocks. I want to use wildcard certificate but my DNS hosting doesn't support plugins for auto renewing, so I requested certificates through certbot and copied them to caddy folder.

My config is:

{
    auto_https disable_certs
}

service1.example.com {
    tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/privkey.pem
    respond "ok1"
}

service2.example.com {
    # no tls option
    respond "ok2"
}

When I curl either https://service1.example.com or service2 they both use wildcard certificate from the tls option in service1 block. Is this behavior intentional? If yes then why tls directive in not global?

PS: caddy 2.8.4 on Arch Linux

PPS: Docs page for tls directive never mentions this implicit behavior.

polarathene commented 2 months ago

both use wildcard certificate from the tls option in service1 block. Is this behavior intentional?

I don't think it is intentional. You should get a better idea with caddy adapt to view the JSON that Caddyfile is converted to.

There is a WIP feature PR that is meant to get the behaviour you describe from a wildcard site address becoming the priority over provisioning new certs for subdomains that the wildcard could be used for.

In that PR they state if you have a single domain you don't want to use wildcard with, you would not use the feature. But since you've relied on the tls directive that is externally provisioned cert rather than managed by Caddy, the behaviour you get is different AFAIK, so you've triggered a bug I assume (at least with the Caddyfile logic), and I don't think we have tls acme to explicitly configure the default cert provisioning logic 🤔 (which would probably resolve the linked PR use-case too where a user wants some site blocks to ignore the wildcard and provision a new cert).


EDIT: As pointed out below, I misunderstood the auto_https mode.. disable_certs instructed Caddy not to provision certs for a site address, so it found and used the one you manually loaded from an external source that was a valid match.

francislavoie commented 2 months ago

@vehlwn I think in your case, you want auto_https ignore_loaded_certs, not disable_certs. See https://caddyserver.com/docs/caddyfile/options#tls-options

The reason the Caddyfile's tls was designed as a directive in sites is because it's convenient to be able to associate the site addresses (domains) with the TLS automation policies (i.e. how to get a cert for the domains of that site) or TLS connection policies (if you need client auth, requiring clients to pass a certificate). If it was global then you'd have to make that association yourself (i.e. duplicate your domain in the config). The TLS layer is "global" though (it's an "app", as @polarathene said you can adapt your config to JSON to get an idea of how it actually looks with caddy adapt -p). The auto_https modes are there to provide escape hatches for that automatic wiring behaviour.

vehlwn commented 2 months ago

ignore_loaded_certs manages certificates automatically. I don't want caddy to issue different certificates for each domain because I don't want all my domains to be publicly available for hackers to scrap them. See https://crt.sh/?q=archlinux.org for example.

In nginx ssl_certificate option can be used outside any server block so multiple ssl servers can inherit it.

francislavoie commented 2 months ago

I don't understand what your complaint is about then. Just use tls <cert> <key> as you already were.

polarathene commented 2 months ago

I don't want caddy to issue different certificates for each domain because I don't want all my domains to be publicly available for hackers to scrap them.

That is why you use the wildcard certificate.

In nginx ssl_certificate option can be used outside any server block so multiple ssl servers can inherit it.

So your actual complaint is not about the wildcard cert being used, but why each site block has tls directive instead of a global directive?

This was explained by looking at the JSON output.

francislavoie commented 2 months ago

I'll close this as inactive. I'm not sure there's anything actionable here.