feat(sso-mfa): T05 SSO stack pivot — Keycloak → Authelia + LLDAP + KeyCape (NK-WP-0001-T05)

Replaces the Keycloak+privacyIDEA SSO tier with the lightweight stack built
during KEY-WP-0001: Authelia (password frontend), LLDAP (directory), and
KeyCape (OIDC orchestration). privacyIDEA is retained as the MFA engine.

Stack:
  kc.coulomb.social   — KeyCape OIDC server (stateless, custom Go)
  auth.coulomb.social — Authelia login portal (password auth → Authelia OIDC → KeyCape)
  lldap.coulomb.social — LLDAP admin UI (IP-restricted)
  pink.coulomb.social — privacyIDEA MFA engine (unchanged)

Changes:
- Remove sso-mfa/k8s/keycloak/ (7 files)
- Add sso-mfa/k8s/lldap/ (pvc, deployment, middleware, ingress, create-secrets, README)
- Add sso-mfa/k8s/authelia/ (pvc, configmap, deployment, ingress, create-secrets, README)
- Add sso-mfa/k8s/keycape/ (deployment, middleware, ingress, create-secrets, create-pi-token, README)
- Update network-policies/netpol-sso.yaml for new component topology
- Update verify-t05.sh: checks LLDAP + Authelia + KeyCape (23 checks)
- Update CONFIG.md: fix CP-NK-004 (KeyCape), add CP-NK-005 (Authelia), CP-NK-006 (LLDAP)
- Update bootstrap/gen-secrets.sh: add LLDAP/Authelia/KeyCape sections, remove Keycloak
- Update k8s/README.md: network policy table reflects new traffic paths
- Add sso-mfa/WORKPLAN.md: resumable task checklist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 08:31:51 +00:00
parent d0ed7d9cd6
commit 0754dc32e6
31 changed files with 2098 additions and 1077 deletions

View File

