prole/docs/local-dev-knoe-auth.md
chrisfu 903f84f200 feat: ship Junie #3 (image rename) + Phase 2 OIDC GKE deploy + k3d chrisfu seed
Three independent lines of work landing in one commit because they were all
on disk together end-of-session and the cross-file edits (TODO, knoe-system,
junie/README) interleave cleanly:

1. **Queue #3 — image rename `knoe-authority` → `knoe-auth`** (Junie).
   Closes drift R6. The Maven artifact has been `knoe-auth.jar` since
   commit b355855; the deploy manifests now match.
     authority/Dockerfile.app                                         (NEW)
     deploy/gcp/gke/knoe-auth-deployment.yaml                         (3 image tags renamed)
     deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml     (2 image tags renamed)
     Makefile                                                         (docker-build-auth +
                                                                       docker-push-auth + REGISTRY/KNOE_AUTH_VERSION
                                                                       defaults)
     docs/plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md  (brief, kept as design record)

2. **Phase 2 OIDC provider — GKE deploy** (Junie). Source landed via the
   merge that brought claude/crazy-bose-fec256 back; the k3d sandbox
   shipped earlier today (commit 93157b0). This commit completes the GKE
   path: Kong route `/auth` → knoe-auth, default values added, signing
   key wired via `knoe-auth-oidc` secret per the brief.
     supabase/helm/knoe-supabase/templates/kong/config.yaml           (Kong route /auth, gated
                                                                       on .Values.knoeAuth.enabled)
     supabase/helm/knoe-supabase/values.yaml                          (knoeAuth.enabled=false default;
                                                                       studioIngress block — also
                                                                       fixes the helm lint issue noted
                                                                       in earlier commit 03bb731)
     docs/plans/junie/phase2-oidc-gke-deploy.md                       (brief, kept as design record)

3. **k3d dev-user seed: `chrisfu` + `knoe_developer`** (Claude). Closes
   "I want auth as chrisfu@knoey.com to my local cnpg database" for the
   local dev loop. `etc/init_knoe_auth.sh --mode k3d` now runs
   `seed_dev_users_k3d()` after the schema bootstrap, creating:
     - `knoe_developer` group role with R/W on `knoe`+`public` (mirrors
       the GKE production layout from docs/db-access.md; was hand-rolled
       in production per the 2026-04-30 onboarding work, never baked into
       postInitTemplateSQL).
     - `chrisfu` LOGIN role with password `chrisfu-dev` (idempotent —
       resets on every `make k3d-knoe-up`, so the rebuild loop is
       deterministic). Granted into `knoe_developer`.
   From the host with port-forward up:
     PGPASSWORD=chrisfu-dev psql "postgresql://chrisfu@localhost:5432/knoe-db?sslmode=require"

Cross-cutting doc updates:
  docs/TODO.md            — Phase 2 GKE entry removed from "In progress"
                            (now "(none — all items shipped or paused)");
                            queue #3 + reality table R6 removed; pg_oauth
                            paused note flipped to "OIDC issuer now
                            deployed to GKE — pg_oauth can resume"; Done
                            section gets new entries for #3, Phase 2 GKE,
                            and chrisfu seed.
  docs/knoe-system.md     — Phase 2 GKE row → Shipped; pg_oauth → Ready;
                            knoe-auth-deployment.yaml note "knoe-auth:latest"
                            instead of "knoe-authority:latest"; "One-time
                            cluster setup" mentions the chrisfu seed.
  docs/local-dev-knoe-auth.md — "Verify psql connectivity" rewritten to
                            connect as chrisfu (was `knoe`); new "Reset /
                            rebuild loop" section explaining idempotency.
  docs/plans/junie/README.md — two new rows under Shipped: brief 03 and
                            phase2-oidc-gke-deploy.

