Files
net-kingdom/sso-mfa/k8s/authelia/create-secrets.sh
Bernd Worsch 0754dc32e6 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>
2026-03-19 08:31:51 +00:00

114 lines
5.2 KiB
Bash

#!/usr/bin/env bash
# create-secrets.sh — create the authelia-secrets K8s Secret
#
# Usage:
# ./create-secrets.sh [secrets-dir]
#
# <secrets-dir> is the output directory from sso-mfa/bootstrap/gen-secrets.sh
# (default: ../../bootstrap/secrets).
#
# Creates ONE Secret in the sso namespace:
# authelia-secrets — all Authelia sensitive values as named files:
# jwt_secret ← AUTHELIA_JWT_SECRET_FILE
# session_secret ← AUTHELIA_SESSION_SECRET_FILE
# storage_encryption_key ← AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE
# ldap_password ← AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE
# oidc_hmac_secret ← AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE
# oidc_issuer_private_key ← AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE
# keycape_client_secret_hash ← AUTHELIA_IDENTITY_PROVIDERS_OIDC_CLIENTS_0_SECRET_FILE
#
# The keycape_client_secret_hash is the bcrypt hash of AUTHELIA_KEYCAPE_CLIENT_SECRET
# from secrets/authelia/secrets.env. The plaintext is stored in KeyCape's secret.
#
# Requires: kubectl, openssl, python3 (for bcrypt hash generation)
set -euo pipefail
SECRETS_DIR="${1:-../../bootstrap/secrets}"
AUTHELIA_ENV="$SECRETS_DIR/authelia/secrets.env"
LLDAP_ENV="$SECRETS_DIR/lldap/secrets.env"
for f in "$AUTHELIA_ENV" "$LLDAP_ENV"; do
if [[ ! -f "$f" ]]; then
echo "ERROR: $f not found" >&2
echo "Run sso-mfa/bootstrap/gen-secrets.sh first." >&2
exit 1
fi
done
read_env() { bash -c "source '$1' 2>/dev/null; echo \${$2}"; }
AUTHELIA_JWT_SECRET=$(read_env "$AUTHELIA_ENV" AUTHELIA_JWT_SECRET)
AUTHELIA_SESSION_SECRET=$(read_env "$AUTHELIA_ENV" AUTHELIA_SESSION_SECRET)
AUTHELIA_STORAGE_ENCRYPTION_KEY=$(read_env "$AUTHELIA_ENV" AUTHELIA_STORAGE_ENCRYPTION_KEY)
AUTHELIA_OIDC_HMAC_SECRET=$(read_env "$AUTHELIA_ENV" AUTHELIA_OIDC_HMAC_SECRET)
AUTHELIA_OIDC_PRIVATE_KEY=$(read_env "$AUTHELIA_ENV" AUTHELIA_OIDC_PRIVATE_KEY_FILE)
AUTHELIA_KEYCAPE_CLIENT_SECRET=$(read_env "$AUTHELIA_ENV" AUTHELIA_KEYCAPE_CLIENT_SECRET)
LLDAP_LDAP_USER_PASS=$(read_env "$LLDAP_ENV" LLDAP_LDAP_USER_PASS)
# Validate required values
REQUIRED=(AUTHELIA_JWT_SECRET AUTHELIA_SESSION_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY
AUTHELIA_OIDC_HMAC_SECRET AUTHELIA_KEYCAPE_CLIENT_SECRET LLDAP_LDAP_USER_PASS)
for var in "${REQUIRED[@]}"; do
if [[ -z "${!var}" ]]; then
echo "ERROR: $var is empty — re-run gen-secrets.sh" >&2
exit 1
fi
done
# The OIDC issuer private key is stored as a file path in secrets.env.
# Read the actual PEM content from the path referenced there.
AUTHELIA_OIDC_PRIVATE_KEY_PATH=$(read_env "$AUTHELIA_ENV" AUTHELIA_OIDC_PRIVATE_KEY_FILE)
if [[ -z "$AUTHELIA_OIDC_PRIVATE_KEY_PATH" ]]; then
# Fall back: generate a new RSA key on the fly (dev only)
echo "WARN: AUTHELIA_OIDC_PRIVATE_KEY_FILE not set — generating a new RSA-2048 key."
echo " Store the generated key in KeePassXC as net-kingdom/Authelia/oidc-private-key."
TMP_KEY=$(mktemp)
openssl genrsa -out "$TMP_KEY" 2048 2>/dev/null
AUTHELIA_OIDC_PRIVATE_KEY_CONTENT=$(cat "$TMP_KEY")
shred -u "$TMP_KEY"
elif [[ -f "$AUTHELIA_OIDC_PRIVATE_KEY_PATH" ]]; then
AUTHELIA_OIDC_PRIVATE_KEY_CONTENT=$(cat "$AUTHELIA_OIDC_PRIVATE_KEY_PATH")
else
echo "ERROR: OIDC private key file not found: $AUTHELIA_OIDC_PRIVATE_KEY_PATH" >&2
exit 1
fi
# Hash the Authelia-KeyCape client secret with bcrypt (cost 12).
# Authelia requires the bcrypt hash for OIDC client secrets.
echo "Hashing KeyCape client secret with bcrypt (this may take a moment)..."
if command -v python3 &>/dev/null && python3 -c "import bcrypt" 2>/dev/null; then
KEYCAPE_CLIENT_SECRET_HASH=$(python3 -c "
import bcrypt, sys
pw = sys.argv[1].encode()
print(bcrypt.hashpw(pw, bcrypt.gensalt(rounds=12)).decode())
" "$AUTHELIA_KEYCAPE_CLIENT_SECRET")
elif command -v htpasswd &>/dev/null; then
KEYCAPE_CLIENT_SECRET_HASH=$(htpasswd -nbBC 12 "" "$AUTHELIA_KEYCAPE_CLIENT_SECRET" | cut -d: -f2)
else
echo "ERROR: bcrypt hash generation requires python3+bcrypt or apache2-utils (htpasswd)" >&2
echo " Install: pip3 install bcrypt OR apt install apache2-utils" >&2
exit 1
fi
echo "Creating K8s Secret: authelia-secrets (namespace: sso)"
kubectl create secret generic authelia-secrets \
--namespace=sso \
--from-literal=jwt_secret="$AUTHELIA_JWT_SECRET" \
--from-literal=session_secret="$AUTHELIA_SESSION_SECRET" \
--from-literal=storage_encryption_key="$AUTHELIA_STORAGE_ENCRYPTION_KEY" \
--from-literal=ldap_password="$LLDAP_LDAP_USER_PASS" \
--from-literal=oidc_hmac_secret="$AUTHELIA_OIDC_HMAC_SECRET" \
--from-literal=oidc_issuer_private_key="$AUTHELIA_OIDC_PRIVATE_KEY_CONTENT" \
--from-literal=keycape_client_secret_hash="$KEYCAPE_CLIENT_SECRET_HASH" \
--dry-run=client -o yaml | kubectl apply -f -
echo ""
echo "Done. Secret authelia-secrets created in namespace: sso"
echo ""
echo "Next:"
echo " Apply deployment.yaml, then ingress.yaml."
echo " The plaintext Authelia-KeyCape client secret is in secrets/authelia/secrets.env"
echo " as AUTHELIA_KEYCAPE_CLIENT_SECRET. This value goes into keycape/create-secrets.sh"
echo " as the authelia.clientSecret in the KeyCape config."