Add OpenBao restore evidence validator

This commit is contained in:
2026-06-01 23:57:00 +02:00
parent c0d4ec9037
commit 123b9aafce
5 changed files with 156 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ OPENBAO_NAMESPACE ?= openbao
OPENBAO_RELEASE ?= openbao
OPENBAO_VALUES ?= helm/openbao-values.yaml
OPENBAO_VERIFY_AUTH_ARGS ?=
OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json
##@ CloudNative PG (cnpg) — primary database operator
@@ -126,6 +127,10 @@ openbao-verify-authenticated: ## Run authenticated non-mutating OpenBao audit/au
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-authenticated.sh $(OPENBAO_VERIFY_AUTH_ARGS)
openbao-validate-restore-evidence: ## Validate non-secret OpenBao restore-drill evidence JSON
OPENBAO_RESTORE_EVIDENCE='$(OPENBAO_RESTORE_EVIDENCE)' \
scripts/openbao-validate-restore-evidence.sh
##@ Backup
backup: ## Backup platform services (PostgreSQL logical dump) — age-encrypted to Nextcloud
@@ -138,4 +143,4 @@ help: ## Show this help
/^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 } \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
.PHONY: db-deploy db-status db-shell db-logs apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-verify-authenticated backup help
.PHONY: db-deploy db-status db-shell db-logs apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-verify-authenticated openbao-validate-restore-evidence backup help

View File

@@ -0,0 +1,22 @@
{
"drill_date": "2026-06-01",
"operator": "platform-root",
"source_cluster": "railiance01",
"source_namespace": "openbao",
"source_pod": "openbao-0",
"snapshot_created": true,
"snapshot_sha256": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"snapshot_encrypted": true,
"encrypted_snapshot_sha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"encrypted_snapshot_location": "operator-local encrypted restore drill workspace or approved encrypted custody location",
"isolated_environment": "disposable cluster, VM, or namespace reference",
"isolated_restore_completed": true,
"unseal_verified_in_isolation": true,
"test_secret_read_verified": true,
"post_restore_status_verified": true,
"post_restore_verification": "bao status, mount/auth/policy checks, and a non-production test secret read succeeded in the isolated environment",
"isolated_environment_destroyed": true,
"destroyed_environment_evidence": "operator note, VM deletion id, namespace deletion timestamp, or equivalent non-secret proof",
"no_secret_material_recorded": true,
"notes": "Do not record OpenBao tokens, root tokens, unseal shares, decrypted snapshots, private keys, passwords, OTP seeds, or recovery codes."
}

View File

@@ -271,6 +271,15 @@ Before any live application secrets move into OpenBao:
4. Run an isolated restore drill before treating OpenBao as live secret
custody. The drill must prove that a fresh OpenBao instance can restore the
snapshot, unseal, and read a test secret.
Record only non-secret evidence using
`docs/openbao-restore-drill-evidence.example.json` as a template, then
validate it with:
```bash
make openbao-validate-restore-evidence \
OPENBAO_RESTORE_EVIDENCE=/path/to/evidence.json
```
5. Decide where audit logs are shipped durably. The audit PVC alone is not a
durable audit sink. The interim `audit-core` mock file backend can prove API
and setup wiring, but it writes to `/tmp` and is not production retention.

View File

@@ -0,0 +1,112 @@
#!/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:")
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}")
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

View File

@@ -294,6 +294,13 @@ days; it is suitable for interface wiring and setup validation only. Railiance
still owns the OpenBao file audit device and PVC, while production retention,
tenant policy, and tamper-evident archive belong to Audit Core.
**2026-06-01:** Added a non-secret OpenBao restore-drill evidence template and
`make openbao-validate-restore-evidence`. The validator requires concrete
review evidence such as snapshot hashes, encrypted snapshot location, isolated
restore completion, unseal/status/test-secret verification, isolated
environment destruction, and a `no_secret_material_recorded` assertion. This
keeps `NET-WP-0017-T02` from relying on a bare UI checkbox for restore proof.
### T07 - Cross-Repo Transition Tasks
```task