From 76c3a6aaa7560a1c1f5656973de5683f9e51404d Mon Sep 17 00:00:00 2001 From: Audiobuzz Date: Sun, 5 Jul 2026 01:44:23 +0000 Subject: [PATCH] =?UTF-8?q?fleet-manager:=20drop=20self-TLS=20admin=20mode?= =?UTF-8?q?=20=E2=80=94=20the=20manager=20never=20terminates=20TLS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Traefik (and the k8s Ingress later) owns UI TLS, so remove the vestigial self-TLS path: adminTLS(), the certReloader type, the adminCert/adminKey config + FLEET_MGR_ADMIN_CERT/_KEY env, and the now-redundant adminBehindProxy flag. The admin plane always serves plaintext now — two modes: INSECURE (dev, no auth) or default (passkey behind a trusted TLS proxy). Drops the crypto/tls, fmt, sync imports and the errString helper. --- serving/fleet-manager/main.go | 105 +++++----------------------------- 1 file changed, 14 insertions(+), 91 deletions(-) diff --git a/serving/fleet-manager/main.go b/serving/fleet-manager/main.go index fd71c24..a320b2a 100644 --- a/serving/fleet-manager/main.go +++ b/serving/fleet-manager/main.go @@ -12,25 +12,21 @@ package main import ( - "crypto/tls" - "fmt" "log" "net/http" "os" "strconv" "strings" - "sync" "time" "github.com/go-webauthn/webauthn/webauthn" ) type config struct { - adminAddr string - adminInsecure bool - adminBehindProxy bool // passkey auth ON, but TLS terminated by a trusted upstream proxy - adminCert string - adminKey string + // The admin plane always serves plaintext: passkey auth behind a trusted + // TLS-terminating proxy (Traefik / k8s Ingress), or no auth in insecure dev. + adminAddr string + adminInsecure bool agentAddr string agentCert string @@ -61,11 +57,8 @@ type config struct { func loadConfig() config { return config{ - adminAddr: env("FLEET_MGR_ADMIN_ADDR", "127.0.0.1:9500"), - adminInsecure: env("FLEET_MGR_ADMIN_INSECURE", "") == "true", - adminBehindProxy: env("FLEET_MGR_ADMIN_BEHIND_PROXY", "") == "true", - adminCert: env("FLEET_MGR_ADMIN_CERT", ""), - adminKey: env("FLEET_MGR_ADMIN_KEY", ""), + adminAddr: env("FLEET_MGR_ADMIN_ADDR", "127.0.0.1:9500"), + adminInsecure: env("FLEET_MGR_ADMIN_INSECURE", "") == "true", agentAddr: env("FLEET_MGR_AGENT_ADDR", "127.0.0.1:9600"), agentCert: env("FLEET_MGR_AGENT_CERT", ""), @@ -235,84 +228,18 @@ func main() { IdleTimeout: 60 * time.Second, } - switch { - case cfg.adminInsecure: + if cfg.adminInsecure { log.Printf("WARNING: admin plane INSECURE (plaintext, NO passkey auth) on %s — dev/lab only", cfg.adminAddr) log.Fatal(srv.ListenAndServe()) - - case cfg.adminBehindProxy: - // Passkey auth ON, but a trusted upstream proxy (Traefik / k8s Ingress) - // terminates TLS and forwards plaintext. WebAuthn only needs RP_ORIGIN to - // match the browser's HTTPS origin, not our own transport; session cookies - // stay Secure (the browser sees HTTPS). The listener MUST be reachable ONLY - // via the proxy — bind it private (loopback / a private net), never a - // routable interface. - log.Printf("fleet-manager admin (passkey, behind TLS proxy) plaintext listening on %s (rp=%s) — ensure only the proxy can reach this address", cfg.adminAddr, cfg.rpID) - log.Fatal(srv.ListenAndServe()) - - default: - tlsCfg, err := adminTLS(cfg) - if err != nil { - log.Fatalf("admin TLS: %v", err) - } - srv.TLSConfig = tlsCfg - log.Printf("fleet-manager admin (HTTPS + passkey) listening on %s (rp=%s)", cfg.adminAddr, cfg.rpID) - log.Fatal(srv.ListenAndServeTLS("", "")) // certs are in TLSConfig } -} -// adminTLS builds the admin plane's SERVER-AUTH TLS. Browser identity is a passkey -// (WebAuthn), not a client cert, so there is no ClientCAs/ClientAuth here — just -// our server cert. (manager→broker mTLS is separate; see broker.go.) -// -// The cert is served via a hot-reloading GetCertificate so an out-of-band renewal -// is picked up WITHOUT a restart. The intended deployment is a publicly-trusted -// cert obtained by an external ACME client via Let's Encrypt DNS-01 (the UI is on -// the mgmt network, not internet-reachable, so HTTP-01/TLS-ALPN-01 can't validate; -// DNS-01 proves control via a TXT record and supports wildcards). The ACME client -// (lego/certbot + a DNS provider) writes cert+key to FLEET_MGR_ADMIN_CERT/_KEY on -// a shared volume; this reloader notices the mtime bump and swaps the cert live. -func adminTLS(cfg config) (*tls.Config, error) { - if cfg.adminCert == "" || cfg.adminKey == "" { - return nil, errString("FLEET_MGR_ADMIN_CERT and FLEET_MGR_ADMIN_KEY are required (or set FLEET_MGR_ADMIN_INSECURE=true for dev)") - } - rl := &certReloader{certFile: cfg.adminCert, keyFile: cfg.adminKey} - if _, err := rl.GetCertificate(nil); err != nil { // fail fast on a bad/missing cert - return nil, err - } - return &tls.Config{ - MinVersion: tls.VersionTLS12, - GetCertificate: rl.GetCertificate, - }, nil -} - -// certReloader serves the admin server cert, reloading from disk when the file's -// mtime advances (an ACME renewal), so Let's Encrypt DNS-01 rotations apply live. -type certReloader struct { - certFile, keyFile string - mu sync.Mutex - cached *tls.Certificate - mtime time.Time -} - -func (c *certReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { - c.mu.Lock() - defer c.mu.Unlock() - fi, statErr := os.Stat(c.certFile) - if statErr == nil && (c.cached == nil || fi.ModTime().After(c.mtime)) { - cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile) - if err != nil { - if c.cached != nil { - return c.cached, nil // serve the last good cert through a transient mid-rotation error - } - return nil, err - } - c.cached, c.mtime = &cert, fi.ModTime() - } - if c.cached == nil { - return nil, fmt.Errorf("admin cert unavailable: %v", statErr) - } - return c.cached, nil + // Secure mode: passkey auth over PLAINTEXT behind a trusted TLS-terminating + // proxy (Traefik / k8s Ingress) that owns the cert — the manager never + // terminates TLS. WebAuthn only needs RP_ORIGIN to match the browser's HTTPS + // origin, not our transport; session cookies stay Secure. The listener MUST be + // bound private (loopback / a private net) so only the proxy reaches it. + log.Printf("fleet-manager admin (passkey, behind TLS proxy) plaintext listening on %s (rp=%s) — ensure only the proxy can reach this address", cfg.adminAddr, cfg.rpID) + log.Fatal(srv.ListenAndServe()) } // ---- tiny helpers --------------------------------------------------------- @@ -348,7 +275,3 @@ func envInt(k string, def int) int { } return def } - -type errString string - -func (e errString) Error() string { return string(e) }