feat(hub): v0.57.0 reinstall-of-existing-customer arc — claim/offsite/escrow continuity
F2 claim re-issue on clean-slate re-enroll (ReissueForReenroll, host-enroll mint path, single-bump, reset code; hub never stores the password so fork B). F3 offsite re-issue on re-enroll (ReissueOffsiteForCustomer, same machinery as the manual button). 2.3 escrow honesty (red-proofed): re-issuing offsite marks the escrow stale (MarkEscrowStale), withholds the mismatched restic hash from auto-confirm, DR checklist shows stale not done. Events: claim_reissued_reenroll / offsite_reissued / escrow_stale. Controller + scripts unchanged (source contradicted both premises): the controller reads escrow prereqs live from the agent; the installer can't know the descriptor-provisioned storage id. F4 root fix is agent-side -> ROADMAP R-22; demo unblocked live (Part 0 ACL grant). VALIDATION doc F2 erratum + F3/F4 dispositions. Green gate + Scenario-C red-proof pass.
This commit is contained in:
@@ -66,6 +66,12 @@ type Handler struct {
|
||||
// desired-state tick). nil = no cascade hook (pre-v0.51.0 behavior). Runs in a detached
|
||||
// goroutine; must never delay or fail the registration response.
|
||||
wgRegisteredHook func(ctx context.Context, customerID string)
|
||||
|
||||
// offsiteReissuer (F3, v0.57.0) re-issues the customer's offsite credentials on clean-slate
|
||||
// re-enrollment — main.go wires it to the web server's ReissueOffsiteForCustomer (same machinery
|
||||
// as the manual "Re-issue offsite credentials" button, so escrow invalidation + events ride
|
||||
// along). nil = no auto re-issue; a no-op when offsite isn't provisioned/enabled for the customer.
|
||||
offsiteReissuer func(ctx context.Context, customerID string) error
|
||||
}
|
||||
|
||||
// SetClaimEngine wires the customer-claim code engine (nil-safe everywhere it is used).
|
||||
@@ -73,6 +79,11 @@ func (h *Handler) SetClaimEngine(e *claim.Engine) {
|
||||
h.claimEngine = e
|
||||
}
|
||||
|
||||
// SetOffsiteReissuer wires the clean-slate re-enroll offsite re-issue seam (nil-safe).
|
||||
func (h *Handler) SetOffsiteReissuer(f func(ctx context.Context, customerID string) error) {
|
||||
h.offsiteReissuer = f
|
||||
}
|
||||
|
||||
// SetWGRegisteredHook wires the post-WG-registration cascade hook (v0.51.0; nil-safe).
|
||||
func (h *Handler) SetWGRegisteredHook(f func(ctx context.Context, customerID string)) {
|
||||
h.wgRegisteredHook = f
|
||||
@@ -971,11 +982,49 @@ func (h *Handler) handleHostEnroll(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
h.logger.Printf("[INFO] host enrolled: %s (customer %s)", hostID, req.CustomerID)
|
||||
|
||||
// F2 (v0.57.0) — clean-slate reinstall of an existing customer. This mint path fires exactly
|
||||
// once per fresh host record (the clean-slate flow deletes the stale host, so re-enroll mints),
|
||||
// so it is the natural single-shot re-enroll hook. For a CLAIMED customer the fresh box has no
|
||||
// password; auto-issue a reset code so the customer isn't stranded at the claim page hunting for
|
||||
// the manual "request a new code" button (delivery rides the report ACK). No-op for an unclaimed
|
||||
// customer (first provision). Also re-issue offsite credentials to the fresh box (F3) — the
|
||||
// one-time offsite password only ever reached the OLD controller, so the fresh one has no target.
|
||||
h.reissueOnReenroll(cc)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]string{"host_id": hostID, "api_key": apiKey})
|
||||
}
|
||||
|
||||
// reissueOnReenroll runs the F2+F3 clean-slate re-enrollment side effects for a customer whose box
|
||||
// was wiped and re-minted a host record: re-issue the claim code (if claimed) and re-issue offsite
|
||||
// credentials (if provisioned). Every action that fires emits a visible customer event. Best-effort
|
||||
// and non-fatal — a failure here never fails the enrollment (the box is already minted).
|
||||
func (h *Handler) reissueOnReenroll(cc *store.CustomerConfig) {
|
||||
// F2 — claim continuity.
|
||||
if h.claimEngine != nil {
|
||||
if gen, reissued, err := h.claimEngine.ReissueForReenroll(cc); err != nil {
|
||||
h.logger.Printf("[WARN] claim re-issue on re-enroll for %s failed: %v", cc.CustomerID, err)
|
||||
} else if reissued {
|
||||
if _, serr := h.store.SaveEvent(cc.CustomerID, "claim_reissued_reenroll", "info",
|
||||
fmt.Sprintf("Új beállító kódot küldtünk a szerver újratelepítése után (%d. generáció) az ügyfél címére.", gen),
|
||||
"", "hub"); serr != nil {
|
||||
h.logger.Printf("[WARN] save claim_reissued_reenroll for %s: %v", cc.CustomerID, serr)
|
||||
}
|
||||
}
|
||||
}
|
||||
// F3 — offsite continuity: re-stage the one-time offsite password to the fresh controller (the
|
||||
// one-time password only ever reached the OLD controller). The re-issuer resets the restic repo
|
||||
// password, which makes the OLD escrow blob stale — the offsite provisioner invalidates the
|
||||
// escrow (2.3) and emits both events. Skips silently when offsite isn't provisioned/enabled.
|
||||
if h.offsiteReissuer != nil {
|
||||
if err := h.offsiteReissuer(context.Background(), cc.CustomerID); err != nil {
|
||||
h.logger.Printf("[WARN] offsite re-issue on re-enroll for %s failed: %v", cc.CustomerID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// escrowUploadRequest is the agent→hub wire shape for the OPAQUE PBS recovery-code escrow blob
|
||||
// (slice 7, doc 03 §8a). It MUST stay in lockstep with the agent's emit struct
|
||||
// (felhom-agent cmd/felhom-agent escrowUploadRequest). The hub stores the bytes and NEVER decrypts
|
||||
@@ -1463,6 +1512,10 @@ var allowedEventTypes = map[string]bool{
|
||||
"node_stale": true,
|
||||
"node_down": true,
|
||||
"node_recovered": true,
|
||||
// v0.57.0 reinstall arc (F2/F3/2.3) — hub-emitted on clean-slate re-enrollment / offsite re-issue
|
||||
"claim_reissued_reenroll": true, // reset code auto-issued to a reinstalled claimed customer
|
||||
"offsite_reissued": true, // offsite one-time password re-staged (manual button or re-enroll)
|
||||
"escrow_stale": true, // key-escrow blob invalidated by an offsite password re-issue
|
||||
// Hub-generated host-domain events (v0.7.0, slice 3)
|
||||
"host_stale": true,
|
||||
"host_down": true,
|
||||
|
||||
Reference in New Issue
Block a user