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>
This commit is contained in:
chrisfu 2026-05-02 13:07:40 -07:00
parent 93157b0a86
commit 903f84f200
13 changed files with 466 additions and 43 deletions

View File

@ -14,9 +14,11 @@ PIPELINE_DIR ?= deploy/opentofu/k3s
DEPLOYMENT_GIT_DIR ?= knoe/deployment
GITEA_SCRIPT ?= knoe/etc/gitea.sh
KUBECONFIG_PATH ?= $(CURDIR)/knoe-k3s.kubeconfig
REGISTRY ?= us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system
KNOE_AUTH_VERSION ?= latest
DEPLOYMENT_REPO_URL ?= http://gitea.local/knoe/deployment.git
.PHONY: all knoe build build-auth install deploy init clean help requirements test pyconv start \
.PHONY: all knoe build build-auth docker-build-auth docker-push-auth install deploy init clean help requirements test pyconv start \
k3d-knoe-up k3d-knoe-pf k3d-knoe-down
all: build
@ -66,6 +68,18 @@ build-auth:
mvn -f authority/pom.xml -DskipTests package
@echo "✓ knoe-auth jar: authority/target/knoe-auth.jar"
docker-build-auth: build-auth
@command -v docker >/dev/null 2>&1 || (echo "Error: docker not found in PATH." && exit 1)
@echo "Building Docker image knoe-auth:$(KNOE_AUTH_VERSION)..."
docker build -f authority/Dockerfile.app -t knoe-auth:$(KNOE_AUTH_VERSION) .
@echo "✓ Docker image: knoe-auth:$(KNOE_AUTH_VERSION)"
docker-push-auth: docker-build-auth
@echo "Tagging and pushing $(REGISTRY)/knoe-auth:$(KNOE_AUTH_VERSION)..."
docker tag knoe-auth:$(KNOE_AUTH_VERSION) $(REGISTRY)/knoe-auth:$(KNOE_AUTH_VERSION)
docker push $(REGISTRY)/knoe-auth:$(KNOE_AUTH_VERSION)
@echo "✓ Pushed: $(REGISTRY)/knoe-auth:$(KNOE_AUTH_VERSION)"
install:
@echo "Running silent install..."
KNOE_CONF=$(KNOE_CONF) ./install.sh -s -c $(KNOE_CONF)/knoe.cfg

30
authority/Dockerfile.app Normal file
View File

@ -0,0 +1,30 @@
# Dockerfile.app — Spring Boot runtime image for knoe-auth
# Build the jar first: make build-auth
# Then build this image: make docker-build-auth
#
# Multi-stage: build stage compiles the jar; runtime stage is minimal JRE.
# ── Build stage ──────────────────────────────────────────────────────────────
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY authority/pom.xml pom.xml
# Download dependencies first (layer-cache friendly)
RUN mvn -f pom.xml dependency:go-offline -q
COPY authority/src src
RUN mvn -f pom.xml -DskipTests package -q && \
mv target/knoe-auth.jar /knoe-auth.jar
# ── Runtime stage ─────────────────────────────────────────────────────────────
FROM eclipse-temurin:21-jre-jammy
LABEL org.opencontainers.image.title="knoe-auth" \
org.opencontainers.image.description="Knoe authentication service (Spring Boot)" \
org.opencontainers.image.source="https://github.com/chrisfu/knoe-db"
RUN groupadd --system knoe && useradd --system --gid knoe knoe
WORKDIR /app
COPY --from=build /knoe-auth.jar knoe-auth.jar
RUN chown knoe:knoe knoe-auth.jar
USER knoe
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/knoe-auth.jar"]

View File

@ -28,7 +28,7 @@ spec:
# to create the HTTP service principal and export the keytab before the
# Spring Boot container starts.
- name: keytab-bootstrap
image: us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system/knoe-authority:latest
image: us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system/knoe-auth:latest
imagePullPolicy: Always
command:
- /bin/bash
@ -130,7 +130,7 @@ spec:
containers:
# ── Spring Boot / knoe-auth ──────────────────────────────────────────
- name: knoe-auth
image: us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system/knoe-authority:latest
image: us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system/knoe-auth:latest
imagePullPolicy: Always
ports:
- name: http
@ -199,7 +199,7 @@ spec:
value: "https://api.knoe.dev/auth"
# ── OIDC Provider (Path B) ────────────────────────────────────
- name: KNOE_AUTH_OIDC_ENABLED
value: "${KNOE_AUTH_OIDC_ENABLED:-false}"
value: "true"
- name: KNOE_AUTH_OIDC_ISSUER
value: "https://api.knoe.dev/auth"
- name: KNOE_AUTH_OIDC_CLIENT_ID
@ -242,7 +242,7 @@ spec:
# ── MIT Kerberos KDC sidecar ─────────────────────────────────────────
- name: kdc
image: us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system/knoe-authority:latest
image: us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system/knoe-auth:latest
imagePullPolicy: Always
command: ["/bin/bash", "/opt/knoe-kdc/entrypoint.sh"]
env:

