From cc33fcbcc8fb56fa35ff64807efe04aafc8dfe68 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Thu, 21 May 2026 13:27:05 -0600 Subject: [PATCH] caddy: add caddy-dns/rfc2136 + test-rfc2136 site -- self-hosted ACME flow Wires Caddy as the ACME client side of our new self-hosted DNS-01 flow. Proves the design end-to-end: caddy-dns/rfc2136 -> our CoreDNS rfc2136 plugin -> zone file write -> git auto-commit -> HE AXFR -> LE validates -> cert issued. Changes: - caddy/Dockerfile: --with github.com/caddy-dns/rfc2136 added alongside the existing caddy-dns/vultr. - caddy/Caddyfile: new test-rfc2136.supported.systems site that uses the new provider. server coredns:53 (docker internal), key from env, propagation_delay 60s + timeout 600s to accommodate HE pull. - docker-compose.yml: ACME_TSIG_SECRET passed to the caddy container (the same secret CoreDNS verifies on the other side of the loop). First cert issued in production: 2026-05-21 ~13:23 UTC. ~5.5 min end-to-end from Caddy starting to cert in hand. Documented in session notes; the cert sits unused in caddy-data/ until/unless something publishes ports 80/443 for that hostname. --- caddy/Caddyfile | 30 ++++++++++++++++++++++++++++++ caddy/Dockerfile | 3 ++- docker-compose.yml | 3 +++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 5d9ec5b..9c9535b 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -38,3 +38,33 @@ # "Caddy is alive" sanity check inside the compose network. respond "CoreDNS DoT/DoH endpoint. DoT: port 853. DoH: /dns-query" 200 } + +# ─── Test domain using caddy-dns/rfc2136 → our own CoreDNS plugin ── +# +# Proves the full self-hosted ACME DNS-01 loop end-to-end: +# 1. Caddy attempts to issue a cert for test-rfc2136.supported.systems +# 2. Caddy uses caddy-dns/rfc2136 to UPDATE _acme-challenge. +# TXT record into supported.systems via our CoreDNS plugin +# 3. Plugin verifies TSIG, writes the zone file, bumps SOA +# 4. CoreDNS reloads (auto plugin, ~30s) +# 5. HE pulls the new serial within ~300s +# 6. Let's Encrypt validates from public DNS via HE +# 7. Caddy issues the cert +test-rfc2136.supported.systems { + tls { + dns rfc2136 { + key_name acme-update-key. + key_alg hmac-sha256 + key {env.ACME_TSIG_SECRET} + # `coredns` resolves on the docker network to the CoreDNS + # container; port 53 is its in-container listener. + server coredns:53 + } + # Use public resolvers (not docker's embedded DNS) for the + # post-update propagation check. Allow HE plenty of time to pull. + resolvers 1.1.1.1 9.9.9.9 1.0.0.1 + propagation_delay 60s + propagation_timeout 600s + } + respond "test-rfc2136: cert issued via self-hosted RFC 2136 ACME flow!" 200 +} diff --git a/caddy/Dockerfile b/caddy/Dockerfile index 7adcd92..9c59fb0 100644 --- a/caddy/Dockerfile +++ b/caddy/Dockerfile @@ -8,7 +8,8 @@ FROM caddy:2.10.0-builder AS builder # a plugin's minimum Go version moves. ENV GOTOOLCHAIN=auto RUN xcaddy build \ - --with github.com/caddy-dns/vultr + --with github.com/caddy-dns/vultr \ + --with github.com/caddy-dns/rfc2136 FROM caddy:2.10.0 COPY --from=builder /usr/bin/caddy /usr/bin/caddy diff --git a/docker-compose.yml b/docker-compose.yml index 1147848..136476c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,9 @@ services: environment: - CADDY_HOSTNAME=${CADDY_HOSTNAME} - ACME_EMAIL=${ACME_EMAIL} + # Used by caddy-dns/rfc2136 for the test-rfc2136 site -- same + # secret CoreDNS's rfc2136 plugin verifies on the other side. + - ACME_TSIG_SECRET=${ACME_TSIG_SECRET} # Optional: only required for Caddy's DNS-01 cert renewal via Vultr's # API. Cert is valid ~90 days; this env var only matters within the # final 30d renewal window. Empty default keeps `docker compose up`