sec(data): DORMANT postgres-customers admin lockdown (truehomie root-cause vector)#61
Merged
mastermanas805 merged 2 commits intoJun 6, 2026
Conversation
…cause vector)
Closes the OPEN root cause of the truehomie-db DROP incident (2026-06-03): a
direct public-internet admin connection to the shared customer Postgres that
can DROP DATABASE/ROLE with no audit_log row.
CONFIRMED via config + SAFE checks (verify-don't-assert, 2026-06-06):
- pg.instanode.dev -> 152.42.154.144 (same DO LB IP as api/redis/mongo); pg
is publicly DNS-routed.
- `nc -z pg.instanode.dev 5432` succeeds from the public internet (TCP
handshake only -- NO auth, NO SQL attempted).
- postgres-customers pod runs stock pgvector/pgvector:pg16 with NO custom
pg_hba/postgresql.conf/POSTGRES_HOST_AUTH_METHOD and no config mount ->
image default `host all all all scram-sha-256` (catch-all): the admin role
(instant_cust) can authenticate from anywhere that reaches the listener.
STILL HYPOTHESIS (not asserted): that an external actor actually used this to
drop truehomie (we did not attempt auth); the instant-pg-proxy's own role-gate
(its config lives in the separate InstaNode-dev/instant-pg-proxy repo).
Fix (staged DORMANT -- operator-applies in a maintenance window; rule 15: infra
has no auto-apply, high blast radius):
- k8s/data/postgres-customers-lockdown.yaml: custom pg_hba.conf ConfigMap that
REJECTS the admin role (instant_cust/postgres) from the public internet while
still allowing in-cluster admin (provisioner/migrator/worker via pod CIDR)
AND customer usr_* roles from anywhere (customer connect path preserved).
- k8s/data/networkpolicy.yaml: truehomie context annotations + a DORMANT
pg-proxy ingress rule (network-layer second line; admin boundary does not
depend on it -- pg_hba role-reject holds regardless of SNAT source).
- POSTGRES-CUSTOMERS-LOCKDOWN-RUNBOOK.md: confirmed-vs-hypothesis, legitimate-
consumer map, pre-apply verification (live admin role name, pg-proxy SNAT/
NetPol interaction, hunt for public-admin automation), apply, legit-access-
still-works verify, external-admin-closed verify (SAFE rejection test), and
rollback.
NOT applied to prod. Requires USER/OPERATOR review + maintenance window.
Validated locally: yamllint (repo config) + kubeconform -strict (5/5 valid).
Defense-in-depth: this is the infra half. App half already shipped (provisioner
guardedDrop chokepoint + DDL-audit + CI guard, PR #50; drop-metric alert/tile/
catalog, infra PR #60). Audit: docs/ci/DATA-INTEGRITY-DROP-PATH-AUDIT.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…roxy-IP reject Live pre-apply verification (do-nyc3-instant-prod, 2026-06-06) found the PR's pg_hba was incomplete and would NOT have closed the truehomie vector: 1. TWO superusers on the prod customer pod, not one: `instanode_admin` (the role api/worker connect with via CUSTOMER_DATABASE_URL — the CONFIRMED truehomie vector role) AND `instant_cust` (provisioner's POSTGRES_CUSTOMERS_URL POSTGRES_USER). The PR rejected only `instant_cust`, leaving the actual vector (`instanode_admin`) matching the catch-all customer allow. Both now rejected. 2. The pg-proxy SNAT problem: instant-pg-proxy is a normal in-cluster pod that re-originates TCP, so an external `-U instanode_admin` arrives SNAT'd to a proxy pod IP inside 10.0.0.0/8 — a plain `10.0.0.0/8 allow` would match it. Baseline probe confirmed the live vector is OPEN (external admin reaches scram). Added proxy-pod-IP `reject` lines (10.109.4.113, 10.109.0.101) ordered BEFORE the in-cluster allow so the SNAT'd external admin is rejected while legit consumer pods (direct svc DSN, different IPs) still authenticate. Documented the churn dependency + the durable pg-proxy role-gate follow-up. 3. Preserved live local/loopback/replication `trust` lines (readiness probe + backup pg_dumpall) instead of switching to peer. Added doadmin belt-suspenders. Runbook: added §3a (pg-proxy SNAT + proxy-IP churn operator note) and the L1-L5 live-confirmed findings table (supersedes H1-H3). NetworkPolicy NOT applied (verified not enforced in prod; applying as-is would default-deny the proxy path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mastermanas805
added a commit
that referenced
this pull request
Jun 6, 2026
…+ drill log Follow-up to the 2026-06-06 apply of PR #61. The lockdown was applied to prod via `kubectl patch` (imperative). This makes the durable repo manifest match the live state so a future apply of postgres-customers.yaml does NOT silently revert the lockdown back to the vulnerable catch-all pg_hba: - mount the postgres-customers-hba ConfigMap at /etc/postgresql/pg_hba.conf (subPath) - start postgres with `-c hba_file=... -c password_encryption=scram-sha-256` - strategy RollingUpdate → Recreate (the RWO PVC deadlocks a rolling update on a Multi-Attach error; Recreate terminates the old pod first — brief downtime, acceptable for single-replica stateful) Runbook §9 Drill Log records the apply result: external admin (instanode_admin + instant_cust) now REJECTED at pg_hba (verified live; baseline reached scram), all in-cluster admin + customer usr_* paths preserved (verified), no rollback. Lists the operator follow-ups (durable pg-proxy role-gate; proxy-IP churn refresh; networkpolicy.yaml apply-exclude). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mastermanas805
added a commit
that referenced
this pull request
Jun 6, 2026
…+ drill log (#63) Follow-up to the 2026-06-06 apply of PR #61. The lockdown was applied to prod via `kubectl patch` (imperative). This makes the durable repo manifest match the live state so a future apply of postgres-customers.yaml does NOT silently revert the lockdown back to the vulnerable catch-all pg_hba: - mount the postgres-customers-hba ConfigMap at /etc/postgresql/pg_hba.conf (subPath) - start postgres with `-c hba_file=... -c password_encryption=scram-sha-256` - strategy RollingUpdate → Recreate (the RWO PVC deadlocks a rolling update on a Multi-Attach error; Recreate terminates the old pod first — brief downtime, acceptable for single-replica stateful) Runbook §9 Drill Log records the apply result: external admin (instanode_admin + instant_cust) now REJECTED at pg_hba (verified live; baseline reached scram), all in-cluster admin + customer usr_* paths preserved (verified), no rollback. Lists the operator follow-ups (durable pg-proxy role-gate; proxy-IP churn refresh; networkpolicy.yaml apply-exclude). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Hardens the OPEN root cause of the truehomie-db DROP incident (2026-06-03) — a direct, public-internet admin connection to the shared customer Postgres that can
DROP DATABASE/DROP ROLEwith noaudit_logrow (the missing-audit-row signature of the incident). Matchesdocs/ci/DATA-INTEGRITY-DROP-PATH-AUDIT.md§truehomie root-cause hypotheses H1 (the confirmed vector).CONFIRMED via config + SAFE checks (verify-don't-assert, 2026-06-06)
pg.instanode.dev→152.42.154.144— the same IP asapi/redis/mongo.instanode.dev(the shared DO LoadBalancer fronting ingress-nginx). pg is publicly DNS-routed.nc -z -w5 pg.instanode.dev 5432succeeds from the public internet — TCP handshake only (no auth, no SQL, no DDL attempted).postgres-customerspod runs the stockpgvector/pgvector:pg16image with NO custompg_hba.conf/postgresql.conf/POSTGRES_HOST_AUTH_METHODand no config mount → the image defaulthost all all all scram-sha-256(catch-all) is in effect: the admin role (instant_cust) can authenticate from any source that reaches the listener.postgres-customersService is ClusterIP; the public exposure is via the externalinstant-pg-proxy+ ingress-nginxtcp-services(not in this repo).postgres-customers-ingressNetworkPolicy already exists (provisioner/migrator/worker only); its prod-apply state is unverified (no auto-apply).Still hypothesis (NOT asserted)
instant-pg-proxy's own role-gate — its config lives in the separateInstaNode-dev/instant-pg-proxyrepo.Net: the hardening agent's #1 hypothesis is corroborated, not refuted.
What this changes
k8s/data/postgres-customers-lockdown.yaml(new) — custompg_hba.confConfigMap that rejects the admin role (instant_cust/postgres) from the public internet while still allowing in-cluster admin (provisioner/migrator/worker via pod CIDR10.0.0.0/8) and customerusr_*roles from anywhere (customer connect path preserved). Keyed on the role name so it holds even if pg-proxy SNATs customer traffic to a pod IP.k8s/data/networkpolicy.yaml(edit) — truehomie-context annotations + a dormant (commented) pg-proxy ingress rule (network-layer second line; the admin boundary does NOT depend on it).POSTGRES-CUSTOMERS-LOCKDOWN-RUNBOOK.md(new) — confirmed-vs-hypothesis, the legitimate-consumer map, pre-apply verification (confirm the live admin role name; the pg-proxy SNAT/NetPol interaction; hunt for any public-admin automation), apply (apply CM → patch pod to mount +-c hba_file=...→ rollout), legit-access-still-works verify (provisioner adminselect 1, real/db/new+ customer connect, backup CronJob smoke), external-admin-closed verify (SAFE rejection test — no SQL, no valid creds needed), and rollback.Legitimate consumers preserved
provisioner (admin DSN, in-cluster), migrator, worker (read), customers (
usr_*via pg-proxy), backup CronJob — all preserved (see runbook §2). The one thing closed: apsql -U instant_custfrom outside the cluster.Safety
kubeconform -strict(5/5 valid).Defense-in-depth context
Infra half of the truehomie fix. App half already shipped: provisioner
guardedDropchokepoint + DDL-audit + CI guard (PR #50); drop-metric alert/tile/catalog (infra PR #60).🤖 Generated with Claude Code