View File

@ -16,7 +16,7 @@ spec:
spec:
initContainers:
- name: keytab-bootstrap
image: myrddin.prole.org:5000/knoe-authority:latest
image: myrddin.prole.org:5000/knoe-auth:latest
imagePullPolicy: IfNotPresent
command:
- /bin/bash
@ -202,7 +202,7 @@ spec:
subPath: krb5.conf
readOnly: true
- name: kdc
image: myrddin.prole.org:5000/knoe-authority:latest
image: myrddin.prole.org:5000/knoe-auth:latest
imagePullPolicy: IfNotPresent
command: ["/bin/bash", "/opt/knoe-kdc/entrypoint.sh"]
env:

View File

@ -13,10 +13,11 @@ The Kanban "Now" section at top is the only place this doc imposes structure. Ev
## Now (Kanban)
### In progress
- **Phase 2 OIDC provider — GKE deploy.** k3d sandbox shipped 2026-05-02 (commit pending): `application-k3d.yml` profile, `etc/gen_oidc_signing_key.sh` keypair generator, OIDC endpoints (`/.well-known/openid-configuration`, `/jwks.json`, `/authorize`, `/token`, `/userinfo`) reachable locally via `make k3d-knoe-up && make k3d-knoe-pf && mvn -pl authority spring-boot:run -Dspring-boot.run.profiles=k3d`. Remaining work to ship Phase 2 in production (this is the open thread): rebuild the knoe-auth image with the merged Phase 2 source from `main` (gated on queue #3 — also renames `knoe-authority``knoe-auth`); generate a production RS256 keypair (OpenBao `knoe-auth/oidc-signing-key`); create K8s Secret in `knoe-system` namespace; add `KNOE_AUTH_OIDC_ENABLED=true`, `KNOE_AUTH_OIDC_ISSUER=https://api.knoe.dev/auth`, `KNOE_AUTH_OIDC_SIGNING_KEY` (from secret) env vars to `deploy/gcp/gke/knoe-auth-deployment.yaml`; rollout; verify discovery + JWKS resolve through Kong at `https://api.knoe.dev/auth`. Design ref: [`docs/knoe-auth-phase-2.md`](knoe-auth-phase-2.md).
*(none — all items shipped or paused)*
### Paused
- **pg_oauth in install/deploy.sh + k3d build***previously assigned to Junie; paused.* The Phase 1 work (per-engineer roles, external LB at `pg.0.knoe.dev`, cert SANs, pg_hba tightening, [`docs/db-access.md`](db-access.md)) is committed. This phase replaces the SCRAM password mechanism with PG18 native OAUTHBEARER, wires it into `install.sh` / `supabase/deploy.sh` so a fresh deploy lands with the OIDC mechanism active by default, and ports the Service + cluster cert pattern to k3d. Connection target stays at `pg.0.knoe.dev`; only the credential issuance changes (libpq Device Flow → knoe-auth Bearer token). Resumes once Phase 2 OIDC is deployed to GKE (depends on the OIDC issuer being reachable from PostgreSQL).
- **pg_oauth in install/deploy.sh + k3d build***previously assigned to Junie; paused.* The Phase 1 work (per-engineer roles, external LB at `pg.0.knoe.dev`, cert SANs, pg_hba tightening, [`docs/db-access.md`](db-access.md)) is committed. This phase replaces the SCRAM password mechanism with PG18 native OAUTHBEARER, wires it into `install.sh` / `supabase/deploy.sh` so a fresh deploy lands with the OIDC mechanism active by default, and ports the Service + cluster cert pattern to k3d. Connection target stays at `pg.0.knoe.dev`; only the credential issuance changes (libpq Device Flow → knoe-auth Bearer token). OIDC issuer now deployed to GKE (2026-05-02) — pg_oauth can resume.
### Up next
*(empty — pull from the ranked queue below)*
@ -34,10 +35,6 @@ The Kanban "Now" section at top is the only place this doc imposes structure. Ev
> the **Done** section.
3. **Image rename `knoe-authority` → `knoe-auth`** — three deployment manifests still reference `knoe-authority:latest`:
- `deploy/gcp/gke/knoe-auth-deployment.yaml` (3 lines)
- `deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml` (2 lines)
The Maven artifact is now `knoe-auth.jar` (post-rename in commit [b355855](#)). Image rebuild + registry push needed; both manifests then bump.
@ -63,7 +60,6 @@ Items where docs describe an intended state that doesn't match live state. Each
| # | Reality (current) | Intended state | Documented at | Tracked work item |
|---|---|---|---|---|
| R6 | Image refs `knoe-authority:latest` in deploy manifests | `knoe-auth:latest` (matches Maven artifact `knoe-auth.jar` post-rename) | Phase C plan follow-ups | Queue item #3 |
---
@ -92,8 +88,16 @@ Items where docs describe an intended state that doesn't match live state. Each
*(items get archived here when they land, with a date and commit reference; or just delete if not worth memorializing)*
- 2026-05-02 — **Phase 2 OIDC provider — GKE deploy** (brief: `docs/plans/junie/phase2-oidc-gke-deploy.md`). `KNOE_AUTH_OIDC_ENABLED=true` and `KNOE_AUTH_OIDC_ISSUER=https://api.knoe.dev/auth` set in `deploy/gcp/gke/knoe-auth-deployment.yaml`. Kong `/auth` route added to `supabase/helm/knoe-supabase/templates/kong/config.yaml` (proxies to `knoe-auth.knoe-system.svc.cluster.local:8080`; GoTrue `/auth/v1/*` routes unaffected). `studioIngress` and `knoeAuth` default blocks added to `values.yaml` (fixes `helm lint`). Signing key wired via `knoe-auth-oidc` K8s Secret sourced from 1Password via `init_knoe_auth.sh initialize`. Unblocks pg_oauth resume.
- 2026-05-02 — **Image rename `knoe-authority` → `knoe-auth`** (queue #3 / drift R6). `authority/Dockerfile.app` added (Spring Boot multi-stage build). `make docker-build-auth` / `make docker-push-auth` targets added. Three image refs in `deploy/gcp/gke/knoe-auth-deployment.yaml` and two in `deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml` updated from `knoe-authority:latest``knoe-auth:latest`. `docs/knoe-system.md` §3 caveat removed. Brief: [`docs/plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md`](plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md).
- 2026-05-02 — **Image rename `knoe-authority` → `knoe-auth`** (queue #3 / drift R6). `authority/Dockerfile.app` added (Spring Boot multi-stage build). `make docker-build-auth` / `make docker-push-auth` targets added. Three image refs in `deploy/gcp/gke/knoe-auth-deployment.yaml` and two in `deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml` updated from `knoe-authority:latest``knoe-auth:latest`. `docs/knoe-system.md` §3 caveat removed. Brief: [`docs/plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md`](plans/junie/03-image-rename-knoe-authority-to-knoe-auth.md).
- 2026-05-02 — **k3d-mirror-of-GKE Phase 1: laptop dev loop for knoe-auth** (k3d brief). Smallest k3d-resident stack (CNPG single-replica + standalone KDC, realm `KNOE.LOCAL`) that lets a host-side knoe-auth iterate against real Postgres + Kerberos. Deliverables: `k8s/knoe/knoe-kdc-{configmap,deployment,service,pvc,init-job}.yaml`, `etc/init_knoe_auth.sh --mode k3d` flag (extends existing script; GKE behavior unchanged), `make k3d-knoe-{up,pf,down}` targets, `etc/krb5.local.conf` (TCP-forced, `udp_preference_limit=1`), `docs/local-dev-knoe-auth.md`, `scripts/k3d-knoe-{up,pf,down,smoke}.sh`. Design choice: extended `init_knoe_auth.sh` with `--mode k3d` rather than a sibling script — minimizes drift, same `schema`/`status` subcommands work in both modes. Brief: [`docs/plans/junie/k3d-knoe-auth-dev-loop.md`](plans/junie/k3d-knoe-auth-dev-loop.md).
- 2026-05-02 — **k3d dev-user seed: `chrisfu` + `knoe_developer`** (commit pending). `etc/init_knoe_auth.sh --mode k3d` (called by `cmd_initialize_k3d`) now runs `seed_dev_users_k3d()` after the schema bootstrap. Creates the `knoe_developer` group role with R/W on `knoe`+`public` (mirrors the GKE production layout from `docs/db-access.md`) and a `chrisfu` LOGIN role with password `chrisfu-dev` granted into the group. Idempotent: rebuild loop (`make k3d-knoe-down && make k3d-knoe-up`) deterministically resets chrisfu's password back to `chrisfu-dev` so the engineer always knows what to use post-rebuild. From the host with port-forward up: `PGPASSWORD=chrisfu-dev psql "postgresql://chrisfu@localhost:5432/knoe-db?sslmode=require"` connects. `docs/local-dev-knoe-auth.md` "Verify psql connectivity" + "Reset / rebuild loop" sections updated. `docs/knoe-system.md` §"One-time cluster setup" mentions the chrisfu seed. Closes the local-side of "I want auth as chrisfu@knoey.com to my local cnpg" — Kerberos principal + knoe.user/knoe.identity rows are deferred (separate work if needed).
- 2026-05-02 — **Phase 2 OIDC k3d dev sandbox** (commit pending). Companion to the k3d Phase 1 brief — completes the laptop dev loop for knoe-auth Phase 2. New `authority/src/main/resources/application-k3d.yml` Spring profile activates the OIDC surface with local issuer (`http://localhost:8080`); new `etc/gen_oidc_signing_key.sh` idempotently generates an RS256 PKCS#8 keypair at `etc/secrets/knoe-auth-oidc-key.{pem,b64}` (gitignored); `scripts/k3d-knoe-up.sh` runs the generator after schema seed; `scripts/k3d-knoe-pf.sh` prints the env-var export the engineer needs (`export KNOE_AUTH_OIDC_SIGNING_KEY=$(cat etc/secrets/knoe-auth-oidc-key.b64)`) plus the OIDC endpoint surface to curl-test. `docs/local-dev-knoe-auth.md` updated to use `-Dspring-boot.run.profiles=k3d`, the env var is exported, and the verify section now includes a `/jwks.json` curl. `docs/knoe-system.md` Phase 2 status flipped from "Planned" to "Shipped (k3d sandbox)" / "Pending (GKE deploy)". GKE-side Phase 2 deploy is now the open work item in TODO §"In progress" (gated on queue #3 image rename for the rebuild).
- 2026-05-02 — **k3d Phase 1 dev loop for knoe-auth** (commit `5d36008`). Junie's Phase 1 brief landed — laptop dev loop with CNPG (single-replica) + KDC (realm `KNOE.LOCAL`) reachable from host via `make k3d-knoe-{up,pf,down}`. New: 5 KDC manifests under `k8s/knoe/knoe-kdc-*.yaml`, 4 helper scripts under `scripts/k3d-knoe-*.sh`, `etc/krb5.local.conf` (with `udp_preference_limit=1` to dodge kubectl port-forward UDP flakiness on macOS), `docs/local-dev-knoe-auth.md` engineer doc, and `docs/knoe-system.md` unified reference. `etc/init_knoe_auth.sh` extended with `--mode k3d` flag (GKE behavior unchanged). Brief: [`docs/plans/junie/k3d-knoe-auth-dev-loop.md`](plans/junie/k3d-knoe-auth-dev-loop.md). Architectural plan: [`docs/plans/k3d-gke-mirror.md`](plans/k3d-gke-mirror.md).

View File

@ -38,8 +38,8 @@ external Google Workspace — any verified Google account works once invited.
| 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`](local-dev-knoe-auth.md) §"Daily loop". |
| Phase 2 OIDC provider — **GKE deploy** | **Pending** | Source on `main`; needs image rebuild (queue #3) + K8s Secret with signing key + env-var wiring on `knoe-auth-deployment.yaml`. |
| pg_oauth (PG18 OAUTHBEARER) | **Paused** | Resumes once Phase 2 OIDC is deployed to GKE |
| 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 |
---
@ -61,7 +61,7 @@ routing; both clusters share the same VPC in `us-west3`).
| File | Purpose |
|---|---|
| `deploy/gcp/gke/knoe-auth-deployment.yaml` | Deployment: KDC sidecar + keytab-bootstrap initContainer + Spring Boot container. Realm `KNOE.DEV`. **Note:** image tag still `knoe-authority:latest` — rename to `knoe-auth:latest` is queue item #3. |
| `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. |
@ -153,7 +153,10 @@ make k3d-knoe-up
Takes 35 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`), and seeds the `knoe.*` schema.
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:
@ -279,24 +282,9 @@ k3d modes.
## 7. Open work items
- **Queue #3** — Image rename `knoe-authority``knoe-auth` in
`deploy/gcp/gke/knoe-auth-deployment.yaml` and
`deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml`.
Maven artifact is already `knoe-auth.jar`; manifests still pull
`knoe-authority:latest`. Needs image rebuild + registry push.
- **Phase 2 OIDC provider** — k3d dev sandbox **shipped 2026-05-02**.
`application-k3d.yml` enables the OIDC surface; `etc/gen_oidc_signing_key.sh`
generates a persistent RS256 keypair on first `make k3d-knoe-up`.
Endpoints reachable locally: `/.well-known/openid-configuration`,
`/jwks.json`, `/authorize`, `/token`, `/userinfo`. **GKE deploy still
pending** — needs queue #3 (image rebuild as `knoe-auth:latest`) plus a
K8s Secret holding the signing key and env-var additions on the
deployment. Design doc: [`docs/knoe-auth-phase-2.md`](knoe-auth-phase-2.md).
- **pg_oauth (Phase 2 pg_hba)** — Paused. Replaces SCRAM with PG18
- **pg_oauth (Phase 2 pg_hba)** — Ready to resume. Replaces SCRAM with PG18
native OAUTHBEARER; wires into `install.sh` / `supabase/deploy.sh`.
Resumes after Phase 2 OIDC issuer ships.
OIDC issuer now reachable at `https://api.knoe.dev/auth` — pg_oauth can proceed.
- **Round 1.5 OpenBao transit-key encryption**`UserProvisioningService.java:84`
has the TODO. TOTP secrets currently have PG-level encryption only.

View File

@ -123,15 +123,35 @@ verify you started with `-Dspring-boot.run.profiles=k3d` (or that the
## 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
PGPASSWORD=knoe-dev-password psql \
"postgresql://knoe@localhost:5432/knoe-db?sslmode=require" \
# 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)
```
You should see ≥6 tables (`knoe.invitation`, `knoe.identity`, `knoe.totp_credential`, etc.).
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.
---
@ -181,16 +201,45 @@ Spring DevTools is active — saving a `.java` file triggers a hot reload automa
---
## Reset
## Reset / rebuild loop
To wipe and recreate the cluster from scratch (~3 min):
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.
```
This is fully idempotent. The cluster name, namespaces, secrets, and schema
are all recreated from scratch.
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`).
---

View File

@ -0,0 +1,84 @@
# Brief 03 — Image rename: knoe-authority → knoe-auth
**Status:** Active
**Filed:** 2026-05-02
**Author:** Junie
---
## 1. Problem
The Maven artifact has been `knoe-auth.jar` since commit b355855, but the Docker image is still
tagged `knoe-authority:latest` in Artifact Registry and in both deployment manifests.
There is no Spring Boot `Dockerfile` in the repo — `authority/Dockerfile` is the Samba/KDC image,
not the application image.
Drift tracked in `docs/TODO.md` as queue item #3 and Reality table row R6.
---
## 2. Deliverables
### 2.1 New Spring Boot Dockerfile
`authority/Dockerfile.app` — multi-stage build:
1. **Build stage** (`maven:3.9-eclipse-temurin-21`) — runs `mvn -f authority/pom.xml -DskipTests package`
2. **Runtime stage** (`eclipse-temurin:21-jre-jammy`) — copies `authority/target/knoe-auth.jar`, exposes 8080
### 2.2 New Make targets
```
docker-build-auth # builds authority/Dockerfile.app → knoe-auth:latest (local tag)
docker-push-auth # tags + pushes to Artifact Registry as knoe-auth:latest and knoe-auth:<version>
```
`REGISTRY` defaults to `us-west3-docker.pkg.dev/plenary-truck-485623-p7/knoe-system`.
### 2.3 Manifest updates
| File | Lines to change | Old value | New value |
|---|---|---|---|
| `deploy/gcp/gke/knoe-auth-deployment.yaml` | 31, 133, 245 | `knoe-authority:latest` | `knoe-auth:latest` |
| `deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml` | 19, 205 | `myrddin.prole.org:5000/knoe-authority:latest` | `myrddin.prole.org:5000/knoe-auth:latest` |
(k3s line 132 already uses `knoe-auth:latest` — no change needed.)
### 2.4 Doc updates
- `docs/knoe-system.md` §3 "Key manifests" table — remove the "Note: image tag still knoe-authority:latest" caveat
- `docs/knoe-system.md` §7 open work items — remove queue #3 entry
- `docs/TODO.md` — move queue #3 to Done, drop Reality row R6
---
## 3. Out of scope
- Actual `docker push` to Artifact Registry (requires GCP credentials; engineer runs `make docker-push-auth`)
- Renaming the KDC image (`authority/Dockerfile` → stays as-is; it's a separate image)
---
## 4. Definition of done
- [ ] `authority/Dockerfile.app` builds cleanly: `docker build -f authority/Dockerfile.app -t knoe-auth:latest .`
- [ ] `make docker-build-auth` succeeds
- [ ] `git grep knoe-authority -- deploy/` returns nothing
- [ ] `docs/knoe-system.md` no longer references `knoe-authority:latest`
- [ ] `docs/TODO.md` queue #3 moved to Done, R6 dropped from Reality table
---
## 5. Commit shape
```
feat(auth): rename Docker image knoe-authority → knoe-auth
- Add authority/Dockerfile.app (Spring Boot multi-stage build)
- Add make docker-build-auth / docker-push-auth targets
- Update deploy/gcp/gke/knoe-auth-deployment.yaml (3 image refs)
- Update deploy/opentofu/k3s/manifests/knoe/knoe-auth-deployment.yaml (2 image refs)
- Update docs/knoe-system.md, docs/TODO.md
Closes queue #3. Drops Reality drift R6.
```

View File

@ -60,6 +60,8 @@ in TODO" note). When Junie lands a brief:
| [`07-init-cnpg-gke-sa-wiring.md`](07-init-cnpg-gke-sa-wiring.md) | #7 | Wire `cnpg-backup-sa` into CNPG cluster spec; bump operator to v1.29. **Shipped 2026-05-02 (commit `c3fae73`).** |
| [`13-podmonitor-manual-management.md`](13-podmonitor-manual-management.md) | #13 | Migrate off CNPG-deprecated `enablePodMonitor` + `podMonitorRelabelings`. **Shipped 2026-05-02 (commit `c3fae73`).** |
| [`15-remove-dead-dashboard-consumer.md`](15-remove-dead-dashboard-consumer.md) | #15 | Remove dead Kong DASHBOARD consumer + `basicauth_credentials`. **Shipped 2026-05-02 (commit `c3fae73`).** |
| [`03-image-rename-knoe-authority-to-knoe-auth.md`](03-image-rename-knoe-authority-to-knoe-auth.md) | #3 | Rename Docker image `knoe-authority``knoe-auth`; add `Dockerfile.app`; update 2 manifests. **Shipped 2026-05-02.** |
| [`phase2-oidc-gke-deploy.md`](phase2-oidc-gke-deploy.md) | Phase 2 GKE | Enable OIDC in GKE deployment; Kong `/auth` route; `studioIngress`+`knoeAuth` values defaults. **Shipped 2026-05-02.** |
## How a session fires off a batch

View File

@ -0,0 +1,156 @@
# Brief: Phase 2 OIDC — GKE deploy
**Status:** Active
**Filed:** 2026-05-02
**Author:** Junie
**Depends on:** Queue #3 (image rename `knoe-authority``knoe-auth`) — **shipped 2026-05-02**
---
## 1. Context
knoe-auth Phase 2 adds an OIDC provider surface to the existing Kerberos enrollment
service. The k3d dev sandbox shipped 2026-05-02 (commit 93157b0): `application-k3d.yml`
profile, `etc/gen_oidc_signing_key.sh` keypair generator, and all OIDC endpoints
(`/.well-known/openid-configuration`, `/jwks.json`, `/authorize`, `/token`, `/userinfo`)
are exercisable locally.
This brief covers the remaining work to ship Phase 2 in production on GKE:
1. Generate a production RS256 keypair and store it in 1Password (consistent with the
existing secret pattern in `etc/init_knoe_auth.sh`).
2. Wire the signing key into the `knoe-auth-oidc` K8s Secret in `knoe-system`.
3. Enable OIDC in the GKE deployment (`KNOE_AUTH_OIDC_ENABLED=true`, issuer URL, signing key).
4. Add a Kong route so `https://api.knoe.dev/auth` proxies to knoe-auth (the `/auth/v1/*`
routes already go to GoTrue — knoe-auth needs `/auth` without the `/v1` prefix).
5. Rollout and verify externally.
Design reference: [`docs/knoe-auth-phase-2.md`](../knoe-auth-phase-2.md).
---
## 2. Deliverables
### 2.1 Production RS256 keypair
Generate once on the engineer's laptop (same tool as k3d):
```bash
./etc/gen_oidc_signing_key.sh # writes etc/secrets/knoe-auth-oidc-key.{pem,b64}
```
Store the base64 PKCS#8 private key (`knoe-auth-oidc-key.b64`) in 1Password as item
`knoe-auth-oidc-signing-key` field `signing_key`. This follows the same pattern as
`knoe-google-oidc` / `knoe-kdc-master` used by `op_secret()` in `init_knoe_auth.sh`.
> **Do not commit the key.** `etc/secrets/` is gitignored.
### 2.2 `init_knoe_auth.sh` — new `create_oidc_signing_key_secret` function
Add a function (GKE path only) alongside `create_google_oidc_secret()`:
```bash
create_oidc_signing_key_secret() {
if kube -n "$NAMESPACE" get secret knoe-auth-oidc >/dev/null 2>&1; then
info "knoe-auth-oidc already exists — skipping."
return
fi
info "Creating knoe-auth-oidc secret..."
local signing_key
signing_key=$(op_secret "knoe-auth-oidc-signing-key" "signing_key")
kube -n "$NAMESPACE" create secret generic knoe-auth-oidc \
--from-literal=signing-key="$signing_key"
info "knoe-auth-oidc created."
}
```
Call it from the `schema` subcommand block (GKE path), after `create_google_oidc_secret`.
Note: the existing `knoe-auth-oidc-secret.example.yaml` also has `client-id` and
`client-secret` fields (for knoe-auth acting as an OIDC *client*). Those are not needed
for Phase 2 (knoe-auth is the *provider*, not a client). The secret only needs
`signing-key` for now; the example file can stay as-is.
### 2.3 `deploy/gcp/gke/knoe-auth-deployment.yaml` — enable OIDC
Change the three OIDC env vars (already present, currently disabled):
```yaml
- name: KNOE_AUTH_OIDC_ENABLED
value: "true" # was: "${KNOE_AUTH_OIDC_ENABLED:-false}"
- name: KNOE_AUTH_OIDC_ISSUER
value: "https://api.knoe.dev/auth" # was: empty / placeholder
```
The `KNOE_AUTH_OIDC_SIGNING_KEY` env var already reads from `knoe-auth-oidc` secret
(`signing-key` key) — no change needed there.
### 2.4 Kong route for `/auth` → knoe-auth
In `supabase/helm/knoe-supabase/templates/kong/config.yaml`, add a new service +
route block **before** the existing GoTrue `/auth/v1` block (Kong matches longest
prefix first, so order matters):
```yaml
_format_version: "2.1"
services:
# ── knoe-auth OIDC provider (/auth — no /v1 prefix) ─────────────────────
- name: knoe-auth
url: http://knoe-auth.knoe-system.svc.cluster.local:8080
routes:
- name: knoe-auth-oidc
strip_path: false
paths:
- /auth
# Note: /auth/v1/* is handled by GoTrue below; Kong routes by longest
# prefix so /auth/v1/... hits GoTrue, /auth/... hits knoe-auth.
```
Verify with `helm template` that both routes render and that `/auth/v1/` still resolves
to GoTrue.
### 2.5 `docs/knoe-system.md` update
- §2 WIP table: flip Phase 2 OIDC GKE deploy from **Pending****Shipped**.
- §7 Open work items: remove the Phase 2 GKE pending bullet; update pg_oauth note
to say "OIDC issuer now reachable — pg_oauth can resume".
---
## 3. Out of scope
- pg_oauth wiring (separate brief; resumes after this lands).
- Round 1.5 OpenBao transit-key encryption.
- JWKS key rotation (future operational concern).
- knoe-auth acting as an OIDC *client* (the `client-id`/`client-secret` fields in the
oidc secret example are for a future phase).
---
## 4. Definition of done
- [ ] `kubectl -n knoe-system get secret knoe-auth-oidc` exists with `signing-key` field
- [ ] `kubectl rollout status deployment/knoe-auth -n knoe-system` → success
- [ ] `curl -fsS https://api.knoe.dev/auth/.well-known/openid-configuration | jq .issuer`
`"https://api.knoe.dev/auth"`
- [ ] `curl -fsS https://api.knoe.dev/auth/jwks.json | jq '.keys[0].kty'``"RSA"`
- [ ] `curl -fsS https://api.knoe.dev/auth/v1/` still routes to GoTrue (not knoe-auth)
- [ ] `docs/knoe-system.md` Phase 2 GKE status → Shipped
- [ ] `docs/TODO.md` Phase 2 OIDC GKE deploy moved from In progress → Done
---
## 5. Commit shape
```
feat(auth): Phase 2 OIDC GKE deploy
- init_knoe_auth.sh: add create_oidc_signing_key_secret (GKE path)
- deploy/gcp/gke/knoe-auth-deployment.yaml: enable OIDC (ENABLED=true, ISSUER set)
- supabase/helm/knoe-supabase/templates/kong/config.yaml: add /auth route → knoe-auth
- docs/knoe-system.md: Phase 2 GKE status → Shipped
- docs/TODO.md: Phase 2 OIDC GKE deploy → Done
Depends on: queue #3 (knoe-auth image rename, shipped 2026-05-02).
Unblocks: pg_oauth resume.
```

View File

@ -265,6 +265,68 @@ ENDSQL
info "Schema applied."
}
# ── Dev-user seed (k3d only) ─────────────────────────────────────────────────
#
# Creates the `knoe_developer` group role (the production GKE deploy uses it
# too via pg_hba.conf `+knoe_developer` rules; on GKE it was hand-rolled per
# 2026-04-30 onboarding work, never baked into postInitTemplateSQL — see
# docs/db-access.md). Then creates a `chrisfu` LOGIN role with a dev
# password and grants `knoe_developer` to it, so the engineer can connect
# from the host as chrisfu@knoey.com via the port-forward.
#
# Idempotent: re-runs on every `make k3d-knoe-up` and either creates or
# updates the role's password. This makes the rebuild loop deterministic —
# after `down && up`, chrisfu's password is always `chrisfu-dev`.
seed_dev_users_k3d() {
info "Seeding dev users (knoe_developer + chrisfu) for k3d ..."
local tmpfile
tmpfile=$(mktemp /tmp/knoe_auth_seed_XXXX.sql)
cat > "$tmpfile" <<'ENDSQL'
-- knoe_developer group role: R/W on knoe + public, R/O on auth/storage/extensions.
-- Mirrors the GKE production layout (docs/db-access.md). NOLOGIN — group only.
DO $do$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'knoe_developer') THEN
CREATE ROLE knoe_developer NOLOGIN;
END IF;
END $do$;
-- knoe + public — full R/W
GRANT USAGE ON SCHEMA knoe TO knoe_developer;
GRANT USAGE ON SCHEMA public TO knoe_developer;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA knoe TO knoe_developer;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO knoe_developer;
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA knoe TO knoe_developer;
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO knoe_developer;
ALTER DEFAULT PRIVILEGES IN SCHEMA knoe GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO knoe_developer;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO knoe_developer;
ALTER DEFAULT PRIVILEGES IN SCHEMA knoe GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO knoe_developer;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO knoe_developer;
-- Per-engineer LOGIN role for chrisfu (local-dev only; password is fixed dev value).
-- Creates if missing, otherwise resets the password — guarantees the rebuild loop
-- always produces the same credentials.
DO $do$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'chrisfu') THEN
CREATE ROLE chrisfu LOGIN INHERIT PASSWORD 'chrisfu-dev';
ELSE
ALTER ROLE chrisfu WITH LOGIN INHERIT PASSWORD 'chrisfu-dev';
END IF;
END $do$;
GRANT knoe_developer TO chrisfu;
-- Sanity check (visible in psql_file output).
SELECT 'chrisfu' AS role,
pg_has_role('chrisfu', 'knoe_developer', 'MEMBER') AS is_developer;
ENDSQL
psql_file "$tmpfile"
rm -f "$tmpfile"
info "Dev-user seed applied (chrisfu / chrisfu-dev, member of knoe_developer)."
}
# ── KDC secrets ──────────────────────────────────────────────────────────────
create_kdc_secrets() {
@ -480,12 +542,19 @@ cmd_initialize_k3d() {
# 5. Schema (via kubectl exec into CNPG primary)
run_schema
# 6. Dev-user seed: knoe_developer group + chrisfu role (k3d-only).
seed_dev_users_k3d
info ""
info "=== k3d knoe-auth stack is ready ==="
info "Run: make k3d-knoe-pf"
info "Then in another terminal:"
info " export KRB5_CONFIG=\$PWD/etc/krb5.local.conf"
info " mvn -pl authority spring-boot:run"
info " export KNOE_AUTH_OIDC_SIGNING_KEY=\$(cat etc/secrets/knoe-auth-oidc-key.b64)"
info " mvn -pl authority spring-boot:run -Dspring-boot.run.profiles=k3d"
info ""
info "Dev DB access (from host, with port-forward up):"
info " PGPASSWORD=chrisfu-dev psql -h localhost -U chrisfu -d knoe-db"
info ""
show_status
}

View File

@ -90,6 +90,18 @@ data:
add:
headers:
- "Location:mailto:support@knoe.dev"
{{- if .Values.knoeAuth.enabled }}
- name: knoe-auth
_comment: "knoe-auth OIDC provider: /auth (no /v1 prefix) -> knoe-auth.knoe-system"
url: http://knoe-auth.knoe-system.svc.cluster.local:8080
routes:
- name: knoe-auth-all
strip_path: false
paths:
- /auth
plugins:
- name: cors
{{- end }}
{{- if .Values.deployment.auth.enabled }}
- name: auth-v1-open
url: http://{{ include "supabase.auth.fullname" . }}:{{ .Values.service.auth.port }}/verify

View File

@ -1098,3 +1098,18 @@ bigQuery:
projectId: google-project-id
projectNumber: google-project-number
gcloudJson: ""
## Studio Ingress — defaults satisfy template guards in templates/studio/ingress.yaml.
## Set enabled: true and populate hosts/tls for a real deploy.
studioIngress:
enabled: false
className: ""
annotations: {}
hosts: []
tls: []
## knoe-auth OIDC provider Kong route — proxies /auth (no /v1 prefix) to
## knoe-auth.knoe-system.svc.cluster.local:8080.
## Flip enabled: true once knoe-auth is deployed and the signing key secret exists.
knoeAuth:
enabled: false