// host.go — the mTLS HOST plane: enrolled hosts get their per-boot SSH host key // signed by the fleet SSH CA. // // This is the THIRD listener, distinct from the two in main.go: // // HOST (mTLS, boot-LAN reachable) -- fleet-CA host client cert: // POST /ssh/hostcert {publicKey, principals, keyID} -> {cert} // // It is the automatic, self-authenticating counterpart of enrolment: a host // proves it is enrolled by presenting its TPM identity as the mTLS client cert // (verified to the fleet CA), and the broker has step-ca sign an SSH HOST cert — // no human approval, no CA secret on the host. Admins then trust every fleet host // via one `@cert-authority` line instead of per-boot TOFU (F-02 / POA&M P-17). // Keep step-ca itself off the boot LAN; the broker is the single front door. package main import ( "crypto/tls" "crypto/x509" "encoding/json" "log" "net/http" "os" "time" ) type sshHostCertRequest struct { PublicKey string `json:"publicKey"` Principals []string `json:"principals"` KeyID string `json:"keyID"` } type sshHostCertResponse struct { Cert string `json:"cert,omitempty"` Error string `json:"error,omitempty"` } func (b *broker) serveHost() { if b.cfg.hostAddr == "" { log.Printf("host plane disabled (set BROKER_HOST_ADDR + BROKER_HOST_CERT/KEY to enable SSH host-cert issuance)") return } mux := http.NewServeMux() mux.HandleFunc("POST /ssh/hostcert", b.handleSSHHostCert) mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) srv := &http.Server{ Addr: b.cfg.hostAddr, Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 40 * time.Second, // signing blocks on step-ca IdleTimeout: 30 * time.Second, MaxHeaderBytes: 16 << 10, } if b.cfg.hostInsecure { log.Printf("WARNING: BROKER_HOST_INSECURE=true — SSH host plane is PLAINTEXT with NO client-cert auth. DEV ONLY.") log.Fatalf("host listener: %v", srv.ListenAndServe()) return } if b.cfg.hostCert == "" || b.cfg.hostKey == "" { log.Fatalf("host mTLS requires BROKER_HOST_CERT and BROKER_HOST_KEY (or BROKER_HOST_INSECURE=true for dev)") } clientCAPath := b.cfg.hostClientCA if clientCAPath == "" { clientCAPath = b.cfg.caRoot // default: the fleet root that signs host identities } cert, err := tls.LoadX509KeyPair(b.cfg.hostCert, b.cfg.hostKey) if err != nil { log.Fatalf("host server cert: %v", err) } caPEM, err := os.ReadFile(clientCAPath) if err != nil { log.Fatalf("host client CA: %v", err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caPEM) { log.Fatalf("host client CA %s contains no certificates", clientCAPath) } srv.TLSConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, // must be an enrolled fleet host ClientCAs: pool, MinVersion: tls.VersionTLS12, } log.Printf("host plane up: %s (mTLS, fleet-CA host client certs) ssh-signer=%s", b.cfg.hostAddr, b.sshSigner.Kind()) log.Fatalf("host listener: %v", srv.ListenAndServeTLS("", "")) } func (b *broker) handleSSHHostCert(w http.ResponseWriter, r *http.Request) { // RequireAndVerifyClientCert already guaranteed the peer chains to the fleet // CA; the presented identity is who we're issuing for. peerCN := hostPeerCN(r) r.Body = http.MaxBytesReader(w, r.Body, 16<<10) var req sshHostCertRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeSSHErr(w, http.StatusBadRequest, "invalid JSON body") return } if req.KeyID == "" { req.KeyID = peerCN } if len(req.Principals) == 0 { writeSSHErr(w, http.StatusBadRequest, "no principals requested") return } ctx, cancel := ctxWithTimeout(35 * time.Second) defer cancel() certLine, err := b.sshSigner.SignHost(ctx, req.PublicKey, req.KeyID, req.Principals) if err != nil { log.Printf("audit ssh-hostcert-FAILED cn=%q principals=%v err=%v", peerCN, req.Principals, err) writeSSHErr(w, http.StatusBadGateway, "signing failed: "+err.Error()) return } log.Printf("audit SSH-HOSTCERT cn=%q keyid=%q principals=%v", peerCN, req.KeyID, req.Principals) writeJSON(w, http.StatusOK, sshHostCertResponse{Cert: certLine}) } func hostPeerCN(r *http.Request) string { if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 { if cn := r.TLS.PeerCertificates[0].Subject.CommonName; cn != "" { return cn } return "unknown-host" } return "dev-insecure" } func writeSSHErr(w http.ResponseWriter, code int, msg string) { writeJSON(w, code, sshHostCertResponse{Error: msg}) }