Add OpenBao restore drill actions

This commit is contained in:
2026-05-25 18:48:23 +02:00
parent e2540529f0
commit 82d69e006f
2 changed files with 142 additions and 0 deletions

View File

@@ -13,6 +13,7 @@ import base64
import hashlib
import html
import json
import shlex
import subprocess
import sys
import urllib.error
@@ -1338,6 +1339,8 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_output = yes(data, "openbao_init_output_produced")
initialized = yes(data, "openbao_initialized")
initial_config_applied = yes(data, "openbao_initial_config_applied")
restore_done = yes(data, "restore_drill_passed")
trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete")
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
@@ -1366,6 +1369,12 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
lockdown_status = "blocked"
lockdown_location = "OpenBao is not recorded as unsealed; sealing is only meaningful while it is serving requests."
restore_status = "done" if restore_done else "todo"
restore_location = "Create, encrypt, and isolated-restore a Railiance OpenBao Raft snapshot before live secrets move in."
if not initial_config_applied:
restore_status = "blocked"
restore_location = "Apply OpenBao initial configuration before proving backup and restore."
return [
add_taint(
{
@@ -1403,12 +1412,26 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
},
openbao_downstream_taint if initialized else {},
),
add_taint(
{
"name": "Restore drill",
"description": "Prove that Railiance OpenBao can be snapshotted, restored into isolation, unsealed, and verified before production trust.",
"subsystem": "Railiance OpenBao",
"responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"),
"location": restore_location,
"state": restore_status,
},
openbao_downstream_taint if initialized else {},
),
]
def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_output = yes(data, "openbao_init_output_produced")
initialized = yes(data, "openbao_initialized")
initial_config_applied = yes(data, "openbao_initial_config_applied")
restore_done = yes(data, "restore_drill_passed")
trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete")
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
@@ -1454,6 +1477,59 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
"unset OPENBAO_TOKEN"
)
restore_status = "done" if restore_done else "todo"
restore_reason = "Restore drill is recorded." if restore_done else "Create encrypted snapshot evidence and complete an isolated restore proof."
if not initial_config_applied:
restore_status = "blocked"
restore_reason = "OpenBao initial configuration must be applied before the restore drill."
restore_taint = openbao_downstream_taint if initialized else {}
public_key = extract_age_public_key(data.get("custodian_age_public_key"))
quoted_public_key = shlex.quote(public_key if public_key else "<age-recipient>")
snapshot_workspace_command = (
'export RESTORE_DRILL_DIR="${RESTORE_DRILL_DIR:-/tmp/netkingdom-openbao-restore-drill}"\n'
'mkdir -p "$RESTORE_DRILL_DIR"\n'
'chmod 700 "$RESTORE_DRILL_DIR"\n'
'printf "Restore drill workspace: %s\\n" "$RESTORE_DRILL_DIR"'
)
snapshot_command = (
'export RESTORE_DRILL_DIR="${RESTORE_DRILL_DIR:-/tmp/netkingdom-openbao-restore-drill}"\n'
'mkdir -p "$RESTORE_DRILL_DIR"\n'
'chmod 700 "$RESTORE_DRILL_DIR"\n'
"printf 'OpenBao token: ' >&2\n"
"read -rs OPENBAO_TOKEN\n"
"printf '\\n' >&2\n"
'printf \'%s\\n\' "$OPENBAO_TOKEN" | kubectl exec -i -n openbao openbao-0 -- '
"sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao operator raft snapshot save /tmp/openbao-raft.snap'\n"
"unset OPENBAO_TOKEN\n"
'kubectl cp openbao/openbao-0:/tmp/openbao-raft.snap "$RESTORE_DRILL_DIR/openbao-raft.snap"\n'
'kubectl exec -n openbao openbao-0 -- rm -f /tmp/openbao-raft.snap\n'
'sha256sum "$RESTORE_DRILL_DIR/openbao-raft.snap" | tee "$RESTORE_DRILL_DIR/openbao-raft.snap.sha256"'
)
encrypt_snapshot_status = restore_status if public_key else "blocked"
encrypt_snapshot_reason = restore_reason if public_key else "Record the custodian public age recipient before encrypting snapshot custody material."
encrypt_snapshot_command = (
'export RESTORE_DRILL_DIR="${RESTORE_DRILL_DIR:-/tmp/netkingdom-openbao-restore-drill}"\n'
f'age -r {quoted_public_key} -o "$RESTORE_DRILL_DIR/openbao-raft.snap.age" "$RESTORE_DRILL_DIR/openbao-raft.snap"\n'
'sha256sum "$RESTORE_DRILL_DIR/openbao-raft.snap.age" | tee "$RESTORE_DRILL_DIR/openbao-raft.snap.age.sha256"\n'
'if command -v shred >/dev/null 2>&1; then\n'
' shred -u "$RESTORE_DRILL_DIR/openbao-raft.snap"\n'
"else\n"
' rm -f "$RESTORE_DRILL_DIR/openbao-raft.snap"\n'
"fi"
)
isolated_restore_command = (
"cat <<'RESTORE_DRILL'\n"
"Isolated Railiance OpenBao restore drill evidence required:\n"
"1. Use a disposable cluster, VM, or namespace. Do not restore into namespace=openbao.\n"
"2. Deploy a fresh OpenBao instance with empty storage.\n"
"3. Decrypt the encrypted snapshot only inside the isolated drill workspace.\n"
"4. Restore with: bao operator raft snapshot restore -force /tmp/openbao-raft.snap\n"
"5. Unseal the isolated instance with the current trial/drill shares.\n"
"6. Verify status, mounts, auth methods, policies, and a non-production test secret read.\n"
"7. Destroy the isolated environment and record only non-secret evidence in this UI.\n"
"RESTORE_DRILL"
)
return [
add_taint(
{
@@ -1545,6 +1621,66 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
},
openbao_downstream_taint if initialized else {},
),
add_taint(
{
"name": "Prepare restore drill workspace",
"description": "Create a local restricted directory for temporary snapshot evidence.",
"status": restore_status,
"status_reason": restore_reason,
"command": snapshot_workspace_command,
},
restore_taint,
),
add_taint(
{
"name": "Create encrypted-restore snapshot source",
"description": "Prompt locally for an OpenBao token, create a Raft snapshot in the pod, copy it out, remove the pod copy, and record a plaintext hash before encryption.",
"status": restore_status,
"status_reason": restore_reason,
"command": snapshot_command,
},
restore_taint,
),
add_taint(
{
"name": "Encrypt restore snapshot",
"description": "Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot.",
"status": encrypt_snapshot_status,
"status_reason": encrypt_snapshot_reason,
"command": encrypt_snapshot_command,
},
restore_taint,
),
add_taint(
{
"name": "Run isolated restore proof",
"description": "Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.",
"status": restore_status,
"status_reason": restore_reason,
"command": isolated_restore_command,
},
restore_taint,
),
add_taint(
{
"name": "Run post-restore readiness check",
"description": "Re-run the Railiance post-unseal checks after restore evidence has been captured.",
"status": restore_status,
"status_reason": restore_reason,
"command": "make -C ../railiance-platform openbao-verify-post-unseal",
},
restore_taint,
),
add_taint(
{
"name": "Record restore drill passed",
"description": "Non-secret metadata checkbox after encrypted snapshot evidence and isolated restore proof are complete.",
"status": restore_status,
"status_reason": restore_reason,
"command": "Use the checkbox: Restore drill passed",
},
restore_taint,
),
]