Files
OpenBAO/scripts/unseal-unsealer.sh
lutz 14275a5627 Auto-Unseal beim Boot und internes Zertifikat fuer gitea.famfi.home
Beim ersten Neustart seit 106 Tagen (2026-09-20) zeigte sich: der Unsealer kommt
versiegelt hoch, die Hauptinstanz startet daraufhin endlos neu und nichts kann mehr
Secrets lesen - Backups, Traefik-Zertifikate, Skripte. Bisher musste das jemand
von Hand entsiegeln.

- scripts/unseal-unsealer.sh + systemd/openbao-auto-unseal.service: entsiegelt die
  Sidecar nach docker.service, startet die Hauptinstanz nach und schreibt die Metrik
  openbao_unsealer_unsealed (cp1 alarmiert darauf). Idempotent.
  Zwei Fallstricke, die das Skript umgeht: `bao operator unseal -` (Schluessel ueber
  stdin) wird abgelehnt, und `bao status` liefert Exit 2 bei versiegelt - was trotzdem
  heisst, dass der Container antwortet.
  Der Schluessel liegt in /etc/openbao-unsealer.key (root, 0400), nicht im Repo.
- scripts/renew-gitea-cert.sh + systemd/gitea-cert-renew.*: gitea.famfi.home bekommt
  ein Zertifikat aus der eigenen PKI (90 Tage, Erneuerung ab 21 Tagen Restlaufzeit),
  Aufbau wie bei renew-openbao-cert.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-26 09:25:44 +02:00

81 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Entsiegelt die openbao-unsealer-Sidecar nach einem Host-Neustart.
#
# Warum: der Unsealer ist Shamir-versiegelt (1 Schluessel). Bis er offen ist, kann die
# Hauptinstanz ihren Transit-Seal nicht aufmachen und startet endlos neu (2026-09-20 beobachtet,
# erster Reboot seit 106 Tagen). Danach entsiegelt sich die Hauptinstanz von selbst.
#
# Der Schluessel steht in /etc/openbao-unsealer.key (root, 0400). Das ist bewusst ein Kompromiss:
# wer root auf diesem Host hat, kann entsiegeln — er liegt aber ohnehin schon in
# ~/Projects/OpenBAO/unsealer-init.json. Echte Trennung braeuchte den Unsealer auf einem
# anderen Host (siehe README, Abschnitt Auto-unseal).
set -uo pipefail
KEY_FILE="${KEY_FILE:-/etc/openbao-unsealer.key}"
CONTAINER="${CONTAINER:-openbao-unsealer}"
MAIN="${MAIN:-openbao}"
TEXTFILE_DIR="${TEXTFILE_DIR:-/var/lib/prometheus/node-exporter}"
PROM="${TEXTFILE_DIR}/openbao_unseal.prom"
TIMEOUT="${TIMEOUT:-300}" # Sekunden warten, bis Docker und der Container da sind
log() { printf '%s [unseal] %s\n' "$(date '+%F %T')" "$*"; }
metric() { # $1 = 1 (offen) | 0 (zu)
[ -d "$TEXTFILE_DIR" ] || return 0
{
echo "# HELP openbao_unsealer_unsealed 1 = openbao-unsealer ist entsiegelt"
echo "# TYPE openbao_unsealer_unsealed gauge"
echo "openbao_unsealer_unsealed $1"
echo "# HELP openbao_unsealer_last_attempt_timestamp_seconds Letzter Entsiegelungsversuch"
echo "# TYPE openbao_unsealer_last_attempt_timestamp_seconds gauge"
echo "openbao_unsealer_last_attempt_timestamp_seconds $(date +%s)"
} > "${PROM}.tmp" && mv "${PROM}.tmp" "$PROM" && chmod 644 "$PROM"
}
[ -r "$KEY_FILE" ] || { log "ABBRUCH: $KEY_FILE fehlt oder ist nicht lesbar"; metric 0; exit 1; }
# 1. Warten, bis der Container laeuft und seine API antwortet.
# 'bao status' liefert 0 = offen, 2 = versiegelt, 1 = nicht erreichbar. 0 und 2 heissen
# beide "der Container antwortet" — genau darauf wird gewartet.
deadline=$(( $(date +%s) + TIMEOUT ))
while :; do
docker exec "$CONTAINER" bao status >/dev/null 2>&1
rc=$?
case "$rc" in 0|2) break;; esac
[ "$(date +%s)" -lt "$deadline" ] || { log "ABBRUCH: $CONTAINER antwortet nicht binnen ${TIMEOUT}s (rc=$rc)"; metric 0; exit 1; }
sleep 5
done
# 2. Schon offen? Dann nichts tun (idempotent — die Unit darf jederzeit laufen).
if docker exec "$CONTAINER" bao status 2>/dev/null | grep -qE '^Sealed[[:space:]]+false'; then
log "$CONTAINER ist bereits entsiegelt"
metric 1
exit 0
fi
# 3. Entsiegeln. Der Schluessel geht als Umgebungsvariable in den Container;
# 'bao operator unseal -' (stdin) funktioniert nicht, es lehnt den Schluessel ab.
UK="$(tr -d '\r\n' < "$KEY_FILE")"
docker exec -e UK="$UK" "$CONTAINER" sh -c 'bao operator unseal "$UK"' >/dev/null 2>&1
unset UK
if docker exec "$CONTAINER" bao status 2>/dev/null | grep -qE '^Sealed[[:space:]]+false'; then
log "$CONTAINER entsiegelt"
metric 1
else
log "FEHLER: $CONTAINER ist weiterhin versiegelt"
metric 0
exit 1
fi
# 4. Die Hauptinstanz anstupsen, damit sie nicht erst den naechsten Restart-Zyklus abwartet.
if docker inspect "$MAIN" >/dev/null 2>&1; then
docker restart "$MAIN" >/dev/null 2>&1 && log "$MAIN neu gestartet"
for i in $(seq 1 24); do
docker exec "$MAIN" bao status 2>/dev/null | grep -qE '^Sealed[[:space:]]+false' && { log "$MAIN ist entsiegelt"; exit 0; }
sleep 5
done
log "HINWEIS: $MAIN ist nach 120s noch nicht entsiegelt — Logs pruefen"
fi
exit 0