prole/deploy/gcp/gke/knoe-db.yaml
chrisfu 00f0ebec07 Merge claude/crazy-bose-fec256 into main
Bringing the long-running session-feature branch back into main in one
deliberate sweep. The branch carried the cluster work that's been live for
weeks (cross-cluster CNPG metrics, Grafana w/ Google OAuth, supabase
oauth2-proxy, cluster recovery, pg.0.knoe.dev + per-engineer onboarding,
GCS-backed CNPG backups via Workload Identity, the env-contamination
guard, the Junie brief queue, the cnpg-grafana CSRF + memory-request
fixes from today), while main accumulated Junie's parallel knoe-auth
Phase 2 OIDC work (full provider surface: discovery, authorize, token,
userinfo, JWKS, RS256 signing, code exchange, session services).

Key decision: the two branches did COMPETING rebrands off the same
starting point (5ba9b63, 2026-04-27):

  - claude branch (commit b355855, earlier): org.prole.authority.* →
                                              dev.knoe.auth.*
                                              (artifact renamed to
                                              knoe-auth.jar)
  - main (commit 9daa94b, recent): org.prole.authority.* →
                                    dev.knoe.authority.*
                                    (kept "authority" artifact name)

dev.knoe.auth wins: cluster runs from this name, the Maven artifact is
already knoe-auth.jar, and the broader rename is the documented
namespace direction (per ~/.claude/projects/-Users-chrisfu-dev-knoe-db/
memory/MEMORY.md). All of main's recent Phase 2 OIDC content was ported
from authority/src/.../dev/knoe/authority/ into
authority/src/.../dev/knoe/auth/ with package declarations rewritten.

== File-level resolution summary ==

Textual conflicts (4):

  authority/pom.xml
    - Took our artifactId="auth"
    - Took our branch's removal of spring-security-kerberos-client
      (verified: Junie's Phase 2 OIDC code does not import it; the dep
      was already-dead config)

  docs/pipeline-phases.md
    - Took our branch's "Phase 1 not started" status. Main had a
      misplaced "✅ Complete" with a knoe-auth-Phase-1 commit ref
      in the autobuild Phase 1 section — different domain.

  docs/plans/knoe-auth-round-1.md
    - Took our branch's dev.knoe.auth file table (vs main's
      dev.knoe.authority listing). Pure rename mismatch.

  supabase/helm/knoe-supabase/templates/kong/config.yaml
    - Took our branch's onboard route + plain dashboard wiring.
      Main had an oauth2proxy.enabled toggle that put oauth2-proxy as
      a Kong upstream — but the deployed architecture (commit 25f1b2e)
      has oauth2-proxy in FRONT of Kong, not behind. Main's wrapper
      reflected an architecture that was never deployed.
    - Took our branch's removal of basic-auth from dashboard route
      (queue #15 brief still tracks the matching values.yaml /
      kong/deployment.yaml cleanup).

Java tree reconciliation (44 file-pairs):

  20 dual-path source files + 2 dual-path tests
    Body-identical between main's authority/ and our branch's auth/
    after stripping package decls — main's commit 9daa94b was a pure
    rebrand. Took our branch's auth/ version for all 22.

  8 main-only source files (Phase 2 OIDC), ported into auth/:
    web/JwksController.java
    web/OidcAuthorizeController.java
    web/OidcDiscoveryController.java
    web/OidcTokenController.java
    web/OidcUserInfoController.java
    session/OidcCodeService.java
    session/OidcTokenService.java
    session/SessionService.java

  12 main-only test files, ported into auth/:
    HealthControllerTest.java
    enroll/EnrollValueTypesTest.java
    enroll/EnrollmentControllerTest.java
    enroll/TotpServiceTest.java
    kerberos/KadminClientTest.java
    kerberos/KerberosSpnegoResultTest.java
    web/LoginControllerTest.java
    admin/AdminControllerTest.java
    user/PrincipalNormalizerTest.java
    regression/IdentityRegressionTest.java
    session/OidcCodeServiceTest.java
    session/SessionServiceTest.java

  Port mechanics: read main:authority/...<file> via git show, then sed
  rewrite `package dev.knoe.authority` → `package dev.knoe.auth` and
  `import dev.knoe.authority` → `import dev.knoe.auth`. Body content
  unchanged.

  authority/src/main/java/dev/knoe/authority/ — DELETED (duplicate)
  authority/src/test/java/dev/knoe/authority/  — DELETED (duplicate)

