feat: secrets verify — manifest↔store consistency + decrypt integrity (EGB-698)
Read-only integrity check, the safety net for the stage-2 store migration. Default mode (current project) cross-checks $PWD/.secrets.json against the store both ways — declared-but-missing blobs and orphaned blobs (no manifest entry) — and decrypt-tests every dotenv + external blob with the current key, streaming plaintext to /dev/null so nothing is ever written to disk. `verify --all` decrypt-tests every blob in every project (integrity only; the store carries no manifests, so consistency can't be checked store-wide). Both recurse the whole project tree (find -type f), the same walk rekey/list use, so nested manifest blobs are covered. Exits non-zero on any finding so it can gate `migrate --finalize` and CI. 12 bats tests (clean, nested+external, missing blob, decrypt failure, orphan, missing external, no-manifest die, symlink refusal, --all clean/corrupt/orphan, nested decrypt failure). Full suite 205/205. bash 3.2 clean.
This commit is contained in:
parent
fb71b956da
commit
52528f2e06
4 changed files with 291 additions and 1 deletions
|
|
@ -731,3 +731,132 @@ m_nojq_path() {
|
|||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"packages/web/.env.development"* ]] || false
|
||||
}
|
||||
|
||||
# ─── K: secrets verify — manifest↔store consistency + decrypt integrity (EGB-698) ─
|
||||
|
||||
@test "verify: clean pushed project reports OK and exits 0" {
|
||||
init_with_remote
|
||||
create_project_dir verifyok
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"OK"* ]] || false
|
||||
}
|
||||
|
||||
@test "verify: nested + external entries all pass" {
|
||||
init_with_remote
|
||||
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
|
||||
create_project_dir verifymix
|
||||
mkdir -p packages/web
|
||||
echo "N=nested" > packages/web/.env.development
|
||||
"$SECRETS_BIN" add packages/web/.env.development >/dev/null
|
||||
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "verify: declared-but-missing blob is a finding (exit 1)" {
|
||||
init_with_remote
|
||||
create_project_dir verifymiss
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
rm "$SECRETS_DIR/verifymiss/.env.staging.age"
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *".env.staging"* ]] || false
|
||||
}
|
||||
|
||||
@test "verify: a blob that fails to decrypt is a finding (exit 1)" {
|
||||
init_with_remote
|
||||
create_project_dir verifycorrupt
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
printf 'not-a-valid-age-blob' > "$SECRETS_DIR/verifycorrupt/.env.age"
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *".env"* ]] || false
|
||||
}
|
||||
|
||||
@test "verify: orphan blob (no manifest entry) is a finding (exit 1)" {
|
||||
init_with_remote
|
||||
create_project_dir verifyorphan
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
# A valid, decryptable blob with no manifest entry — pure consistency miss.
|
||||
cp "$SECRETS_DIR/verifyorphan/.env.age" "$SECRETS_DIR/verifyorphan/.stray.age"
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"stray"* ]] || false
|
||||
}
|
||||
|
||||
@test "verify: missing external blob is a finding (exit 1)" {
|
||||
init_with_remote
|
||||
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
|
||||
create_project_dir verifyextmiss
|
||||
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
rm "$SECRETS_DIR/verifyextmiss/external/"*.age
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "verify: no manifest in cwd dies with a directed message" {
|
||||
init_with_remote
|
||||
local dir="$WORK_DIR/verifynomani"; mkdir -p "$dir"; cd "$dir"
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *".secrets.json"* ]] || false
|
||||
[[ "$output" == *"--all"* ]] || false
|
||||
}
|
||||
|
||||
@test "verify: symlinked manifest is refused" {
|
||||
init_with_remote
|
||||
create_project_dir verifysymlink
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
rm .secrets.json
|
||||
ln -s /etc/hosts .secrets.json
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"symlink"* ]] || false
|
||||
}
|
||||
|
||||
@test "verify --all: clean store passes (decrypt-only)" {
|
||||
init_with_remote
|
||||
create_project_dir verifyall1
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
cd "$WORK_DIR"
|
||||
run "$SECRETS_BIN" verify --all
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "verify --all: a corrupted blob anywhere fails (exit 1)" {
|
||||
init_with_remote
|
||||
create_project_dir verifyall2
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
printf 'garbage' > "$SECRETS_DIR/verifyall2/.env.age"
|
||||
cd "$WORK_DIR"
|
||||
run "$SECRETS_BIN" verify --all
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "verify --all: an orphan that decrypts is NOT flagged (no consistency check)" {
|
||||
init_with_remote
|
||||
create_project_dir verifyall3
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
# Orphan blob that decrypts fine — default mode flags it, --all does not.
|
||||
cp "$SECRETS_DIR/verifyall3/.env.age" "$SECRETS_DIR/verifyall3/.stray.age"
|
||||
cd "$WORK_DIR"
|
||||
run "$SECRETS_BIN" verify --all
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "verify: nested blob that fails to decrypt is caught (rekey-orphan guard)" {
|
||||
init_with_remote
|
||||
create_project_dir verifynested
|
||||
mkdir -p packages/web
|
||||
echo "N=nested" > packages/web/.env.development
|
||||
"$SECRETS_BIN" add packages/web/.env.development >/dev/null
|
||||
"$SECRETS_BIN" push >/dev/null 2>&1
|
||||
printf 'broken' > "$SECRETS_DIR/verifynested/packages/web/.env.development.age"
|
||||
run "$SECRETS_BIN" verify
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"packages/web/.env.development"* ]] || false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue