fleet-manager: finish self-TLS cleanup (compose, env, docs, delete acme/)

Follow the code removal through the rest of the tree now that Traefik owns UI TLS:
- compose: drop FLEET_MGR_ADMIN_BEHIND_PROXY + ADMIN_CERT/_KEY from the manager
  env; correct the ./pki mount comment (agent + broker certs only).
- .env.example: drop the BEHIND_PROXY + cert vars + self-TLS note.
- DEPLOY.md / README: remove the self-TLS "alternative" and the BEHIND_PROXY /
  ADMIN_CERT config rows.
- server-pki.sh: point the admin UI cert at Traefik, not ./pki/admin.
- delete serving/fleet-manager/acme/ (the retired ACME side-car).
This commit is contained in:
Audiobuzz
2026-07-05 01:46:52 +00:00
parent 76c3a6aaa7
commit e6b1f0308e
6 changed files with 16 additions and 107 deletions
+2 -5
View File
@@ -9,14 +9,11 @@ FLEET_MGR_RP_NAME=Fleet Command
# --- admin plane (behind the Traefik ingress) ---------------------------------- # --- admin plane (behind the Traefik ingress) ----------------------------------
# Traefik terminates the LE cert on :443 and forwards plaintext here; the manager # Traefik terminates the LE cert on :443 and forwards plaintext here; the manager
# keeps passkey auth ON but serves plaintext on loopback (only Traefik reaches it). # serves passkey auth over plaintext on loopback (only Traefik reaches it). The
# manager never terminates TLS itself.
FLEET_MGR_ADMIN_ADDR=127.0.0.1:9500 FLEET_MGR_ADMIN_ADDR=127.0.0.1:9500
FLEET_MGR_ADMIN_BEHIND_PROXY=true
# Dev-only escape hatch: plaintext, NO passkey auth. NEVER set true in prod. # Dev-only escape hatch: plaintext, NO passkey auth. NEVER set true in prod.
FLEET_MGR_ADMIN_INSECURE=false FLEET_MGR_ADMIN_INSECURE=false
# Only for self-TLS mode (BEHIND_PROXY=false + a cert, no Traefik — see DEPLOY.md):
FLEET_MGR_ADMIN_CERT=/pki/admin/fullchain.pem
FLEET_MGR_ADMIN_KEY=/pki/admin/privkey.pem
# --- Traefik ACME (Let's Encrypt DNS-01 via Route 53) -------------------------- # --- Traefik ACME (Let's Encrypt DNS-01 via Route 53) --------------------------
# Traefik issues + auto-renews the :443 cert. Set the UI hostname in # Traefik issues + auto-renews the :443 cert. Set the UI hostname in
-6
View File
@@ -149,12 +149,6 @@ also keeps the WebAuthn secure-context clean.
> manager binds loopback (`127.0.0.1:9500`) — only Traefik reaches it; never expose > manager binds loopback (`127.0.0.1:9500`) — only Traefik reaches it; never expose
> that port directly. > that port directly.
> **Self-TLS alternative (no Traefik):** set `FLEET_MGR_ADMIN_BEHIND_PROXY=false`
> and give the manager a cert at `/pki/admin/{fullchain,privkey}.pem` (e.g. via the
> `acme/renew-loop.sh` helper under a `harbor.bitasylum.org/dockerhub/goacme/lego`
> container). The manager then
> terminates TLS itself and the origin includes the port it listens on.
<a name="network-paths"></a> <a name="network-paths"></a>
### Network paths (three planes, three trust stages) ### Network paths (three planes, three trust stages)
+4 -8
View File
@@ -41,11 +41,9 @@ that's service-to-service — see [Authorise](#authorise-pillar).)
a publicly-trusted **Let's Encrypt** cert (**Route 53 DNS-01** — the UI is on the a publicly-trusted **Let's Encrypt** cert (**Route 53 DNS-01** — the UI is on the
mgmt network, not internet-reachable, so HTTP-01/TLS-ALPN-01 can't validate) on mgmt network, not internet-reachable, so HTTP-01/TLS-ALPN-01 can't validate) on
:443 and forwards plaintext to the manager, which runs :443 and forwards plaintext to the manager, which runs
`FLEET_MGR_ADMIN_BEHIND_PROXY=true` (passkey auth ON, plaintext on loopback — passkey auth ON, plaintext on loopback (WebAuthn only needs the origin to match,
WebAuthn only needs the origin to match, not the app's own transport). Traefik not the app's own transport). The manager never terminates TLS itself. Traefik
is the k8s ingress-controller + cert-manager role in miniature. *(Self-TLS mode is the k8s ingress-controller + cert-manager role in miniature.
without Traefik is still available: `BEHIND_PROXY=false` + a cert at
`FLEET_MGR_ADMIN_CERT/_KEY`, e.g. from `acme/renew-loop.sh`.)*
## Monitoring path (no host ever touches InfluxDB) ## Monitoring path (no host ever touches InfluxDB)
@@ -116,9 +114,7 @@ inventory for offline delivery. Metrics writing is behind a `MetricsSink`
| Var | Default | Notes | | Var | Default | Notes |
|---|---|---| |---|---|---|
| `FLEET_MGR_ADMIN_ADDR` | `127.0.0.1:9500` | admin listener (loopback — Traefik forwards here) | | `FLEET_MGR_ADMIN_ADDR` | `127.0.0.1:9500` | admin listener (loopback — Traefik forwards here; always plaintext) |
| `FLEET_MGR_ADMIN_BEHIND_PROXY` | `false` | passkey ON, plaintext listener (Traefik/ingress terminates TLS). Compose defaults it `true` |
| `FLEET_MGR_ADMIN_CERT` / `_KEY` | — | UI server cert/key — **self-TLS mode only** (`BEHIND_PROXY=false`, no Traefik) |
| `FLEET_MGR_RP_ID` | — | WebAuthn RP ID = UI hostname (`diskless-server.bitasylum.org`) | | `FLEET_MGR_RP_ID` | — | WebAuthn RP ID = UI hostname (`diskless-server.bitasylum.org`) |
| `FLEET_MGR_RP_ORIGIN` | — | WebAuthn origin (`https://diskless-server.bitasylum.org`) | | `FLEET_MGR_RP_ORIGIN` | — | WebAuthn origin (`https://diskless-server.bitasylum.org`) |
| `FLEET_MGR_RP_NAME` | `Fleet Command` | RP display name | | `FLEET_MGR_RP_NAME` | `Fleet Command` | RP display name |
-74
View File
@@ -1,74 +0,0 @@
#!/bin/sh
# renew-loop.sh — admin-UI cert issue/renew loop (self-TLS alternative).
#
# NOTE: the default deployment fronts the admin UI with Traefik (docker-compose.yml),
# which owns ACME itself — this script is NOT used then. It remains as the cert
# helper for the SELF-TLS admin mode (FLEET_MGR_ADMIN_BEHIND_PROXY=false, no
# Traefik): run it under a harbor.bitasylum.org/dockerhub/goacme/lego container to
# write ./pki/admin/.
#
# Runs inside the goacme/lego image (alpine-based). Obtains the fleet-manager
# admin-UI cert from Let's Encrypt via DNS-01 over AWS Route 53, and writes it
# into /out — bind-mounted to the SAME ./pki/admin dir the manager reads, so the
# manager's certReloader hot-swaps it on the next mtime bump (NO restart).
#
# The UI is on the mgmt network (not internet-reachable), so only DNS-01 can
# validate; Route 53 automates the _acme-challenge TXT record via the AWS API.
# lego renews only within --days of expiry, so this can loop harmlessly.
#
# Env (from compose/.env): ACME_DOMAIN, ACME_EMAIL, AWS_* creds (or an instance
# role), optional ACME_STAGING=1 for a dry-run against LE staging.
set -eu
DOMAIN=${ACME_DOMAIN:?set ACME_DOMAIN}
EMAIL=${ACME_EMAIL:?set ACME_EMAIL}
UID_OUT=${RUNTIME_UID:-65532} # distroless "nonroot" — must be able to read the key
INTERVAL_OK=${INTERVAL_OK:-43200} # 12h between checks after success
INTERVAL_ERR=${INTERVAL_ERR:-3600} # 1h retry after a failure
STAGING=""
[ -n "${ACME_STAGING:-}" ] && {
STAGING="--server https://acme-staging-v02.api.letsencrypt.org/directory"
echo "acme: using LE STAGING — certs are UNTRUSTED (dry run)"
}
# The goacme/lego image ships the binary at /lego (not on PATH); fall back to it
# when we've overridden the entrypoint with the shell.
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
set -- renew --days 30
else
set -- run
fi
echo "acme: lego $* for $DOMAIN (Route 53 DNS-01)"
# shellcheck disable=SC2086 # $STAGING/$RESOLVERS intentionally word-split
if "$LEGO" --accept-tos --email "$EMAIL" --dns route53 --domains "$DOMAIN" \
--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 \
|| echo "acme: WARN could not chown to $UID_OUT (manager may not read the key)"
echo "acme: staged /out/{fullchain,privkey}.pem; next check in ${INTERVAL_OK}s"
sleep "$INTERVAL_OK"
else
echo "acme: lego FAILED (check AWS creds / zone perms); retry in ${INTERVAL_ERR}s"
sleep "$INTERVAL_ERR"
fi
done
+7 -11
View File
@@ -71,15 +71,12 @@ services:
influxdb: influxdb:
condition: service_healthy condition: service_healthy
environment: environment:
# admin plane — passkey auth behind the Traefik mgmt ingress: Traefik # admin plane — passkey auth over PLAINTEXT behind the Traefik mgmt ingress:
# terminates the LE cert on :443 and forwards plaintext here on loopback, # Traefik terminates the LE cert on :443 and forwards plaintext here on
# so the listener MUST stay private (127.0.0.1) — only Traefik reaches it. # loopback, so the listener MUST stay private (127.0.0.1) — only Traefik
# reaches it. INSECURE=true is the dev/no-auth escape hatch.
FLEET_MGR_ADMIN_ADDR: "${FLEET_MGR_ADMIN_ADDR:-127.0.0.1:9500}" FLEET_MGR_ADMIN_ADDR: "${FLEET_MGR_ADMIN_ADDR:-127.0.0.1:9500}"
FLEET_MGR_ADMIN_BEHIND_PROXY: "${FLEET_MGR_ADMIN_BEHIND_PROXY:-true}"
FLEET_MGR_ADMIN_INSECURE: "${FLEET_MGR_ADMIN_INSECURE:-false}" FLEET_MGR_ADMIN_INSECURE: "${FLEET_MGR_ADMIN_INSECURE:-false}"
# Only used for self-TLS mode (BEHIND_PROXY=false + a cert, no Traefik):
FLEET_MGR_ADMIN_CERT: "${FLEET_MGR_ADMIN_CERT:-/pki/admin/fullchain.pem}"
FLEET_MGR_ADMIN_KEY: "${FLEET_MGR_ADMIN_KEY:-/pki/admin/privkey.pem}"
# WebAuthn RP — MUST be the UI's real origin (origin-bound passkeys). Behind # WebAuthn RP — MUST be the UI's real origin (origin-bound passkeys). Behind
# Traefik on :443 this is the PORT-LESS https origin. # Traefik on :443 this is the PORT-LESS https origin.
FLEET_MGR_RP_ID: "${FLEET_MGR_RP_ID:?set FLEET_MGR_RP_ID in .env}" FLEET_MGR_RP_ID: "${FLEET_MGR_RP_ID:?set FLEET_MGR_RP_ID in .env}"
@@ -113,9 +110,8 @@ services:
volumes: volumes:
# Durable state (inventory + admin passkeys + enrol key). Back this up. # Durable state (inventory + admin passkeys + enrol key). Back this up.
- fleet-data:/data - fleet-data:/data
# PKI, read-only: admin LE cert, agent-plane fleet-CA server cert, and the # PKI, read-only: agent-plane fleet-CA server cert + the broker admin-client
# broker admin-client cert. See DEPLOY.md / server-pki.sh for what lands here. # cert. (UI TLS is Traefik's — no admin cert here.) See DEPLOY.md/server-pki.sh.
# The admin LE cert under ./pki/admin/ is produced by the `acme` side-car.
- ./pki:/pki:ro - ./pki:/pki:ro
# --- management-plane ingress (Traefik) — admin UI on :443 ------------------ # --- management-plane ingress (Traefik) — admin UI on :443 ------------------
@@ -132,7 +128,7 @@ services:
# collide with a boot-LAN nginx :80 on a single-host lab. Route 53 DNS-01 needs # collide with a boot-LAN nginx :80 on a single-host lab. Route 53 DNS-01 needs
# no :80. Set the Host() rule in traefik/dynamic/fleet-manager.yml to your UI host. # no :80. Set the Host() rule in traefik/dynamic/fleet-manager.yml to your UI host.
traefik: traefik:
image: traefik:v3.1 # mirror via harbor if no dockerhub egress image: harbor.bitasylum.org/dockerhub/library/traefik:v3.1 # mirror via harbor if no dockerhub egress
restart: unless-stopped restart: unless-stopped
network_mode: host network_mode: host
command: command:
+3 -3
View File
@@ -9,8 +9,8 @@
# fleet step-ca (serving/dot1x-bench/), the same CA that signs enrolment certs. # fleet step-ca (serving/dot1x-bench/), the same CA that signs enrolment certs.
# #
# This does NOT issue: # This does NOT issue:
# - the ADMIN-plane UI cert -> Let's Encrypt DNS-01 (external ACME client; # - the ADMIN-plane UI cert -> owned by the Traefik ingress (Let's Encrypt
# drop fullchain/privkey into ./pki/admin/). See DEPLOY.md. # DNS-01); the manager never terminates TLS. See DEPLOY.md §2c.
# - the manager's BROKER client cert -> ../enrol-broker/admin-pki.sh (admin CA): # - the manager's BROKER client cert -> ../enrol-broker/admin-pki.sh (admin CA):
# ../enrol-broker/admin-pki.sh ../dot1x-bench/pki/broker fleet-manager # ../enrol-broker/admin-pki.sh ../dot1x-bench/pki/broker fleet-manager
# #
@@ -66,6 +66,6 @@ echo " server.crt / server.key -> FLEET_MGR_AGENT_CERT / _KEY"
echo " fleet-ca.crt -> FLEET_MGR_AGENT_CLIENT_CA (verifies host TPM certs)" echo " fleet-ca.crt -> FLEET_MGR_AGENT_CLIENT_CA (verifies host TPM certs)"
echo echo
echo "next:" echo "next:"
echo " - admin UI cert : Let's Encrypt DNS-01 -> ./pki/admin/{fullchain,privkey}.pem (DEPLOY.md)" echo " - admin UI cert : owned by the Traefik ingress (LE DNS-01) — DEPLOY.md §2c"
echo " - broker client : ../enrol-broker/admin-pki.sh ../dot1x-bench/pki/broker fleet-manager" echo " - broker client : ../enrol-broker/admin-pki.sh ../dot1x-bench/pki/broker fleet-manager"
echo " then: cp -r ../dot1x-bench/pki/broker ./pki/broker" echo " then: cp -r ../dot1x-bench/pki/broker ./pki/broker"