== Verification ==

- grep -rln '<<<<<<<' across .java/.md/.yaml/.yml/.sh/.xml/.tpl: clean
- find authority/src -path '*/dev/knoe/authority*': empty (subtree gone)
- grep 'package dev.knoe.authority' across repo: clean
- bash -n install.sh deploy.sh etc/preflight_kubecontext.sh: clean
- git ls-files -u | wc -l: 0 unmerged paths
- helm lint supabase/helm/knoe-supabase: pre-existing failure on
  studioIngress.enabled undefined in values.yaml (introduced by Junie
  on main; unrelated to this merge — flagging as follow-up).

== Followups (carried into TODO ranked queue or noted here) ==

  - helm lint failure: studioIngress block in values.yaml is missing
    enable flag; templates/studio/{ingress,oauth2proxy-deployment,
    oauth2proxy-service}.yaml all reference studioIngress.enabled with
    no default. Pre-existing on main; not introduced by this merge.
  - The five Junie briefs filed on this branch are now reachable from
    main at docs/plans/junie/{02,06,07,13,15}-*.md. Junie can pick them
    up in any order.
  - knoe-auth Phase 2 OIDC source (now at dev.knoe.auth.*) is not yet
    deployed to the cluster. Deployment is its own task.
  - The branch claude/crazy-bose-fec256 stays in place (worktree at
    .claude/worktrees/crazy-bose-fec256 may have ongoing context for
    Claude Code sessions). Safe to delete once next session starts
    cleanly from main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:39:10 -07:00

