fleet-manager: point acme DNS-01 propagation check at a public resolver

Split-horizon DNS made lego's propagation check query the container's internal
resolver, which doesn't serve the public _acme-challenge TXT record, so the
challenge timed out. Add ACME_RESOLVERS (comma-separated host:port, default
8.8.8.8:53) → one --dns.resolvers flag each. Documented in .env + DEPLOY.md.
This commit is contained in:
Audiobuzz
2026-07-04 10:24:42 +00:00
parent d31c0f18eb
commit 693cf94695
4 changed files with 26 additions and 2 deletions
+4
View File
@@ -31,6 +31,10 @@ AWS_REGION=us-east-1
# Optional but recommended: pin the Route 53 zone. Skips ListHostedZonesByName
# (so you can drop it from the IAM policy) and disambiguates public/private zones.
AWS_HOSTED_ZONE_ID=
# Public resolver(s) for the DNS-01 propagation check (comma-separated host:port).
# Needed because internal/split-horizon DNS won't resolve the public challenge
# record. Defaults to Google DNS; override if 8.8.8.8 is blocked (e.g. 1.1.1.1:53).
ACME_RESOLVERS=8.8.8.8:53
# ACME_STAGING=1 # uncomment for a dry-run vs LE staging (UNTRUSTED certs, no rate limit)
# --- agent plane (gRPC + mTLS, fleet CA) ---------------------------------------
+6
View File
@@ -151,6 +151,12 @@ A publicly-trusted cert also keeps the WebAuthn secure-context clean (no browser
warnings). The side-car keeps renewing on its 12-hour loop; the manager reloads
each new cert automatically.
> **Split-horizon DNS:** the side-car defaults `ACME_RESOLVERS=8.8.8.8:53` so
> lego checks TXT propagation against **public** DNS. Without it, lego uses the
> container's internal resolver, which doesn't serve the public
> `_acme-challenge` record, and the challenge times out. Override if 8.8.8.8 is
> blocked (e.g. `1.1.1.1:53`, comma-separated for several).
> **Origin match:** the cert CN/SAN (`FLEET_MGR_RP_ID`) must equal the origin the
> browser uses *and* `FLEET_MGR_RP_ORIGIN` — passkeys are origin-bound. The admin
> listener binds loopback (`127.0.0.1:9500`) by default; expose it as
+13 -2
View File
@@ -32,6 +32,17 @@ LEGO=$(command -v lego 2>/dev/null || true)
[ -z "$LEGO" ] && [ -x /lego ] && LEGO=/lego
[ -z "$LEGO" ] && { echo "acme: FATAL lego binary not found (looked on PATH and /lego)"; exit 1; }
# Force the DNS-01 propagation check against a PUBLIC resolver — the container's
# internal/split-horizon DNS doesn't serve the public _acme-challenge record, so
# lego's default (system resolver) never sees the TXT and times out. Accepts a
# comma-separated list of host:port; each becomes a --dns.resolvers flag.
RESOLVERS=""
if [ -n "${ACME_RESOLVERS:-}" ]; then
OLDIFS=$IFS; IFS=','
for r in $ACME_RESOLVERS; do RESOLVERS="$RESOLVERS --dns.resolvers $r"; done
IFS=$OLDIFS
fi
while true; do
# First pass issues; later passes renew when near expiry.
if [ -f "/le/certificates/$DOMAIN.crt" ]; then
@@ -41,9 +52,9 @@ while true; do
fi
echo "acme: lego $* for $DOMAIN (Route 53 DNS-01)"
# shellcheck disable=SC2086 # $STAGING is intentionally two words when set
# shellcheck disable=SC2086 # $STAGING/$RESOLVERS intentionally word-split
if "$LEGO" --accept-tos --email "$EMAIL" --dns route53 --domains "$DOMAIN" \
--path /le $STAGING "$@"; then
--path /le $RESOLVERS $STAGING "$@"; then
install -m 644 "/le/certificates/$DOMAIN.crt" /out/fullchain.pem
install -m 600 "/le/certificates/$DOMAIN.key" /out/privkey.pem
chown "$UID_OUT:$UID_OUT" /out/fullchain.pem /out/privkey.pem 2>/dev/null \
+3
View File
@@ -125,6 +125,9 @@ services:
ACME_DOMAIN: "${FLEET_MGR_RP_ID:?set FLEET_MGR_RP_ID in .env}" # = the UI hostname
ACME_EMAIL: "${ACME_EMAIL:?set ACME_EMAIL in .env}"
ACME_STAGING: "${ACME_STAGING:-}" # set 1 for a dry-run
# Public resolver(s) for the propagation check — the internal/split-horizon
# DNS doesn't serve the public _acme-challenge record. Comma-separated host:port.
ACME_RESOLVERS: "${ACME_RESOLVERS:-8.8.8.8:53}"
# AWS creds for the Route 53 DNS-01 challenge (or omit + use an instance role).
AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID:-}"
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY:-}"