Skip to content

fix(pgwire): scope catalog resolution and trust auth to the connecting tenant#32

Merged
farhan-syah merged 2 commits intomainfrom
bug/pgwire-tenant-scoping-29-30
Apr 15, 2026
Merged

fix(pgwire): scope catalog resolution and trust auth to the connecting tenant#32
farhan-syah merged 2 commits intomainfrom
bug/pgwire-tenant-scoping-29-30

Conversation

@farhan-syah
Copy link
Copy Markdown
Contributor

Closes #29
Closes #30

Summary

  • pgwire: tenant users get 'unknown table' on INSERT/SELECT against their own collections #29 — The pgwire handler built a single QueryContext at construction with a hardcoded tenant_id = 1, and the prepared-statement parser did the same. Every query from a tenant > 1 user planned against tenant 1's catalog and failed with unknown table, even on a collection the same connection had just created. Fixed by removing the tenant from CatalogInputs entirely and threading it per plan call via build_adapter(tenant_id); parser.rs now resolves the connecting user's tenant from pgwire metadata before building its catalog.
  • pgwire: default Trust auth mode accepts connections for non-existent users with any password #30 — Trust mode skipped the credential-store lookup and resolve_identity fabricated a tenant-1 superuser for any unknown username. Fixed by (a) rejecting unknown users at connect time via NoopStartupHandler::post_startup with SQLSTATE 28000, (b) removing the silent-superuser fallback in resolve_identity, and (c) keeping the empty-store bootstrap path but persisting the first connecting user so subsequent queries resolve strictly.

The root causes were localised to three files (planner/context.rs, pgwire/handler/core.rs, pgwire/handler/prepared/parser.rs) plus pgwire/factory.rs for the auth audit path. All downstream QueryContext constructors (for_state, for_state_with_lease, with_catalog) lost their now-meaningless tenant_id parameter; 14 call sites updated.

Tests

New wire-level test file nodedb/tests/pgwire_tenant_scoping.rs — 8 tests covering the full class of bug, not just the reported symptom:

  • Tenant-2 user SELECT / INSERT / UPDATE / DELETE on own collection
  • Tenant-2 user prepare against own collection (extended protocol → parser.rs path)
  • Tenant-2 user SELECT against a tenant-1 collection must surface unknown table, not silently return an empty result (asymmetric-isolation guard)
  • Unknown Trust-mode username rejected at connect time
  • Unknown Trust-mode username, if admitted, cannot run superuser-only DDL (defense-in-depth regression guard against silent-superuser fabrication)

Harness additions in tests/common/pgwire_harness.rs: pub pg_port and connect_as(user, password) so multiple concurrent connections under different usernames can share one listener.

Test plan

  • cargo nextest run -p nodedb --test pgwire_tenant_scoping — 8/8 pass
  • cargo nextest run -p nodedb — 2827/2827 pass
  • cargo clippy -p nodedb --all-targets -- -D warnings — clean
  • cargo fmt --all — clean

…g tenant

QueryContext was constructed once per handler with a fixed tenant id (always
1), causing every SQL plan built during a session to resolve collection
metadata against tenant 1 regardless of which tenant the connecting user
belonged to.

Remove tenant_id from QueryContext / CatalogInputs construction and instead
pass it through to build_adapter() at plan time, so each plan_sql() call
uses the tenant derived from the authenticated identity on that connection.

The same hardcoded-1 pattern was present in the HTTP server, native session,
event trigger dispatcher, procedural executor, scatter-gather planner,
CDC consume path, and topic publish path — all updated to use the
tenant-free construction form.

Fix Trust-mode authentication to perform strict identity resolution rather
than silently admitting any username as a tenant-1 superuser. Implement
NoopStartupHandler::post_startup to reject unknown users with SQLSTATE 28000,
matching PostgreSQL trust-method semantics. A one-time bootstrap exception
admits and persists the first connecting user when the credential store is
empty, so fresh deployments do not require out-of-band provisioning.

Fix parse-time catalog lookup in NodeDbQueryParser to derive tenant_id from
the connecting user's pgwire metadata rather than always using tenant 1, so
Describe responses for prepared statements include field info from the correct
tenant's schema.
…and trust auth

Cover the two regression classes fixed in the preceding commit:

- Tenant-scoped users can only see and modify collections owned by their
  own tenant; a query against a collection belonging to another tenant
  returns a resolution error, not results from the wrong catalog.

- Trust mode rejects usernames that were never provisioned with SQLSTATE
  28000 rather than silently admitting them as superusers.

Extend pgwire_harness::TestServer with connect_as() to open additional
connections under arbitrary credentials, and pre-provision the default
"nodedb" superuser so the bootstrap exception does not fire during tests
that subsequently create additional users.
@farhan-syah farhan-syah merged commit dcd47c1 into main Apr 15, 2026
1 of 2 checks passed
@farhan-syah farhan-syah deleted the bug/pgwire-tenant-scoping-29-30 branch April 15, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant