generated from coulomb/repo-seed
privacyidea/privacyidea:3.12 does not exist on Docker Hub. Correct image: privacyidea/otpserver:3.12.2 (port 5001). Updated files: - deployment.yaml: image, containerPort, probes, service port - ingress.yaml: backend service port - netpol-mfa.yaml: ingress port + keycloak → keycape label - netpol-sso.yaml: KeyCape egress port to privacyIDEA Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
298 lines
8.5 KiB
Markdown
298 lines
8.5 KiB
Markdown
---
|
|
id: NK-WP-0003
|
|
type: workplan
|
|
title: "KeyCape + privacyIDEA Stack — Cluster Deployment"
|
|
domain: netkingdom
|
|
repo: net-kingdom
|
|
status: active
|
|
owner: custodian
|
|
topic_slug: netkingdom
|
|
created: "2026-03-20"
|
|
updated: "2026-03-20"
|
|
state_hub_workstream_id: "f24cefd4-a09b-4fa1-9b25-94bf783b425e"
|
|
---
|
|
|
|
# KeyCape + privacyIDEA Stack — Cluster Deployment
|
|
|
|
## Goal
|
|
|
|
Deploy the full NetKingdom identity stack on the live k3s cluster without
|
|
Keycloak. KeyCape (v0.1, complete) is the OIDC orchestration layer; it
|
|
binds LLDAP (directory), Authelia (auth sessions), and privacyIDEA (MFA).
|
|
|
|
NK-WP-0001 was scoped around Keycloak and is deferred. This workplan
|
|
covers everything needed to reach a production-ready identity plane.
|
|
|
|
## Pre-conditions
|
|
|
|
- [x] k3s cluster healthy — RAIL-BS-WP-0002 ✓
|
|
- [x] kubeconfig available at `~/.kube/config-hosteurope` — RAIL-BS-WP-0005 ✓
|
|
- [x] All manifests committed — net-kingdom `sso-mfa/k8s/` ✓
|
|
- [x] KeyCape v0.1 complete — KEY-WP-0001 ✓
|
|
- [ ] SOPS + age integrated into net-kingdom (T01 below)
|
|
- [ ] Credential ops-bundle generated and stored in KeePassXC (T01 below)
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Internet → Traefik (k3s) → cert-manager TLS
|
|
├── auth.coulomb.social → Authelia
|
|
├── pink.coulomb.social → privacyIDEA portal
|
|
└── id.coulomb.social → KeyCape (OIDC)
|
|
|
|
KeyCape ──► Authelia (session, password)
|
|
──► LLDAP (directory, user lookup)
|
|
──► privacyIDEA (MFA challenges via trigger-admin token)
|
|
|
|
privacyIDEA ──► PostgreSQL (privacyidea_db via CloudNativePG)
|
|
LLDAP ──► PostgreSQL (lldap_db via CloudNativePG)
|
|
Authelia ──► PostgreSQL (authelia_db via CloudNativePG)
|
|
```
|
|
|
|
## Tasks
|
|
|
|
### T01 — Credential setup: SOPS + age + ops-bundle
|
|
|
|
```task
|
|
id: NK-WP-0003-T01
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "6a22e17e-5854-4f8b-b419-9dc86d490357"
|
|
```
|
|
|
|
Net-kingdom currently uses a manual KeePassXC + age-bundle approach while
|
|
railiance-infra uses SOPS with age keys. This task aligns them under the
|
|
Credential Management Standard (`canon/standards/credential-management_v0.1.md`).
|
|
|
|
Steps:
|
|
1. Verify the operator age keypair exists at `~/.config/sops/age/key.txt`
|
|
(reuse the railiance key if already present — one keypair per operator)
|
|
2. Add `.sops.yaml` to net-kingdom root (mirror railiance-infra pattern):
|
|
- Encrypt files matching `secrets/.*` and `**/*.sops.yaml`
|
|
- Recipient: operator age public key
|
|
3. Run `sso-mfa/bootstrap/gen-secrets.sh ./secrets` to generate all service secrets
|
|
4. Store each secret in KeePassXC under the `net-kingdom/` group hierarchy
|
|
(see credential management standard for group layout)
|
|
5. Run `sso-mfa/bootstrap/pack-bundle.sh ./secrets <age-pub-key>` → encrypted ops bundle
|
|
6. Store ops bundle offsite (separate from KeePassXC)
|
|
7. Shred plaintext secrets: `find secrets/ -type f -exec shred -u {} \;`
|
|
|
|
### T02 — Apply cluster foundations
|
|
|
|
```task
|
|
id: NK-WP-0003-T02
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "a14e3a6b-18ee-4172-8a47-bd531f21e55a"
|
|
```
|
|
|
|
Apply the K8s infrastructure foundations. All manifests already committed.
|
|
|
|
```bash
|
|
export KUBECONFIG=~/.kube/config-hosteurope
|
|
kubectl apply -f sso-mfa/k8s/namespaces/
|
|
kubectl apply -f sso-mfa/k8s/network-policies/
|
|
kubectl apply -f sso-mfa/k8s/cert-manager/
|
|
```
|
|
|
|
Verify: `bash sso-mfa/k8s/verify-t02.sh`
|
|
|
|
Expected: namespaces `sso`, `mfa`, `databases` exist; NetworkPolicies applied;
|
|
cert-manager pods Running.
|
|
|
|
### T03 — Deploy PostgreSQL (CloudNativePG)
|
|
|
|
```task
|
|
id: NK-WP-0003-T03
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "19e375d0-66bd-4cf0-9c2d-59d5c0d5989e"
|
|
```
|
|
|
|
Deploy the shared database cluster with three databases:
|
|
- `privacyidea_db` — privacyIDEA
|
|
- `lldap_db` — LLDAP
|
|
- `authelia_db` — Authelia
|
|
|
|
```bash
|
|
kubectl apply -f sso-mfa/k8s/postgres/
|
|
```
|
|
|
|
Wait for cluster to be `Ready`, then verify: `bash sso-mfa/k8s/verify-t03.sh`
|
|
|
|
**Note**: Do not proceed to T04 until the CloudNativePG cluster is fully
|
|
healthy. Migration jobs will fail on a partially-started cluster.
|
|
|
|
### T04 — Deploy privacyIDEA
|
|
|
|
```task
|
|
id: NK-WP-0003-T04
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "9c9c1ec9-0cf5-4546-a83e-d74dbf3b27af"
|
|
```
|
|
|
|
Deploy privacyIDEA into the `mfa` namespace.
|
|
|
|
> **Image fix applied (2026-03-20):** `privacyidea/privacyidea:3.12` does not exist.
|
|
> Corrected to `privacyidea/otpserver:3.12.2` on port 5001.
|
|
> Updated: `deployment.yaml`, `ingress.yaml`, `netpol-mfa.yaml`, `netpol-sso.yaml`.
|
|
|
|
**Step 1 — Create K8s secrets from KeePassXC:**
|
|
```bash
|
|
cd sso-mfa/k8s/privacyidea
|
|
bash create-secrets.sh # reads from env vars; source from KeePassXC
|
|
```
|
|
|
|
**Step 2 — Apply manifests:**
|
|
```bash
|
|
kubectl apply -f pvc.yaml
|
|
kubectl apply -f configmap.yaml
|
|
kubectl apply -f middleware.yaml
|
|
kubectl apply -f deployment.yaml
|
|
kubectl apply -f ingress.yaml
|
|
```
|
|
|
|
**Step 3 — Bootstrap key material (time-sensitive):**
|
|
Run immediately once the pod reaches `Running` state. This window must not
|
|
be missed — if the pod is deleted before this runs, the enckey is lost.
|
|
```bash
|
|
bash enckey-bootstrap.sh # extracts PI_ENCFILE + audit keys → K8s Secrets + KeePassXC
|
|
```
|
|
|
|
**Step 4 — Create admin accounts:**
|
|
```bash
|
|
bash bootstrap-admin.sh # creates pi-admin + trigger-admin, sets policies
|
|
# store trigger-admin token in KeePassXC net-kingdom/privacyidea/trigger-admin
|
|
```
|
|
|
|
Verify: `bash sso-mfa/k8s/verify-t04.sh`
|
|
|
|
Expected: pod Running, TLS cert issued for `pink.coulomb.social`, admin
|
|
accounts exist, enckey backed up.
|
|
|
|
### T05 — Deploy LLDAP
|
|
|
|
```task
|
|
id: NK-WP-0003-T05
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "82fc90f7-8eb4-4718-b02a-dfd5fa39e5bc"
|
|
```
|
|
|
|
Deploy LLDAP into the `sso` namespace.
|
|
|
|
```bash
|
|
cd sso-mfa/k8s/lldap
|
|
bash create-secrets.sh
|
|
kubectl apply -f deployment.yaml
|
|
kubectl apply -f ingress.yaml
|
|
kubectl apply -f middleware.yaml
|
|
bash bootstrap-users.sh # creates base OU structure + initial admin user
|
|
```
|
|
|
|
Verify pod Running and LDAP bind works on `ldap.coulomb.social`.
|
|
|
|
### T06 — Deploy Authelia
|
|
|
|
```task
|
|
id: NK-WP-0003-T06
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "3a28ff10-fbfa-443b-a64d-bbfe6153c544"
|
|
```
|
|
|
|
Deploy Authelia into the `sso` namespace.
|
|
|
|
```bash
|
|
cd sso-mfa/k8s/authelia
|
|
bash create-secrets.sh
|
|
kubectl apply -f configmap.yaml
|
|
kubectl apply -f deployment.yaml
|
|
kubectl apply -f ingress.yaml
|
|
```
|
|
|
|
Verify: `bash sso-mfa/k8s/verify-t05.sh` (covers LLDAP + Authelia together)
|
|
|
|
### T07 — Deploy KeyCape
|
|
|
|
```task
|
|
id: NK-WP-0003-T07
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "496a97c9-3e2a-486e-ba62-18449868c6cf"
|
|
```
|
|
|
|
Deploy KeyCape into the `sso` namespace.
|
|
|
|
```bash
|
|
cd sso-mfa/k8s/keycape
|
|
bash create-secrets.sh # includes privacyIDEA trigger-admin token
|
|
bash create-pi-token.sh # registers KeyCape as a privacyIDEA application
|
|
kubectl apply -f deployment.yaml
|
|
kubectl apply -f ingress.yaml
|
|
kubectl apply -f middleware.yaml
|
|
```
|
|
|
|
Verify: OIDC discovery endpoint reachable at
|
|
`https://id.coulomb.social/.well-known/openid-configuration`
|
|
|
|
### T08 — End-to-end authentication test
|
|
|
|
```task
|
|
id: NK-WP-0003-T08
|
|
status: todo
|
|
priority: high
|
|
state_hub_task_id: "0fba3392-c916-43fd-a2c1-24ce39481043"
|
|
```
|
|
|
|
Prove the full auth flow works:
|
|
1. OIDC discovery resolves at `id.coulomb.social`
|
|
2. Authelia password auth succeeds for a test user
|
|
3. privacyIDEA TOTP challenge issued and accepted
|
|
4. KeyCape issues a valid access token
|
|
5. Token introspection returns expected claims (sub, groups, email)
|
|
|
|
Use the KeyCape acceptance test suite:
|
|
```bash
|
|
cd /home/worsch/key-cape
|
|
go test ./tests/... -run TestProfileBaseline -v
|
|
```
|
|
|
|
### T09 — Backup, DR, and monitoring
|
|
|
|
```task
|
|
id: NK-WP-0003-T09
|
|
status: todo
|
|
priority: medium
|
|
state_hub_task_id: "a82751d8-4de8-4668-8568-8dc140a6322b"
|
|
```
|
|
|
|
Operational hardening:
|
|
|
|
1. Deploy backup CronJob for CloudNativePG → MinIO/S3
|
|
```bash
|
|
kubectl apply -f sso-mfa/k8s/backup/
|
|
```
|
|
2. Execute DB restore drill (mandatory before production traffic):
|
|
restore `privacyidea_db` from a backup into a test namespace, verify
|
|
privacyIDEA starts cleanly with the restored data
|
|
3. Deploy break-glass admin access (disabled by default):
|
|
```bash
|
|
bash sso-mfa/k8s/lldap/break-glass.sh setup
|
|
```
|
|
4. Verify Prometheus scraping for privacyIDEA and Authelia metrics
|
|
5. Confirm NetworkPolicies block all unexpected egress
|
|
|
|
Verify: `bash sso-mfa/k8s/verify-t08.sh` (if exists) or manual checklist
|
|
from NK-WP-0001 T08 scope.
|
|
|
|
## Done criteria
|
|
|
|
- [ ] All verify-t*.sh scripts exit 0
|
|
- [ ] KeyCape acceptance test suite passes
|
|
- [ ] DB restore drill completed
|
|
- [ ] All key material backed up in KeePassXC + ops bundle
|
|
- [ ] privacyIDEA enckey backed up (K8s Secret + KeePassXC)
|
|
- [ ] Monitoring active (Prometheus scraping all three services)
|