Files
railiance-platform/scripts/openbao-validate-restore-evidence.sh

129 lines
3.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
EVIDENCE="${OPENBAO_RESTORE_EVIDENCE:-${1:-/tmp/netkingdom-openbao-restore-drill/evidence.json}}"
usage() {
cat <<'USAGE'
Usage: scripts/openbao-validate-restore-evidence.sh [evidence.json]
Validates non-secret OpenBao restore-drill evidence. The evidence file should
record hashes, dates, isolated environment references, and verification flags,
but must not contain OpenBao tokens, unseal shares, decrypted snapshots, private
keys, passwords, OTP seeds, or recovery codes.
Environment:
OPENBAO_RESTORE_EVIDENCE Evidence JSON path. Default:
/tmp/netkingdom-openbao-restore-drill/evidence.json
USAGE
}
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
usage
exit 0
fi
python3 - "$EVIDENCE" <<'PY'
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
path = Path(sys.argv[1])
if not path.exists():
print(f"[FAIL] restore evidence file is missing: {path}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
print(f"[FAIL] restore evidence is not valid JSON: {exc}", file=sys.stderr)
sys.exit(1)
if not isinstance(data, dict):
print("[FAIL] restore evidence root must be a JSON object", file=sys.stderr)
sys.exit(1)
required_strings = [
"drill_date",
"operator",
"source_cluster",
"source_namespace",
"source_pod",
"isolated_environment",
"snapshot_sha256",
"encrypted_snapshot_sha256",
"encrypted_snapshot_location",
"post_restore_verification",
"destroyed_environment_evidence",
]
required_true = [
"snapshot_created",
"snapshot_encrypted",
"isolated_restore_completed",
"unseal_verified_in_isolation",
"test_secret_read_verified",
"post_restore_status_verified",
"isolated_environment_destroyed",
"no_secret_material_recorded",
]
errors: list[str] = []
for key in required_strings:
value = data.get(key)
if not isinstance(value, str) or not value.strip():
errors.append(f"missing non-empty string: {key}")
sha_pattern = re.compile(r"^(sha256:)?[0-9a-fA-F]{64}$")
for key in ("snapshot_sha256", "encrypted_snapshot_sha256"):
value = str(data.get(key, ""))
if value and not sha_pattern.match(value):
errors.append(f"{key} must be a sha256 hex digest, optionally prefixed with sha256:")
digest = value.removeprefix("sha256:").lower()
if digest and len(set(digest)) <= 1:
errors.append(f"{key} must not be a placeholder digest")
for key in required_true:
if data.get(key) is not True:
errors.append(f"must be true: {key}")
encoded = json.dumps(data, sort_keys=True)
secret_markers = [
"OPENBAO_ROOT_TOKEN",
"VAULT_TOKEN",
"BEGIN PRIVATE KEY",
"BEGIN OPENSSH PRIVATE KEY",
"AGE-SECRET-KEY-1",
"-----BEGIN",
"hvs.",
]
for marker in secret_markers:
if marker in encoded:
errors.append(f"secret-looking marker present: {marker}")
placeholder_markers = [
"YYYY-MM-DD",
"example",
"operator-local encrypted restore drill workspace",
"approved encrypted custody location",
"disposable cluster, VM, or namespace reference",
"Do not record",
"<",
]
for marker in placeholder_markers:
if marker in encoded:
errors.append(f"template placeholder present: {marker}")
if errors:
for error in errors:
print(f"[FAIL] {error}", file=sys.stderr)
sys.exit(1)
print(f"[OK] restore evidence is structurally valid: {path}")
print(f"[OK] drill_date: {data['drill_date']}")
print(f"[OK] source: {data['source_cluster']}/{data['source_namespace']}/{data['source_pod']}")
print(f"[OK] isolated_environment: {data['isolated_environment']}")
PY