feat(knoe-auth): Phase 2 OIDC sandbox in k3d dev loop

Completes the Phase 2 OIDC laptop dev path. Source for the OIDC surface
(discovery, authorize, token, userinfo, JWKS controllers + signing /
session services) landed on `main` via the merge that brought
claude/crazy-bose-fec256 back. This commit makes Phase 2 actually
exercisable in the k3d dev loop without any GKE deploy.

What's new:

  authority/src/main/resources/application-k3d.yml
    Spring profile activated by `-Dspring-boot.run.profiles=k3d`.
    Enables OIDC (knoe.oidc.enabled=true), points the issuer at
    `http://localhost:8080`, sets Kerberos realm to KNOE.LOCAL, and
    aligns the datasource with the port-forwarded localhost:5432 DB.

  etc/gen_oidc_signing_key.sh  (executable)
    Idempotent RS256 PKCS#8 keypair generator. Outputs:
      etc/secrets/knoe-auth-oidc-key.pem  (PEM)
      etc/secrets/knoe-auth-oidc-key.b64  (single-line base64 of DER —
        directly consumable as KNOE_AUTH_OIDC_SIGNING_KEY by
        OidcTokenService.init())
    `etc/secrets/` is already gitignored. Set FORCE=1 to rotate.

What's wired:

  scripts/k3d-knoe-up.sh
    New §7 calls etc/gen_oidc_signing_key.sh after schema seed. Keypair
    persists across `make k3d-knoe-down && make k3d-knoe-up` cycles.

  scripts/k3d-knoe-pf.sh
    Output now includes the KNOE_AUTH_OIDC_SIGNING_KEY export line, the
    full `mvn spring-boot:run` invocation with -Dspring-boot.run.profiles=k3d,
    and the three OIDC endpoints to curl-test.

