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:
@@ -699,6 +699,45 @@ func (s *Server) handleOffsiteReissue(w http.ResponseWriter, r *http.Request, cu
|
||||
http.Redirect(w, r, "/customers/"+customerID+"?flash=offsite_reissued#tab=edit", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// ReissueOffsiteForCustomer is the programmatic form of handleOffsiteReissue — the seam the API
|
||||
// host-enroll path calls on a clean-slate re-enrollment (F3, v0.57.0): the offsite one-time password
|
||||
// only ever reached the OLD controller, so the fresh box has no target. It re-issues (and, via
|
||||
// ReissueCredentials, invalidates the now-stale escrow + emits events), then bumps ConfigVersion so
|
||||
// the controller re-pulls and the bridge consumes the fresh password. Silent NO-OP (nil) when the
|
||||
// customer has no provisioned/enabled offsite tier — that is the common non-DR case, not an error.
|
||||
func (s *Server) ReissueOffsiteForCustomer(ctx context.Context, customerID string) error {
|
||||
if s.offsite == nil {
|
||||
return nil // offsite not configured on this hub
|
||||
}
|
||||
cfg, err := s.store.GetCustomerConfig(customerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("offsite re-issue: customer lookup: %w", err)
|
||||
}
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
var overrides struct {
|
||||
Offsite struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Type string `json:"type"`
|
||||
} `json:"offsite"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(cfg.ConfigJSON), &overrides)
|
||||
if !overrides.Offsite.Enabled || overrides.Offsite.Type == "" {
|
||||
return nil // no provisioned offsite tier — nothing to re-issue
|
||||
}
|
||||
rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 3*time.Minute)
|
||||
defer cancel()
|
||||
if err := s.offsite.ReissueCredentials(rctx, customerID, overrides.Offsite.Type); err != nil {
|
||||
return fmt.Errorf("offsite re-issue: %w", err)
|
||||
}
|
||||
if err := s.store.SaveCustomerConfig(cfg); err != nil {
|
||||
return fmt.Errorf("offsite re-issue: config bump: %w", err)
|
||||
}
|
||||
s.logger.Printf("[INFO] offsite credentials re-issued for %s on re-enroll (fresh one-time password; ConfigVersion bumped)", customerID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleOffsiteFreeze (SLICE 4) freezes/unfreezes the customer's shared sub-account (readonly) — an
|
||||
// OPERATOR lever, never automatic (freezing also blocks prune, the customer's only way down from
|
||||
// over-quota). Shared model only; the exactly-1 label guard lives in the provisioner. Action logged,
|
||||
|
||||
Reference in New Issue
Block a user