Files
net-kingdom/sso-mfa/k8s/keycape/README.md
tegwick efbdab4652 feat(keycape): add netkingdom OIDC mount and bao.coulomb.social callbacks
Configure OpenBao auth for both netkingdom and keycape mounts with browser
redirect URIs; update verify scripts and runtime architecture notes.
2026-06-18 01:23:02 +02:00

248 lines
9.2 KiB
Markdown

# 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.
The Authelia `baseURL` in `create-secrets.sh` must be the browser-facing
`https://auth.coulomb.social` URL. KeyCape uses it to build the redirect sent
to the user's browser during `/authorize`; a cluster-internal service URL or
relative Authelia path will make the public OIDC login flow land on a 404 even
when discovery and health checks are working.
## 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
bash ./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
bash ./create-secrets.sh
# 3. Restart KeyCape to pick up the new Secret
kubectl rollout restart deployment/keycape -n sso
```
If the browser flow reaches the KeyCape OTP screen and then reports
`mfa check error`, refresh the live privacyIDEA token without printing it:
```bash
cd sso-mfa/k8s/keycape
KEYCAPE_PI_REALM=coulomb KUBECTL="${KUBECTL:-kubectl}" \
bash ./refresh-pi-token-live.sh platform-root
```
The helper prompts for the `pi-admin` password, writes the token only into
Kubernetes Secrets, and restarts KeyCape. The current live privacyIDEA realm is
`coulomb`; use `KEYCAPE_PI_REALM=netkingdom` only for an explicit future realm
migration. The helper also restores `privacyidea.requireForAll: true`, which
keeps KeyCape from using the admin token-list API as the MFA-required check.
## OIDC client registration
Downstream applications are registered in the `clients:` block in
`keycape/create-secrets.sh`. The NetKingdom bootstrap console and Railiance
OpenBao admin clients are code-defined there; operators should not create
those clients manually in a separate UI. After changing the block:
```bash
bash ./create-secrets.sh # regenerates keycape-config Secret
kubectl rollout restart deployment/keycape -n sso
```
The `openbao-admin` client is intentionally a public PKCE client for the
current operator flow. It registers both the OpenBao CLI callback URIs and the
browser UI callbacks for `bao.coulomb.social`:
```text
http://localhost:8250/oidc/callback
http://127.0.0.1:8250/oidc/callback
https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback
https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback
```
The browser UI callback is paired with the Railiance Platform OpenBao ingress
at `https://bao.coulomb.social`. The preferred browser auth mount is
`netkingdom`; `keycape` remains a compatibility alias. Keep the localhost
callbacks unless there is a separate decision to retire CLI login.
To add or refresh only the OpenBao client in a live cluster, do not decrypt the
bootstrap secret bundle and do not re-run the full secret generator. Patch the
existing live `keycape-config` Secret in place:
```bash
cd sso-mfa/k8s/keycape
bash ./patch-openbao-client.sh
kubectl rollout restart deployment/keycape -n sso
kubectl rollout status deployment/keycape -n sso --timeout=60s
bash ./verify-openbao-client.sh
```
The patch script preserves existing secret values and does not print the
decoded `config.yaml` or signing key. The verifier checks the live Secret and
then opens a short local `kubectl port-forward` to KeyCape; it does not require
`curl` or `wget` inside the KeyCape container image.
After the live KeyCape client is present, configure Railiance OpenBao to trust
KeyCape:
```bash
bash ./configure-openbao-oidc.sh
```
That script registers the browser UI callbacks on the OpenBao
`auth/netkingdom/role/platform-admin` role and the compatibility
`auth/keycape/role/platform-admin` role. Browser operators should use the
OpenBao UI at `https://bao.coulomb.social`, leave namespace blank, choose
OIDC, set mount path `netkingdom`, and use role `platform-admin`; root-token
browser use is outside the approved operator path.
The script prompts for a root/sudo-capable OpenBao token inside the pod TTY.
OpenBao currently requires `oidc_client_secret` for OIDC auth config, while
KeyCape's `openbao-admin` client is public PKCE and does not validate a
downstream client secret. The script therefore writes the explicit
non-secret compatibility value `keycape-public-pkce-compatibility-value`.
Replace that with a real managed client secret when KeyCape supports
confidential downstream clients.
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"
```
For the local NetKingdom bootstrap console login check, keep the dedicated
bootstrap client registered with exact local callback URIs:
```yaml
clients:
- clientId: "netkingdom-bootstrap-console"
displayName: "NetKingdom Bootstrap Console"
redirectUris:
- "http://127.0.0.1:8876/oidc/callback"
- "http://localhost:8876/oidc/callback"
allowedScopes: ["openid", "profile", "email", "groups"]
grantTypes: ["authorization_code"]
clientType: "public"
```
The local callback page exchanges the authorization code and displays only
non-secret claims. KeyCape presents a browser OTP challenge between Authelia
password login and the final OIDC redirect whenever privacyIDEA requires MFA.
## 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
# Browser login redirect should start at KeyCape and then leave the kc host for
# Authelia. If it redirects to /api/oidc/authorization on kc.coulomb.social,
# regenerate keycape-config and restart KeyCape after confirming the Authelia
# browserBaseURL above.
```