diff --git a/.env.example b/.env.example index b0a5343..b2da828 100644 --- a/.env.example +++ b/.env.example @@ -12,12 +12,26 @@ OBMP_DATA_ROOT=/var/openbmp # (Kafka external listener, BMP source, ExaBGP peering). HOST_IP=changeme +# Auth mode: +# local — Grafana built-in login (admin / openbmp). Lab default. +# OBMP_DOMAIN / OBMP_COOKIE_DOMAIN below can stay blank. +# authelia — Authelia in front (docker compose --profile auth). +# OBMP_DOMAIN and OBMP_COOKIE_DOMAIN must be set, and a +# reverse proxy must terminate TLS at OBMP_DOMAIN. +OBMP_AUTH_MODE=local + # Public domain fronting Grafana / Authelia / portal (TLS terminates upstream). -OBMP_DOMAIN=changeme.example.com +# Only required when OBMP_AUTH_MODE=authelia. +OBMP_DOMAIN= # Authelia session-cookie domain — the parent domain of OBMP_DOMAIN so the -# cookie is valid across subpaths/subdomains. -OBMP_COOKIE_DOMAIN=example.com +# cookie is valid across subpaths/subdomains. Only required when +# OBMP_AUTH_MODE=authelia. +OBMP_COOKIE_DOMAIN= + +# Grafana self-generated URL (alerts, share links). setup.sh writes this +# automatically based on OBMP_AUTH_MODE — leave blank, it will be filled in. +GF_SERVER_ROOT_URL= # Container memory limits. Lab defaults shown; raise for production # (see docs/production-sizing.md). psql-app's limit must exceed its MEM heap. diff --git a/docker-compose.yml b/docker-compose.yml index b635650..3d82a71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -115,7 +115,11 @@ services: environment: - GF_SECURITY_ADMIN_PASSWORD=openbmp - GF_AUTH_ANONYMOUS_ENABLED=false - - GF_SERVER_ROOT_URL=https://${OBMP_DOMAIN:-bmp.apodacalab.com}/grafana/ + # setup.sh writes GF_SERVER_ROOT_URL into .env based on OBMP_AUTH_MODE + # (http://HOST_IP:3000/grafana/ for local, https://OBMP_DOMAIN/grafana/ + # for authelia). The fallback only matters if compose is run before + # setup.sh — keeps Grafana up at a sane URL. + - GF_SERVER_ROOT_URL=${GF_SERVER_ROOT_URL:-http://localhost:3000/grafana/} - GF_SERVER_SERVE_FROM_SUB_PATH=true - GF_AUTH_PROXY_ENABLED=true - GF_AUTH_PROXY_HEADER_NAME=Remote-User diff --git a/setup.sh b/setup.sh index 216338d..15f2d2d 100755 --- a/setup.sh +++ b/setup.sh @@ -4,10 +4,14 @@ # deployment: every step is guarded and never overwrites live config. # # cp .env.example .env # first run only -# $EDITOR .env # fill in HOST_IP, OBMP_DOMAIN, ... -# ./setup.sh -# docker compose up -d # collector core -# docker compose --profile test --profile auth up -d # full stack +# $EDITOR .env # at minimum fill in HOST_IP +# ./setup.sh # local-auth mode by default +# docker compose up -d # collector core +# docker compose --profile test up -d # + lab feeders (ExaBGP, gobgp, ...) +# +# For Authelia mode, set OBMP_AUTH_MODE=authelia plus OBMP_DOMAIN and +# OBMP_COOKIE_DOMAIN in .env before running setup.sh, then bring up with +# docker compose --profile test --profile auth up -d # set -euo pipefail cd "$(dirname "$0")" @@ -18,7 +22,8 @@ AUTHELIA_IMAGE="authelia/authelia:4.38" if [ ! -f .env ]; then cp .env.example .env echo "Created .env from .env.example." - echo "Edit it (HOST_IP, OBMP_DOMAIN, OBMP_COOKIE_DOMAIN, credentials), then re-run ./setup.sh" + echo "Edit it (set HOST_IP at minimum; OBMP_DOMAIN/OBMP_COOKIE_DOMAIN only" + echo "if OBMP_AUTH_MODE=authelia), then re-run ./setup.sh" exit 1 fi @@ -42,10 +47,40 @@ OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}" OBMP_DOMAIN="$(get_env OBMP_DOMAIN)" OBMP_COOKIE_DOMAIN="$(get_env OBMP_COOKIE_DOMAIN)" HOST_IP="$(get_env HOST_IP)" +OBMP_AUTH_MODE="$(get_env OBMP_AUTH_MODE)" +if [ -z "$OBMP_AUTH_MODE" ]; then + # Back-compat: existing deployments predate OBMP_AUTH_MODE. If Authelia was + # ever initialized (session secret present) or a real OBMP_DOMAIN is set, + # this is an Authelia host — default to that mode rather than flipping it + # to local and breaking the next `docker compose up`. Fresh .env from + # .env.example explicitly sets OBMP_AUTH_MODE=local, so this branch only + # triggers for pre-existing installs. + if [ -n "$(get_env AUTHELIA_SESSION_SECRET)" ] || \ + { [ -n "$OBMP_DOMAIN" ] && [[ "$OBMP_DOMAIN" != changeme* ]]; }; then + OBMP_AUTH_MODE="authelia" + echo "OBMP_AUTH_MODE not set; inferred 'authelia' from existing .env" + else + OBMP_AUTH_MODE="local" + echo "OBMP_AUTH_MODE not set; defaulting to 'local'" + fi + set_env OBMP_AUTH_MODE "$OBMP_AUTH_MODE" +fi # --- validate --------------------------------------------------------------- +# HOST_IP is always required. OBMP_DOMAIN / OBMP_COOKIE_DOMAIN are only +# required when OBMP_AUTH_MODE=authelia (Authelia needs a real cookie domain +# and the public URL it lives behind). fail=0 -for var in HOST_IP OBMP_DOMAIN OBMP_COOKIE_DOMAIN; do +required=(HOST_IP) +case "$OBMP_AUTH_MODE" in + local) ;; + authelia) required+=(OBMP_DOMAIN OBMP_COOKIE_DOMAIN) ;; + *) + echo "ERROR: OBMP_AUTH_MODE='$OBMP_AUTH_MODE' (must be 'local' or 'authelia')" >&2 + exit 1 + ;; +esac +for var in "${required[@]}"; do val="$(get_env "$var")" if [ -z "$val" ] || [[ "$val" == changeme* ]]; then echo "ERROR: $var is unset or still 'changeme' in .env" >&2 @@ -53,6 +88,7 @@ for var in HOST_IP OBMP_DOMAIN OBMP_COOKIE_DOMAIN; do fi done [ "$fail" -eq 0 ] || { echo "Fix .env and re-run." >&2; exit 1; } +echo "Auth mode: $OBMP_AUTH_MODE" # --- privilege helper ------------------------------------------------------- # $OBMP_DATA_ROOT is often root-owned (e.g. /var/openbmp). Use sudo only if the @@ -77,38 +113,56 @@ $SUDO chmod -R 777 "$OBMP_DATA_ROOT" 2>/dev/null || true echo "Syncing Grafana provisioning ..." $SUDO cp -r obmp-grafana/provisioning/. "$OBMP_DATA_ROOT/grafana/provisioning/" -# --- Authelia secrets ------------------------------------------------------- -# Generate only if blank/absent; never overwrite an existing value. -for key in AUTHELIA_SESSION_SECRET AUTHELIA_JWT_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY; do - cur="$(get_env "$key")" - if [ -z "$cur" ]; then - set_env "$key" "$(openssl rand -hex 32)" - echo "Generated $key" - fi -done -AUTHELIA_SESSION_SECRET="$(get_env AUTHELIA_SESSION_SECRET)" -AUTHELIA_JWT_SECRET="$(get_env AUTHELIA_JWT_SECRET)" -AUTHELIA_STORAGE_ENCRYPTION_KEY="$(get_env AUTHELIA_STORAGE_ENCRYPTION_KEY)" - -# --- Authelia config (fresh-deploy only — never clobber a live config) ------ -export AUTHELIA_SESSION_SECRET AUTHELIA_JWT_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY \ - OBMP_DOMAIN OBMP_COOKIE_DOMAIN -SUBST='${AUTHELIA_SESSION_SECRET} ${AUTHELIA_JWT_SECRET} ${AUTHELIA_STORAGE_ENCRYPTION_KEY} ${OBMP_DOMAIN} ${OBMP_COOKIE_DOMAIN}' - -if [ ! -f "$OBMP_DATA_ROOT/authelia/configuration.yml" ]; then - envsubst "$SUBST" < authelia/configuration.yml.template \ - | $SUDO tee "$OBMP_DATA_ROOT/authelia/configuration.yml" > /dev/null - echo "Rendered authelia/configuration.yml" -else - echo "authelia/configuration.yml exists — left untouched" +# --- Grafana root URL ------------------------------------------------------- +# In local mode Grafana is hit directly on :3000; in authelia mode the public +# URL is https://OBMP_DOMAIN/grafana/. setup.sh writes the right value into +# .env so docker-compose just references ${GF_SERVER_ROOT_URL}. +case "$OBMP_AUTH_MODE" in + local) desired_root_url="http://${HOST_IP}:3000/grafana/" ;; + authelia) desired_root_url="https://${OBMP_DOMAIN}/grafana/" ;; +esac +current_root_url="$(get_env GF_SERVER_ROOT_URL)" +if [ "$current_root_url" != "$desired_root_url" ]; then + set_env GF_SERVER_ROOT_URL "$desired_root_url" + echo "Set GF_SERVER_ROOT_URL=$desired_root_url" fi -if [ ! -f "$OBMP_DATA_ROOT/authelia/users_database.yml" ]; then - $SUDO cp authelia/users_database.yml.template \ - "$OBMP_DATA_ROOT/authelia/users_database.yml" - echo "Rendered authelia/users_database.yml (demo user: openbmp)" +# --- Authelia setup (authelia mode only) ------------------------------------ +if [ "$OBMP_AUTH_MODE" = "authelia" ]; then + # Generate secrets only if blank/absent; never overwrite an existing value. + for key in AUTHELIA_SESSION_SECRET AUTHELIA_JWT_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY; do + cur="$(get_env "$key")" + if [ -z "$cur" ]; then + set_env "$key" "$(openssl rand -hex 32)" + echo "Generated $key" + fi + done + AUTHELIA_SESSION_SECRET="$(get_env AUTHELIA_SESSION_SECRET)" + AUTHELIA_JWT_SECRET="$(get_env AUTHELIA_JWT_SECRET)" + AUTHELIA_STORAGE_ENCRYPTION_KEY="$(get_env AUTHELIA_STORAGE_ENCRYPTION_KEY)" + + # Config files: fresh-deploy only — never clobber a live config. + export AUTHELIA_SESSION_SECRET AUTHELIA_JWT_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY \ + OBMP_DOMAIN OBMP_COOKIE_DOMAIN + SUBST='${AUTHELIA_SESSION_SECRET} ${AUTHELIA_JWT_SECRET} ${AUTHELIA_STORAGE_ENCRYPTION_KEY} ${OBMP_DOMAIN} ${OBMP_COOKIE_DOMAIN}' + + if [ ! -f "$OBMP_DATA_ROOT/authelia/configuration.yml" ]; then + envsubst "$SUBST" < authelia/configuration.yml.template \ + | $SUDO tee "$OBMP_DATA_ROOT/authelia/configuration.yml" > /dev/null + echo "Rendered authelia/configuration.yml" + else + echo "authelia/configuration.yml exists — left untouched" + fi + + if [ ! -f "$OBMP_DATA_ROOT/authelia/users_database.yml" ]; then + $SUDO cp authelia/users_database.yml.template \ + "$OBMP_DATA_ROOT/authelia/users_database.yml" + echo "Rendered authelia/users_database.yml (demo user: openbmp)" + else + echo "authelia/users_database.yml exists — left untouched" + fi else - echo "authelia/users_database.yml exists — left untouched" + echo "Skipping Authelia setup (OBMP_AUTH_MODE=$OBMP_AUTH_MODE)" fi # --- gobgpd.conf rendering (host-side -- gobgp image is distroless) --------- @@ -122,16 +176,39 @@ for d in gobgp gobgp-evpn; do done # --- images ----------------------------------------------------------------- +# Always pull / build the `test` profile (ExaBGP, traffic-gen, gobgp -- the +# lab feeder bits). Pull `auth` images only when that mode is selected so a +# local-mode bootstrap on a fresh host doesn't drag in Authelia + portal. echo "Pulling and building images ..." -docker compose pull --quiet -docker compose --profile test --profile auth build +if [ "$OBMP_AUTH_MODE" = "authelia" ]; then + docker compose --profile test --profile auth pull --quiet + docker compose --profile test --profile auth build +else + docker compose --profile test pull --quiet + docker compose --profile test build +fi # --- done ------------------------------------------------------------------- cat <