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>
11 KiB
knoe-system — knoe-auth deployment and k3d TDD guide
Single reference for the knoe-auth identity service: what it does, how it is
deployed on the GKE knoe-db stack, and how to spin up a local k3d cluster
for test-driven development.
1. What knoe-auth is
knoe-auth is the identity system for the knoe.dev platform. It decides who
you are, what you can access, and how new contributors come on board.
Round 1 (shipped) — Durable Kerberos identity + contributor enrollment.
A Spring Boot service (authority/) wraps a KDC sidecar. Engineers enroll
via an invite OTP → Google corroboration → TOTP setup flow; the result is a
Kerberos principal (alice@KNOE.DEV) and a knoe.user row in PostgreSQL.
Downstream provisioning (GitLab, Gitea, CNPG roles) is queued automatically.
Phase 2 (planned, not yet implemented) — knoe-auth becomes a full OIDC
provider (issuer: https://api.knoe.dev/auth). Services that currently trust
Google directly will trust knoe-auth instead; knoe-auth federates upstream to
Google. See docs/knoe-auth-phase-2.md and the
paused pg_oauth item in docs/TODO.md.
Trust model (Round 1)
The invite OTP is the trust anchor. Google sign-in is corroboration. TOTP is the ongoing second factor. knoe.dev never pre-configures trust with any external Google Workspace — any verified Google account works once invited.
2. Work-in-progress status
| Item | Status | Notes |
|---|---|---|
| Round 1 Kerberos enrollment | Shipped | Operational on GKE |
| k3d dev loop (Phase 1 brief) | Shipped | make k3d-knoe-up |
| Phase 2 OIDC provider — k3d setup | Shipped | application-k3d.yml + etc/gen_oidc_signing_key.sh; OIDC discovery / JWKS / token endpoints exercise locally. See docs/local-dev-knoe-auth.md §"Daily loop". |
| Phase 2 OIDC provider — GKE deploy | Shipped | KNOE_AUTH_OIDC_ENABLED=true set; Kong /auth route added; signing key wired via knoe-auth-oidc secret (1Password). Run ./etc/init_knoe_auth.sh initialize to create the secret. |
| pg_oauth (PG18 OAUTHBEARER) | Ready | OIDC issuer shipped to GKE; pg_oauth can resume |
| Round 1.5 OpenBao transit-key encryption | Later | UserProvisioningService.java:84 TODO |
3. GKE deployment — knoe-auth on knoe-db
Clusters
| Resource | Cluster | Namespace |
|---|---|---|
| knoe-auth pod (Spring Boot + KDC sidecar) | knoe-dev-0 (app cluster) |
knoe-system |
PostgreSQL (CNPG knoe-db) |
knoe-dev-cnpg-0 (DB cluster) |
knoe-db-0 |
knoe-auth connects to PostgreSQL over the internal service
knoe-db-rw.knoe-db-0.svc.cluster.local:5432 (cross-cluster via VPC-native
routing; both clusters share the same VPC in us-west3).
Key manifests
| File | Purpose |
|---|---|
deploy/gcp/gke/knoe-auth-deployment.yaml |
Deployment: KDC sidecar + keytab-bootstrap initContainer + Spring Boot container. Realm KNOE.DEV. Image: knoe-auth:latest. |
deploy/gcp/gke/knoe-kdc-configmap.yaml |
krb5.conf + kdc.conf for realm KNOE.DEV. |
deploy/gcp/gke/knoe-kdc-secrets.yaml |
Master key + admin password. Production values come from OpenBao; file is a placeholder only — no real secrets committed. |
deploy/gcp/gke/knoe-auth-google-oidc-secret.example.yaml |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET template. |
deploy/gcp/gke/workload-identity.yaml |
KSA↔GSA bindings for GCP secret access. |
deploy/gcp/gke/knoe-db.yaml |
CNPG Cluster manifest (3-replica, GCS barman backup). |
Init script
etc/init_knoe_auth.sh provisions the knoe.* schema and seeds initial
state. Run it after applying the manifests:
# GKE (default mode — requires kubectl context pointing at knoe-dev-0)
./etc/init_knoe_auth.sh schema
./etc/init_knoe_auth.sh status
Subcommands: schema (create/migrate tables), status (print principal +
table counts), invite <email> (issue an enrollment invite).
Environment variables (Spring Boot container)
| Variable | Purpose |
|---|---|
KNOE_KDC_REALM |
Kerberos realm (default KNOE.DEV) |
KNOE_KERBEROS_SERVICE_PRINCIPAL |
Service principal for SPNEGO (HTTP/auth.knoe.dev@KNOE.DEV) |
KNOE_AUTH_BASE_URL |
Public base URL (https://api.knoe.dev/auth) |
KNOE_AUTH_COOKIE_DOMAIN |
Cookie scope (knoe.dev) |
KNOE_AUTH_SESSION_SECRET |
HMAC key for session JWTs |
KNOE_AUTH_OIDC_ENABLED |
false until Phase 2 ships |
KNOE_AUTH_OIDC_ISSUER |
Phase 2 issuer URL |
KNOE_AUTH_OIDC_SIGNING_KEY |
Base64 PKCS#8 RS256 private key (Phase 2) |
KNOE_AUTH_GOOGLE_CLIENT_ID |
Upstream Google OAuth client |
KNOE_AUTH_GOOGLE_CLIENT_SECRET |
Upstream Google OAuth secret |
Apply sequence (fresh GKE deploy)
# 1. Ensure kubectl context is knoe-dev-0
kubectl config use-context gke_plenary-truck-485623-p7_us-west3_knoe-dev-0
# 2. Apply KDC config + secrets (secrets must be pre-populated from OpenBao)
kubectl apply -f deploy/gcp/gke/knoe-kdc-configmap.yaml
kubectl apply -f deploy/gcp/gke/knoe-kdc-secrets.yaml # populate first!
# 3. Apply the deployment
kubectl apply -f deploy/gcp/gke/knoe-auth-deployment.yaml
# 4. Wait for rollout
kubectl -n knoe-system rollout status deployment/knoe-auth
# 5. Seed schema (runs against the DB cluster via the pod's psql)
./etc/init_knoe_auth.sh schema
./etc/init_knoe_auth.sh status
4. k3d local dev loop (TDD)
Run knoe-auth from IntelliJ or mvn spring-boot:run against a real
PostgreSQL + Kerberos KDC, both in a local k3d cluster. No GKE, no
1Password, no Google OAuth tenant required.
Full details: docs/local-dev-knoe-auth.md.
Quick reference below.
Prerequisites (install once)
brew install k3d kubectl maven openjdk@21
# Docker Desktop must be running
Verify:
k3d version # ≥ 5.x
kubectl version --client
mvn -version # Maven 3.x, Java 21
docker info # must not error
One-time cluster setup
make k3d-knoe-up
Takes 3–5 minutes. Creates a single-node k3d cluster (k3d-knoe), installs
the CNPG operator (v1.29.0), applies a single-replica knoe-db PostgreSQL
cluster, deploys the KDC (realm KNOE.LOCAL), seeds the knoe.* schema,
and seeds the knoe_developer group role + per-engineer LOGIN role
chrisfu (password chrisfu-dev, member of knoe_developer) so the
host can connect as chrisfu@knoey.com over the port-forward.
Verify:
kubectl --context=k3d-k3d-knoe -n knoe-db-0 get cluster knoe-db
# → knoe-db Cluster in healthy state
make k3d-knoe-smoke # full smoke check
Daily dev loop
# Terminal 1 — keep port-forwards alive
make k3d-knoe-pf
# Opens: localhost:5432 → PostgreSQL, localhost:88 → KDC (TCP), localhost:464 → kpasswd
# Terminal 2 — get a Kerberos ticket
export KRB5_CONFIG=$(pwd)/etc/krb5.local.conf
kinit developer@KNOE.LOCAL # password: developer
klist # confirm ticket
# Terminal 3 — run knoe-auth
cd authority
mvn spring-boot:run \
-Dspring-boot.run.jvmArguments="-Djava.security.krb5.conf=$(pwd)/../etc/krb5.local.conf" \
-Dspring-boot.run.profiles=k3d
# Verify
curl http://localhost:8080/health # → {"status":"ok"}
IntelliJ run config
Add a Spring Boot run configuration for KnoeAuthApplication with:
- VM options:
-Djava.security.krb5.conf=$PROJECT_DIR$/etc/krb5.local.conf - Active profiles:
k3d - Working directory:
$MODULE_WORKING_DIR$
Kerberos realm differences
| k3d (local) | GKE (production) | |
|---|---|---|
| Realm | KNOE.LOCAL |
KNOE.DEV |
| KDC address | localhost:88 (port-forwarded) |
knoe-kdc.knoe-system.svc |
| Admin principal | admin/admin@KNOE.LOCAL |
OpenBao-managed |
| Developer principal | developer@KNOE.LOCAL (password: developer) |
per-engineer invite flow |
| krb5 config | etc/krb5.local.conf |
pod-mounted from knoe-kdc-configmap.yaml |
etc/krb5.local.conf sets udp_preference_limit = 1 to force TCP — required
because kubectl port-forward does not proxy UDP on macOS.
Tear down
make k3d-knoe-down # deletes the k3d cluster entirely
Smoke check details
scripts/k3d-knoe-smoke.sh verifies:
- CNPG cluster
knoe-dbis inCluster in healthy state - At least 6
knoe.*tables exist in PostgreSQL - KDC Deployment is
1/1 Ready admin/admin@KNOE.LOCALprincipal exists in the KDC database
5. Schema overview
The knoe.* schema lives in the CNPG PostgreSQL cluster (knoe-db-0
namespace, database knoe). Core tables:
| Table | Purpose |
|---|---|
knoe.user |
Canonical user record (id, username, email, created_at) |
knoe.identity |
Per-provider identity rows (google, kerberos) linked to knoe.user |
knoe.invitation |
Pending invites; OTP stored as bcrypt hash |
knoe.totp_credential |
TOTP secrets per user (AES-GCM encrypted at rest; Round 1.5 will add OpenBao transit-key envelope) |
knoe.knobject |
Provisioned resources (GitLab project, Gitea repo, CNPG role, …) |
knoe.access_grant |
User↔knobject grants |
knoe.provisioning_job |
Async job queue polled by ProvisioningWorker |
Schema is applied/migrated by etc/init_knoe_auth.sh schema in both GKE and
k3d modes.
6. Source map
Java application (authority/)
| File | Responsibility |
|---|---|
KnoeAuthApplication.java |
@SpringBootApplication entry point |
HealthController.java |
GET /health |
web/LoginController.java |
Form-login + SPNEGO challenge |
web/VerifyController.java |
Token-verify endpoint for downstream services |
session/SessionTokenService.java |
HMAC-SHA256 JWT cookies |
kerberos/KerberosSpnegoService.java |
SPNEGO challenge/response |
kerberos/KadminClient.java |
Shells out to kadmin.local to addprinc/cpw (input sanitized) |
enroll/EnrollmentController.java |
Enrollment web flow (OTP → Google → TOTP → complete) |
enroll/InviteService.java |
Invite CRUD, OTP bcrypt hashing, rate limiting |
enroll/UserProvisioningService.java |
Transactional orchestrator: inserts rows, calls KadminClient, queues jobs |
admin/AdminController.java |
POST /auth/admin/invites, GET /auth/admin/users — SPNEGO + admin-role gated |
provisioning/ProvisioningWorker.java |
@Scheduled poller for knoe.provisioning_job WHERE status='pending' |
config/AuthProperties.java |
Typed binding for knoe.auth.* config keys |
k3d manifests (k8s/knoe/)
| File | Purpose |
|---|---|
knoe-db.yaml |
CNPG Cluster (single-replica, local-path storage) |
knoe-kdc-configmap.yaml |
KDC krb5.conf + kdc.conf, realm KNOE.LOCAL |
knoe-kdc-deployment.yaml |
KDC Deployment (debian:bookworm-slim + MIT Kerberos) |
knoe-kdc-service.yaml |
ClusterIP service for KDC (ports 88, 464) |
knoe-kdc-pvc.yaml |
PVC for KDC database persistence |
knoe-kdc-init-job.yaml |
One-shot Job: creates admin/admin + developer principals |
7. Open work items
-
pg_oauth (Phase 2 pg_hba) — Ready to resume. Replaces SCRAM with PG18 native OAUTHBEARER; wires into
install.sh/supabase/deploy.sh. OIDC issuer now reachable athttps://api.knoe.dev/auth— pg_oauth can proceed. -
Round 1.5 OpenBao transit-key encryption —
UserProvisioningService.java:84has the TODO. TOTP secrets currently have PG-level encryption only.