fleet-manager: checkpoint 5 — admin WebAuthn/passkey auth (replaces browser mTLS)
The human->UI hop now authenticates with a hardware passkey (YubiKey), not an mTLS client cert: phishing-resistant, origin-bound, better UX. (manager->broker stays mTLS — service-to-service.) - webauthn.go: credential store (registered admins + passkeys + a one-time-token jti replay ledger; JSON file, atomic writes) + the webauthn.User impl + RP setup. - cli.go: 'fleet-manager register-admin <name>' mints a short-lived HMAC-signed enrolment link over a shared enrol key; the browser completes the WebAuthn create ceremony. No anyone-can-register endpoint; single-use via the ledger. - authapi.go: register/login ceremonies (usernameless discoverable login, userVerification required) + logout + whoami; short-lived in-memory ceremony store between begin/finish. - session.go: opaque server-side sessions (Secure/HttpOnly/SameSite=Strict cookie) + requireSession middleware gating /api/*, with a CSRF header check on mutations. adminName now comes from the session (audit attribution). - main.go: register-admin subcommand dispatch; admin TLS switched to SERVER-AUTH (no client cert) with a HOT-RELOADING cert (certReloader) so a Let's Encrypt DNS-01 renewal is picked up without a restart. RP = diskless-server.bitasylum.org. /api/* mounted behind requireSession; /auth/* + /healthz public. Cert strategy (per design decision): the UI HTTPS cert is a publicly-trusted LE cert via DNS-01 (mgmt-network UI isn't internet-reachable, so HTTP-01/TLS-ALPN-01 can't validate; DNS-01 proves control of bitasylum.org via TXT + supports wildcards). External ACME client issues+renews to the cert path; manager reloads. Deps: github.com/go-webauthn/webauthn (server-side). Tests: enrol-token mint/verify/tamper/expiry, session middleware (401/403/CSRF/expiry), cred-store persistence + jti ledger. build/test/vet/gofmt clean. Live passkey ceremony (virtual authenticator / real YubiKey) is a bench item.
This commit is contained in:
@@ -43,7 +43,7 @@ func TestAuthoriseApproveBirthsInventory(t *testing.T) {
|
||||
store, _ := newJSONStore("")
|
||||
s := &server{store: store, broker: &brokerClient{base: bs.URL, hc: bs.Client()}}
|
||||
mux := http.NewServeMux()
|
||||
s.routes(mux)
|
||||
s.apiRoutes(mux)
|
||||
|
||||
// pending proxies through
|
||||
pw := httptest.NewRecorder()
|
||||
@@ -93,7 +93,7 @@ func TestAuthoriseDisabledWhenNoBroker(t *testing.T) {
|
||||
store, _ := newJSONStore("")
|
||||
s := &server{store: store, broker: nil} // authorise not configured
|
||||
mux := http.NewServeMux()
|
||||
s.routes(mux)
|
||||
s.apiRoutes(mux)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
mux.ServeHTTP(rw, httptest.NewRequest(http.MethodGet, "/api/enrol/pending", nil))
|
||||
|
||||
Reference in New Issue
Block a user