foundation/bootstrap/vault-unseal.sh
Andreas Niemann 0e81635d88 feat(bootstrap): vault init/unseal + capture to encrypted config (T05)
foundation-vault (hashicorp/vault:1.18, digest-pinned) with integrated raft
storage in foundation-vault-data (-> /vault/file, which the entrypoint chowns to
the vault user), IPC_LOCK for mlock, internal only (8200 unpublished). Init +
unseal reuse the olsitec-core pattern but over docker-exec/SSH (ADR-007): the
foundation-vault-init command inits 1-of-1 Shamir, unseals, and emits keys + root
token on stdout — marked secret and NOT streamed (logging:Stderr) so they never
reach the terminal/logs (D2). run.sh captures them into vaultCredentials:* (the
one bootstrap secret that cannot live in Vault, CONTRACT_002 §2.4) with an
idempotent guard that avoids churning the config. vault-unseal.sh is the
passphrase-gated reboot helper (ADR-004): reads keys from config, unseals over an
SSH stdin pipe. run.sh also now pins the Pulumi backend per-process
(PULUMI_BACKEND_URL) instead of a global `pulumi login`.

Live on cx33 Helsinki: initialized + unsealed (raft 1.18.5), keys captured to
encrypted config, idempotent re-up reuses stored keys, container-restart reseal
recovered by vault-unseal.sh. Acceptance T05 met.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 21:32:52 +02:00

34 lines
1.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# Passphrase-gated Vault unseal helper (ADR-004). After a VM reboot foundation-vault
# re-seals; this reads the unseal keys from the passphrase-encrypted Pulumi config
# and unseals over SSH via docker exec (ADR-007). No external KMS, no SaaS — reboots
# require the master passphrase to be available (the ratified Layer-0 trade-off).
#
# `pulumi up` unseals as part of a deploy (the foundation-vault-init command); this
# helper is the break-glass for a reboot WITHOUT a deploy.
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
export PULUMI_BACKEND_URL="file://${DIR}/state"
export PULUMI_CONFIG_PASSPHRASE="$(pass olsitec-foundation/PULUMI_CONFIG_PASSPHRASE)"
cd "$DIR"
pulumi stack select foundation >/dev/null
KEYS_JSON=$(pulumi config get vaultCredentials:unsealKeys 2>/dev/null || true)
[ -n "$KEYS_JSON" ] || { echo "no vaultCredentials:unsealKeys in config — is Vault initialized?" >&2; exit 1; }
HOST=$(pulumi config get foundation:vm.host)
PORT=$(pulumi config get foundation:vm.sshPort 2>/dev/null || echo 22)
SSHUSER=$(pulumi config get foundation:vm.user)
KEY="${SSH_PRIVATE_KEY_PATH:-${HOME}/.ssh/foundation-test_ed25519}"
# Keys travel over the SSH stdin pipe, NEVER on the command line. The remote shell
# unseals foundation-vault with each one via docker exec.
printf '%s' "$KEYS_JSON" | jq -r '.[]' | \
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i "$KEY" -p "$PORT" "$SSHUSER@$HOST" '
n=0
while IFS= read -r k; do
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 foundation-vault vault operator unseal "$k" >/dev/null && n=$((n+1))
done
echo "applied $n unseal key(s)"
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 foundation-vault vault status -format=json | jq -r "\"sealed: \" + (.sealed|tostring)"
'
echo "vault-unseal.sh: done"