reference-ansible/scripts/bao-seed.sh
Simon Bärlocher a8c8ac3e1e
fix(bao-seed): seed proxy_outpost_token, garage and nextcloud secrets
The seed script only generated authentik LDAP, opnform, homarr and
bookstack secrets. Proxy outposts, garage and nextcloud were never
seeded, so a fresh demo mount was missing garage/*, nextcloud/* and
authentik/proxy_outpost_token — the proxy outpost then failed to
authenticate (403) and garage/nextcloud roles aborted on missing vault
keys. Add the three blocks; merge semantics keep existing keys.
2026-07-02 17:22:08 +02:00

206 lines
6.1 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Seed OpenBao secrets for a demo inventory. Merge semantics: existing
# keys at a given path are kept; only missing keys are generated. OIDC
# client secrets are synced between <mount>/data/authentik and the
# per-service secret so authentik and the service agree on the same
# value.
#
# Usage:
# scripts/bao-seed.sh demo-gymburgdorf
#
# Requirements:
# - bao CLI in $PATH, authenticated (make bao)
# - jq in $PATH
# - openssl in $PATH
#
# Environment overrides:
# BAO_ADDR default https://bao.digitalboard.ch
# DRY_RUN=1 print what would change without writing
set -eu
MOUNT="${1:-}"
if [[ -z "$MOUNT" ]]; then
echo "usage: $0 <bao-mount> (e.g. demo-gymburgdorf)" >&2
exit 1
fi
: "${BAO_ADDR:=https://bao.digitalboard.ch}"
export BAO_ADDR
for cmd in bao jq openssl mktemp; do
command -v "$cmd" >/dev/null || { echo "missing: $cmd" >&2; exit 1; }
done
if ! bao token lookup >/dev/null 2>&1; then
echo "not authenticated — run 'make bao' first" >&2
exit 1
fi
DRY_RUN="${DRY_RUN:-0}"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
# Read current data for a KV-v2 secret into $WORKDIR/<path>.json.
# Writes {} if the secret does not exist or has no data yet.
read_secret() {
local path="$1"
local out="$WORKDIR/$path.json"
local raw="$WORKDIR/$path.raw"
if bao kv get -format=json "$MOUNT/$path" >"$raw" 2>/dev/null; then
if jq -e -c '.data.data // {}' "$raw" >"$out" 2>/dev/null; then
return 0
fi
fi
echo '{}' >"$out"
}
# Write the in-memory secret back to bao using `bao kv put @file` so
# values containing `=` (e.g. base64 padding) survive intact.
write_secret() {
local path="$1"
local src="$WORKDIR/$path.json"
if [[ "$DRY_RUN" == "1" ]]; then
echo " [dry-run] would write $MOUNT/$path"
return
fi
bao kv put "$MOUNT/$path" "@$src" >/dev/null
}
# Print the current value of a key from a secret file, empty string if
# absent.
get_key() {
local file="$1"
local key="$2"
jq -r --arg k "$key" '.[$k] // ""' "$file"
}
# Set a key inside the secret file to the given literal value.
set_key() {
local file="$1"
local key="$2"
local value="$3"
local tmp="$file.tmp"
jq --arg k "$key" --arg v "$value" '.[$k] = $v' "$file" >"$tmp"
mv "$tmp" "$file"
}
# Generate a key in the secret file if missing.
ensure_key() {
local path="$1"
local key="$2"
local generator="$3"
local file="$WORKDIR/$path.json"
if [[ -n "$(get_key "$file" "$key")" ]]; then
return
fi
local value
value="$($generator)"
set_key "$file" "$key" "$value"
echo " + $key"
}
# Force a key on path2 to match path1's value for the named key.
# Prints when a change happens.
sync_key_from() {
local src_path="$1"
local src_key="$2"
local dst_path="$3"
local dst_key="$4"
local src_file="$WORKDIR/$src_path.json"
local dst_file="$WORKDIR/$dst_path.json"
local src_val
src_val="$(get_key "$src_file" "$src_key")"
if [[ -z "$src_val" ]]; then
echo " ! $src_path/$src_key missing — cannot sync to $dst_path/$dst_key" >&2
return
fi
local dst_val
dst_val="$(get_key "$dst_file" "$dst_key")"
if [[ "$src_val" == "$dst_val" ]]; then
return
fi
set_key "$dst_file" "$dst_key" "$src_val"
echo " = $dst_key (synced from $src_path/$src_key)"
}
gen_hex32() { openssl rand -hex 32; }
gen_hex64() { openssl rand -hex 64; }
gen_pass() { openssl rand -base64 24 | tr -d '/+='; }
gen_long_pass() { openssl rand -base64 32 | tr -d '/+='; }
gen_app_key() { echo "base64:$(openssl rand -base64 32)"; }
# OpnForm requires: min 8 chars, a letter, a digit, AND one of the
# special chars @$!%*#?&-_+=.,:;<>^()[]{}|~. The base64 generator alone
# only produces [A-Za-z0-9], so we append a fixed-position special char
# and digit to guarantee the rule passes regardless of entropy outcome.
gen_opnform_pass() { echo "$(openssl rand -base64 18 | tr -d '/+=')!1Aa"; }
# ---------------------------------------------------------------- main
echo "==> seeding $MOUNT (dry-run=$DRY_RUN)"
echo "-> authentik"
read_secret authentik
ensure_key authentik secret_key gen_hex64
ensure_key authentik postgres_password gen_pass
ensure_key authentik admin_password gen_pass
ensure_key authentik ldap_outpost_token gen_hex32
ensure_key authentik proxy_outpost_token gen_hex32
ensure_key authentik nextcloud_oidc_secret gen_hex32
ensure_key authentik opnform_oidc_secret gen_hex32
ensure_key authentik homarr_oidc_secret gen_hex32
ensure_key authentik bookstack_oidc_secret gen_hex32
write_secret authentik
echo "-> nextcloud"
read_secret nextcloud
ensure_key nextcloud postgres_password gen_long_pass
ensure_key nextcloud admin_password gen_pass
write_secret nextcloud
echo "-> garage"
read_secret garage
ensure_key garage rpc_secret gen_hex32
ensure_key garage admin_token gen_hex32
ensure_key garage metrics_token gen_hex32
ensure_key garage webui_password gen_pass
write_secret garage
echo "-> opnform"
read_secret opnform
ensure_key opnform app_key gen_app_key
ensure_key opnform jwt_secret gen_hex32
ensure_key opnform front_api_secret gen_hex32
ensure_key opnform db_password gen_long_pass
ensure_key opnform admin_password gen_opnform_pass
ensure_key opnform oidc_client_secret gen_hex32
sync_key_from authentik opnform_oidc_secret opnform oidc_client_secret
write_secret opnform
echo "-> homarr"
read_secret homarr
ensure_key homarr secret_encryption_key gen_hex32
ensure_key homarr admin_password gen_pass
ensure_key homarr oidc_client_secret gen_hex32
sync_key_from authentik homarr_oidc_secret homarr oidc_client_secret
write_secret homarr
echo "-> bookstack"
read_secret bookstack
ensure_key bookstack db_root_password gen_long_pass
ensure_key bookstack db_password gen_long_pass
ensure_key bookstack admin_password gen_pass
ensure_key bookstack oidc_client_secret gen_hex32
sync_key_from authentik bookstack_oidc_secret bookstack oidc_client_secret
write_secret bookstack
echo "==> done"