v0.54.0: Phase 2b — restore-from-recovery-unit + fail-closed data-key gate
Restore recreates an app from its on-drive unit + the guest's own secrets, regenerating nothing. reconcileRestoreSecrets (pure, unit-tested) merges the unit's non-secret env with secrets recovered from the live app.yaml and FAILS CLOSED if a data-encrypting key is unrecoverable (refuse — a PBS whole-guest restore is needed — rather than regenerate and corrupt). Resettable secrets missing → warn + proceed. - backup: RestoreFromRecoveryUnit (manifest -> recover secrets -> gate -> restore volumes -> recreate definition + redeploy w/ re-pull); falls back to volume-only. - seams: RecoverStackSecrets/RecreateStackFromUnit (adapter +encKey), stacks.RedeployFromEnv. Wired into /backup/restore. - tests: gate (refuse/proceed/verbatim) + data_key parsing. Gate + reconcile + data_key parsing unit-tested; capture live-validated (v0.53.1). Full readable-data e2e vs AdventureLog needs the auth-gated dashboard restore — pending. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDataKeyParsing proves the catalog `data_key: true` annotation flows through .felhom.yml parsing
|
||||
// into Metadata.DataKeyEnvVars() — the capture-side half of the Phase 2b fail-closed mechanism. The
|
||||
// fail-closed gate itself is unit-tested in internal/backup (reconcileRestoreSecrets).
|
||||
func TestDataKeyParsing(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// Mirrors adventurelog/.felhom.yml: SECRET_KEY is a data-encrypting key, DB_PASSWORD is resettable.
|
||||
yml := `display_name: AdventureLog
|
||||
deploy_fields:
|
||||
- env_var: SECRET_KEY
|
||||
label: "Titkosítási kulcs"
|
||||
type: secret
|
||||
data_key: true
|
||||
- env_var: DB_PASSWORD
|
||||
label: "Adatbázis jelszó"
|
||||
type: secret
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, ".felhom.yml"), []byte(yml), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
meta := LoadMetadata(dir)
|
||||
dk := meta.DataKeyEnvVars()
|
||||
if len(dk) != 1 || dk[0] != "SECRET_KEY" {
|
||||
t.Fatalf("DataKeyEnvVars() = %v, want [SECRET_KEY]", dk)
|
||||
}
|
||||
|
||||
// Both secrets are sensitive (stripped from the unit); only SECRET_KEY is a data_key (fail-closed).
|
||||
sens := SensitiveEnvVars(&meta)
|
||||
if len(sens) != 2 {
|
||||
t.Errorf("SensitiveEnvVars() = %v, want both SECRET_KEY and DB_PASSWORD", sens)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user