@@ -33,7 +33,9 @@ rnd_b64() { openssl rand -base64 "$1" | tr -d '\n/+=' | head -c "$2"; }
mkdir -p \
"$OUT_DIR/privacyidea" \
"$OUT_DIR/postgres" \
"$OUT_DIR/keycloak" \
"$OUT_DIR/lldap" \
"$OUT_DIR/authelia" \
"$OUT_DIR/keycape" \
"$OUT_DIR/breakglass"
# ── privacyIDEA ────────────────────────────────────────────────────────────────
@@ -63,31 +65,78 @@ EOF
# ── PostgreSQL ─────────────────────────────────────────────────────────────────
PG_ROOT_PASS="$(rnd_b64 32 40)"
PG_KC_PASS="$(rnd_b64 32 40)"
# privacyIDEA DB user reuses PI_DB_PASS (single source of truth)
# Note: no keycloak DB user — Keycloak was replaced by the Authelia+LLDAP+KeyCape stack.
cat > "$OUT_DIR/postgres/secrets.env" <<EOF
# PostgreSQL secrets — KeePassXC group: net-kingdom/PostgreSQL
# Entry: postgres root → username=postgres password=PG_ROOT_PASSWORD
# Entry: keycloak user → username=keycloak password=PG_KEYCLOAK_PASSWORD
# Entry: privacyidea user → username=privacyidea password=PI_DB_PASSWORD (copy from privacyIDEA entry)
PG_ROOT_PASSWORD=$PG_ROOT_PASS
PG_KEYCLOAK_PASSWORD=$PG_KC_PASS
# PI_DB_PASSWORD is in privacyidea/secrets.env — do NOT copy here; link in KeePassXC
EOF
# ── Keycloak ───────────────────────────────────────────────────────────────────
KC_ADMIN_PASS="$(rnd_b64 32 40)"
# Keycloak DB password == PG_KEYCLOAK_PASSWORD (single source of truth)
# ── LLDAP ──────────────────────────────────────────────────────────────────────
LLDAP_JWT_SECRET="$(rnd_hex 32)" # 64 hex chars — LLDAP JWT signing key
LLDAP_LDAP_USER_PASS="$(rnd_b64 32 40)" # 40 printable chars — LLDAP admin + Authelia/KeyCape bind password
cat > "$OUT_DIR/keycloak/secrets.env" <<EOF
# Keycloak secrets — KeePassXC group: net-kingdom/Keycloak
# Entry: admin → username=admin password=KC_ADMIN_PASSWORD
# Entry: database → username=keycloak password=KC_DB_PASSWORD (copy from postgres/keycloak user)
cat > "$OUT_DIR/lldap/secrets.env" <<EOF
# LLDAP secrets — KeePassXC group: net-kingdom/LLDAP
# Entry: jwt-secret → password=LLDAP_JWT_SECRET (no username)
# Entry: admin → username=admin password=LLDAP_LDAP_USER_PASS
#
# LLDAP_LDAP_USER_PASS is also the LDAP bind password for Authelia and KeyCape.
# It is read from this file by both authelia/create-secrets.sh and keycape/create-secrets.sh.
KC_ADMIN_PASSWORD=$KC_ADMIN_PASS
KC_DB_PASSWORD=$PG_KC_PASS
LLDAP_JWT_SECRET=$LLDAP_JWT_SECRET
LLDAP_LDAP_USER_PASS=$LLDAP_LDAP_USER_PASS
EOF
# ── Authelia ───────────────────────────────────────────────────────────────────
AUTHELIA_JWT_SECRET="$(rnd_hex 32)" # 64 hex chars — session JWT signing
AUTHELIA_SESSION_SECRET="$(rnd_hex 32)" # 64 hex chars — session cookie encryption
AUTHELIA_STORAGE_ENCRYPTION_KEY="$(rnd_b64 48 64)" # 64 printable chars — SQLite encryption
AUTHELIA_OIDC_HMAC_SECRET="$(rnd_hex 32)" # 64 hex chars — OIDC HMAC
AUTHELIA_KEYCAPE_CLIENT_SECRET="$(rnd_b64 32 40)" # 40 printable chars — Authelia→KeyCape OIDC client secret
# OIDC issuer private key: generated by authelia/create-secrets.sh on first run,
# or set AUTHELIA_OIDC_PRIVATE_KEY_FILE to a path below and generate with:
# openssl genrsa -out secrets/authelia/oidc_private_key.pem 2048
cat > "$OUT_DIR/authelia/secrets.env" <<EOF
# Authelia secrets — KeePassXC group: net-kingdom/Authelia
# Entry: jwt-secret → password=AUTHELIA_JWT_SECRET (no username)
# Entry: session-secret → password=AUTHELIA_SESSION_SECRET (no username)
# Entry: storage-encryption-key → password=AUTHELIA_STORAGE_ENCRYPTION_KEY (no username)
# Entry: oidc-hmac-secret → password=AUTHELIA_OIDC_HMAC_SECRET (no username)
# Entry: keycape-client-secret → password=AUTHELIA_KEYCAPE_CLIENT_SECRET (no username)
# Entry: oidc-private-key → binary attachment oidc_private_key.pem
#
# AUTHELIA_KEYCAPE_CLIENT_SECRET is the plaintext — authelia/create-secrets.sh hashes
# it with bcrypt before storing in the K8s Secret.
# The same plaintext goes into KeyCape's config as authelia.clientSecret.
AUTHELIA_JWT_SECRET=$AUTHELIA_JWT_SECRET
AUTHELIA_SESSION_SECRET=$AUTHELIA_SESSION_SECRET
AUTHELIA_STORAGE_ENCRYPTION_KEY=$AUTHELIA_STORAGE_ENCRYPTION_KEY
AUTHELIA_OIDC_HMAC_SECRET=$AUTHELIA_OIDC_HMAC_SECRET
AUTHELIA_KEYCAPE_CLIENT_SECRET=$AUTHELIA_KEYCAPE_CLIENT_SECRET
# AUTHELIA_OIDC_PRIVATE_KEY_FILE= # leave blank — authelia/create-secrets.sh will generate
EOF
# ── KeyCape ────────────────────────────────────────────────────────────────────
# KeyCape has no generated secrets here:
# key.pem — RSA signing key, generated by keycape/create-secrets.sh on first run
# pi_admin_token — generated by keycape/create-pi-token.sh AFTER privacyIDEA is bootstrapped
# We create the directory and a placeholder file so operators know to check it.
cat > "$OUT_DIR/keycape/secrets.env" <<EOF
# KeyCape secrets — KeePassXC group: net-kingdom/KeyCape
# Entry: jwt-signing-key → binary attachment key.pem (generated by keycape/create-secrets.sh)
# Entry: pi-admin-token → password=PI_ADMIN_TOKEN (generated by keycape/create-pi-token.sh)
#
# This file is a placeholder. No values to set here manually.
# Both secrets are generated by the respective create-*.sh scripts.
EOF
# ── Break-glass ────────────────────────────────────────────────────────────────
@@ -107,20 +156,31 @@ echo "Generated: $(date -Iseconds)"
echo "Output : $OUT_DIR/"
echo ""
echo " privacyIDEA:"
echo " PI_SECRET_KEY : $(wc -c < "$OUT_DIR/privacyidea/secrets.env" > /dev/null; echo "${PI_SECRET_KEY:0:16}")"
echo " PI_SECRET_KEY : ${PI_SECRET_KEY:0:16}"
echo " PI_PEPPER : ${PI_PEPPER:0:8}"
echo " PI_DB_PASSWORD : ${PI_DB_PASS:0:8}"
echo " PI_ADMIN_PASSWORD: ${PI_ADMIN_PASS:0:8}"
echo " PI_ENCFILE : *** generate after container deploy (see comments) ***"
echo ""
echo " PostgreSQL:"
echo " PG_ROOT_PASSWORD : ${PG_ROOT_PASS:0:8}"
echo " PG_KEYCLOAK_PASSWORD: ${PG_KC_PASS:0:8}"
echo " PG_PI_PASSWORD : (same as PI_DB_PASSWORD)"
echo " PG_ROOT_PASSWORD : ${PG_ROOT_PASS:0:8}"
echo " PG_PI_PASSWORD : (same as PI_DB_PASSWORD)"
echo ""
echo " Keycloak:"
echo " KC_ADMIN_PASSWORD: ${KC_ADMIN_PASS:0:8}"
echo " KC_DB_PASSWORD : (same as PG_KEYCLOAK_PASSWORD)"
echo " LLDAP:"
echo " LLDAP_JWT_SECRET : ${LLDAP_JWT_SECRET:0:8}"
echo " LLDAP_LDAP_USER_PASS: ${LLDAP_LDAP_USER_PASS:0:8}"
echo ""
echo " Authelia:"
echo " AUTHELIA_JWT_SECRET : ${AUTHELIA_JWT_SECRET:0:8}"
echo " AUTHELIA_SESSION_SECRET : ${AUTHELIA_SESSION_SECRET:0:8}"
echo " AUTHELIA_STORAGE_ENCRYPTION_KEY: ${AUTHELIA_STORAGE_ENCRYPTION_KEY:0:8}"
echo " AUTHELIA_OIDC_HMAC_SECRET : ${AUTHELIA_OIDC_HMAC_SECRET:0:8}"
echo " AUTHELIA_KEYCAPE_CLIENT_SECRET : ${AUTHELIA_KEYCAPE_CLIENT_SECRET:0:8}"
echo " AUTHELIA_OIDC_PRIVATE_KEY : *** generated by authelia/create-secrets.sh ***"
echo ""
echo " KeyCape:"
echo " key.pem : *** generated by keycape/create-secrets.sh ***"
echo " pi_admin_token : *** generated by keycape/create-pi-token.sh (after T04 bootstrap) ***"
echo ""
echo " Break-glass:"
echo " BREAKGLASS_PASSWORD: ${BG_PASS:0:8}"