fleet-manager: add behind-proxy admin mode (passkey over plaintext behind a TLS proxy)
Decouples "passkey auth ON" from "the listener terminates TLS". A new FLEET_MGR_ADMIN_BEHIND_PROXY=true keeps WebAuthn/session auth fully on but serves plaintext, for running behind a trusted TLS-terminating ingress (Traefik now, a k8s Ingress later) that owns the LE cert. 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/mgmt) so only the proxy reaches it — logged as a warning. Insecure (dev, no auth) and self-TLS (default) modes unchanged.
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
|||||||
type config struct {
|
type config struct {
|
||||||
adminAddr string
|
adminAddr string
|
||||||
adminInsecure bool
|
adminInsecure bool
|
||||||
|
adminBehindProxy bool // passkey auth ON, but TLS terminated by a trusted upstream proxy
|
||||||
adminCert string
|
adminCert string
|
||||||
adminKey string
|
adminKey string
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ func loadConfig() config {
|
|||||||
return config{
|
return config{
|
||||||
adminAddr: env("FLEET_MGR_ADMIN_ADDR", "127.0.0.1:9500"),
|
adminAddr: env("FLEET_MGR_ADMIN_ADDR", "127.0.0.1:9500"),
|
||||||
adminInsecure: env("FLEET_MGR_ADMIN_INSECURE", "") == "true",
|
adminInsecure: env("FLEET_MGR_ADMIN_INSECURE", "") == "true",
|
||||||
|
adminBehindProxy: env("FLEET_MGR_ADMIN_BEHIND_PROXY", "") == "true",
|
||||||
adminCert: env("FLEET_MGR_ADMIN_CERT", ""),
|
adminCert: env("FLEET_MGR_ADMIN_CERT", ""),
|
||||||
adminKey: env("FLEET_MGR_ADMIN_KEY", ""),
|
adminKey: env("FLEET_MGR_ADMIN_KEY", ""),
|
||||||
|
|
||||||
@@ -233,11 +235,22 @@ func main() {
|
|||||||
IdleTimeout: 60 * time.Second,
|
IdleTimeout: 60 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.adminInsecure {
|
switch {
|
||||||
|
case cfg.adminInsecure:
|
||||||
log.Printf("WARNING: admin plane INSECURE (plaintext, NO passkey auth) on %s — dev/lab only", cfg.adminAddr)
|
log.Printf("WARNING: admin plane INSECURE (plaintext, NO passkey auth) on %s — dev/lab only", cfg.adminAddr)
|
||||||
log.Fatal(srv.ListenAndServe())
|
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)
|
tlsCfg, err := adminTLS(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("admin TLS: %v", err)
|
log.Fatalf("admin TLS: %v", err)
|
||||||
@@ -245,6 +258,7 @@ func main() {
|
|||||||
srv.TLSConfig = tlsCfg
|
srv.TLSConfig = tlsCfg
|
||||||
log.Printf("fleet-manager admin (HTTPS + passkey) listening on %s (rp=%s)", cfg.adminAddr, cfg.rpID)
|
log.Printf("fleet-manager admin (HTTPS + passkey) listening on %s (rp=%s)", cfg.adminAddr, cfg.rpID)
|
||||||
log.Fatal(srv.ListenAndServeTLS("", "")) // certs are in TLSConfig
|
log.Fatal(srv.ListenAndServeTLS("", "")) // certs are in TLSConfig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// adminTLS builds the admin plane's SERVER-AUTH TLS. Browser identity is a passkey
|
// adminTLS builds the admin plane's SERVER-AUTH TLS. Browser identity is a passkey
|
||||||
|
|||||||
Reference in New Issue
Block a user