Skip to content

feat: tunnel raw TCP database connections through HTTP CONNECT proxy#357

Open
jaymiracola wants to merge 7 commits into
crossplane-contrib:masterfrom
jaymiracola:feat/http-connect-proxy
Open

feat: tunnel raw TCP database connections through HTTP CONNECT proxy#357
jaymiracola wants to merge 7 commits into
crossplane-contrib:masterfrom
jaymiracola:feat/http-connect-proxy

Conversation

@jaymiracola
Copy link
Copy Markdown

@jaymiracola jaymiracola commented Apr 9, 2026

Summary

  • Adds HTTP CONNECT proxy support to PostgreSQL, MySQL, and MSSQL database drivers
  • When HTTP_PROXY or HTTPS_PROXY is set, connections are tunnelled through the proxy via HTTP CONNECT rather than dialling the database endpoint directly
  • Mirrors the behaviour that net/http-based providers get automatically, extending it to raw TCP database drivers that bypass net/http entirely

Background

Database drivers (lib/pq, go-sql-driver/mysql, go-mssqldb) use net.Dial directly and do not respect HTTP_PROXY/HTTPS_PROXY environment variables. This means provider-sql could not reach database endpoints that require an HTTP CONNECT proxy, for example when running under Upbound Private Network Agent which injects proxy env vars to route traffic to private VPC endpoints.

Implementation

Each driver uses its own dialer injection API:

  • PostgreSQL: pq.NewConnector + connector.Dialer(&httpProxyDialer{}) + sql.OpenDB
  • MySQL: gomysql.ParseDSN + cfg.DialFunc + gomysql.NewConnector + sql.OpenDB
  • MSSQL: mssqldb.NewConnector + connector.Dialer + sql.OpenDB

Proxy detection uses http.ProxyFromEnvironment, the same function the standard library uses, so NO_PROXY and other proxy env var semantics are respected automatically.

When no proxy is configured, openDB falls through to sql.Open with the same driver name and DSN as before. No other code runs. The stdlib itself uses this same pattern in (*Transport).roundTrip: check proxy URL, fall through to direct dial if nil.

The dialer injection APIs used here are each driver's documented extension point:

  • PostgreSQL: pq.Connector.Dialer is the interface pq exports specifically for custom dialers
  • MySQL: mysql.Config.DialFunc is the field go-sql-driver provides for dial interception
  • MSSQL: mssqldb.Connector.Dialer is the field go-mssqldb exposes for custom dialers

Connections that do not match the proxy scope (via NO_PROXY, scheme filtering, etc.) are handled by http.ProxyFromEnvironment before any driver code is touched.

Test plan

  • Unit tests pass: go test ./pkg/clients/...
  • No proxy configured: openDB falls through to sql.Open, behaviour identical to before (TestOpenDBNoProxy)
  • Proxy configured: tunnel completes HTTP CONNECT handshake and returns a usable connection (TestTunnelSuccess)
  • Proxy rejects connection: non-200 response surfaces as an error (TestTunnelProxyRejected

@jaymiracola jaymiracola force-pushed the feat/http-connect-proxy branch 2 times, most recently from b1c4391 to a7cfc19 Compare April 13, 2026 13:35
Copy link
Copy Markdown
Collaborator

@fernandezcuesta fernandezcuesta left a comment

Choose a reason for hiding this comment

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

similarly for the rest

Comment thread pkg/clients/mssql/proxy.go
Comment thread pkg/clients/mssql/proxy.go Outdated
Comment thread pkg/clients/mssql/proxy.go Outdated
Comment thread pkg/clients/mssql/proxy.go Outdated
jaymiracola and others added 6 commits April 14, 2026 10:52
When HTTP_PROXY or HTTPS_PROXY is set, PostgreSQL, MySQL, and MSSQL
connections are tunnelled through the proxy via HTTP CONNECT. This
mirrors how net/http handles proxies automatically for HTTP-based
providers, extending the same behaviour to raw TCP database drivers
which bypass net/http entirely.

Signed-off-by: Jay Miracola <jaymiracola@gmail.com>
Co-authored-by: J. Fernández <7312236+fernandezcuesta@users.noreply.github.com>
Signed-off-by: Jay Miracola <jaymiracola@gmail.com>
Co-authored-by: J. Fernández <7312236+fernandezcuesta@users.noreply.github.com>
Signed-off-by: Jay Miracola <jaymiracola@gmail.com>
Co-authored-by: J. Fernández <7312236+fernandezcuesta@users.noreply.github.com>
Signed-off-by: Jay Miracola <jaymiracola@gmail.com>
Co-authored-by: J. Fernández <7312236+fernandezcuesta@users.noreply.github.com>
Signed-off-by: Jay Miracola <jaymiracola@gmail.com>
Apply the defer-based error handling pattern consistently across all
three proxy files. Named return values allow the deferred closure to
capture and wrap close errors alongside the original error rather than
discarding them silently.

Signed-off-by: Jay Miracola <jaymiracola@gmail.com>
@jaymiracola jaymiracola force-pushed the feat/http-connect-proxy branch from f6dc878 to 08a59bb Compare April 14, 2026 14:53
return nil, err
}
var resp *http.Response
resp, err = http.ReadResponse(bufio.NewReader(conn), req)
Copy link
Copy Markdown
Collaborator

@chlunde chlunde May 15, 2026

Choose a reason for hiding this comment

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

this will possibly hijack bytes from the database server - as NewReader does a 4 KB read and that could be some of the bytes not just from the proxy but the sql server banner data?

return conn, nil
}

// openDB opens a *sql.DB for the given DSN. When HTTP_PROXY or HTTPS_PROXY is
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does it respect HTTP_PROXY? you construct a https URL below?

// set and applicable to the target endpoint, connections are tunnelled through
// the proxy via HTTP CONNECT.
func openDB(endpoint, port, dsn string) (*sql.DB, error) {
req, _ := http.NewRequest(http.MethodConnect, "https://"+endpoint+":"+port, nil)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

will this panic on invalid URL?

Copy link
Copy Markdown
Collaborator

@chlunde chlunde May 15, 2026

Choose a reason for hiding this comment

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

You construct a &http.Request{ manually in the code the other place - should probably use the same here? this would avoid discarding the error too

@fernandezcuesta
Copy link
Copy Markdown
Collaborator

Also, shall we consolidate this to pkg/clients/xsql to avoid duplicated code?

Comment thread pkg/clients/mssql/proxy.go
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.

3 participants