From 93157b0a8639b3695b923133fa028fc06dc06375 Mon Sep 17 00:00:00 2001 From: chrisfu Date: Sat, 2 May 2026 11:55:50 -0700 Subject: [PATCH] feat(knoe-auth): Phase 2 OIDC sandbox in k3d dev loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../src/main/resources/application-k3d.yml | 68 +++++++++++++++++++ docs/TODO.md | 8 ++- docs/knoe-system.md | 16 +++-- docs/local-dev-knoe-auth.md | 37 ++++++++-- etc/gen_oidc_signing_key.sh | 65 ++++++++++++++++++ scripts/k3d-knoe-pf.sh | 12 ++++ scripts/k3d-knoe-up.sh | 11 ++- 7 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 authority/src/main/resources/application-k3d.yml create mode 100755 etc/gen_oidc_signing_key.sh diff --git a/authority/src/main/resources/application-k3d.yml b/authority/src/main/resources/application-k3d.yml new file mode 100644 index 0000000..d9e1185 --- /dev/null +++ b/authority/src/main/resources/application-k3d.yml @@ -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:} diff --git a/docs/TODO.md b/docs/TODO.md index e1a053f..c532a46 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -13,10 +13,10 @@ The Kanban "Now" section at top is the only place this doc imposes structure. Ev ## Now (Kanban) ### 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 -- **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 *(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 — **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 — **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). diff --git a/docs/knoe-system.md b/docs/knoe-system.md index 15073cd..7c506d4 100644 --- a/docs/knoe-system.md +++ b/docs/knoe-system.md @@ -37,8 +37,9 @@ 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 | **Planned** | Resumes once k3d loop is in place — see TODO §Paused | -| pg_oauth (PG18 OAUTHBEARER) | **Paused** | Needs Phase 2 OIDC issuer first | +| 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 | | 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 `knoe-authority:latest`. Needs image rebuild + registry push. -- **Phase 2 OIDC provider** — Paused. Resumes once the k3d dev loop - (now in place) gives the OIDC implementation somewhere to run locally. - Design doc: [`docs/knoe-auth-phase-2.md`](knoe-auth-phase-2.md). +- **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 native OAUTHBEARER; wires into `install.sh` / `supabase/deploy.sh`. diff --git a/docs/local-dev-knoe-auth.md b/docs/local-dev-knoe-auth.md index ad7601d..7fc0288 100644 --- a/docs/local-dev-knoe-auth.md +++ b/docs/local-dev-knoe-auth.md @@ -86,22 +86,38 @@ export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/knoe-db?sslmode=re export SPRING_DATASOURCE_USERNAME=knoe 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 ```bash curl http://localhost:8080/health -# → ok +# → {"status":"ok"} -curl http://localhost:8080/.well-known/openid-configuration | jq .issuer -# → "http://localhost:5000" +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" ``` -> **Note:** The issuer URL is configured in `authority/src/main/resources/application.properties` -> (or `application-local.properties`). For local dev it defaults to `http://localhost:5000` -> or `http://localhost:8080` depending on your profile. Check the startup log for the actual value. +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). --- @@ -143,6 +159,8 @@ klist 3. Set: - **Module:** `authority` - **Main class:** `dev.knoe.auth.KnoeAuthApplication` + - **Active profiles:** `k3d` *(enables Phase 2 OIDC + dev-only defaults)* + - **VM options:** `-Djava.security.krb5.conf=$PROJECT_DIR$/etc/krb5.local.conf` 4. Under **Environment variables**, add: ``` @@ -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_USERNAME=knoe SPRING_DATASOURCE_PASSWORD=knoe-dev-password +KNOE_AUTH_OIDC_SIGNING_KEY= ``` + IntelliJ's environment-variable field doesn't expand `$(cat …)` shell + substitution, so paste the base64 string directly (it's one line, ~1.6 KB). + Or use a `.env` plugin like *EnvFile* and point it at the `.b64` file. + 5. Click **OK**, then **Run**. Spring DevTools is active — saving a `.java` file triggers a hot reload automatically. diff --git a/etc/gen_oidc_signing_key.sh b/etc/gen_oidc_signing_key.sh new file mode 100755 index 0000000..ffaafcb --- /dev/null +++ b/etc/gen_oidc_signing_key.sh @@ -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})" diff --git a/scripts/k3d-knoe-pf.sh b/scripts/k3d-knoe-pf.sh index b528cd5..54739e8 100755 --- a/scripts/k3d-knoe-pf.sh +++ b/scripts/k3d-knoe-pf.sh @@ -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_USERNAME=knoe │" 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 "│ Press ^C to stop all port-forwards │" echo "└─────────────────────────────────────────────────────────────────┘" diff --git a/scripts/k3d-knoe-up.sh b/scripts/k3d-knoe-up.sh index d23e582..d7243d0 100755 --- a/scripts/k3d-knoe-up.sh +++ b/scripts/k3d-knoe-up.sh @@ -15,6 +15,8 @@ # 4. Waits for CNPG cluster to be healthy # 5. Applies KDC manifests (configmap, pvc, deployment, service) # 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 @@ -152,11 +154,14 @@ log "Provisioning KDC (realm KNOE.LOCAL)..." KNOE_MODE=k3d APP_CLUSTER_KUBECONTEXT="$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 "=== k3d-knoe is up ===" 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 "" -log "Next: make k3d-knoe-pf (opens port-forwards)" -log "Then: export KRB5_CONFIG=\$PWD/etc/krb5.local.conf" -log " mvn -pl authority spring-boot:run" +log "Next: make k3d-knoe-pf (opens port-forwards + prints env vars to export)" +log "Then: see docs/local-dev-knoe-auth.md for the daily loop"