214 lines
10 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: knoe-db
namespace: knoe-db-0
spec:
instances: 3
enablePDB: false
# Image pulled from GCP Artifact Registry — set ARTIFACT_REGISTRY in conf/prod/gcp.cfg
# e.g. us-central1-docker.pkg.dev/<project>/knoe-system/knoe-db:<pg-release-tag>
imageName: "${ARTIFACT_REGISTRY}/knoe-db:${KNOE_DB_IMAGE_TAG}"
postgresUID: 100
postgresGID: 101
maxSyncReplicas: 1
affinity:
enablePodAntiAffinity: true
# Keep spread as a preference for small dedicated Standard DB clusters so 3 pods can still
# schedule while nodes reconcile; strict topology can be enforced in later rollout.
podAntiAffinityType: preferred
topologyKey: kubernetes.io/hostname # physical node boundary (not zone)
tolerations:
# Allow scheduling on GKE Spot nodes when explicitly enabled for this DB cluster.
# Without this toleration the cluster-autoscaler predicate simulation fails
# for any MIG whose nodes carry the spot taint, blocking scale-up entirely.
- key: "cloud.google.com/gke-spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
# nodeSelector removed: knoe-cnpg-0 is a dedicated DB cluster — all nodes are
# available to CNPG. A workload label selector here causes scheduling failures
# when CNPG v1.28 translates it into requiredDuringScheduling nodeAffinity.
postgresql:
parameters:
shared_buffers: 64MB # ~25% of 256Mi request; restore to 128MB when resources increase
pg_stat_statements.max: '10000'
pg_stat_statements.track: all
shared_preload_libraries:
- pg_stat_statements
- pg_tde
pg_hba:
# Local Unix-socket connections (CNPG default + knoe role)
- local all postgres trust
- local all knoe scram-sha-256
# postgres / knoe-db / knoe roles: cluster-internal (RFC1918) only.
# Cluster pod CIDRs: db cluster 10.24.0.0/14, app cluster 10.84.0.0/14;
# node subnet 10.180.0.0/16. 10.0.0.0/8 covers all of those.
- host all postgres 10.0.0.0/8 scram-sha-256
- host knoe knoe-db 10.0.0.0/8 scram-sha-256
- hostssl knoe knoe-db 10.0.0.0/8 scram-sha-256
# PHASE 1 EXTERNAL ACCESS — any member of `knoe_developer`, over TLS+SCRAM.
# `+rolename` in pg_hba matches role membership (not just literal name),
# so `etc/onboard_engineer.sh` adds new engineers via `GRANT knoe_developer
# TO <user>` without ever editing pg_hba — that's the reusable property.
# Phase 2 (queued for Junie) replaces this with libpq OAUTHBEARER:
# hostssl all all 0.0.0.0/0 oauth issuer=https://accounts.google.com validator=knoe_oauth scope="openid email"
- hostssl all +knoe_developer all scram-sha-256
# Internal cluster (RFC1918) — all roles, SCRAM (allows the supabase
# services in app cluster knoe-dev-0 to reach the DB cluster).
- host all all 10.0.0.0/8 scram-sha-256
- hostssl all all 10.0.0.0/8 scram-sha-256
# Block any plaintext from external (TLS required for the public LB)
- hostnossl all all 0.0.0.0/0 reject
# Catch-all reject for anything not matched above
- host all all 0.0.0.0/0 reject
- hostssl all all 0.0.0.0/0 reject
bootstrap:
initdb:
database: knoe-db
owner: knoe
localeCollate: 'en_US.utf8'
localeCType: 'en_US.utf8'
secret:
name: knoe-db-user
postInitTemplateSQL:
# Supabase convention: relocatable extensions live in `extensions`, not
# `public`. Studio's Database Advisor flags `public.pg_stat_statements`
# as a Security warning the moment a user opens the dashboard. Without
# an explicit SCHEMA clause `CREATE EXTENSION` lands the relocatable
# extension in the first writable schema in the connecting role's
# search_path, which for `postgres` is `public`.
- CREATE SCHEMA IF NOT EXISTS extensions;
- CREATE EXTENSION IF NOT EXISTS pg_stat_statements SCHEMA extensions;
postInitSQL:
- DO $do$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'knoe') THEN CREATE ROLE knoe LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT; END IF; END $do$;
- DO $do$ DECLARE owner_password text; BEGIN SELECT rolpassword INTO owner_password FROM pg_authid WHERE rolname = 'knoe'; IF owner_password IS NOT NULL THEN EXECUTE format('ALTER ROLE knoe PASSWORD %L', owner_password); END IF; END $do$;
- DO $do$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'knoe_catalog_executor') THEN CREATE ROLE knoe_catalog_executor NOLOGIN; END IF; END $do$;
- CREATE SCHEMA IF NOT EXISTS knoe AUTHORIZATION knoe;
- ALTER SCHEMA knoe OWNER TO knoe;
- REVOKE ALL ON SCHEMA knoe FROM PUBLIC;
- ALTER ROLE knoe SET search_path TO knoe, public;
- CREATE EXTENSION IF NOT EXISTS pg_tde SCHEMA knoe;
- CREATE EXTENSION IF NOT EXISTS pgcrypto SCHEMA knoe;
- CREATE EXTENSION IF NOT EXISTS postgis SCHEMA knoe;
- CREATE EXTENSION IF NOT EXISTS postgis_topology;
- ALTER SCHEMA topology OWNER TO knoe;
- CREATE EXTENSION IF NOT EXISTS vector SCHEMA knoe;
- CREATE EXTENSION IF NOT EXISTS tds_fdw SCHEMA knoe;
- GRANT USAGE ON SCHEMA knoe TO knoe;
- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA knoe TO knoe;
- GRANT USAGE ON SCHEMA knoe TO knoe_catalog_executor;
- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA knoe TO knoe_catalog_executor;
- ALTER DEFAULT PRIVILEGES FOR ROLE knoe IN SCHEMA knoe GRANT EXECUTE ON FUNCTIONS TO knoe_catalog_executor;
- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA topology TO knoe;
- CREATE SCHEMA IF NOT EXISTS storage;
- CREATE SCHEMA IF NOT EXISTS graphql_public;
- DO $do$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon') THEN CREATE ROLE anon NOLOGIN; END IF; END $do$;
- DO $do$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticator') THEN CREATE ROLE authenticator LOGIN; END IF; END $do$;
- GRANT USAGE ON SCHEMA public TO anon;
- GRANT USAGE ON SCHEMA storage TO anon;
- GRANT USAGE ON SCHEMA graphql_public TO anon;
- GRANT anon TO authenticator;
# demo schema for guest read-only access (evolves over time)
- CREATE SCHEMA IF NOT EXISTS demo;
- DO $do$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'guest') THEN CREATE ROLE guest NOLOGIN; END IF; END $do$;
- GRANT USAGE ON SCHEMA demo TO guest;
- ALTER DEFAULT PRIVILEGES IN SCHEMA demo GRANT SELECT ON TABLES TO guest;
# knoe.user — identity registry (Knoey Users)
- CREATE TABLE IF NOT EXISTS knoe.user (id SERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, realm TEXT NOT NULL DEFAULT 'PROLE.LOCAL', email TEXT, display_name TEXT, tenant_realm TEXT, is_realm_admin BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now());
- CREATE TABLE IF NOT EXISTS knoe.user_role (user_id INT NOT NULL REFERENCES knoe.user(id) ON DELETE CASCADE, role TEXT NOT NULL, granted_at TIMESTAMPTZ DEFAULT now(), PRIMARY KEY (user_id, role));
- GRANT SELECT, INSERT, UPDATE ON knoe.user TO knoe;
- GRANT SELECT, INSERT, UPDATE ON knoe.user_role TO knoe;
- GRANT USAGE, SELECT ON SEQUENCE knoe.user_id_seq TO knoe;
managed:
roles:
- name: admin
ensure: present
login: true
superuser: true
comment: "Admin principal — full cluster database access"
- name: guest
ensure: present
login: true
superuser: false
comment: "Guest principal — read-only access to demo schema"
- name: developer
ensure: present
login: false
superuser: false
comment: "Developer group role — granted to knoe-system user accounts"
resources:
requests:
cpu: "100m"
# 512Mi (was 128Mi) — postgres baseline working set is ~290Mi on the
# primary (shared_buffers + wal_buffers + per-backend memory + a small
# OS page cache visible to cgroups), so 128Mi caused the cnpg-grafana
# "Resource Pressure" tile to flag Memory at working_set / request ≈ 2x,
# which the dashboard maps to a red "Data Loss" label (>0.98 ratio).
# Right-sizing to 512Mi puts the steady-state ratio in the green
# "Healthy" zone (<0.8) and gives the scheduler an accurate signal for
# spreading replicas across nodes. Pods still have plenty of headroom:
# 2Gi limit is unchanged.
memory: "512Mi"
limits:
cpu: "500m"
# 2Gi (was 512Mi) — barman-cloud-backup is single-threaded gzip + GCS
# upload buffering and the throughput tops out at the memory ceiling.
# 2Gi cuts a 9 GB DB backup from 30–90 min down to 5–10 min.
memory: "2Gi"
enableSuperuserAccess: true
# CNPG-issued server cert is auto-rotated by the operator. Listing
# pg.0.knoe.dev as an alt DNS name lets engineers connect with
# `sslmode=verify-full` after fetching the CNPG-issued CA cert from the
# `knoe-db-ca` Secret. Phase 1 of the per-engineer psql access plan; replaced
# by libpq OAUTHBEARER + Let's Encrypt in Phase 2.
certificates:
serverAltDNSNames:
- pg.0.knoe.dev
storage:
size: 50Gi
pvcTemplate:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: premium-rwo # pd-ssd; 3×50Gi PGDATA + 3×50Gi WAL = 300Gi total (fits 300GB SSD quota)
walStorage:
size: 50Gi
pvcTemplate:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: premium-rwo # pd-ssd; restore to premium-rwo after quota increase (matches PGDATA above)
monitoring:
# NOTE: enablePodMonitor + podMonitorRelabelings are CNPG-deprecated and
# will be removed in a future release. Migration path: manually manage
# the PodMonitor resource. Tracked in docs/TODO.md as a follow-up.
enablePodMonitor: true # kube-prometheus-stack is installed before CNPG → PodMonitor CRD present
# Add a `cluster` label to every scraped sample, sourced from the
# cnpg.io/cluster pod label. The cnpg-grafana dashboards filter every
# panel by `cluster="$cluster"`, so without this relabel only 2 of 85
# CNPG metrics (the operator-collector ones) render.
podMonitorRelabelings:
- sourceLabels: ["__meta_kubernetes_pod_label_cnpg_io_cluster"]
targetLabel: cluster
action: replace