hub: F-14 part 2 — gated auto-Reissue on the PBS-DR enable dead-end

pbsdrProvisionAtom, on tenantsync token_exists: consult the customer's
MOST RECENT host-deletion record. escrow_acked=true -> invoke the
EXISTING tenantsync Reissue op, store the audit event (pbsdr_auto_reissue,
hub-source, "Previous key destroyed (acknowledged deletion) - credentials
re-issued automatically."), proceed to secret+descriptor as a normal
provision. No record / un-acked -> the pre-existing refusal, byte-unchanged
(never-silently-re-key law; operator ruling 2026-07-13).
Scenario A test drives the REAL handler path over a REAL escrow-ack delete;
scenario B pins the exact non-effect (zero Reissue calls, no state).
Red-proof: bypassing the gate flips both B subtests to 303 (silent re-key).
This commit is contained in:
2026-07-13 14:44:42 +02:00
parent 2321077800
commit 04861a7ed3
2 changed files with 146 additions and 6 deletions
+32 -5
View File
@@ -205,11 +205,38 @@ func (s *Server) pbsdrProvisionAtom(ctx context.Context, customerID string, host
defer cancel()
res, err := s.tenantsync.Provision(ctx, customerID)
if errors.Is(err, tenantsync.ErrTokenExists) {
// ep0 has a token but the hub has no descriptor — state mismatch (lost hub state or a
// half-torn earlier attempt). Never silently re-key: the operator decides via Re-issue.
return "", fmt.Errorf("the endpoint already holds a PBS token for %s but the hub has no descriptor — use the explicit \"Re-issue PBS credentials\" action", customerID)
}
if err != nil {
// ep0 has a token but the hub has no descriptor — state mismatch (lost hub state, a
// half-torn earlier attempt, or the F-14 shape: host deleted, tenancy survived).
//
// F-14 gate (operator ruling 2026-07-13): auto-re-issue is permitted ONLY when the
// hub's own deletion record shows the tenancy's owning host — the customer's most
// recent host deletion — was removed through the escrow-ack flow. Acknowledged
// destruction is not silent re-keying; the old secret went down with the acked host.
// No record / un-acked record → the refusal below, byte-unchanged (manual path).
rec, derr := s.store.LatestHostDeletion(customerID)
if derr != nil {
return "", fmt.Errorf("pbsdr: deletion-provenance lookup for %s: %w", customerID, derr)
}
if rec == nil || !rec.EscrowAcked {
return "", fmt.Errorf("the endpoint already holds a PBS token for %s but the hub has no descriptor — use the explicit \"Re-issue PBS credentials\" action", customerID)
}
res, err = s.tenantsync.Reissue(ctx, customerID) // the EXISTING re-issue op — no new endpoint interaction
if err != nil {
return "", fmt.Errorf("pbsdr: F-14 auto re-issue for %s: %w", customerID, err)
}
note := "Previous key destroyed (acknowledged deletion) — credentials re-issued automatically."
details, _ := json.Marshal(map[string]string{
"deleted_host": rec.HostID,
"deleted_at": rec.DeletedAt.UTC().Format(time.RFC3339),
"new_host": host.HostID,
"token_id": res.TokenID,
})
if _, eerr := s.store.SaveEvent(customerID, "pbsdr_auto_reissue", "info", note, string(details), "hub"); eerr != nil {
s.logger.Printf("[WARN] pbsdr: F-14 audit event for %s not stored: %v", customerID, eerr)
}
s.logger.Printf("[INFO] pbsdr F-14 auto re-issue for %s: owning host %s removed via escrow-ack flow (%s) — %s",
customerID, rec.HostID, rec.DeletedAt.UTC().Format(time.RFC3339), note)
} else if err != nil {
return "", err
}