fleet-manager: drop self-TLS admin mode — the manager never terminates TLS
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.
This commit is contained in:
@@ -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 {
|
||||
// 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
|
||||
adminBehindProxy bool // passkey auth ON, but TLS terminated by a trusted upstream proxy
|
||||
adminCert string
|
||||
adminKey string
|
||||
|
||||
agentAddr string
|
||||
agentCert string
|
||||
@@ -63,9 +59,6 @@ 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", ""),
|
||||
|
||||
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.
|
||||
// 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())
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ---- 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) }
|
||||
|
||||
Reference in New Issue
Block a user