v0.77.0: fork-4 — escrow the offsite restic repo password under R
IdentityBundle gains ResticRepoPassword (rides existing age-under-R WrapIdentityBundle; custody spike febdc56 proved a recovered value opens the real repo). POST /escrow/stage-secret (withGuest, scopedFromBody) transiently stages the controller-pushed password (0600, atomic, NEVER logged), which the escrow-create ceremony auto-injects then wipes. Adds AttachResticPassword + StagedResticPasswordPath + WipeStagedResticPassword; EscrowStagePath injectable for tests. Tests: bundle carries pw byte-exact + not-in-blob + wrong-R fails closed; stage 0600 + non-secret ack + cross-guest 403 + value-not-in-log. Additive; PBS-K escrow untouched. NOT yet live-validated (supervised ceremony). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1710,6 +1710,26 @@ func runSelftestEscrowCreate(ctx context.Context, cfg config.Config, logger *slo
|
||||
logger.Info("escrow: identity bundle: +wg_private_key")
|
||||
}
|
||||
}
|
||||
// fork-4: auto-inject the staged offsite restic repo password (controller-pushed via the local API)
|
||||
// into the escrowed identity, so DR can recover the offsite DATA key with the one recovery code R.
|
||||
// Field NAME only in logs. No staged file → clean no-attach (pre-fork-4 behavior). Wiped after create.
|
||||
resticStaged := false
|
||||
{
|
||||
probe := identity
|
||||
if probe == nil {
|
||||
probe = &escrow.IdentityBundle{}
|
||||
}
|
||||
attached, err := escrow.AttachResticPassword(probe, escrow.StagedResticPasswordPath())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "selftest=escrow-create: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
if attached {
|
||||
identity = probe
|
||||
resticStaged = true
|
||||
logger.Info("escrow: identity bundle: +restic_repo_password")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("=== felhom-agent %s selftest=escrow-create (storage=%s posture=%s identity=%v) ===\n", version, storage, escrow.DefaultPosture, identity != nil)
|
||||
// NB: nothing about R is logged. The logger never sees R; only stdout does, once.
|
||||
@@ -1726,6 +1746,13 @@ func runSelftestEscrowCreate(ctx context.Context, cfg config.Config, logger *slo
|
||||
fmt.Fprintln(os.Stderr, " [FAIL] escrow create:", err)
|
||||
return 1
|
||||
}
|
||||
// fork-4: the staged restic password is now sealed inside the R-wrapped blob — wipe the transient
|
||||
// 0600 staging file so it never lingers on disk (field name only; a wipe failure is a loud warn).
|
||||
if resticStaged {
|
||||
if werr := escrow.WipeStagedResticPassword(); werr != nil {
|
||||
logger.Warn("escrow: could not wipe the staged restic password after create", "err", werr)
|
||||
}
|
||||
}
|
||||
|
||||
// Surface R EXACTLY ONCE — to stdout, with a write-it-down banner. Never logged/persisted.
|
||||
fmt.Println()
|
||||
|
||||
Reference in New Issue
Block a user