diff --git a/scripts/renew-app-tokens.sh b/scripts/renew-app-tokens.sh index 5dfce73..3b9d9d2 100755 --- a/scripts/renew-app-tokens.sh +++ b/scripts/renew-app-tokens.sh @@ -3,27 +3,70 @@ # tokens live only as long as they're renewed within their period; nothing was # renewing these, so they expired (2026-08-01). Runs daily via a systemd timer. # -# Best-effort: reads each ~/.config/openbao/*.token and calls renew-self. A dead -# token logs a failure but never aborts the rest. No secrets are printed. +# Covers TWO sets of tokens: +# 1. $USER_DIR/*.token — user-owned app tokens, renewed against main. +# 2. /etc/openbao-*.token — root-owned infra tokens (backup, cert-renew). +# The /etc set lapsed unnoticed for 24 days (2026-07-29..08-22) because this +# script only walked the user dir, which broke nightly backups and would have +# broken cert renewal — hence it now runs as root to read both. +# +# The unsealer's backup token belongs to the SEPARATE openbao-unsealer instance, +# which publishes no host port, so it is renewed via `docker compose exec` +# rather than curl. Renewing it against main would 403. +# +# Best-effort: a dead token logs a failure but never aborts the rest. No secrets +# are printed. set -uo pipefail ADDR="${BAO_ADDR:-http://127.0.0.1:8200}" -DIR="$HOME/.config/openbao" -LOG="$DIR/token-renew.log" +PROJECT_DIR="/home/lutz/Projects/OpenBAO" +USER_DIR="${TOKEN_DIR:-/home/lutz/.config/openbao}" # explicit: $HOME is /root under the timer +UNSEALER_TOKEN="/etc/openbao-unsealer-backup.token" +LOG="$USER_DIR/token-renew.log" ts="$(date '+%F %T %Z')" shopt -s nullglob renewed=0; failed=0 -for tf in "$DIR"/*.token; do # *.token only — backup files (*.token.bak.*) don't match - tok="$(cat "$tf" 2>/dev/null)" + +ok() { echo "$ts $1 renewed ttl=${2}s" >> "$LOG"; renewed=$((renewed+1)); } +bad() { echo "$ts $1 RENEW FAILED: $2" >> "$LOG"; failed=$((failed+1)); } + +# renew_via_api +renew_via_api() { + local tf="$1" name tok resp ttl name="$(basename "$tf")" - [ -n "$tok" ] || { echo "$ts $name EMPTY" >> "$LOG"; failed=$((failed+1)); continue; } + tok="$(cat "$tf" 2>/dev/null)" + [ -n "$tok" ] || { bad "$name" "EMPTY"; return; } resp="$(curl -sS --max-time 10 -H "X-Vault-Token: $tok" -X POST "$ADDR/v1/auth/token/renew-self" 2>/dev/null)" ttl="$(printf '%s' "$resp" | jq -r '.auth.lease_duration // empty' 2>/dev/null)" - if [ -n "$ttl" ]; then - echo "$ts $name renewed ttl=${ttl}s" >> "$LOG"; renewed=$((renewed+1)) - else - echo "$ts $name RENEW FAILED: $(printf '%s' "$resp" | jq -c '.errors // .' 2>/dev/null)" >> "$LOG"; failed=$((failed+1)) - fi + if [ -n "$ttl" ]; then ok "$name" "$ttl" + else bad "$name" "$(printf '%s' "$resp" | jq -c '.errors // .' 2>/dev/null)"; fi +} + +# renew_via_exec — for the unsealer instance (no published port) +renew_via_exec() { + local tf="$1" name tok resp ttl + name="$(basename "$tf")" + tok="$(cat "$tf" 2>/dev/null)" + [ -n "$tok" ] || { bad "$name" "EMPTY"; return; } + # Bare `bao token renew` (no TOKEN arg) is the renew-self form; there is no + # -self flag in OpenBao's CLI. + resp="$(cd "$PROJECT_DIR" && docker compose exec -T -e BAO_TOKEN="$tok" openbao-unsealer \ + bao token renew -format=json 2>&1)" + ttl="$(printf '%s' "$resp" | jq -r '.auth.lease_duration // empty' 2>/dev/null)" + if [ -n "$ttl" ]; then ok "$name" "$ttl" + else bad "$name" "$(printf '%s' "$resp" | jq -c '.errors // .' 2>/dev/null || printf '%s' "$resp" | tr '\n' ' ')"; fi +} + +# 1. user-owned app tokens (*.token only — backups like *.token.bak.* don't match) +for tf in "$USER_DIR"/*.token; do renew_via_api "$tf"; done + +# 2. root-owned infra tokens; the unsealer one needs the exec path +for tf in /etc/openbao-*.token; do + [ -r "$tf" ] || { bad "$(basename "$tf")" "not readable (run as root)"; continue; } + if [ "$tf" = "$UNSEALER_TOKEN" ]; then renew_via_exec "$tf"; else renew_via_api "$tf"; fi done + echo "$ts summary: renewed=$renewed failed=$failed" >> "$LOG" +# Running as root must not leave the log root-owned for the next user-context read. +chown lutz:lutz "$LOG" 2>/dev/null || true [ "$failed" -eq 0 ] diff --git a/systemd/openbao-token-renew.service b/systemd/openbao-token-renew.service index f1f8c5d..4dd0f41 100644 --- a/systemd/openbao-token-renew.service +++ b/systemd/openbao-token-renew.service @@ -6,7 +6,7 @@ Requires=docker.service [Service] Type=oneshot -User=lutz -Group=lutz +# Runs as root: must read the root-owned /etc/openbao-*.token infra tokens +# (backup x2, cert-renew) alongside the user-owned ~lutz/.config/openbao ones. ExecStart=/home/lutz/Projects/OpenBAO/scripts/renew-app-tokens.sh -# Renews ~/.config/openbao/*.token via auth/token/renew-self. Best-effort. +# Renews both sets via auth/token/renew-self. Best-effort.