fleet-manager: checkpoint 3 — authorise pillar (broker fold-in, births inventory)
The AUTHORISE pillar: the manager owns the enrolment queue UX, the enrol-broker
stays the state machine + step-ca signer (v1 'alongside' fold-in).
enrol-broker (minimal, backward-compatible change):
- admin approve/deny now return the decided Enrolment as JSON when the caller
sends Accept: application/json. Browsers still redirect; other API callers
(verify-serving.sh) still get 204. The cert PEM stays unexported (only the
requesting host gets it via /status) — the JSON carries cert_serial, hostnqn,
context, fingerprint, cn.
fleet-manager:
- broker.go: mTLS client for the broker admin plane (Pending/Approve/Deny),
nil when unconfigured so the manager runs standalone.
- admin.go: GET /api/enrol/pending (proxy), POST /api/enrol/{id}/approve
(drives broker, then BIRTHS the inventory record — identity from the returned
serial/hostnqn/context, binding from the operator's optional body, enrolled
with the HUMAN approver = the manager's admin mTLS CN), POST .../deny.
503 when the broker isn't wired.
Test: httptest mock broker asserts the Accept:application/json approve, pending
proxy, inventory birth (identity+binding+enrolled), and the disabled-503 path.
Broker + manager both build/vet/test/gofmt clean.
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -105,7 +106,7 @@ func (b *broker) handleApprove(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
log.Printf("audit APPROVE id=%s cn=%q serial=%s hostnqn=%s admin=%q", e.ID, e.CN, e.CertSerial, e.Hostnqn, admin)
|
||||
redirectOrJSON(w, r)
|
||||
respondDecision(w, r, e)
|
||||
}
|
||||
|
||||
func (b *broker) handleDeny(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -117,16 +118,28 @@ func (b *broker) handleDeny(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
log.Printf("audit DENY id=%s cn=%q admin=%q", e.ID, e.CN, admin)
|
||||
redirectOrJSON(w, r)
|
||||
respondDecision(w, r, e)
|
||||
}
|
||||
|
||||
// redirectOrJSON sends a browser form back to the queue, or a 204 for API callers.
|
||||
func redirectOrJSON(w http.ResponseWriter, r *http.Request) {
|
||||
if isBrowser(r) {
|
||||
// respondDecision replies to an approve/deny. A browser form gets redirected back
|
||||
// to the queue; a caller that asks for JSON (Accept: application/json — e.g. the
|
||||
// fleet-manager driving the broker as its authorise plane) gets the decided
|
||||
// Enrolment (cert_serial, hostnqn, context, fingerprint — NOT the cert PEM, which
|
||||
// is unexported and only reaches the requesting host); any other API caller keeps
|
||||
// the original 204 (so verify-serving.sh and existing scripts are unaffected).
|
||||
func respondDecision(w http.ResponseWriter, r *http.Request, e *Enrolment) {
|
||||
switch {
|
||||
case isBrowser(r):
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
case wantsJSON(r):
|
||||
writeJSON(w, http.StatusOK, e)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func wantsJSON(r *http.Request) bool {
|
||||
return strings.Contains(r.Header.Get("Accept"), "application/json")
|
||||
}
|
||||
|
||||
func isBrowser(r *http.Request) bool {
|
||||
|
||||
Reference in New Issue
Block a user