feat(sso-mfa): T05 Keycloak manifests (NK-WP-0001-T05)

Deploys Keycloak (SSO core) in the sso namespace.

Files:
  sso-mfa/k8s/keycloak/pvc.yaml          — keycloak-data PVC (build cache)
  sso-mfa/k8s/keycloak/middleware.yaml   — rate-limit, admin-allowlist, HSTS
  sso-mfa/k8s/keycloak/deployment.yaml   — Deployment + Service; init container
                                           downloads privacyIDEA provider JAR
  sso-mfa/k8s/keycloak/ingress.yaml      — Ingress for kc.coulomb.social (CP-NK-004)
  sso-mfa/k8s/keycloak/create-secrets.sh — keycloak-config Secret
  sso-mfa/k8s/keycloak/bootstrap-realm.sh— hardens master realm, creates net-kingdom realm
  sso-mfa/k8s/keycloak/README.md         — apply order, custom image guide, DR
  sso-mfa/k8s/verify-t05.sh              — T05 done-criteria verification script

Config points added: CP-NK-004 (kc.coulomb.social), CP-NK-005 (provider JAR URL).
CP-NK-005 must be set before applying deployment.yaml.

Pending: apply to live cluster, set CP-NK-005, run bootstrap-realm.sh, verify-t05.sh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 02:00:51 +00:00
parent 1d94652ba1
commit d0ed7d9cd6
9 changed files with 1116 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
# T05 — Phase 4: Deploy Keycloak
Phase 4 of NK-WP-0001: deploys the SSO core (Keycloak) in the `sso` namespace.
**Hostname (config point CP-NK-004):**
- `kc.coulomb.social` — OIDC/SAML SSO portal, admin console
**Prerequisites:**
- T02 complete: `sso` namespace and NetworkPolicies applied, cert-manager running.
- T03 complete: PostgreSQL cluster `net-kingdom-pg` in `databases` namespace is Ready.
- T04 complete: privacyIDEA is Running; `bootstrap-admin.sh` has been run so the
`privacyidea-trigger-admin` Secret exists in the `mfa` namespace.
- T01 Phase 0a complete: `gen-secrets.sh` run, all secrets in KeePassXC.
---
## Before you apply: two required edits
### Edit 1 — Provider JAR URL (CP-NK-005, required)
The init container in `deployment.yaml` downloads the privacyIDEA Keycloak Provider
JAR. You must set the URL before applying:
1. Go to https://github.com/privacyIDEA/keycloak-provider/releases
2. Download the JAR for a release compatible with your Keycloak image version.
3. Edit `deployment.yaml`: find `PROVIDER_JAR_URL` and replace `EDIT_BEFORE_APPLY`
with the real URL.
4. Add the URL as CP-NK-005 in `CONFIG.md` (see bottom of this README).
If your cluster has no egress internet access, see **Custom image** below.
### Edit 2 — Admin console IP allowlist (optional but recommended)
Edit `middleware.yaml`: update `keycloak-admin-allowlist.spec.ipAllowList.sourceRange`
to your actual VPN/office CIDRs.
---
## Apply order
### Step 1 — Create secrets
```bash
cd sso-mfa/k8s/keycloak
chmod +x create-secrets.sh bootstrap-realm.sh
./create-secrets.sh
```
Creates `keycloak-config` in the `sso` namespace (KC_DB_URL, KC_DB_PASSWORD,
KC_BOOTSTRAP_ADMIN_PASSWORD).
---
### Step 2 — Set provider JAR URL and apply manifests
After editing `PROVIDER_JAR_URL` in `deployment.yaml`:
```bash
# From sso-mfa/k8s/keycloak/
kubectl apply -f pvc.yaml
kubectl apply -f middleware.yaml
kubectl apply -f deployment.yaml
kubectl apply -f ingress.yaml
```
**Wait for the pod to reach Running+Ready** (DB migrations + provider build on first
boot — allow up to 5 minutes):
```bash
kubectl get pods -n sso -w
# Expected: keycloak-<hash> 1/1 Running
```
If the pod is stuck in `Init`, check the init container logs first:
```bash
kubectl logs -n sso -l app.kubernetes.io/name=keycloak -c install-privacyidea-provider
```
Common causes of `Init` failure:
- `PROVIDER_JAR_URL` still set to `EDIT_BEFORE_APPLY` → edit and reapply
- No egress internet access → use custom image (see below)
If the pod is in `CrashLoopBackOff`, check main container logs:
```bash
kubectl logs -n sso -l app.kubernetes.io/name=keycloak --previous
```
Common causes of Keycloak crash:
- `keycloak-config` Secret missing → run `create-secrets.sh`
- PostgreSQL not reachable → verify T03, check NetworkPolicies
- Wrong DB password → re-run `create-secrets.sh` with corrected secrets
- `KC_DB_URL` format incorrect → must be `jdbc:postgresql://...` (not SQLAlchemy format)
---
### Step 3 — Bootstrap realm
After the pod is Running and Ready:
```bash
./bootstrap-realm.sh
```
This:
1. Authenticates to the Keycloak admin REST API inside the pod.
2. Hardens the master realm (SSL required, brute-force protection, token lifetimes).
3. Creates the `net-kingdom` application realm with equivalent hardening.
**Immediately after bootstrap completes:**
1. Log in to `https://kc.coulomb.social/admin` as `admin`.
2. Create a permanent admin account (with MFA — configure MFA flow in T06 first).
3. Disable or delete the bootstrap `admin` account once the permanent admin is enrolled.
---
### Step 4 — Verify
```bash
cd sso-mfa/k8s
chmod +x verify-t05.sh
./verify-t05.sh
```
---
## Custom image (recommended for production)
The init-container approach downloads the provider JAR from the internet on every pod
restart. For production or air-gapped clusters, build a custom image:
```dockerfile
FROM quay.io/keycloak/keycloak:26.0
# Add the privacyIDEA provider JAR (download from GitHub releases first)
COPY keycloak-provider-VERSION.jar /opt/keycloak/providers/
# Build an optimized image — this bakes in the provider at build time
RUN /opt/keycloak/bin/kc.sh build
```
Push to your registry, then:
1. Update `deployment.yaml`: change the `keycloak` container `image` to your custom image.
2. Change `args: ["start"]` to `args: ["start", "--optimized"]` for faster startup.
3. Remove the `install-privacyidea-provider` init container entirely.
4. The `providers` emptyDir volume and its mount can also be removed.
---
## NetworkPolicy design
Keycloak sits behind the NetworkPolicies applied in T02 (netpol-sso.yaml):
| Source | Destination | Port | Purpose |
|--------|-------------|------|---------|
| Traefik (kube-system) | Keycloak (sso) | 8080 | OIDC/SAML login pages |
| Keycloak (sso) | privacyIDEA (mfa) | 8080 | MFA challenge API (T06) |
| Keycloak (sso) | PostgreSQL (databases) | 5432 | Database |
Outbound to anything other than privacyIDEA, PostgreSQL, and kube-dns is denied.
---
## Post-deploy steps (after verify-t05.sh passes)
### Rotate the bootstrap admin password
The `KC_BOOTSTRAP_ADMIN_PASSWORD` in `keycloak-config` was the initial credential.
After creating a permanent admin:
1. In Keycloak admin console: Users → admin → Disable account (or delete it).
2. Rotate `KC_BOOTSTRAP_ADMIN_PASSWORD` in KeePassXC and re-run `create-secrets.sh`.
3. Restart the Keycloak deployment: `kubectl rollout restart deployment/keycloak -n sso`
### Admin console IP restriction
Update `middleware.yaml` `keycloak-admin-allowlist.spec.ipAllowList.sourceRange`
to your actual VPN/office CIDRs:
```bash
kubectl apply -f middleware.yaml
```
---
## Adding CP-NK-005 to CONFIG.md
After setting the provider JAR URL, add it to `CONFIG.md` as CP-NK-005:
```markdown
| CP-NK-005 | privacyIDEA provider JAR URL | <the URL you used> | `sso-mfa/k8s/keycloak/deployment.yaml` |
```
---
## Disaster recovery
If the `keycloak-data` PVC is lost (build cache only — all state is in PostgreSQL):
1. Create a new PVC with `pvc.yaml`.
2. Restart the deployment — Keycloak will rebuild from PostgreSQL on first start.
Allow 35 minutes for the rebuild + DB reconnect.
3. Realm config, clients, and users are preserved in the database.
If the PostgreSQL `keycloak_db` database is lost, restore from the CNPG backup
(T03 procedure) before restarting Keycloak.