129 lines
3.7 KiB
Bash
Executable File
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
|