#!/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