docs/completed/ — new directory; 9 shipped Junie briefs moved from docs/plans/junie/ (02-k3s-prole-rename, 03-image-rename, 06-patch-garage, 07-init-cnpg-gke-sa-wiring, 13-podmonitor, 15-remove-dead-dashboard, k3d-knoe-auth-dev-loop, phase2-oidc-gke-deploy, todo-1-cfg-save-path-bug). docs/completed/README.md summarises all shipped work with dates/refs. docs/plans/junie/README.md — updated to 2026-05-23; active/pending tables reflect current state; shipped section now points to docs/completed/. conf/service/knoe.cfg — new unattended deploy config for the service/k3s environment (used by ./deploy.sh). Co-authored-by: Junie <junie@jetbrains.com>
13 KiB
Junie brief — Phase 1 of k3d-mirror-of-GKE: laptop dev loop for knoe-auth
Self-contained brief. Reference: parent architectural plan at
../k3d-gke-mirror.md. Read that for the "why" and the multi-phase shape. This brief delivers Phase 1 only.
1. Why
knoe-auth Phase 2 OIDC code is on main but no fast inner-loop
exists for iterating on it. Hitting the GKE cluster on every change
is slow. Running unit tests with testcontainers covers a lot but
doesn't exercise the real DB schema, Kerberos KDC, or the actual HTTP
surface against a Postgres+JDBC stack.
This brief wires up a laptop-resident dev loop: bring up CNPG +
KDC in k3d, port-forward 5432/88/464 to localhost, and let the
engineer run knoe-auth from their IDE/mvn spring-boot:run against
those local ports. Edit Java → re-run → see the change at
http://localhost:8080, with a real PostgreSQL and a real Kerberos
backing it.
knoe-auth itself stays on the host. Image-build-and-load into k3d is a separate later phase (and is in fact mostly orthogonal to this work — Phase 1 finishes when the host loop is solid).
2. What you produce
Concrete deliverables, all in one MR:
2.1 — KDC manifest set for k3d
New files (mirroring the auth-side filename pattern under k8s/knoe/):
k8s/knoe/knoe-kdc-deployment.yaml— single-replicaDeploymentrunningkrb5kdc+kadmind. RealmKNOE.LOCAL. Use a Debian-derived MIT Kerberos image; whatever the GKE sidecar image uses is fine to reuse. Setrealm = KNOE.LOCALexplicitly — do NOT inherit from the existingKNOE.DEVGKE config.k8s/knoe/knoe-kdc-service.yaml— ClusterIP service exposing 88 (TCP+UDP) and 464 (TCP+UDP — kpasswd).k8s/knoe/knoe-kdc-configmap.yaml—krb5.conf+kdc.confwith realmKNOE.LOCALand a[realms] KNOE.LOCAL = { kdc = knoe-kdc:88 ... }block.k8s/knoe/knoe-kdc-pvc.yaml— small PVC (1 Gi) for the KDC database/var/lib/krb5kdc/principal. Local-path provisioner (k3d default).k8s/knoe/knoe-kdc-init-job.yaml— one-shotJobthat runskdb5_util create -s -P <stash>(database init) and thenkadmin.local addprinc -randkey admin/admin@KNOE.LOCALplus a developer principal. Idempotent (skip create if DB exists). Runs once on first apply; subsequent apply is a no-op via a guard inside the script.
Reference existing GKE kdc.conf and krb5.conf shapes from
deploy/gcp/gke/knoe-kdc-configmap.yaml.
Substitute the realm, drop GCE-specific bits.
2.2 — Init-script entry point for k3d
Either:
- (preferred) Extend
etc/init_knoe_auth.shwith a--mode k3dflag that swaps theAPP_CTXdefault tok3d-knoe(or whateverkubectl config current-contextreturns from the k3d-up flow), swaps the realm toKNOE.LOCAL, and skips the GCP/Workload Identity steps (no GCP project on a laptop). Sameschema/invite/statussubcommands work. - (or) Write a thin
etc/init_knoe_auth_k3d.shthat wraps the existing one with the right env vars set.
Pick whichever results in less duplication. The existing script's
schema subcommand already runs the SQL via kubectl exec on the
CNPG primary — that path works in k3d unchanged.
2.3 — Make targets
Three new targets in the top-level Makefile:
make k3d-knoe-up— provisions the local k3d cluster (if not present), installs the CNPG operator, appliesk8s/knoe/knoe-db.yaml(single-replica per the architectural plan §3), waits forCluster in healthy state, runs the knoe-kdc init job, applies the KDC Deployment + Service, runsetc/init_knoe_auth.sh schemato seedknoe.*tables.make k3d-knoe-pf— opens three port-forwards in the foreground:5432→service/knoe-db-rw -n knoe-db-088/464→service/knoe-kdc -n knoe-system(TCP and UDP both)- Prints the JDBC URL and the env vars the engineer should
export(e.g.KRB5_CONFIG=$PWD/etc/krb5.local.conf,KNOE_DB_PASSWORD=...). - On ^C, cleans up all three forwards.
make k3d-knoe-down— tears down the k3d cluster (or just the namespaces if you want incremental cleanup).
The cluster name should be k3d-knoe (so the kubeconfig context is
k3d-k3d-knoe, matching k3d's prefix convention). Reuse anything in
scripts/ that already drives k3d if it's there.
2.4 — Engineer-side krb5 config
A new file etc/krb5.local.conf checked into the repo:
[libdefaults]
default_realm = KNOE.LOCAL
rdns = false
forwardable = true
udp_preference_limit = 1 # workaround for kubectl port-forward UDP flakiness; force TCP
[realms]
KNOE.LOCAL = {
kdc = localhost:88
admin_server = localhost:749
default_domain = local
}
[domain_realm]
.local = KNOE.LOCAL
localhost = KNOE.LOCAL
Engineer's runtime: export KRB5_CONFIG=$PWD/etc/krb5.local.conf and
the JVM picks it up automatically.
2.5 — Engineer-facing doc
New docs/local-dev-knoe-auth.md. Sections:
- Prerequisites — k3d, kubectl, Docker Desktop running, JDK 21, Maven. Brew one-liner.
- One-time setup —
make k3d-knoe-up. Wait ~3 minutes. Verifykubectl --context k3d-k3d-knoe -n knoe-db-0 get cluster knoe-dbshowsHealthy. - Daily loop — open two terminals.
- Terminal A:
make k3d-knoe-pf(leave running). - Terminal B:
export KRB5_CONFIG=$PWD/etc/krb5.local.confthenmvn -pl authority spring-boot:run.
- Terminal A:
- Verify the loop —
curl localhost:8080/healthreturnsok;curl localhost:8080/.well-known/openid-configurationreturns the OIDC discovery JSON;psql "postgresql://postgres:$(make show-db-password)@localhost:5432/knoe-db?sslmode=require"connects. kinitfor SPNEGO testing —kinit -k -t <keytab> admin/admin@KNOE.LOCAL(full SPNEGO E2E is Phase 2; document the expected limitations:curl --negotiate -u : http://localhost:8080/may or may not fully round-trip on macOS depending onudp_preference_limit).- IntelliJ run configuration — paste-ready text or
screenshot. Module
authority, main classdev.knoe.auth.KnoeAuthApplication, env varsKRB5_CONFIG=…/etc/krb5.local.conf,KNOE_DB_PASSWORD=…. - Reset —
make k3d-knoe-down && make k3d-knoe-up. Time: ~3 min.
2.6 — Smoke-test script
scripts/k3d-knoe-smoke.sh — bash, runs after make k3d-knoe-up
to confirm the loop is intact:
#!/usr/bin/env bash
set -euo pipefail
ctx=k3d-k3d-knoe
ns_db=knoe-db-0
ns_sys=knoe-system
# 1. CNPG cluster healthy
phase=$(kubectl --context=$ctx -n $ns_db get cluster knoe-db -o jsonpath='{.status.phase}')
[[ "$phase" == "Cluster in healthy state" ]] || { echo "CNPG: $phase"; exit 1; }
# 2. knoe.* schema present
tables=$(kubectl --context=$ctx -n $ns_db exec sts/knoe-db-1 -c postgres -- \
psql -U postgres -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='knoe'")
[[ "$tables" -ge 6 ]] || { echo "knoe.* tables: $tables (expected ≥6)"; exit 1; }
# 3. KDC has the seed admin principal
kubectl --context=$ctx -n $ns_sys exec deploy/knoe-kdc -- \
kadmin.local listprincs | grep -q 'admin/admin@KNOE.LOCAL' \
|| { echo "missing admin principal"; exit 1; }
echo "k3d-knoe-smoke: PASS"
CI integration is out of scope for Phase 1 — this is engineer-run.
3. Don't break
- The existing GKE knoe-auth deployment must continue to work
unchanged. If you touch
etc/init_knoe_auth.sh, the GKE invocation must remain the default behavior (no--modeflag = current GKE behavior). - The k3s manifests under
deploy/opentofu/k3s/manifests/knoe/must NOT be modified. The k3s deploy mode targets a different use case (on-prem VM); leave it alone. - The CNPG cluster manifest at
k8s/knoe/knoe-db.yamlmust stay the canonical k3d/min Postgres definition. If you need it to behave differently for k3d (e.g. single-replica), make the change configurable, not destructive —_strip_k3d_synology_blocksinknoe/core/ops/cloudnative_pg.pyis the existing precedent for k3d-specific manifest manipulation.
4. Verification (Definition of done)
A new engineer, freshly cloning the repo, completes this in ≤ 8 minutes on a MacBook Air:
git clone <repo> && cd knoe-db
make k3d-knoe-up # 3-5 min
make k3d-knoe-pf & # foreground; backgrounded for this script
sleep 3
scripts/k3d-knoe-smoke.sh # PASS
export KRB5_CONFIG=$PWD/etc/krb5.local.conf
mvn -pl authority spring-boot:run & # backgrounded for the verify
sleep 30
curl -fsS http://localhost:8080/health # ok
curl -fsS http://localhost:8080/.well-known/openid-configuration | jq .issuer
# → "http://localhost:8080"
Tick all of:
make k3d-knoe-upexits 0 on a fresh laptop with k3d/kubectl installed.kubectl --context=k3d-k3d-knoe -n knoe-db-0 get cluster knoe-dbreportsHealthy.kubectl --context=k3d-k3d-knoe -n knoe-system get deploy knoe-kdcreports1/1 ready.scripts/k3d-knoe-smoke.shreportsPASS.- Host-side
psqlconnects vialocalhost:5432aftermake k3d-knoe-pfis running. kinit admin/admin@KNOE.LOCALfrom the host succeeds.mvn -pl authority spring-boot:runstarts cleanly withKRB5_CONFIG=etc/krb5.local.confset;/healthreturnsok;/.well-known/openid-configurationreturns valid JSON.make k3d-knoe-down && make k3d-knoe-upis idempotent.docs/local-dev-knoe-auth.mdexists and is accurate.- GKE deploy mode unchanged:
git diff main -- deploy/gcp/gke/shows no changes; the existing init scripts behave identically when invoked the same way they were before.
5. Out of scope
Captured in the parent plan §6, but call out the highest-friction deferrals here so reviewer expectations are right:
- SPNEGO E2E from a host browser — requires a service principal
for
HTTP/localhost@KNOE.LOCALand a keytab the host knoe-auth process can read. Phase 1 stops at "KDC reachable, principals exist,kinitworks." Phase 2 makes browser SPNEGO actually authenticate. - knoe-auth as a pod inside k3d — image-build-and-load. Phase 3. Don't take this on now.
- Supabase / Studio / Kong / oauth2-proxy on k3d — all explicit Phase 4+.
- OIDC code persistence in DB —
OidcCodeServiceuses an in-memoryConcurrentHashMaptoday. That's a real Phase 2.5 gap but it's the same gap on GKE; not k3d-specific. Don't fold it into this brief.
6. Commit shape
Single commit unless the deliverables genuinely separate into two clean halves (e.g. infra + docs). Suggested message:
feat(k3d): laptop dev loop for knoe-auth — CNPG + KDC + port-forward
Brings up the smallest k3d-resident stack that lets a host-side
knoe-auth (run via mvn spring-boot:run or IntelliJ) iterate against
real Postgres + Kerberos. Closes Phase 1 of the k3d-gke-mirror plan.
Scope:
- k8s/knoe/knoe-kdc-{deployment,service,configmap,pvc,init-job}.yaml
NEW; standalone KDC, realm KNOE.LOCAL (distinct from KNOE.DEV).
- etc/init_knoe_auth.sh: --mode k3d flag, swaps realm + skips
GCP-specific steps. GKE behavior unchanged when flag absent.
- Makefile: k3d-knoe-up, k3d-knoe-pf, k3d-knoe-down.
- etc/krb5.local.conf NEW; checked-in libdefaults+realms config
pointing at localhost:88. udp_preference_limit=1 to dodge
kubectl port-forward UDP flakiness.
- docs/local-dev-knoe-auth.md NEW; one-time setup + daily loop.
- scripts/k3d-knoe-smoke.sh NEW; bash sanity script.
Verified by following docs/local-dev-knoe-auth.md from a fresh
clone: end-to-end in <8 min, knoe-auth at localhost:8080 hits
real DB + KDC; OIDC discovery returns valid JSON.
Out of scope (parent plan docs/plans/k3d-gke-mirror.md §6):
- SPNEGO from host browsers
- knoe-auth-as-pod (image build/load)
- Supabase stack
- OidcCodeService DB persistence
Closes Phase 1; Phase 2+ briefs filed as needed.
7. Notes for reading
- The user is happy to decide between two paths if you surface
them clearly — e.g. "extend
init_knoe_auth.shwith--mode k3dvs write a siblinginit_knoe_auth_k3d.sh". Pick the one that minimizes drift and explain why in the commit. - The user runs macOS. UDP port-forward through kubectl on Mac has
historically been finicky; setting
udp_preference_limit = 1inkrb5.local.conf(force TCP) is the recommended workaround and is why it's in the example above. - If you run into resource ceiling issues on the laptop (Docker Desktop OOM, k3d node not ready), document the workaround in the engineer-side doc rather than working around it in the manifests.
- If the existing Python orchestrator
(
knoe.core.ops.cloudnative_pg) already handles the k3d-CNPG-bring-up cleanly, leverage it rather than writing a shell wrapper from scratch. The Make target can call into it viapython -m knoe.core.ops.cloudnative_pg ...if the entrypoints exist; if they don't, a bash wrapper is fine.