Add OpenBao restore evidence validator
This commit is contained in:
parent
c0d4ec9037
commit
123b9aafce
5 changed files with 156 additions and 1 deletions
7
Makefile
7
Makefile
|
|
@ -14,6 +14,7 @@ OPENBAO_NAMESPACE ?= openbao
|
||||||
OPENBAO_RELEASE ?= openbao
|
OPENBAO_RELEASE ?= openbao
|
||||||
OPENBAO_VALUES ?= helm/openbao-values.yaml
|
OPENBAO_VALUES ?= helm/openbao-values.yaml
|
||||||
OPENBAO_VERIFY_AUTH_ARGS ?=
|
OPENBAO_VERIFY_AUTH_ARGS ?=
|
||||||
|
OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json
|
||||||
|
|
||||||
##@ CloudNative PG (cnpg) — primary database operator
|
##@ 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) \
|
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
|
||||||
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-authenticated.sh $(OPENBAO_VERIFY_AUTH_ARGS)
|
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: ## Backup platform services (PostgreSQL logical dump) — age-encrypted to Nextcloud
|
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 } \
|
/^[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)
|
/^##@/ { 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
|
||||||
|
|
|
||||||
22
docs/openbao-restore-drill-evidence.example.json
Normal file
22
docs/openbao-restore-drill-evidence.example.json
Normal 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."
|
||||||
|
}
|
||||||
|
|
@ -271,6 +271,15 @@ Before any live application secrets move into OpenBao:
|
||||||
4. Run an isolated restore drill before treating OpenBao as live secret
|
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
|
custody. The drill must prove that a fresh OpenBao instance can restore the
|
||||||
snapshot, unseal, and read a test secret.
|
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
|
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
|
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.
|
and setup wiring, but it writes to `/tmp` and is not production retention.
|
||||||
|
|
|
||||||
112
scripts/openbao-validate-restore-evidence.sh
Executable file
112
scripts/openbao-validate-restore-evidence.sh
Executable 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
|
||||||
|
|
@ -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,
|
still owns the OpenBao file audit device and PVC, while production retention,
|
||||||
tenant policy, and tamper-evident archive belong to Audit Core.
|
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
|
### T07 - Cross-Repo Transition Tasks
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue