generated from coulomb/repo-seed
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:
145
sso-mfa/k8s/keycape/README.md
Normal file
145
sso-mfa/k8s/keycape/README.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# T05c — KeyCape (OIDC Orchestration Layer)
|
||||
|
||||
KeyCape is the stateless OIDC server that ties the stack together. It orchestrates
|
||||
the full authentication flow:
|
||||
1. User visits a registered application
|
||||
2. Application redirects to KeyCape (`kc.coulomb.social`) for login
|
||||
3. KeyCape redirects the browser to Authelia (`auth.coulomb.social`) for password auth
|
||||
4. Authelia validates the password against LLDAP and returns an authorization code
|
||||
5. KeyCape exchanges the code for user identity, then calls privacyIDEA for MFA
|
||||
6. On success, KeyCape issues a signed OIDC token to the application
|
||||
|
||||
KeyCape is stateless — all state lives in Authelia (sessions), LLDAP (users), and
|
||||
privacyIDEA (MFA tokens). No PVC is required.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- T04 complete (privacyIDEA is Running and bootstrapped — admin account + enckey done)
|
||||
- T05a complete (LLDAP is Running)
|
||||
- T05b complete (Authelia is Running)
|
||||
- KeyCape container image built and available (see "Building the image" below)
|
||||
- `bootstrap/gen-secrets.sh` run
|
||||
- `kubectl` configured with cluster access
|
||||
|
||||
## Building the image
|
||||
|
||||
KeyCape has no published image. Build it from the source repository and make it
|
||||
available to K3s before applying `deployment.yaml`.
|
||||
|
||||
### Option A — Local import into K3s (dev/single-node)
|
||||
|
||||
```bash
|
||||
cd ~/key-cape
|
||||
docker build -t keycape:v0.1 .
|
||||
|
||||
# Import directly into the K3s containerd runtime (no registry needed)
|
||||
docker save keycape:v0.1 | sudo k3s ctr images import -
|
||||
|
||||
# After import, set imagePullPolicy: Never in deployment.yaml
|
||||
# (the image is now in the K3s local store, not a registry)
|
||||
```
|
||||
|
||||
### Option B — Private registry (production)
|
||||
|
||||
```bash
|
||||
cd ~/key-cape
|
||||
docker build -t <registry>/keycape:v0.1 .
|
||||
docker push <registry>/keycape:v0.1
|
||||
|
||||
# Update the image field in deployment.yaml:
|
||||
# image: <registry>/keycape:v0.1
|
||||
# imagePullPolicy: IfNotPresent (default) is correct for registry images.
|
||||
```
|
||||
|
||||
After building, update `deployment.yaml` line:
|
||||
```yaml
|
||||
image: keycape:v0.1 # replace with your actual tag
|
||||
```
|
||||
|
||||
## Apply order
|
||||
|
||||
```bash
|
||||
# 1. Create Secrets (config.yaml + key.pem)
|
||||
# Run this AFTER T04 bootstrap if you want the privacyIDEA token included.
|
||||
# If T04 is not yet done, run it now and re-run after create-pi-token.sh.
|
||||
cd sso-mfa/k8s/keycape
|
||||
chmod +x create-secrets.sh create-pi-token.sh
|
||||
./create-secrets.sh
|
||||
|
||||
# 2. Apply manifests
|
||||
kubectl apply -f deployment.yaml
|
||||
kubectl apply -f middleware.yaml
|
||||
kubectl apply -f ingress.yaml
|
||||
|
||||
# 3. Wait for pod to be ready
|
||||
kubectl rollout status deployment/keycape -n sso --timeout=60s
|
||||
```
|
||||
|
||||
## Post-deploy: inject privacyIDEA admin token
|
||||
|
||||
If T04 was not complete when you ran `create-secrets.sh`, the privacyIDEA admin
|
||||
token is a placeholder. After T04 bootstrap is done:
|
||||
|
||||
```bash
|
||||
# 1. Fetch the token from privacyIDEA and store it
|
||||
chmod +x create-pi-token.sh
|
||||
./create-pi-token.sh
|
||||
|
||||
# 2. Re-run create-secrets.sh to update keycape-config with the real token
|
||||
./create-secrets.sh
|
||||
|
||||
# 3. Restart KeyCape to pick up the new Secret
|
||||
kubectl rollout restart deployment/keycape -n sso
|
||||
```
|
||||
|
||||
## OIDC client registration
|
||||
|
||||
Downstream applications are registered in the `clients:` block in
|
||||
`keycape/create-secrets.sh`. After editing:
|
||||
|
||||
```bash
|
||||
./create-secrets.sh # regenerates keycape-config Secret
|
||||
kubectl rollout restart deployment/keycape -n sso
|
||||
```
|
||||
|
||||
Example entry (public client, PKCE, for a SPA):
|
||||
```yaml
|
||||
clients:
|
||||
- clientId: "my-app"
|
||||
displayName: "My Application"
|
||||
redirectUris:
|
||||
- "https://my-app.coulomb.social/callback"
|
||||
allowedScopes: ["openid", "profile", "email", "groups"]
|
||||
grantTypes: ["authorization_code"]
|
||||
clientType: "public"
|
||||
```
|
||||
|
||||
## Secrets managed
|
||||
|
||||
| Secret name | Keys | Purpose |
|
||||
|-------------|------|---------|
|
||||
| `keycape-config` | `config.yaml` | Full KeyCape configuration (LLDAP URL + creds, Authelia URL + client secret, privacyIDEA URL + admin token, OIDC clients) |
|
||||
| | `key.pem` | RSA-2048 private key for signing OIDC tokens issued to downstream applications |
|
||||
| `keycape-pi-token` | `pi_admin_token` | privacyIDEA admin JWT — created by `create-pi-token.sh`, referenced in `config.yaml` |
|
||||
|
||||
Store `key.pem` in KeePassXC as a binary attachment. If it is lost, all active
|
||||
sessions become invalid (tokens cannot be verified) and all applications must
|
||||
re-authenticate.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Pod status
|
||||
kubectl get pod -n sso -l app.kubernetes.io/name=keycape
|
||||
|
||||
# Health check
|
||||
kubectl run -n sso --rm -it kc-test --image=busybox --restart=Never \
|
||||
-- wget -qO- http://keycape.sso.svc.cluster.local:8080/healthz
|
||||
|
||||
# OIDC discovery (public endpoint)
|
||||
curl -s https://kc.coulomb.social/.well-known/openid-configuration | jq .
|
||||
|
||||
# Check issuer matches CP-NK-004
|
||||
curl -s https://kc.coulomb.social/.well-known/openid-configuration \
|
||||
| jq -r .issuer # should be: https://kc.coulomb.social
|
||||
```
|
||||
96
sso-mfa/k8s/keycape/create-pi-token.sh
Normal file
96
sso-mfa/k8s/keycape/create-pi-token.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# create-pi-token.sh — fetch a privacyIDEA admin JWT and store it for KeyCape
|
||||
#
|
||||
# Usage:
|
||||
# ./create-pi-token.sh [secrets-dir]
|
||||
#
|
||||
# Run this script AFTER T04 bootstrap (privacyIDEA admin account created).
|
||||
# It authenticates to the privacyIDEA API, fetches a long-lived admin JWT,
|
||||
# and writes it to secrets/keycape/pi_admin_token.
|
||||
#
|
||||
# After running this script, re-run create-secrets.sh to update the
|
||||
# keycape-config K8s Secret with the real token, then restart KeyCape:
|
||||
# ./create-secrets.sh
|
||||
# kubectl rollout restart deployment/keycape -n sso
|
||||
#
|
||||
# The privacyIDEA admin token does NOT expire by default (it is a permanent
|
||||
# service account token). Store it in KeePassXC as:
|
||||
# net-kingdom/KeyCape/pi-admin-token
|
||||
#
|
||||
# Requires: kubectl, curl, jq
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SECRETS_DIR="${1:-../../bootstrap/secrets}"
|
||||
PI_ENV="$SECRETS_DIR/privacyidea/secrets.env"
|
||||
TOKEN_FILE="$SECRETS_DIR/keycape/pi_admin_token"
|
||||
|
||||
if [[ ! -f "$PI_ENV" ]]; then
|
||||
echo "ERROR: $PI_ENV not found — run sso-mfa/bootstrap/gen-secrets.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_env() { bash -c "source '$1' 2>/dev/null; echo \${$2}"; }
|
||||
PI_ADMIN_PASSWORD=$(read_env "$PI_ENV" PI_ADMIN_PASSWORD)
|
||||
|
||||
if [[ -z "$PI_ADMIN_PASSWORD" ]]; then
|
||||
echo "ERROR: PI_ADMIN_PASSWORD is empty in $PI_ENV" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine privacyIDEA base URL — use cluster-internal URL if kubectl is available
|
||||
# and we can reach the service, otherwise fall back to the public hostname.
|
||||
PI_BASE_URL=""
|
||||
if kubectl get service privacyidea -n mfa &>/dev/null 2>&1; then
|
||||
# Prefer running a one-shot pod inside the cluster to avoid needing
|
||||
# public TLS to be up during bootstrap.
|
||||
PI_BASE_URL="http://privacyidea.mfa.svc.cluster.local:8080"
|
||||
USE_CLUSTER=true
|
||||
else
|
||||
PI_BASE_URL="https://pink.coulomb.social"
|
||||
USE_CLUSTER=false
|
||||
fi
|
||||
|
||||
echo "Fetching privacyIDEA admin token from: $PI_BASE_URL"
|
||||
|
||||
if [[ "$USE_CLUSTER" == "true" ]]; then
|
||||
# Run curl inside the cluster (avoids needing public TLS to be live)
|
||||
TOKEN=$(kubectl run -n mfa --rm -i --restart=Never pi-token-fetch \
|
||||
--image=curlimages/curl:8 --quiet \
|
||||
-- curl -sf \
|
||||
-X POST "$PI_BASE_URL/auth" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=pi-admin&password=${PI_ADMIN_PASSWORD}" \
|
||||
2>/dev/null \
|
||||
| python3 -c "import sys,json; data=json.load(sys.stdin); print(data['result']['value']['token'])" \
|
||||
2>/dev/null || echo "")
|
||||
else
|
||||
TOKEN=$(curl -sf \
|
||||
-X POST "$PI_BASE_URL/auth" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=pi-admin&password=${PI_ADMIN_PASSWORD}" \
|
||||
| python3 -c "import sys,json; data=json.load(sys.stdin); print(data['result']['value']['token'])" \
|
||||
2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "ERROR: failed to fetch token from privacyIDEA." >&2
|
||||
echo " Verify that privacyIDEA is Running and the pi-admin account exists." >&2
|
||||
echo " Check: kubectl logs -n mfa \$(kubectl get pod -n mfa -l app.kubernetes.io/name=privacyidea -o name | head -1)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$TOKEN_FILE")"
|
||||
echo -n "$TOKEN" > "$TOKEN_FILE"
|
||||
chmod 600 "$TOKEN_FILE"
|
||||
|
||||
echo ""
|
||||
echo "Token written to: $TOKEN_FILE"
|
||||
echo "Token preview : ${TOKEN:0:32}…"
|
||||
echo ""
|
||||
echo "IMPORTANT: Store this token in KeePassXC → net-kingdom/KeyCape/pi-admin-token"
|
||||
echo " as a password entry. It cannot be recovered without re-authenticating."
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Re-run create-secrets.sh to update keycape-config with the real token."
|
||||
echo " 2. Restart KeyCape: kubectl rollout restart deployment/keycape -n sso"
|
||||
127
sso-mfa/k8s/keycape/create-secrets.sh
Normal file
127
sso-mfa/k8s/keycape/create-secrets.sh
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# create-secrets.sh — create the keycape-config K8s Secret
|
||||
#
|
||||
# Usage:
|
||||
# ./create-secrets.sh [secrets-dir]
|
||||
#
|
||||
# Creates ONE Secret in the sso namespace:
|
||||
# keycape-config — config.yaml (full KeyCape config) + key.pem (RSA signing key)
|
||||
#
|
||||
# The privacyIDEA admin token is a separate Secret (keycape-pi-token) created
|
||||
# by create-pi-token.sh AFTER privacyIDEA is bootstrapped (T04 complete).
|
||||
# The PI admin token is read from that Secret at startup via config.yaml.
|
||||
#
|
||||
# Re-run this script to:
|
||||
# - Rotate the Authelia client secret (update secrets/authelia/secrets.env first)
|
||||
# - Add or modify OIDC client registrations (edit CLIENTS block below)
|
||||
# - Rotate the RSA signing key (delete and regenerate secrets/keycape/key.pem)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SECRETS_DIR="${1:-../../bootstrap/secrets}"
|
||||
KEYCAPE_ENV="$SECRETS_DIR/keycape/secrets.env"
|
||||
LLDAP_ENV="$SECRETS_DIR/lldap/secrets.env"
|
||||
AUTHELIA_ENV="$SECRETS_DIR/authelia/secrets.env"
|
||||
KEY_FILE="$SECRETS_DIR/keycape/key.pem"
|
||||
|
||||
for f in "$KEYCAPE_ENV" "$LLDAP_ENV" "$AUTHELIA_ENV"; do
|
||||
if [[ ! -f "$f" ]]; then
|
||||
echo "ERROR: $f not found — run sso-mfa/bootstrap/gen-secrets.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
read_env() { bash -c "source '$1' 2>/dev/null; echo \${$2}"; }
|
||||
|
||||
LLDAP_BIND_PW=$(read_env "$LLDAP_ENV" LLDAP_LDAP_USER_PASS)
|
||||
AUTHELIA_CLIENT_SECRET=$(read_env "$AUTHELIA_ENV" AUTHELIA_KEYCAPE_CLIENT_SECRET)
|
||||
|
||||
if [[ -z "$LLDAP_BIND_PW" || -z "$AUTHELIA_CLIENT_SECRET" ]]; then
|
||||
echo "ERROR: could not read LLDAP_LDAP_USER_PASS or AUTHELIA_KEYCAPE_CLIENT_SECRET" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The privacyIDEA admin token is read from a separate Secret at runtime.
|
||||
# Placeholder here — create-pi-token.sh populates the real value.
|
||||
PI_ADMIN_TOKEN="PENDING_create-pi-token.sh"
|
||||
if [[ -f "$SECRETS_DIR/keycape/pi_admin_token" ]]; then
|
||||
PI_ADMIN_TOKEN=$(cat "$SECRETS_DIR/keycape/pi_admin_token")
|
||||
echo "INFO: Using privacyIDEA admin token from $SECRETS_DIR/keycape/pi_admin_token"
|
||||
fi
|
||||
|
||||
# ── RSA signing key ───────────────────────────────────────────────────────────
|
||||
if [[ ! -f "$KEY_FILE" ]]; then
|
||||
echo "Generating RSA-2048 signing key for KeyCape JWT tokens..."
|
||||
mkdir -p "$(dirname "$KEY_FILE")"
|
||||
openssl genrsa -out "$KEY_FILE" 2048 2>/dev/null
|
||||
chmod 600 "$KEY_FILE"
|
||||
echo " Generated: $KEY_FILE"
|
||||
echo " IMPORTANT: Store this key in KeePassXC → net-kingdom/KeyCape/jwt-signing-key"
|
||||
echo " as a binary attachment. It cannot be recovered if lost."
|
||||
else
|
||||
echo "INFO: Using existing key: $KEY_FILE"
|
||||
fi
|
||||
KEY_CONTENT=$(cat "$KEY_FILE")
|
||||
|
||||
# ── Build config.yaml ─────────────────────────────────────────────────────────
|
||||
# Edit the OIDC clients block below to register downstream applications.
|
||||
# Re-run this script after any change.
|
||||
CONFIG_YAML=$(cat <<EOF
|
||||
issuer: "https://kc.coulomb.social"
|
||||
port: 8080
|
||||
tokenLifetime: "15m"
|
||||
privateKeyPem: "/etc/keycape/key.pem"
|
||||
environment: "production"
|
||||
|
||||
lldap:
|
||||
url: "ldap://lldap.sso.svc.cluster.local:3890"
|
||||
bindDN: "uid=admin,ou=people,dc=netkingdom,dc=local"
|
||||
bindPW: "${LLDAP_BIND_PW}"
|
||||
baseDN: "dc=netkingdom,dc=local"
|
||||
|
||||
authelia:
|
||||
baseURL: "http://authelia.sso.svc.cluster.local:9091"
|
||||
clientId: "keycape"
|
||||
clientSecret: "${AUTHELIA_CLIENT_SECRET}"
|
||||
redirectURI: "https://kc.coulomb.social/authorize/callback"
|
||||
|
||||
privacyidea:
|
||||
baseURL: "http://privacyidea.mfa.svc.cluster.local:8080"
|
||||
adminToken: "${PI_ADMIN_TOKEN}"
|
||||
realm: "netkingdom"
|
||||
|
||||
# ── OIDC client registrations ─────────────────────────────────────────────────
|
||||
# Add one entry per downstream application.
|
||||
# clientType: "public" for SPAs/native apps (PKCE, no client secret)
|
||||
# "confidential" for server-side apps (client secret required)
|
||||
clients: []
|
||||
# Example:
|
||||
# clients:
|
||||
# - clientId: "my-app"
|
||||
# displayName: "My Application"
|
||||
# redirectUris:
|
||||
# - "https://my-app.coulomb.social/callback"
|
||||
# allowedScopes: ["openid", "profile", "email", "groups"]
|
||||
# grantTypes: ["authorization_code"]
|
||||
# clientType: "public"
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Creating K8s Secret: keycape-config (namespace: sso)"
|
||||
kubectl create secret generic keycape-config \
|
||||
--namespace=sso \
|
||||
--from-literal=config.yaml="$CONFIG_YAML" \
|
||||
--from-literal=key.pem="$KEY_CONTENT" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
echo ""
|
||||
echo "Done. Secret keycape-config created in namespace: sso"
|
||||
echo ""
|
||||
if [[ "$PI_ADMIN_TOKEN" == "PENDING_create-pi-token.sh" ]]; then
|
||||
echo "WARN: privacyIDEA admin token is a placeholder."
|
||||
echo " After T04 bootstrap is complete, run:"
|
||||
echo " ./create-pi-token.sh"
|
||||
echo " Then re-run this script to update keycape-config."
|
||||
echo ""
|
||||
fi
|
||||
echo "Next: apply deployment.yaml, middleware.yaml, ingress.yaml"
|
||||
136
sso-mfa/k8s/keycape/deployment.yaml
Normal file
136
sso-mfa/k8s/keycape/deployment.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# Deployment + Service — KeyCape (namespace: sso)
|
||||
#
|
||||
# KeyCape is the OIDC orchestration layer. It is stateless: all persistent
|
||||
# state lives in Authelia (session), LLDAP (users), and privacyIDEA (MFA tokens).
|
||||
# No PVC is required.
|
||||
#
|
||||
# Configuration is stored entirely in the keycape-config Secret, which holds
|
||||
# a complete config.yaml and the RSA private key used to sign OIDC tokens
|
||||
# issued to downstream applications.
|
||||
#
|
||||
# Prerequisites (apply in order):
|
||||
# 1. keycape-config Secret — run keycape/create-secrets.sh
|
||||
# 2. keycape-pi-token Secret — run keycape/create-pi-token.sh (after T04 bootstrap)
|
||||
# 3. This file
|
||||
# 4. middleware.yaml + ingress.yaml
|
||||
#
|
||||
# Container image:
|
||||
# KeyCape has no published image. Build from ~/key-cape/ and push to a registry,
|
||||
# or import directly into K3s (see README.md "Building the image").
|
||||
# Image tag below is a placeholder — update before applying.
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: keycape
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/name: keycape
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
net-kingdom/component: sso
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: keycape
|
||||
strategy:
|
||||
type: RollingUpdate # stateless — safe to roll
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: keycape
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
net-kingdom/component: sso
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65534 # nobody — matches distroless static image
|
||||
fsGroup: 65534
|
||||
|
||||
containers:
|
||||
- name: keycape
|
||||
# EDIT before applying — see README.md "Building the image".
|
||||
# Option A (registry): docker build -t <registry>/keycape:v0.1 ~/key-cape/ && docker push ...
|
||||
# Option B (K3s local): docker build -t keycape:v0.1 ~/key-cape/ &&
|
||||
# docker save keycape:v0.1 | sudo k3s ctr images import -
|
||||
# After Option B, set imagePullPolicy: Never.
|
||||
image: keycape:v0.1
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
|
||||
env:
|
||||
- name: KEYCAPE_CONFIG
|
||||
value: /etc/keycape/config.yaml
|
||||
|
||||
volumeMounts:
|
||||
# keycape-config Secret provides config.yaml and key.pem
|
||||
- name: config-secret
|
||||
mountPath: /etc/keycape
|
||||
readOnly: true
|
||||
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 3
|
||||
failureThreshold: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 15
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8080
|
||||
initialDelaySeconds: 0
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: "25m"
|
||||
memory: "32Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
|
||||
volumes:
|
||||
- name: config-secret
|
||||
secret:
|
||||
secretName: keycape-config
|
||||
# Secret must contain two keys: config.yaml and key.pem
|
||||
items:
|
||||
- key: config.yaml
|
||||
path: config.yaml
|
||||
- key: key.pem
|
||||
path: key.pem
|
||||
mode: 0400 # key.pem is sensitive; restrict to owner read only
|
||||
|
||||
---
|
||||
# Service — ClusterIP; Traefik reaches KeyCape via port 8080.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: keycape
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/name: keycape
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
net-kingdom/component: sso
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: keycape
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
42
sso-mfa/k8s/keycape/ingress.yaml
Normal file
42
sso-mfa/k8s/keycape/ingress.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
# Ingress — KeyCape OIDC server (namespace: sso)
|
||||
#
|
||||
# kc.coulomb.social — OIDC discovery, /authorize, /token, /jwks, /userinfo
|
||||
#
|
||||
# This hostname is public — applications redirect users here for login.
|
||||
# The auth.coulomb.social hostname (Authelia login UI) is where users
|
||||
# actually enter their passwords; browsers are redirected there by KeyCape.
|
||||
#
|
||||
# Config points (see CONFIG.md):
|
||||
# CP-NK-004 kc.coulomb.social
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: keycape
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/name: keycape
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
net-kingdom/component: sso
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.middlewares: >-
|
||||
sso-keycape-rate-limit@kubernetescrd,
|
||||
sso-keycape-hsts@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: kc.coulomb.social
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: keycape
|
||||
port:
|
||||
number: 8080
|
||||
tls:
|
||||
- secretName: kc-tls
|
||||
hosts:
|
||||
- kc.coulomb.social
|
||||
36
sso-mfa/k8s/keycape/middleware.yaml
Normal file
36
sso-mfa/k8s/keycape/middleware.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
# Traefik Middlewares for KeyCape (namespace: sso)
|
||||
#
|
||||
# Middleware names referenced in ingress.yaml:
|
||||
# sso-keycape-rate-limit@kubernetescrd
|
||||
# sso-keycape-hsts@kubernetescrd
|
||||
|
||||
# ── Rate limit — all OIDC endpoints ──────────────────────────────────────────
|
||||
# OIDC discovery + JS app calls are bursty; keep limit generous.
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: keycape-rate-limit
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
net-kingdom/component: sso
|
||||
spec:
|
||||
rateLimit:
|
||||
average: 100
|
||||
period: 1m
|
||||
burst: 20
|
||||
---
|
||||
# ── HSTS ─────────────────────────────────────────────────────────────────────
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: keycape-hsts
|
||||
namespace: sso
|
||||
labels:
|
||||
app.kubernetes.io/part-of: net-kingdom-sso-mfa
|
||||
net-kingdom/component: sso
|
||||
spec:
|
||||
headers:
|
||||
stsSeconds: 31536000
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
Reference in New Issue
Block a user