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.
206 lines
6.1 KiB
Bash
Executable file
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"
|