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:
@@ -9,14 +9,11 @@ FLEET_MGR_RP_NAME=Fleet Command
|
||||
|
||||
# --- admin plane (behind the Traefik ingress) ----------------------------------
|
||||
# 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_BEHIND_PROXY=true
|
||||
# Dev-only escape hatch: plaintext, NO passkey auth. NEVER set true in prod.
|
||||
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 issues + auto-renews the :443 cert. Set the UI hostname in
|
||||
|
||||
@@ -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
|
||||
> 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>
|
||||
### Network paths (three planes, three trust stages)
|
||||
|
||||
|
||||
@@ -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
|
||||
mgmt network, not internet-reachable, so HTTP-01/TLS-ALPN-01 can't validate) on
|
||||
:443 and forwards plaintext to the manager, which runs
|
||||
`FLEET_MGR_ADMIN_BEHIND_PROXY=true` (passkey auth ON, plaintext on loopback —
|
||||
WebAuthn only needs the origin to match, not the app's own transport). Traefik
|
||||
is the k8s ingress-controller + cert-manager role in miniature. *(Self-TLS mode
|
||||
without Traefik is still available: `BEHIND_PROXY=false` + a cert at
|
||||
`FLEET_MGR_ADMIN_CERT/_KEY`, e.g. from `acme/renew-loop.sh`.)*
|
||||
passkey auth ON, plaintext on loopback (WebAuthn only needs the origin to match,
|
||||
not the app's own transport). The manager never terminates TLS itself. Traefik
|
||||
is the k8s ingress-controller + cert-manager role in miniature.
|
||||
|
||||
## Monitoring path (no host ever touches InfluxDB)
|
||||
|
||||
@@ -116,9 +114,7 @@ inventory for offline delivery. Metrics writing is behind a `MetricsSink`
|
||||
|
||||
| Var | Default | Notes |
|
||||
|---|---|---|
|
||||
| `FLEET_MGR_ADMIN_ADDR` | `127.0.0.1:9500` | admin listener (loopback — Traefik forwards here) |
|
||||
| `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_ADMIN_ADDR` | `127.0.0.1:9500` | admin listener (loopback — Traefik forwards here; always plaintext) |
|
||||
| `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_NAME` | `Fleet Command` | RP display name |
|
||||
|
||||
@@ -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
|
||||
@@ -71,15 +71,12 @@ services:
|
||||
influxdb:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# admin plane — passkey auth behind the Traefik mgmt ingress: Traefik
|
||||
# terminates the LE cert on :443 and forwards plaintext here on loopback,
|
||||
# so the listener MUST stay private (127.0.0.1) — only Traefik reaches it.
|
||||
# admin plane — passkey auth over PLAINTEXT behind the Traefik mgmt ingress:
|
||||
# Traefik terminates the LE cert on :443 and forwards plaintext here on
|
||||
# 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_BEHIND_PROXY: "${FLEET_MGR_ADMIN_BEHIND_PROXY:-true}"
|
||||
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
|
||||
# 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}"
|
||||
@@ -113,9 +110,8 @@ services:
|
||||
volumes:
|
||||
# Durable state (inventory + admin passkeys + enrol key). Back this up.
|
||||
- fleet-data:/data
|
||||
# PKI, read-only: admin LE cert, agent-plane fleet-CA server cert, and the
|
||||
# broker admin-client cert. See DEPLOY.md / server-pki.sh for what lands here.
|
||||
# The admin LE cert under ./pki/admin/ is produced by the `acme` side-car.
|
||||
# PKI, read-only: agent-plane fleet-CA server cert + the broker admin-client
|
||||
# cert. (UI TLS is Traefik's — no admin cert here.) See DEPLOY.md/server-pki.sh.
|
||||
- ./pki:/pki:ro
|
||||
|
||||
# --- 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
|
||||
# no :80. Set the Host() rule in traefik/dynamic/fleet-manager.yml to your UI host.
|
||||
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
|
||||
network_mode: host
|
||||
command:
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
# fleet step-ca (serving/dot1x-bench/), the same CA that signs enrolment certs.
|
||||
#
|
||||
# This does NOT issue:
|
||||
# - the ADMIN-plane UI cert -> Let's Encrypt DNS-01 (external ACME client;
|
||||
# drop fullchain/privkey into ./pki/admin/). See DEPLOY.md.
|
||||
# - the ADMIN-plane UI cert -> owned by the Traefik ingress (Let's Encrypt
|
||||
# DNS-01); the manager never terminates TLS. See DEPLOY.md §2c.
|
||||
# - the manager's BROKER client cert -> ../enrol-broker/admin-pki.sh (admin CA):
|
||||
# ../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
|
||||
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 " then: cp -r ../dot1x-bench/pki/broker ./pki/broker"
|
||||
|
||||
Reference in New Issue
Block a user