Verification (the bits I ran locally):
  - bash -n etc/init_knoe_auth.sh                                     OK
  - The seed SQL is idempotent (DO blocks with EXISTS checks +
    ALTER ROLE on the password reset path)
  - The Kong /auth route is gated on .Values.knoeAuth.enabled — default
    false, no behavior change until someone flips it on per Junie's brief

Out of scope for this commit:
  - Actually flipping knoeAuth.enabled=true on the live GKE chart and
    rolling out — Junie's brief covers the runbook; needs the
    knoe-auth-oidc K8s secret populated from 1Password first
  - Rebuilding + pushing the new knoe-auth:latest image — `make
    docker-push-auth` is wired, just hasn't been run yet
  - Round 1.5 OpenBao transit-key encryption (still queued)

Co-authored-by: Junie <junie@jetbrains.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:07:40 -07:00

312 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Local dev loop for knoe-auth
Run knoe-auth from IntelliJ or `mvn spring-boot:run` against a real
PostgreSQL + Kerberos KDC, both running in a local k3d cluster.
No GKE cluster, no deploy pipeline, no Google OAuth tenant needed.
---
## Prerequisites
Install once on your Mac:
```bash
brew install k3d kubectl maven openjdk@21
```
Also required: **Docker Desktop** running (k3d uses it for the cluster node).
Verify:
```bash
k3d version # ≥ 5.x
kubectl version --client
mvn -version # Apache Maven 3.x, Java 21
docker info # must not error
```
---
## One-time setup
```bash
git clone <repo> && cd knoe-db
make k3d-knoe-up
```
This takes **3–5 minutes** on a fresh laptop. It:
1. Creates a single-node k3d cluster named `k3d-knoe`
2. Installs the CNPG operator (v1.29.0)
3. Applies a single-replica `knoe-db` CNPG Cluster (PostgreSQL 17)
4. Waits for the cluster to reach `Cluster in healthy state`
5. Applies the KDC Deployment + Service (realm `KNOE.LOCAL`)
6. Seeds the `knoe.*` schema via `etc/init_knoe_auth.sh schema --mode k3d`
Verify it worked:
```bash
kubectl --context=k3d-k3d-knoe -n knoe-db-0 get cluster knoe-db
# → knoe-db Cluster in healthy state
kubectl --context=k3d-k3d-knoe -n knoe-system get deploy knoe-kdc
# → knoe-kdc 1/1 ...
scripts/k3d-knoe-smoke.sh
# → k3d-knoe-smoke: PASS
```
---
## Daily loop
Open **two terminals** from the repo root.
### Terminal A — port-forwards (leave running)
```bash
make k3d-knoe-pf
```
This opens:
| Local port | Destination | Purpose |
|---|---|---|
| `5432` | `knoe-db-rw` (knoe-db-0) | PostgreSQL primary |
| `88` | `knoe-kdc` (knoe-system) | Kerberos KDC (TCP) |
| `464` | `knoe-kdc` (knoe-system) | kpasswd (TCP) |
The script prints the JDBC URL and env vars to export. Leave it running; press `^C` to stop.
### Terminal B — knoe-auth
```bash
export KRB5_CONFIG=$PWD/etc/krb5.local.conf
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=require
export SPRING_DATASOURCE_USERNAME=knoe
export SPRING_DATASOURCE_PASSWORD=knoe-dev-password
# Phase 2 OIDC signing key — generated by `make k3d-knoe-up` (idempotent;
# persisted at etc/secrets/knoe-auth-oidc-key.b64). If absent, OidcTokenService
# falls back to an ephemeral key (loops still work; tokens just don't survive
# a knoe-auth restart).
export KNOE_AUTH_OIDC_SIGNING_KEY=$(cat etc/secrets/knoe-auth-oidc-key.b64)
mvn -pl authority spring-boot:run \
-Dspring-boot.run.jvmArguments="-Djava.security.krb5.conf=$PWD/etc/krb5.local.conf" \
-Dspring-boot.run.profiles=k3d
```
The `k3d` Spring profile (`authority/src/main/resources/application-k3d.yml`)
enables Phase 2 OIDC, points the issuer at `http://localhost:8080`, sets the
Kerberos realm to `KNOE.LOCAL`, and aligns the datasource defaults with the
port-forwards.
### Verify the loop
```bash
curl http://localhost:8080/health
# → {"status":"ok"}
curl -s http://localhost:8080/.well-known/openid-configuration | jq .issuer
# → "http://localhost:8080"
curl -s http://localhost:8080/jwks.json | jq '.keys[0].kty'
# → "RSA"
```
If `/.well-known/openid-configuration` returns 404, OIDC isn't enabled —
verify you started with `-Dspring-boot.run.profiles=k3d` (or that the
`k3d` profile is active in your IntelliJ run config).
---
## Verify psql connectivity
`make k3d-knoe-up` seeds two roles for local dev (in addition to the
CNPG-internal `postgres`/`knoe` superusers):
| Role | Password | Group | Purpose |
|---|---|---|---|
| `chrisfu` | `chrisfu-dev` | `knoe_developer` | Per-engineer LOGIN role; mirrors the GKE pattern (see [`docs/db-access.md`](db-access.md)). Use this for daily psql/JDBC work as `chrisfu@knoey.com`. |
| `knoe_developer` | (none — NOLOGIN) | — | Group role with R/W on `knoe`+`public`. Granted to chrisfu. Add new dev users with `GRANT knoe_developer TO <user>`. |
With `make k3d-knoe-pf` running:
```bash
# Connect as chrisfu (the engineer identity)
PGPASSWORD=chrisfu-dev psql \
"postgresql://chrisfu@localhost:5432/knoe-db?sslmode=require" \
-c "\dt knoe.*"
# → ≥6 tables: knoe.invitation, knoe.identity, knoe.totp_credential,
# knoe.knobject, knoe.access_grant, knoe.provisioning_job (+ knoe.user)
```
Same connection string for IntelliJ DataGrip / DBeaver — host
`localhost`, port `5432`, db `knoe-db`, user `chrisfu`, password
`chrisfu-dev`, sslmode `require`.
> **Why `chrisfu`?** The k3d seed sets up the same identity layout as
> production (where chrisfu and ron are the human engineer roles).
> Re-running `make k3d-knoe-up` is idempotent — the password is reset
> to `chrisfu-dev` on every up, so you always know what to use after a
> rebuild.
---
## `kinit` for Kerberos testing (Phase 1 — KDC reachable)
With `make k3d-knoe-pf` running and `KRB5_CONFIG` exported:
```bash
kinit admin/admin@KNOE.LOCAL
# Password: knoe-local-admin-dev
klist
# → Credentials cache: ... admin/admin@KNOE.LOCAL
```
> **Phase 1 scope:** The KDC is reachable and principals exist. Full SPNEGO
> E2E (browser `Negotiate` header → knoe-auth) requires a service principal
> keytab for `HTTP/localhost@KNOE.LOCAL` — that's Phase 2.
---
## IntelliJ run configuration
1. Open the project in IntelliJ.
2. **Run → Edit Configurations → + → Spring Boot**
3. Set:
- **Module:** `authority`
- **Main class:** `dev.knoe.auth.KnoeAuthApplication`
- **Active profiles:** `k3d` *(enables Phase 2 OIDC + dev-only defaults)*
- **VM options:** `-Djava.security.krb5.conf=$PROJECT_DIR$/etc/krb5.local.conf`
4. Under **Environment variables**, add:
```
KRB5_CONFIG=/path/to/knoe-db/etc/krb5.local.conf
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=require
SPRING_DATASOURCE_USERNAME=knoe
SPRING_DATASOURCE_PASSWORD=knoe-dev-password
KNOE_AUTH_OIDC_SIGNING_KEY=<paste contents of etc/secrets/knoe-auth-oidc-key.b64>
```
IntelliJ's environment-variable field doesn't expand `$(cat …)` shell
substitution, so paste the base64 string directly (it's one line, ~1.6 KB).
Or use a `.env` plugin like *EnvFile* and point it at the `.b64` file.
5. Click **OK**, then **Run**.
Spring DevTools is active — saving a `.java` file triggers a hot reload automatically.
---
## Reset / rebuild loop
The whole stack is designed to be **repeatedly rebuilt**. Each
`make k3d-knoe-up` is idempotent:
- Cluster name, namespaces, secrets, schema, and dev-user roles
(`chrisfu` with password `chrisfu-dev`, member of `knoe_developer`)
are all recreated from scratch.
- The OIDC RS256 keypair at `etc/secrets/knoe-auth-oidc-key.b64` is
**kept** across rebuilds (so JWTs you issued before a rebuild still
validate). Pass `FORCE=1 bash etc/gen_oidc_signing_key.sh` if you
want to rotate.
Two cadences:
```bash
# Full rebuild (~3-5 min) — wipes the k3d cluster, recreates everything.
# Use after schema changes, manifest edits, or when in doubt.
make k3d-knoe-down && make k3d-knoe-up
# Mid-iteration: knoe-auth restart only.
# In Terminal B (the one running mvn), just ^C and re-run:
mvn -pl authority spring-boot:run \
-Dspring-boot.run.jvmArguments="-Djava.security.krb5.conf=$PWD/etc/krb5.local.conf" \
-Dspring-boot.run.profiles=k3d
# Spring DevTools also picks up `.java` saves automatically — no manual restart needed.
```
If you only changed the schema or want to reseed roles without rebuilding the
cluster:
```bash
./etc/init_knoe_auth.sh schema --mode k3d # re-runs the schema SQL
./etc/init_knoe_auth.sh initialize --mode k3d # full re-init incl. dev-user seed
```
These are safe to run while the cluster's up — both use `IF NOT EXISTS` /
`ALTER ROLE` patterns and are non-destructive to existing data (apart from
resetting `chrisfu`'s password back to `chrisfu-dev`).
---
## Troubleshooting
### `make k3d-knoe-up` fails at CNPG operator install
Check Docker Desktop is running and has network access:
```bash
docker info
curl -I https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.29/releases/cnpg-1.29.0.yaml
```
### CNPG cluster stuck in `Setting up primary`
The PostgreSQL image pull may be slow on first run. Watch:
```bash
kubectl --context=k3d-k3d-knoe -n knoe-db-0 get pods -w
```
### Port 5432 already in use
Stop any local PostgreSQL instance:
```bash
brew services stop postgresql@17 # or whatever version
```
Or run the port-forward on a different port:
```bash
kubectl --context=k3d-k3d-knoe -n knoe-db-0 port-forward service/knoe-db-rw 15432:5432
# then set SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:15432/knoe-db?sslmode=require
```
### `kinit` fails with `Cannot contact any KDC`
Ensure `make k3d-knoe-pf` is running and `KRB5_CONFIG` is exported:
```bash
echo $KRB5_CONFIG # should be .../etc/krb5.local.conf
# If empty:
export KRB5_CONFIG=$PWD/etc/krb5.local.conf
kinit admin/admin@KNOE.LOCAL
```
The `udp_preference_limit = 1` in `krb5.local.conf` forces TCP, which works
reliably through `kubectl port-forward` on macOS.
### Schema not applied (`knoe.*` tables missing)
Re-run the schema step manually:
```bash
etc/init_knoe_auth.sh schema --mode k3d
```
---
## Architecture reference
See [`docs/plans/k3d-gke-mirror.md`](plans/k3d-gke-mirror.md) for the
multi-phase plan and the "why" behind this setup.
Phase 1 (this doc): CNPG + KDC in k3d, knoe-auth on host.
Phase 2 (future): Full SPNEGO E2E with keytab.
Phase 3 (future): knoe-auth as a pod inside k3d (image build/load).