fix(postgres): match portless allow patterns for data-plane traffic#53
Conversation
hostPattern.port == 0 (an unspecified pattern port) means "match only ports 80 and 443" -- an HTTP-scheme default enforced by matchesPattern. The Postgres data plane's network-policy check called that same HTTP-oriented matchHost/checkNetworkPolicy with the literal Postgres port (5432), so a portless pattern like "*.neon.tech" could never match a live Postgres connection: every connection was denied under strict policy, regardless of the allow list. This left the network-policy matcher inconsistent with the credential-resolver matcher for the identical connection -- postgresResolverFromEntries already defaults an unspecified pattern port to 5432 for resolver matching, so a host could be accepted for credential resolution and simultaneously rejected by policy. Both examples/gatekeeper-postgres.yaml and docs/content/guides/13-postgres-neon.md document exactly this configuration, so the shipped example was broken as written. Adds matchHostPostgres (proxy/postgres.go) and Proxy.checkNetworkPolicyPostgres (proxy/proxy.go), mirroring the existing HTTP-facing matchHost/checkNetworkPolicy but applying the same port-0-defaults-to-5432 override postgresResolverFromEntries already uses, so the two matchers can no longer disagree. Security-scoped to the Postgres data plane only: matchHost, matchesPattern, checkNetworkPolicy, and checkNetworkPolicyForRequest (the shared matcher and its HTTP/CONNECT callers) are untouched -- a portless allow pattern still means "ports 80 and 443 only" for HTTP traffic, and a pattern pinned to an explicit non-5432 port still denies Postgres connections. Covered by new tests proving: the portless-pattern allow now succeeds, an unlisted host is still denied, an explicit wrong-port pattern is still denied, an explicit correct port pattern still works, and checkNetworkPolicy's HTTP port defaults are unchanged.
|
Claude finished @andybons's task in 2m 13s —— View job Claude finished the code reviewTasks
Note: SummaryThis is a clean, well-scoped fix. I traced the logic carefully and didn't find any bugs.
Minor (non-blocking) observation
No inline comments filed — I didn't find any correctness, security, race, or error-handling issues to anchor them to. |
Problem
Under
network.policy: strict, a portless allow pattern likeallow: ["*.neon.tech"]never matched Postgres data-plane traffic, so every Postgres connection was denied before the resolver ran (client:FATAL: connection not allowed by network policy, SQLSTATE 28000). The shippedexamples/gatekeeper-postgres.yamluses exactly this pattern underpolicy: strictand was therefore broken as written.Root cause:
matchesPattern(proxy/hosts.go) treats a portless pattern as 80/443-only (an HTTP-scheme default), but the Postgres plane evaluates on port 5432 — while the resolver matcher (postgresResolverFromEntries) already defaults an unspecified pattern port to 5432. So for the same connection to the same host, the resolver matcher said "I handle*.neon.tech" while the policy matcher said "denied." An internal inconsistency and a footgun.Fix
Added
matchHostPostgres+Proxy.checkNetworkPolicyPostgres— Postgres-specific matchers that default the pattern port to 5432, byte-identical to the resolver's existing logic and to the HTTP matcher except for that port default. The Postgres plane's policy check now uses them. HTTP/CONNECT policy is untouched —matchHost/matchesPattern/checkNetworkPolicyare not modified and no HTTP path calls the new matchers.Discovered during a Neon connectivity investigation; this was one of two boundaries collapsing into an opaque failure.
Security
This touches the allow/deny boundary, so it got a dedicated review. The change only alters which port a portless pattern is compared against, and only on the Postgres data-plane path: host matching (wildcard/exact/case) is identical,
evil.comstill denies,*.neon.tech:5433still denies a 5432 connection, and reaching the Postgres plane still requires run-token auth + a matching resolver. No new host is reachable that wasn't already reachable via the resolver.Tests
Test-first (red demonstrated):
TestPostgresPolicyAllowsPortlessPattern(end-to-end TLS + SCRAM + policy gate) plus deny/regression cases, andTestCheckNetworkPolicyHTTPPortDefaultsUnchangedpinning the HTTP 80/443-only default.go test -race ./..., vet, gofmt all clean.CHANGELOG
v0.17.3.