# Direct database access — psql + Rust apps Per-engineer access to the `knoe-db` Postgres cluster (PG18 / Percona) at `pg.0.knoe.dev:5432`. > **Phase 1 (current)**: per-engineer postgres roles with strong passwords > (30-day rotation), TLS required, named-roles-only ACL on the public LB. > **Phase 2 (queued for Junie)**: replaces the password mechanism with > PG18 native OAUTHBEARER (libpq drives a Google Device Flow) + a small > validator library bundled into our `knoe-db` image. Connection target stays > the same; engineers' `psql` invocation only changes from `-W` to setting > `oauth_issuer` / `oauth_client_id` in the connection string. --- ## First-day flow (engineer-facing) See **[`docs/onboarding.md`](onboarding.md)** — the canonical engineer checklist. Two-line summary: 1. chrisfu runs `etc/onboard_engineer.sh `. Script outputs a one-time URL (and QR-code rendering of it) plus a 24h temp password. 2. Engineer opens URL on phone or laptop → page at `db.0.knoe.dev/onboard.html` shows their password + connection string with [Copy] buttons. Engineer pastes into psql, immediately rotates via `\password`, saves their new password to **personal** 1Password (no shared vault required). The CA cert lives at [`etc/knoe-db-ca.crt`](../etc/knoe-db-ca.crt) in this repo — engineers reference it with `sslrootcert=etc/knoe-db-ca.crt` after cloning. It's a public CA root, not a secret. Auto-rotates with CNPG; we re-export and commit when it does. Connection string (engineers substitute their own role): ```bash psql "host=pg.0.knoe.dev port=5432 user=$USER dbname=postgres \ sslmode=verify-full sslrootcert=etc/knoe-db-ca.crt" ``` --- ## Rust apps (sqlx / tokio-postgres / deadpool-postgres) Connection string with the same TLS configuration: ```rust let url = format!( "postgres://{user}:{password}@pg.0.knoe.dev:5432/postgres\ ?sslmode=verify-full&sslrootcert={ca_path}", user = std::env::var("KNOE_PG_USER")?, password = std::env::var("KNOE_PG_PASSWORD")?, ca_path = format!("{}/.knoe/knoe-db-ca.crt", std::env::var("HOME")?), ); let pool = sqlx::PgPool::connect(&url).await?; ``` For `tokio-postgres` directly, configure the `MakeTlsConnector` with the same CA bundle (use `postgres-native-tls` or `tokio-postgres-rustls`). **Service apps on GKE** (Phase 2.5 territory): the same connection string, but `KNOE_PG_USER` / `KNOE_PG_PASSWORD` come from a per-app k8s Secret rather than 1Password. Phase 2 swaps this to Workload-Identity-issued OAUTHBEARER tokens — the app code change is one line. --- ## What can a `knoe_developer` do? The per-engineer roles (`chrisfu`, `ron`) are members of the `knoe_developer` group. Grants: | Schema | Permission | Notes | |--------------|---------------------------------------|-------| | `knoe` | `SELECT, INSERT, UPDATE, DELETE` | Application schema; ALTER DEFAULT PRIVILEGES propagates to new tables | | `public` | `SELECT, INSERT, UPDATE, DELETE` | Same | | `auth` | `SELECT` only | Read-only on Supabase GoTrue tables (debugging / inspection) | | `storage` | `SELECT` only | Read-only on Supabase storage tables | | `extensions` | `USAGE` | Lets queries reach `extensions.pg_stat_statements` etc. | What you can NOT do: - Drop / truncate `auth.*` or `storage.*` (read-only) - `ALTER ROLE`, `CREATE ROLE`, `CREATE EXTENSION` (those are postgres-only) - Connect as `postgres`, `supabase_admin`, `authenticator` from outside the cluster - Connect at all without TLS (pg_hba `hostnossl ... reject`) Need broader access? Ask chrisfu — easier to extend the `knoe_developer` group than to mint role-specific grants per engineer. --- ## Onboarding a new engineer (admin-facing) ```bash ./etc/onboard_engineer.sh ``` Outputs: - A one-time URL: `https://db.0.knoe.dev/onboard.html#user=...&pw=...&exp=...` - A QR-code rendering of that URL (in your terminal, via `qrencode`) - The plaintext temp password (fallback) Deliver the URL to the new engineer via Gmail (to their `@knoey.com` address) or by showing the QR on screenshare for them to scan with their phone. The landing page handles all the engineer-side UX (copy buttons, connection string, rotation guidance). They save the password to their **personal** 1Password — no shared vault required. See [`docs/onboarding.md`](onboarding.md). The script idempotently CREATEs / ALTERs the role: - LOGIN INHERIT VALID UNTIL `` - PASSWORD set to a fresh 192-bit random - GRANT knoe_developer The pg_hba.conf `hostssl all +knoe_developer all scram-sha-256` rule means the new role is reachable via `pg.0.knoe.dev` immediately — no pg_hba edits needed per onboard. Other actions: ```bash ./etc/onboard_engineer.sh --rotate # fresh 24h temp password ./etc/onboard_engineer.sh --revoke # DROP ROLE ``` --- ## Rotate your password The engineer-side path is `\password` inside psql — they pick a new password locally, postgres updates the SCRAM verifier, the password never leaves their machine. No coordination with chrisfu needed. If chrisfu needs to issue a fresh **temp** password (engineer locked out, forgot to rotate before `VALID UNTIL` expired): ```bash ./etc/onboard_engineer.sh --rotate ``` Same delivery (URL/QR/email) as initial onboarding. --- ## Troubleshooting - `pg_hba.conf rejects connection for host ... no encryption` — you set `sslmode=disable`. Use `verify-full` (requires the CA cert) or `require` (encrypted but not verified). - `pg_hba.conf rejects connection for host , user , no encryption` — you're on a network that's stripping TLS, OR your psql client is too old. Update libpq to ≥17. - `password authentication failed for user ""` — typically wrong password, but also fires for roles not in our allow list (postgres, knoe-db, etc.) when connecting from outside the cluster. Check you're using your own role. - `FATAL: role "" is not permitted to log in` — your `VALID UNTIL` lapsed. Ask for rotation. --- ## Related - **Phase 2 plan (queued, owned by Junie)**: install/deploy.sh switches to `pg_oauth` as the default until knoey-system/authority auth is live. Includes building a validator library against PG18's `OAuthValidatorCallbacks` API and bundling into the knoe-db image. See `docs/TODO.md` follow-up for the active reference. - **k3d build**: same Service + cluster cert pattern in k3d mode. Owned by Junie alongside the install.sh true-up. - **knoe-auth Round 1**: the longer-term identity story (Kerberos KDC + Java authority). Becomes the Phase 2 issuer when it deploys, but Phase 2 doesn't block on it — Google's issuer is sufficient for now. ## Files - [`deploy/gcp/gke/knoe-db.yaml`](../deploy/gcp/gke/knoe-db.yaml) — CNPG cluster spec; pg_hba block, server cert SAN list. - [`deploy/gcp/gke/knoe-db-external-lb.yaml`](../deploy/gcp/gke/knoe-db-external-lb.yaml) — external TCP LoadBalancer Service.