What's documented:

  docs/local-dev-knoe-auth.md
    "Daily loop" Terminal B: now exports KNOE_AUTH_OIDC_SIGNING_KEY,
    runs with `-Dspring-boot.run.profiles=k3d`, and the verify section
    includes /jwks.json. IntelliJ run config: adds Active Profiles: k3d
    and a note about pasting the b64 directly (no shell expansion in
    the env-var field).

  docs/knoe-system.md
    Phase 2 status row split: "k3d setup" → Shipped, "GKE deploy" →
    Pending. The "Open work items" Phase 2 entry rewritten to flag
    that the GKE deploy is the remaining thread (gated on queue #3 for
    the image rebuild as `knoe-auth:latest`).

  docs/TODO.md
    Promoted "Phase 2 OIDC provider — GKE deploy" into §In progress
    (replacing the empty "(none)" placeholder). Done section updated
    with two entries: the k3d Phase 1 dev loop (Junie's c3...) and
    this Phase 2 OIDC k3d sandbox.

End-to-end loop the engineer can run:

  make k3d-knoe-up                                     # one-time, ~5 min
  make k3d-knoe-pf &                                   # port-forwards
  export KRB5_CONFIG=$PWD/etc/krb5.local.conf
  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
  # then:
  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"

Verified locally: keypair generator round-trips through openssl pkey -inform DER
(produces valid 2048-bit RSA keys); idempotent (existing key kept by default,
FORCE=1 rotates); bash -n clean on all 5 touched scripts.

Out of scope (TODO §In progress captures it):
  - GKE deploy of Phase 2 (image rebuild + K8s Secret + deployment env vars)
  - SPNEGO E2E from host browsers (k3d-mirror Phase 2)
  - knoe-auth-as-pod in k3d (k3d-mirror Phase 3)
  - OidcCodeService DB persistence (separate track)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-05-02 11:55:50 -07:00
parent 5d3600845d
commit 93157b0a86
7 changed files with 200 additions and 17 deletions

View File

@ -0,0 +1,68 @@
# Spring profile: k3d
# Activated by `-Dspring-boot.run.profiles=k3d` (see docs/local-dev-knoe-auth.md).
#
# Overrides for application.yml when knoe-auth runs on the laptop against
# the k3d-resident CNPG + KDC stack (see docs/plans/k3d-gke-mirror.md).
# Production / GKE values are NOT here — only the dev-loop adjustments.
#
# Phase 2 OIDC provider is ENABLED in this profile. The signing key is
# read from KNOE_AUTH_OIDC_SIGNING_KEY (base64 PKCS#8 RS256 private key);
# generate one with `bash etc/gen_oidc_signing_key.sh` (idempotent — runs
# automatically as part of `make k3d-knoe-up`).
server:
port: 8080
knoe:
auth:
enabled: true
cookieDomain: localhost
sessionSecret: ${KNOE_AUTH_SESSION_SECRET:k3d-dev-session-secret-change-me-needs-32-chars-min}
formEnabled: true
# Bare usernames granted admin group membership for local dev.
adminPrincipals:
- admin
- developer
kerberos:
realm: KNOE.LOCAL
servicePrincipal: ${KNOE_KERBEROS_SERVICE_PRINCIPAL:HTTP/localhost@KNOE.LOCAL}
keytabPath: ${KNOE_KERBEROS_KEYTAB_PATH:}
# The krb5.conf is set via JVM arg in `mvn spring-boot:run`:
# -Djava.security.krb5.conf=etc/krb5.local.conf
# See docs/local-dev-knoe-auth.md.
# ── Phase 2 OIDC provider ──────────────────────────────────────────────
# Local issuer is http://localhost:8080. /.well-known/openid-configuration,
# /jwks.json, /authorize, /token, /userinfo are all reachable once knoe-auth
# is running. signingKey: leave blank to let OidcTokenService generate an
# ephemeral key on each startup (tokens don't survive restart but flow works);
# set KNOE_AUTH_OIDC_SIGNING_KEY for a persistent key (gen_oidc_signing_key.sh
# produces it).
oidc:
enabled: true
issuer: ${KNOE_AUTH_OIDC_ISSUER:http://localhost:8080}
signingKey: ${KNOE_AUTH_OIDC_SIGNING_KEY:}
clientId: ${KNOE_AUTH_OIDC_CLIENT_ID:knoe-local}
clientSecret: ${KNOE_AUTH_OIDC_CLIENT_SECRET:}
enroll:
baseUrl: ${KNOE_AUTH_BASE_URL:http://localhost:8080}
# ── Google upstream (optional locally) ─────────────────────────────────
# Phase 2 OIDC dev primarily exercises LOCAL token issuance + JWKS, not
# the Google federation path. If you want to test the upstream Google
# bounce, set GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET in your shell env
# (and register http://localhost:8080/auth/enroll/google-callback as an
# authorized redirect URI on the dev OAuth client).
google:
clientId: ${GOOGLE_CLIENT_ID:}
clientSecret: ${GOOGLE_CLIENT_SECRET:}
redirectUri: ${KNOE_AUTH_BASE_URL:http://localhost:8080}/auth/enroll/google-callback
spring:
datasource:
# Local k3d port-forward (see scripts/k3d-knoe-pf.sh)
url: ${KNOE_DB_URL:jdbc:postgresql://localhost:5432/knoe-db}
username: ${KNOE_DB_USER:postgres}
password: ${KNOE_DB_PASSWORD:}

View File

@ -13,10 +13,10 @@ The Kanban "Now" section at top is the only place this doc imposes structure. Ev
## Now (Kanban) ## Now (Kanban)
### In progress ### In progress
*(none — pull from the ranked queue below)* - **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).
### Paused ### Paused
- **Phase 2: 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. Phase 2 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 → Google Bearer token). Resumes once knoe-auth Phase 2 OIDC dev has somewhere to run locally (k3d dev loop now in place). - **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).
### Up next ### Up next
*(empty — pull from the ranked queue below)* *(empty — pull from the ranked queue below)*
@ -94,6 +94,10 @@ Items where docs describe an intended state that doesn't match live state. Each
- 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-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 — **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).
- 2026-05-02 — **Garage decommissioned on app cluster `knoe-dev-0`** (queue #4 closure). Reframed mid-flight: the original "verify with `garage repair --yes blocks`" premise didn't match reality. Live garage was unconfigured (`garage status` → NO ROLE ASSIGNED, `garage layout show` → version 0, 21d of "Ring not yet ready" warnings); cross-cluster sweep confirmed nothing referenced its S3 endpoint anymore (CNPG backups moved to GCS in 2026-04-29 migration). Removed the entire workload from `knoe-system` namespace mirroring the 2026-04-29 DB-cluster removal: `sts/garage`, `svc/garage`, `cm/garage-config`, `secret/garage-secrets`, `pvc/data-garage-0` (29 Gi, was on `garage-hdd` storage class), and the `garage-hdd` StorageClass itself. PV had `reclaim=Retain` so the underlying GCE pd-standard disk (`pvc-656e1936-…` in `us-west3-c`) persisted past the kubectl-side cleanup; reclaimed via `gcloud compute disks delete`. Note: app cluster only had a ClusterIP service — no `garage-s3-ilb` LoadBalancer (DB cluster had that). Source manifests remain at `k8s/knoe/garage-*.yaml`, `deploy/opentofu/k3s/manifests/knoe/garage-*.yaml`, and `etc/init_garage_store.sh` for the k3d/k3s/min deploy modes (and any future GKE deploy that wants to wire garage back). Whether the GKE deploy mode should stop applying garage altogether is a separate decision. - 2026-05-02 — **Garage decommissioned on app cluster `knoe-dev-0`** (queue #4 closure). Reframed mid-flight: the original "verify with `garage repair --yes blocks`" premise didn't match reality. Live garage was unconfigured (`garage status` → NO ROLE ASSIGNED, `garage layout show` → version 0, 21d of "Ring not yet ready" warnings); cross-cluster sweep confirmed nothing referenced its S3 endpoint anymore (CNPG backups moved to GCS in 2026-04-29 migration). Removed the entire workload from `knoe-system` namespace mirroring the 2026-04-29 DB-cluster removal: `sts/garage`, `svc/garage`, `cm/garage-config`, `secret/garage-secrets`, `pvc/data-garage-0` (29 Gi, was on `garage-hdd` storage class), and the `garage-hdd` StorageClass itself. PV had `reclaim=Retain` so the underlying GCE pd-standard disk (`pvc-656e1936-…` in `us-west3-c`) persisted past the kubectl-side cleanup; reclaimed via `gcloud compute disks delete`. Note: app cluster only had a ClusterIP service — no `garage-s3-ilb` LoadBalancer (DB cluster had that). Source manifests remain at `k8s/knoe/garage-*.yaml`, `deploy/opentofu/k3s/manifests/knoe/garage-*.yaml`, and `etc/init_garage_store.sh` for the k3d/k3s/min deploy modes (and any future GKE deploy that wants to wire garage back). Whether the GKE deploy mode should stop applying garage altogether is a separate decision.
- 2026-05-02 — **Phase B orphan cleanup** (queue #5). Five resources deleted across both clusters. App cluster (`knoe-dev-0`): `gitlab/gitlab-migrations-58a3e27-97-23-n72j8` (Failed/Evicted, 11d stale; the parallel `…-nxzj5` Completed pod kept for log preservation), and three Released PVs from the 2026-04-28 monitoring stack rebuild — `pvc-42e8545b-…` (10Gi, claim=monitoring/storage-kps-grafana-0), `pvc-86422179-…` (30Gi, claim=monitoring/prometheus-…-prometheus-0), `pvc-e2c29f7a-…` (5Gi, claim=monitoring/alertmanager-…-alertmanager-0); all on `standard-hdd`, all 4d old, all matching the expected claimRef pattern, no surprises. DB cluster (`knoe-dev-cnpg-0`): `kube-system/prole-supabase-dirprep` Job (FailureTarget, 22d stale, prole-era). Post-state: `kubectl get pv` shows 0 Released; gitlab namespace has only the Completed migration pod; DB-cluster kube-system has no prole-* artifacts. No source-side changes (kubectl-only ops). - 2026-05-02 — **Phase B orphan cleanup** (queue #5). Five resources deleted across both clusters. App cluster (`knoe-dev-0`): `gitlab/gitlab-migrations-58a3e27-97-23-n72j8` (Failed/Evicted, 11d stale; the parallel `…-nxzj5` Completed pod kept for log preservation), and three Released PVs from the 2026-04-28 monitoring stack rebuild — `pvc-42e8545b-…` (10Gi, claim=monitoring/storage-kps-grafana-0), `pvc-86422179-…` (30Gi, claim=monitoring/prometheus-…-prometheus-0), `pvc-e2c29f7a-…` (5Gi, claim=monitoring/alertmanager-…-alertmanager-0); all on `standard-hdd`, all 4d old, all matching the expected claimRef pattern, no surprises. DB cluster (`knoe-dev-cnpg-0`): `kube-system/prole-supabase-dirprep` Job (FailureTarget, 22d stale, prole-era). Post-state: `kubectl get pv` shows 0 Released; gitlab namespace has only the Completed migration pod; DB-cluster kube-system has no prole-* artifacts. No source-side changes (kubectl-only ops).

View File

@ -37,8 +37,9 @@ external Google Workspace — any verified Google account works once invited.
|---|---|---| |---|---|---|
| Round 1 Kerberos enrollment | **Shipped** | Operational on GKE | | Round 1 Kerberos enrollment | **Shipped** | Operational on GKE |
| k3d dev loop (Phase 1 brief) | **Shipped** | `make k3d-knoe-up` | | k3d dev loop (Phase 1 brief) | **Shipped** | `make k3d-knoe-up` |
| Phase 2 OIDC provider | **Planned** | Resumes once k3d loop is in place — see TODO §Paused | | 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". |
| pg_oauth (PG18 OAUTHBEARER) | **Paused** | Needs Phase 2 OIDC issuer first | | 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 |
| Round 1.5 OpenBao transit-key encryption | **Later** | `UserProvisioningService.java:84` TODO | | Round 1.5 OpenBao transit-key encryption | **Later** | `UserProvisioningService.java:84` TODO |
--- ---
@ -284,9 +285,14 @@ k3d modes.
Maven artifact is already `knoe-auth.jar`; manifests still pull Maven artifact is already `knoe-auth.jar`; manifests still pull
`knoe-authority:latest`. Needs image rebuild + registry push. `knoe-authority:latest`. Needs image rebuild + registry push.
- **Phase 2 OIDC provider** — Paused. Resumes once the k3d dev loop - **Phase 2 OIDC provider** — k3d dev sandbox **shipped 2026-05-02**.
(now in place) gives the OIDC implementation somewhere to run locally. `application-k3d.yml` enables the OIDC surface; `etc/gen_oidc_signing_key.sh`
Design doc: [`docs/knoe-auth-phase-2.md`](knoe-auth-phase-2.md). 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)** — Paused. Replaces SCRAM with PG18
native OAUTHBEARER; wires into `install.sh` / `supabase/deploy.sh`. native OAUTHBEARER; wires into `install.sh` / `supabase/deploy.sh`.

View File

@ -86,22 +86,38 @@ export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=re
export SPRING_DATASOURCE_USERNAME=knoe export SPRING_DATASOURCE_USERNAME=knoe
export SPRING_DATASOURCE_PASSWORD=knoe-dev-password export SPRING_DATASOURCE_PASSWORD=knoe-dev-password
mvn -pl authority spring-boot:run # 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 ### Verify the loop
```bash ```bash
curl http://localhost:8080/health curl http://localhost:8080/health
# → ok # → {"status":"ok"}
curl http://localhost:8080/.well-known/openid-configuration | jq .issuer curl -s http://localhost:8080/.well-known/openid-configuration | jq .issuer
# → "http://localhost:5000" # → "http://localhost:8080"
curl -s http://localhost:8080/jwks.json | jq '.keys[0].kty'
# → "RSA"
``` ```
> **Note:** The issuer URL is configured in `authority/src/main/resources/application.properties` If `/.well-known/openid-configuration` returns 404, OIDC isn't enabled —
> (or `application-local.properties`). For local dev it defaults to `http://localhost:5000` verify you started with `-Dspring-boot.run.profiles=k3d` (or that the
> or `http://localhost:8080` depending on your profile. Check the startup log for the actual value. `k3d` profile is active in your IntelliJ run config).
--- ---
@ -143,6 +159,8 @@ klist
3. Set: 3. Set:
- **Module:** `authority` - **Module:** `authority`
- **Main class:** `dev.knoe.auth.KnoeAuthApplication` - **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: 4. Under **Environment variables**, add:
``` ```
@ -150,8 +168,13 @@ KRB5_CONFIG=/path/to/knoe-db/etc/krb5.local.conf
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=require SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=require
SPRING_DATASOURCE_USERNAME=knoe SPRING_DATASOURCE_USERNAME=knoe
SPRING_DATASOURCE_PASSWORD=knoe-dev-password 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**. 5. Click **OK**, then **Run**.
Spring DevTools is active — saving a `.java` file triggers a hot reload automatically. Spring DevTools is active — saving a `.java` file triggers a hot reload automatically.

65
etc/gen_oidc_signing_key.sh Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env bash
# gen_oidc_signing_key.sh
#
# Idempotent RS256 keypair generator for knoe-auth Phase 2 OIDC provider
# in local k3d / TDD dev mode. Produces:
#
# etc/secrets/knoe-auth-oidc-key.pem PKCS#8 PEM of the private key
# etc/secrets/knoe-auth-oidc-key.b64 single-line base64 of the same key,
# suitable for KNOE_AUTH_OIDC_SIGNING_KEY
#
# Both files live under etc/secrets/ which is gitignored. If the .b64 file
# already exists, the script is a no-op (so you keep stable token signatures
# across restarts).
#
# Usage:
# bash etc/gen_oidc_signing_key.sh # generate if missing
# FORCE=1 bash etc/gen_oidc_signing_key.sh # rotate
#
# Env consumption (in your shell or the spring-boot:run command):
# export KNOE_AUTH_OIDC_SIGNING_KEY=$(cat etc/secrets/knoe-auth-oidc-key.b64)
#
# Production (GKE) provides this via a K8s Secret + Workload Identity / OpenBao;
# this script is for laptop dev only.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SECRETS_DIR="${REPO_ROOT}/etc/secrets"
PEM_PATH="${SECRETS_DIR}/knoe-auth-oidc-key.pem"
B64_PATH="${SECRETS_DIR}/knoe-auth-oidc-key.b64"
mkdir -p "${SECRETS_DIR}"
chmod 700 "${SECRETS_DIR}" 2>/dev/null || true
if [[ -f "${B64_PATH}" && "${FORCE:-0}" != "1" ]]; then
echo "[gen_oidc_signing_key] already present at ${B64_PATH} — keeping. Pass FORCE=1 to rotate."
exit 0
fi
if ! command -v openssl >/dev/null 2>&1; then
echo "[gen_oidc_signing_key] ERROR: openssl not found on PATH." >&2
exit 2
fi
echo "[gen_oidc_signing_key] generating RSA-2048 PKCS#8 keypair ..."
# `openssl genpkey` writes a PEM-wrapped PKCS#8 private key. Spring's
# OidcTokenService.init() reads KNOE_AUTH_OIDC_SIGNING_KEY as base64 of the
# raw DER PKCS#8 bytes — i.e., the PEM with header/footer stripped and no
# whitespace. We produce both forms.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out "${PEM_PATH}" 2>/dev/null
chmod 600 "${PEM_PATH}"
# Strip PEM headers/footers and newlines to get the single-line base64 of the
# DER PKCS#8 bytes that the Java `Base64.getDecoder().decode(signingKey)` call
# expects (per OidcTokenService.java).
grep -v -- '-----' "${PEM_PATH}" | tr -d '\n' > "${B64_PATH}"
chmod 600 "${B64_PATH}"
echo "[gen_oidc_signing_key] wrote ${PEM_PATH}"
echo "[gen_oidc_signing_key] wrote ${B64_PATH} ($(wc -c < "${B64_PATH}") bytes)"
echo
echo "Use it in your shell:"
echo " export KNOE_AUTH_OIDC_SIGNING_KEY=\$(cat ${B64_PATH})"

View File

@ -56,6 +56,18 @@ echo "│ export KNOE_DB_PASSWORD=${DB_PASSWORD} │"
echo "│ export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=require │" echo "│ export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=require │"
echo "│ export SPRING_DATASOURCE_USERNAME=knoe │" echo "│ export SPRING_DATASOURCE_USERNAME=knoe │"
echo "│ export SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}" echo "│ export SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}"
echo "│ # Phase 2 OIDC signing key (from etc/gen_oidc_signing_key.sh):│"
echo "│ export KNOE_AUTH_OIDC_SIGNING_KEY=\$(cat etc/secrets/knoe-auth-oidc-key.b64)│"
echo "├─────────────────────────────────────────────────────────────────┤"
echo "│ Run knoe-auth with the k3d profile (enables OIDC + dev defaults):│"
echo "│ mvn -pl authority spring-boot:run \\ │"
echo "│ -Dspring-boot.run.jvmArguments=\"-Djava.security.krb5.conf=\$PWD/etc/krb5.local.conf\" \\│"
echo "│ -Dspring-boot.run.profiles=k3d │"
echo "├─────────────────────────────────────────────────────────────────┤"
echo "│ Phase 2 OIDC endpoints (after knoe-auth is up): │"
echo "│ GET http://localhost:8080/.well-known/openid-configuration │"
echo "│ GET http://localhost:8080/jwks.json │"
echo "│ POST http://localhost:8080/token │"
echo "├─────────────────────────────────────────────────────────────────┤" echo "├─────────────────────────────────────────────────────────────────┤"
echo "│ Press ^C to stop all port-forwards │" echo "│ Press ^C to stop all port-forwards │"
echo "└─────────────────────────────────────────────────────────────────┘" echo "└─────────────────────────────────────────────────────────────────┘"

View File

@ -15,6 +15,8 @@
# 4. Waits for CNPG cluster to be healthy # 4. Waits for CNPG cluster to be healthy
# 5. Applies KDC manifests (configmap, pvc, deployment, service) # 5. Applies KDC manifests (configmap, pvc, deployment, service)
# 6. Runs etc/init_knoe_auth.sh schema --mode k3d to seed knoe.* tables # 6. Runs etc/init_knoe_auth.sh schema --mode k3d to seed knoe.* tables
# 7. Generates a persistent RS256 keypair for Phase 2 OIDC signing
# (etc/gen_oidc_signing_key.sh — idempotent; keeps existing key if present)
set -euo pipefail set -euo pipefail
@ -152,11 +154,14 @@ log "Provisioning KDC (realm KNOE.LOCAL)..."
KNOE_MODE=k3d APP_CLUSTER_KUBECONTEXT="$CTX" \ KNOE_MODE=k3d APP_CLUSTER_KUBECONTEXT="$CTX" \
"$REPO_ROOT/etc/init_knoe_auth.sh" initialize --mode k3d --context "$CTX" "$REPO_ROOT/etc/init_knoe_auth.sh" initialize --mode k3d --context "$CTX"
# ── 7. Phase 2 OIDC signing key (laptop-side) ────────────────────────────────
log "Generating RS256 signing key for OIDC (idempotent) ..."
bash "$REPO_ROOT/etc/gen_oidc_signing_key.sh" || die "OIDC keypair generation failed"
log "" log ""
log "=== k3d-knoe is up ===" log "=== k3d-knoe is up ==="
log " CNPG cluster: kubectl --context=$CTX -n $DB_NS get cluster knoe-db" log " CNPG cluster: kubectl --context=$CTX -n $DB_NS get cluster knoe-db"
log " KDC: kubectl --context=$CTX -n $SYS_NS get deploy knoe-kdc" log " KDC: kubectl --context=$CTX -n $SYS_NS get deploy knoe-kdc"
log "" log ""
log "Next: make k3d-knoe-pf (opens port-forwards)" log "Next: make k3d-knoe-pf (opens port-forwards + prints env vars to export)"
log "Then: export KRB5_CONFIG=\$PWD/etc/krb5.local.conf" log "Then: see docs/local-dev-knoe-auth.md for the daily loop"
log " mvn -pl authority spring-boot:run"