From 606a5f3e1ec43cc64d3569f27c515c0a0ebd6dd7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 2 Jun 2026 00:08:17 +0200 Subject: [PATCH] Add OpenBao emergency drill evidence validator --- Makefile | 7 +- ...nbao-emergency-drill-evidence.example.json | 21 ++++ docs/openbao.md | 10 ++ ...enbao-validate-emergency-drill-evidence.sh | 107 ++++++++++++++++++ ...P-0002-openbao-platform-secrets-service.md | 8 ++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 docs/openbao-emergency-drill-evidence.example.json create mode 100755 scripts/openbao-validate-emergency-drill-evidence.sh diff --git a/Makefile b/Makefile index 5d4da3f..7bbfa6c 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ OPENBAO_RELEASE ?= openbao OPENBAO_VALUES ?= helm/openbao-values.yaml OPENBAO_VERIFY_AUTH_ARGS ?= OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json +OPENBAO_EMERGENCY_EVIDENCE ?= /tmp/netkingdom-openbao-emergency-drill/evidence.json ##@ CloudNative PG (cnpg) — primary database operator @@ -131,6 +132,10 @@ openbao-validate-restore-evidence: ## Validate non-secret OpenBao restore-drill OPENBAO_RESTORE_EVIDENCE='$(OPENBAO_RESTORE_EVIDENCE)' \ scripts/openbao-validate-restore-evidence.sh +openbao-validate-emergency-evidence: ## Validate non-secret OpenBao emergency seal/unseal drill evidence JSON + OPENBAO_EMERGENCY_EVIDENCE='$(OPENBAO_EMERGENCY_EVIDENCE)' \ + scripts/openbao-validate-emergency-drill-evidence.sh + ##@ Backup backup: ## Backup platform services (PostgreSQL logical dump) — age-encrypted to Nextcloud @@ -143,4 +148,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 openbao-validate-restore-evidence 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 openbao-validate-emergency-evidence backup help diff --git a/docs/openbao-emergency-drill-evidence.example.json b/docs/openbao-emergency-drill-evidence.example.json new file mode 100644 index 0000000..795eb9e --- /dev/null +++ b/docs/openbao-emergency-drill-evidence.example.json @@ -0,0 +1,21 @@ +{ + "drill_date": "2026-06-01", + "operator": "platform-root", + "source_cluster": "railiance01", + "source_namespace": "openbao", + "source_pod": "openbao-0", + "seal_started_at": "2026-06-01T22:00:00Z", + "seal_command_issued": true, + "sealed_status_confirmed": true, + "sealed_status_observed_at": "2026-06-01T22:00:30Z", + "unseal_quorum_available": true, + "unseal_started_at": "2026-06-01T22:01:00Z", + "unseal_completed": true, + "unseal_completed_at": "2026-06-01T22:02:30Z", + "post_unseal_status_verified": true, + "post_unseal_readiness_verified": true, + "post_unseal_verification": "bao status reported Sealed false and make openbao-verify-post-unseal passed after the drill", + "availability_window_minutes": 3, + "no_secret_material_recorded": true, + "notes": "Do not record OpenBao tokens, root tokens, unseal shares, private keys, passwords, OTP seeds, or recovery codes." +} diff --git a/docs/openbao.md b/docs/openbao.md index 0e72712..45945d5 100644 --- a/docs/openbao.md +++ b/docs/openbao.md @@ -322,6 +322,16 @@ Audit Core backend that writes JSONL records under days. Use it only to wire interfaces and setup validation before the durable Audit Core archive exists. +Emergency seal/unseal drills are disruptive and must only run in an attended +window with threshold unseal shares available. Record non-secret drill evidence +using `docs/openbao-emergency-drill-evidence.example.json` as a template, then +validate it with: + +```bash +make openbao-validate-emergency-evidence \ + OPENBAO_EMERGENCY_EVIDENCE=/path/to/evidence.json +``` + Monitoring baseline: - pod readiness and liveness from Kubernetes probes diff --git a/scripts/openbao-validate-emergency-drill-evidence.sh b/scripts/openbao-validate-emergency-drill-evidence.sh new file mode 100755 index 0000000..be5fdfd --- /dev/null +++ b/scripts/openbao-validate-emergency-drill-evidence.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +set -euo pipefail + +EVIDENCE="${OPENBAO_EMERGENCY_EVIDENCE:-${1:-/tmp/netkingdom-openbao-emergency-drill/evidence.json}}" + +usage() { + cat <<'USAGE' +Usage: scripts/openbao-validate-emergency-drill-evidence.sh [evidence.json] + +Validates non-secret OpenBao emergency seal/unseal drill evidence. The evidence +file should record timing, status checks, verification flags, and operator +notes, but must not contain OpenBao tokens, unseal shares, root tokens, private +keys, passwords, OTP seeds, or recovery codes. + +Environment: + OPENBAO_EMERGENCY_EVIDENCE Evidence JSON path. Default: + /tmp/netkingdom-openbao-emergency-drill/evidence.json +USAGE +} + +if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + usage + exit 0 +fi + +python3 - "$EVIDENCE" <<'PY' +from __future__ import annotations + +import json +import sys +from pathlib import Path + +path = Path(sys.argv[1]) +if not path.exists(): + print(f"[FAIL] emergency drill 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] emergency drill evidence is not valid JSON: {exc}", file=sys.stderr) + sys.exit(1) + +if not isinstance(data, dict): + print("[FAIL] emergency drill evidence root must be a JSON object", file=sys.stderr) + sys.exit(1) + +required_strings = [ + "drill_date", + "operator", + "source_cluster", + "source_namespace", + "source_pod", + "seal_started_at", + "sealed_status_observed_at", + "unseal_started_at", + "unseal_completed_at", + "post_unseal_verification", +] +required_true = [ + "seal_command_issued", + "sealed_status_confirmed", + "unseal_quorum_available", + "unseal_completed", + "post_unseal_status_verified", + "post_unseal_readiness_verified", + "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}") + +for key in required_true: + if data.get(key) is not True: + errors.append(f"must be true: {key}") + +window = data.get("availability_window_minutes") +if not isinstance(window, int) or window < 0: + errors.append("availability_window_minutes must be a non-negative integer") + +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] emergency drill 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] availability_window_minutes: {data['availability_window_minutes']}") +PY diff --git a/workplans/RAIL-PL-WP-0002-openbao-platform-secrets-service.md b/workplans/RAIL-PL-WP-0002-openbao-platform-secrets-service.md index 03a676b..3b3b46f 100644 --- a/workplans/RAIL-PL-WP-0002-openbao-platform-secrets-service.md +++ b/workplans/RAIL-PL-WP-0002-openbao-platform-secrets-service.md @@ -301,6 +301,14 @@ 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. +**2026-06-01:** Added the matching non-secret emergency seal/unseal drill +evidence template and `make openbao-validate-emergency-evidence`. The validator +requires an attended seal/unseal evidence file with timing, sealed-state proof, +unseal quorum availability, post-unseal verification, availability-window +duration, and `no_secret_material_recorded`. The validator does not run the +disruptive drill; it only checks the evidence captured after the attended +operation. + ### T07 - Cross-Repo Transition Tasks ```task