railiance-platform/scripts/openbao-isolated-restore/run-isolated-restore.sh
codex a2da4085e0 RPF-WP-0064-T10: break-glass drill mode for the isolated restore adapter
Adds --mode break-glass-drill with its own signed admission (drill:
"break-glass", attempt id RPF-WP-0064-T10-*, snapshot_label). After the
phase A restore and unseal it checks the held root token with lookup-self
only, runs a fresh generate-root ceremony with re-entered shares, does one
read-only sys/auth action, revokes the generated token (204 then 403) and
drills a cancelled attempt. Phase A is unchanged. The verifier types the
receipt extension and rejects token, accessor, OTP, nonce, encoded-token
and share shapes. Offline 34 and native 16 pass. Founder runbooks for the
drill and the read-only production SSO-down walk are in the T10 section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:02:10 +01:00

40 lines
2.7 KiB
Bash

#!/usr/bin/env bash
# Attended launcher: isolated original-OpenBao restore, phase A or T10 break-glass drill (RPF-WP-0064).
# Founder workstation only. Never railiance01, never a kubeconfig, no network.
set +x
set -euo pipefail
ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
cd -- "$ROOT"
umask 077
unset KUBECONFIG BAO_TOKEN VAULT_TOKEN BAO_ADDR VAULT_ADDR
usage='Usage: run-isolated-restore.sh [--break-glass-drill] ADMISSION ADMISSION_SIG ADMISSION_SHA256 CIPHERTEXT RECEIPT_DIR'
MODE=phase-a
if [[ "${1:-}" == --break-glass-drill ]]; then MODE=break-glass-drill; shift; fi
[[ $# == 5 ]] || { printf '%s\n' "$usage"; exit 1; }
[[ "$3" =~ ^sha256:[a-f0-9]{64}$ ]] || { printf '%s\n' 'ERROR: exact sha256:<hex> admission digest required.'; exit 1; }
for index in 1 2 4; do
[[ -f "${!index}" && ! -L "${!index}" ]] || { printf '%s\n' 'ERROR: provide regular owner-controlled input files.'; exit 1; }
done
[[ -d "$5" && ! -L "$5" && "$5" == /* ]] || { printf '%s\n' 'ERROR: RECEIPT_DIR must be an existing absolute directory.'; exit 1; }
[[ -t 0 && -t 1 && -t 2 ]] || { printf '%s\n' 'ERROR: run in your own interactive terminal.'; exit 1; }
[[ "$(hostname -s)" != railiance01* ]] || { printf '%s\n' 'ERROR: never on railiance01.'; exit 1; }
for tool in python3 age age-keygen docker ssh-keygen git; do
command -v "$tool" >/dev/null || { printf 'ERROR: required tool unavailable: %s\n' "$tool"; exit 1; }
done
RECEIPT="$5/rpf64-${MODE}-$(date -u +%Y%m%dT%H%M%SZ).json"
python3 scripts/openbao-isolated-restore/preflight.py --mode "$MODE" --admission "$1" --admission-sig "$2" \
--admission-sha256 "$3" --ciphertext "$4" --receipt "$RECEIPT"
if [[ "$MODE" == break-glass-drill ]]; then
printf '%s\n' 'Break-glass drill (RPF-WP-0064-T10) on an isolated, networkless restore. Never production.'
printf '%s\n' 'Prompts: backup age identity, two unseal shares, the held initial root token (lookup-self only),'
printf '%s\n' 'then two shares AGAIN for a fresh generate-root ceremony. Production root-token validity,'
printf '%s\n' 'production generate-root availability and the SSO-down access path are not claimed.'
else
printf '%s\n' 'Phase A only: networkless restore and metadata readback. No consumer continuity,'
printf '%s\n' 'live JWT login, phase B network or owner acceptance is claimed.'
printf '%s\n' 'You will be asked for the backup age identity, then two original unseal shares.'
fi
printf '%s\n' 'Press Ctrl-C at any time to abort; the container is removed and an aborted receipt written.'
python3 scripts/openbao-isolated-restore/restore_adapter.py --mode "$MODE" --admission "$1" --admission-sig "$2" \
--admission-sha256 "$3" --ciphertext "$4" --receipt "$RECEIPT"
printf 'Safe receipt: %s\n' "$RECEIPT"