Skip to content

Commit d875dd1

Browse files
jdomeracki-coderssncferreiraf0ssel
authored
fix: always verify TLS on aibridgeproxyd upstream transport (#26131) (#26255)
Cherry-pick backport to `release/2.33`. --------- Co-authored-by: Susana Ferreira <susana@coder.com> Co-authored-by: Garrett Delfosse <delfossegarrett@gmail.com>
1 parent 345a88c commit d875dd1

3 files changed

Lines changed: 60 additions & 17 deletions

File tree

docs/ai-coder/ai-gateway/ai-gateway-proxy/setup.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,6 @@ CODER_AIBRIDGE_PROXY_UPSTREAM_CA=/path/to/corporate-ca.crt
272272

273273
If the system already trusts the upstream proxy's CA certificate, [`CODER_AIBRIDGE_PROXY_UPSTREAM_CA`](../../../reference/cli/server.md#--aibridge-proxy-upstream-ca) is not required.
274274

275-
<!-- TODO(ssncferreira): Add Client Configuration section -->
276-
277-
<!-- TODO(ssncferreira): Add Troubleshooting section -->
278-
279275
## Client Configuration
280276

281277
To use AI Gateway Proxy, AI tools must be configured to:
@@ -374,3 +370,21 @@ This provides a seamless experience where users don't need to configure anything
374370
<!-- TODO(ssncferreira): Add registry link for AI Gateway Proxy module for Coder workspaces: https://github.com/coder/internal/issues/1187 -->
375371

376372
For tool-specific configuration details, check the [client compatibility table](../clients/index.md#compatibility) for clients that require proxy-based integration.
373+
374+
## Troubleshooting
375+
376+
### TLS certificate verification failures
377+
378+
When the Coder access URL uses HTTPS, AI Gateway Proxy must trust the TLS certificate served at that URL (either Coder's
379+
own certificate or a load balancer's, if TLS is terminated there) to forward intercepted requests to AI Gateway.
380+
This primarily affects deployments using a self-signed or internal CA, since publicly trusted CAs are typically already
381+
in the system trust store.
382+
If the certificate is signed by a CA not in the system trust store, the connection fails and the Coder server logs:
383+
384+
```shell
385+
WARN: Cannot read TLS response from mitm'd server tls: failed to verify certificate: x509: certificate signed by unknown authority
386+
```
387+
388+
To resolve, add the CA that signed that certificate to the [system trust store](#system-trust-store) of the host running
389+
AI Gateway Proxy (the same host as `coderd`, since the proxy runs in-process), then restart Coder so AI Gateway Proxy
390+
reloads the trust store.

enterprise/aibridgeproxyd/aibridgeproxyd.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,21 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
289289
proxy.CertStore = NewCertCache()
290290
}
291291

292-
// Always set secure TLS defaults, overriding goproxy's default.
293-
// This ensures secure TLS connections for:
294-
// - HTTPS upstream proxy connections
295-
// - MITM'd requests if aibridge uses HTTPS
292+
// Override goproxy's default transport, which has InsecureSkipVerify: true.
293+
// This applies to all proxy.Tr traffic: MITM'd requests forwarded to aibridge,
294+
// passthrough requests, and HTTPS upstream proxy connections. Proxy is
295+
// intentionally unset so MITM'd requests go directly to aibridge, never
296+
// through an upstream proxy or HTTPS_PROXY env var.
296297
rootCAs, err := x509.SystemCertPool()
297298
if err != nil {
298299
return nil, xerrors.Errorf("failed to load system certificate pool: %w", err)
299300
}
301+
proxy.Tr = &http.Transport{
302+
TLSClientConfig: &tls.Config{
303+
MinVersion: tls.VersionTLS12,
304+
RootCAs: rootCAs,
305+
},
306+
}
300307

301308
srv := &Server{
302309
ctx: ctx,
@@ -333,15 +340,6 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
333340
}
334341
}
335342

336-
// Set transport without Proxy to ensure MITM'd requests go directly to aibridge,
337-
// not through any upstream proxy.
338-
proxy.Tr = &http.Transport{
339-
TLSClientConfig: &tls.Config{
340-
MinVersion: tls.VersionTLS12,
341-
RootCAs: rootCAs,
342-
},
343-
}
344-
345343
// Add custom CA certificate if provided (for corporate proxies with private CAs).
346344
// If no CA certificate is provided, the system certificate pool is used.
347345
if opts.UpstreamProxyCA != "" {

enterprise/aibridgeproxyd/aibridgeproxyd_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,6 +1703,37 @@ func TestListenerTLS(t *testing.T) {
17031703
}
17041704
}
17051705

1706+
// TestProxy_AIBridgeTLSVerification verifies the proxy refuses to forward
1707+
// MITM'd requests to an aibridge endpoint whose TLS certificate is not trusted.
1708+
func TestProxy_AIBridgeTLSVerification(t *testing.T) {
1709+
t.Parallel()
1710+
1711+
// HTTPS server with a self-signed cert untrusted by the system pool,
1712+
// standing in for aibridge.
1713+
aibridgeServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
1714+
w.WriteHeader(http.StatusOK)
1715+
}))
1716+
t.Cleanup(aibridgeServer.Close)
1717+
1718+
srv := newTestProxy(t,
1719+
withCoderAccessURL(aibridgeServer.URL),
1720+
withDomainAllowlist(aibridgeproxyd.HostAnthropic),
1721+
)
1722+
1723+
client := newProxyClient(t, srv, makeProxyAuthHeader("test-token"), getProxyCertPool(t), false)
1724+
1725+
req, err := http.NewRequestWithContext(t.Context(), http.MethodPost,
1726+
"https://"+aibridgeproxyd.HostAnthropic+"/v1/messages",
1727+
strings.NewReader(`{}`))
1728+
require.NoError(t, err)
1729+
1730+
resp, err := client.Do(req)
1731+
if resp != nil {
1732+
defer resp.Body.Close()
1733+
}
1734+
require.Error(t, err, "proxy must refuse to forward MITM'd requests to an untrusted aibridge cert")
1735+
}
1736+
17061737
// TestServeCACert validates that a configured certificate file can be served correctly by the API.
17071738
//
17081739
// Note: Tests for certificate file errors (missing file, invalid PEM) are

0 commit comments

Comments
 (0)