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 (commit93157b0). 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 commit03bb731) 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>
9.2 KiB
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:
brew install k3d kubectl maven openjdk@21
Also required: Docker Desktop running (k3d uses it for the cluster node).
Verify:
k3d version # ≥ 5.x
kubectl version --client
mvn -version # Apache Maven 3.x, Java 21
docker info # must not error
One-time setup
git clone <repo> && cd knoe-db
make k3d-knoe-up
This takes 3–5 minutes on a fresh laptop. It:
- Creates a single-node k3d cluster named
k3d-knoe - Installs the CNPG operator (v1.29.0)
- Applies a single-replica
knoe-dbCNPG Cluster (PostgreSQL 17) - Waits for the cluster to reach
Cluster in healthy state - Applies the KDC Deployment + Service (realm
KNOE.LOCAL) - Seeds the
knoe.*schema viaetc/init_knoe_auth.sh schema --mode k3d
Verify it worked:
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)
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
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
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). 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:
# 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-runningmake k3d-knoe-upis idempotent — the password is reset tochrisfu-devon 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:
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
Negotiateheader → knoe-auth) requires a service principal keytab forHTTP/localhost@KNOE.LOCAL— that's Phase 2.
IntelliJ run configuration
- Open the project in IntelliJ.
- Run → Edit Configurations → + → Spring Boot
- 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
- Module:
- 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.
- 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
(
chrisfuwith passwordchrisfu-dev, member ofknoe_developer) are all recreated from scratch. - The OIDC RS256 keypair at
etc/secrets/knoe-auth-oidc-key.b64is kept across rebuilds (so JWTs you issued before a rebuild still validate). PassFORCE=1 bash etc/gen_oidc_signing_key.shif you want to rotate.
Two cadences:
# 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:
./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:
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:
kubectl --context=k3d-k3d-knoe -n knoe-db-0 get pods -w
Port 5432 already in use
Stop any local PostgreSQL instance:
brew services stop postgresql@17 # or whatever version
Or run the port-forward on a different port:
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:
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:
etc/init_knoe_auth.sh schema --mode k3d
Architecture reference
See docs/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).