Skip to content

[proxy]: Resolve upstreams per request from namespace and metadata#81

Merged
pseudomuto merged 1 commit into
mainfrom
templates-upstreams
Jul 22, 2026
Merged

[proxy]: Resolve upstreams per request from namespace and metadata#81
pseudomuto merged 1 commit into
mainfrom
templates-upstreams

Conversation

@pseudomuto

Copy link
Copy Markdown
Collaborator

The proxy could route to multiple named upstreams but only at fixed addresses. Migrations and multi-region setups may require one proxy to reach a different endpoint per namespace (e.g. Cloud per-namespace endpoints), where the dial address and TLS server name depend on the request's namespace and metadata.

The router stamps the namespace it already extracts onto an internal metadata header (internal/transport/meta) so the typed per-upstream hop resolves without re-parsing payloads. connect.Conn wraps the connection pool as a grpc.ClientConnInterface whose target is chosen per call by a Resolver: a StaticResolver for fixed upstreams (dialed eagerly) or a DynamicResolver that renders hostPort/serverName and rebuilds credentials per request. The pool is now keyed by a logical key distinct from the dial target, so two connections to one address with different TLS server names don't collapse. A rendered address that is empty or malformed fails the request loudly instead of dialing garbage.

This finalizes the upstream-routing RFC proposed in #32.

@pseudomuto
pseudomuto requested a review from Copilot July 22, 2026 14:21
@pseudomuto
pseudomuto requested a review from a team as a code owner July 22, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the proxy’s upstream-routing model to support per-request upstream resolution (e.g., per-namespace/per-metadata) by introducing an internal namespace-metadata contract, a per-call resolving grpc.ClientConnInterface, and a connection pool keyed by a logical cache key (not just the dial target). This enables templated upstream host/port and TLS server name resolution without re-parsing request payloads.

Changes:

  • Added internal/transport/meta to stamp/extract the router-derived namespace via internal gRPC metadata and wired the router to stamp it on forwarded calls.
  • Introduced internal/transport/connect.Conn + Resolver abstraction and updated the connection Pool to key by logical key (supporting multiple conns to the same target with different TLS identities).
  • Added internal/proxy.DynamicResolver to render hostPort/serverName per request from namespace + outgoing metadata, and updated proxy fx wiring/server construction accordingly (including splitting Listen from Start).

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/transport/meta/meta.go Defines internal namespace metadata header and helpers to set/get it on outgoing contexts.
internal/transport/meta/meta_test.go Adds unit tests for namespace header stamping and overwrite behavior.
internal/transport/creds/tls.go Adds client TLS server-name override support (SNI + verification).
internal/transport/creds/tls_test.go Expands tests to cover client TLS with/without server name.
internal/transport/connect/pool.go Changes pool keying from host to logical key and adds ConnOrCreate(key,target,...).
internal/transport/connect/pool_test.go Updates/extends pool tests for new key/target semantics and concurrency behavior.
internal/transport/connect/doc.go Updates package docs and introduces the per-call resolving Conn concept.
internal/transport/connect/conn.go Adds Conn wrapper implementing grpc.ClientConnInterface with per-call resolution.
internal/transport/connect/conn_test.go Adds tests for static vs dynamic resolver behavior and error propagation.
internal/router/handler.go Stamps extracted namespace into outgoing metadata before forwarding upstream.
internal/router/handler_test.go Adds an integration-style test proving spoofed namespace headers are overwritten.
internal/router/fx.go Updates upstream socket dialing to use Pool.ConnOrCreate.
internal/proxy/server.go Refactors proxy server to accept a grpc.ClientConnInterface, and splits Listen from Start.
internal/proxy/server_test.go Updates tests for new New(hostPort, cc) API and Listen/Start split.
internal/proxy/translation_test.go Adapts translation tests to new proxy construction and listener lifecycle.
internal/proxy/resolver.go Adds DynamicResolver for per-request hostPort/serverName + dial option resolution.
internal/proxy/resolver_test.go Adds tests for template parsing, metadata behavior, invalid addresses, and option-factory errors.
internal/proxy/fx.go Wires pool-backed resolving conns into upstream proxies; adds resolver selection logic.
internal/proxy/fx_test.go Updates fx tests to accept templated upstreams and requires connect.Module.
internal/config/upstream.go Treats templated TLS server name as making the upstream templated.
internal/config/config_test.go Extends IsTemplated tests to cover templated/static TLS server name behavior.
e2e/templated_upstream_test.go Adds an e2e test proving per-request rendered-address routing reaches the correct upstream.
e2e/harness_test.go Updates harness wiring to include connect.Module and reflects Listen behavior in docs.

Comment thread internal/proxy/resolver.go
Comment thread internal/proxy/fx.go
@pseudomuto
pseudomuto force-pushed the templates-upstreams branch from 09c42d9 to a570175 Compare July 22, 2026 15:54
The proxy could route to multiple named upstreams but only at fixed
addresses. Migrations and multi-region setups may require one proxy to
reach a different endpoint per namespace (e.g. Cloud per-namespace
endpoints), where the dial address and TLS server name depend on the
request's namespace and metadata.

The router stamps the namespace it already extracts onto an internal
metadata header (internal/transport/meta) so the typed per-upstream hop
resolves without re-parsing payloads. connect.Conn wraps the connection
pool as a grpc.ClientConnInterface whose target is chosen per call by a
Resolver: a StaticResolver for fixed upstreams (dialed eagerly) or a
DynamicResolver that renders hostPort/serverName and rebuilds
credentials per request. The pool is now keyed by a logical key distinct
from the dial target, so two connections to one address with different
TLS server names don't collapse. A rendered address that is empty or
malformed fails the request loudly instead of dialing garbage.

For a templated mTLS upstream only the rendered server name varies per
request, so rebuilding credentials would otherwise re-read and re-parse
the certificate and CA files on every request. A shared creds.CertLoader
loads that material once and caches it (safe to share concurrently, as
it is immutable after the first load), so per-request resolution builds
the dial options without touching disk. Fixed-address upstreams load
once as before, so a rotated cert is picked up only on restart either
way.

This finalizes the upstream-routing RFC proposed in #32.
@pseudomuto
pseudomuto force-pushed the templates-upstreams branch from a570175 to 7126782 Compare July 22, 2026 15:59
@pseudomuto
pseudomuto merged commit 95fdab7 into main Jul 22, 2026
6 checks passed
@pseudomuto
pseudomuto deleted the templates-upstreams branch July 22, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants