Build out the home-lab OpenBAO deployment beyond the basic node: - docker-compose: add openbao-unsealer sidecar; main node now transit auto-unseals against it (seal config kept in gitignored config/seal.hcl) - policies/admin.hcl: non-root admin policy; per-engine rules for ssh/pki/pki_int/totp/transit - Internal two-tier CA (pki/ root + pki_int/ intermediate) issues the openbao.famfi.home leaf Traefik serves; root CA published under ca/ - scripts/ + systemd/: daily cert renewal and Raft snapshot backups (both instances), with scoped tokens stored outside the repo - README: full runbook (auto-unseal, PKI, renewal, backups, DR/restore) Secrets (init/unsealer keys, tokens, seal stanza) stay gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2.4 KiB
Bash
Executable File
56 lines
2.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Take Raft snapshots of BOTH OpenBAO instances (main + unsealer) and prune old
|
|
# ones. Run as root (reads the root-only backup tokens, writes /var/backups).
|
|
#
|
|
# DR note: a restored MAIN snapshot can only be unsealed with the unsealer's
|
|
# transit key — so the unsealer snapshot (+ its unseal key in unsealer-init.json
|
|
# + the recovery keys in init-output.json) are part of the same recovery set.
|
|
#
|
|
# Installed as a systemd timer (openbao-backup.timer). Manual run:
|
|
# sudo /home/lutz/Projects/OpenBAO/scripts/backup-raft-snapshots.sh
|
|
set -euo pipefail
|
|
|
|
PROJECT_DIR="/home/lutz/Projects/OpenBAO"
|
|
BACKUP_ROOT="${BACKUP_ROOT:-/var/backups/openbao}"
|
|
KEEP="${KEEP:-14}" # how many snapshots to retain per instance
|
|
STAMP="$(date '+%Y%m%d-%H%M%S')"
|
|
|
|
log() { printf '%s [backup] %s\n' "$(date '+%F %T')" "$*"; }
|
|
die() { log "ERROR: $*"; exit 1; }
|
|
|
|
cd "$PROJECT_DIR" || die "cannot cd to $PROJECT_DIR"
|
|
|
|
# snapshot <service> <token-file> <label>
|
|
snapshot() {
|
|
local svc="$1" tokfile="$2" label="$3"
|
|
[ -r "$tokfile" ] || die "token file $tokfile not readable (run as root?)"
|
|
local tok dir out incontainer="/tmp/${label}-${STAMP}.snap"
|
|
tok="$(cat "$tokfile")"
|
|
dir="${BACKUP_ROOT}/${label}"
|
|
out="${dir}/openbao-${label}-${STAMP}.snap"
|
|
install -d -o root -g root -m 0700 "$dir"
|
|
|
|
docker compose exec -T -e BAO_TOKEN="$tok" "$svc" \
|
|
bao operator raft snapshot save "$incontainer" >/dev/null \
|
|
|| die "snapshot save failed for $svc"
|
|
docker compose cp "${svc}:${incontainer}" "$out" >/dev/null \
|
|
|| die "copy-out failed for $svc"
|
|
docker compose exec -T "$svc" rm -f "$incontainer" >/dev/null 2>&1 || true
|
|
chmod 0600 "$out"
|
|
|
|
# Validate: non-trivial size + gzip integrity (a raft snapshot is a gzip archive)
|
|
local size; size=$(stat -c %s "$out")
|
|
[ "$size" -ge 1024 ] || die "snapshot $out suspiciously small (${size}B)"
|
|
gzip -t "$out" 2>/dev/null && log "OK ${label}: ${out} (${size}B, gzip-valid)" \
|
|
|| die "snapshot $out failed gzip integrity check"
|
|
|
|
# Retention: keep newest $KEEP, delete the rest
|
|
ls -1t "${dir}"/openbao-${label}-*.snap 2>/dev/null | tail -n +$((KEEP + 1)) | while read -r old; do
|
|
rm -f -- "$old"; log "pruned old snapshot $(basename "$old")"
|
|
done
|
|
}
|
|
|
|
snapshot "openbao" "/etc/openbao-backup.token" "main"
|
|
snapshot "openbao-unsealer" "/etc/openbao-unsealer-backup.token" "unsealer"
|
|
log "done; retained up to ${KEEP} snapshots per instance under ${BACKUP_ROOT}"
|