refactor: avoid terminology collision with aibridge by renaming passthrough to tunneled (#21562)

## Description

Renames "passthrough" to "tunneled" in aiproxy to avoid terminology
collision with aibridge, which has its own passthrough concept.

Follow-up from:
https://github.com/coder/coder/pull/21512#discussion_r2698231778

---------

Co-authored-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Susana Ferreira
2026-01-19 13:23:42 +00:00
committed by GitHub
co-authored by Danny Kopping
parent 08343a7a9f
commit a002fbbae6
7 changed files with 66 additions and 66 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ AI BRIDGE PROXY OPTIONS:
The address the AI Bridge Proxy will listen on.
--aibridge-proxy-upstream string, $CODER_AIBRIDGE_PROXY_UPSTREAM
URL of an upstream HTTP proxy to chain passthrough (non-allowlisted)
URL of an upstream HTTP proxy to chain tunneled (non-allowlisted)
requests through. Format: http://[user:pass@]host:port or
https://[user:pass@]host:port.
+1 -1
View File
@@ -798,7 +798,7 @@ aibridgeproxy:
domain_allowlist:
- api.anthropic.com
- api.openai.com
# URL of an upstream HTTP proxy to chain passthrough (non-allowlisted) requests
# URL of an upstream HTTP proxy to chain tunneled (non-allowlisted) requests
# through. Format: http://[user:pass@]host:port or https://[user:pass@]host:port.
# (default: <unset>, type: string)
upstream_proxy: ""
+1 -1
View File
@@ -3549,7 +3549,7 @@ Write out the current server config as YAML to stdout.`,
},
{
Name: "AI Bridge Proxy Upstream Proxy",
Description: "URL of an upstream HTTP proxy to chain passthrough (non-allowlisted) requests through. Format: http://[user:pass@]host:port or https://[user:pass@]host:port.",
Description: "URL of an upstream HTTP proxy to chain tunneled (non-allowlisted) requests through. Format: http://[user:pass@]host:port or https://[user:pass@]host:port.",
Flag: "aibridge-proxy-upstream",
Env: "CODER_AIBRIDGE_PROXY_UPSTREAM",
Value: &c.AI.BridgeProxyConfig.UpstreamProxy,
+1 -1
View File
@@ -1897,7 +1897,7 @@ Path to the CA private key file for AI Bridge Proxy.
| Environment | <code>$CODER_AIBRIDGE_PROXY_UPSTREAM</code> |
| YAML | <code>aibridgeproxy.upstream_proxy</code> |
URL of an upstream HTTP proxy to chain passthrough (non-allowlisted) requests through. Format: http://[user:pass@]host:port or https://[user:pass@]host:port.
URL of an upstream HTTP proxy to chain tunneled (non-allowlisted) requests through. Format: http://[user:pass@]host:port or https://[user:pass@]host:port.
### --aibridge-proxy-upstream-ca
+7 -7
View File
@@ -75,8 +75,8 @@ type Options struct {
// Only requests to these domains will be MITM'd and forwarded to aibridged.
// Requests to other domains will be tunneled directly without decryption.
DomainAllowlist []string
// UpstreamProxy is the URL of an upstream HTTP proxy to chain passthrough
// (non-allowlisted) requests through. If empty, passthrough requests connect
// UpstreamProxy is the URL of an upstream HTTP proxy to chain tunneled
// (non-allowlisted) requests through. If empty, tunneled requests connect
// directly to their destinations.
// Format: http://[user:pass@]host:port or https://[user:pass@]host:port
UpstreamProxy string
@@ -152,7 +152,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
return nil, xerrors.Errorf("failed to load system certificate pool: %w", err)
}
// Configure upstream proxy for passthrough (non-allowlisted) requests.
// Configure upstream proxy for tunneled (non-allowlisted) requests.
// This only affects CONNECT requests to domains not in the allowlist.
// MITM'd requests (allowlisted domains) are handled by aiproxy and forwarded
// to aibridge directly, not through the upstream proxy. AI Bridge respects
@@ -164,7 +164,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
return nil, xerrors.Errorf("invalid upstream proxy URL %q: %w", opts.UpstreamProxy, err)
}
logger.Info(ctx, "configuring upstream proxy for passthrough requests",
logger.Info(ctx, "configuring upstream proxy for tunneled requests",
slog.F("upstream", upstreamURL.Host),
)
@@ -196,7 +196,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
}
}
// Configure passthrough CONNECT requests to go through upstream proxy.
// Configure tunneled CONNECT requests to go through upstream proxy.
// This only affects non-allowlisted domains; allowlisted domains are
// MITM'd and forwarded to aibridge.
proxy.ConnectDial = proxy.NewConnectDialToProxy(opts.UpstreamProxy)
@@ -223,7 +223,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
srv.authMiddleware,
)
// Handle decrypted requests: route to aibridged for known AI providers, or passthrough to original destination.
// Handle decrypted requests: route to aibridged for known AI providers, or tunnel to original destination.
proxy.OnRequest().DoFunc(srv.handleRequest)
// Create listener first so we can get the actual address.
@@ -474,7 +474,7 @@ func (s *Server) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.
slog.F("method", req.Method),
slog.F("path", originalPath),
)
// Passthrough to the original destination.
// Tunnel (forward) directly to the original destination (no aibridge routing for this host).
return req, nil
}
@@ -216,7 +216,7 @@ func getProxyCertPool(t *testing.T) *x509.CertPool {
// newProxyClient creates an HTTP client configured to use the proxy.
// It adds a Proxy-Authorization header with the provided token for authentication.
// The certPool parameter specifies which certificates the client should trust.
// For MITM'd requests, use the proxy's CA. For passthrough, use the target server's cert.
// For MITM'd requests, use the proxy's CA. For tunneled requests, use the target server's cert.
func newProxyClient(t *testing.T, srv *aibridgeproxyd.Server, proxyAuth string, certPool *x509.CertPool) *http.Client {
t.Helper()
@@ -585,17 +585,17 @@ func TestProxy_CertCaching(t *testing.T) {
tests := []struct {
name string
domainAllowlist []string
passthrough bool
tunneled bool
}{
{
name: "AllowlistedDomainCached",
domainAllowlist: nil, // will use targetURL.Hostname()
passthrough: false,
tunneled: false,
},
{
name: "NonAllowlistedDomainNotCached",
domainAllowlist: []string{"other.example.com"},
passthrough: true,
tunneled: true,
},
}
@@ -624,13 +624,13 @@ func TestProxy_CertCaching(t *testing.T) {
withDomainAllowlist(domainAllowlist...),
)
// Build the cert pool for the client to trust.
// Build the cert pool for the client to trust:
// - For tunneled requests, the client connects directly to the target server
// through a tunnel, so it needs to trust the target's self-signed certificate.
// - For MITM'd requests, the client connects through the proxy which generates
// certificates signed by our test CA, so it needs to trust the proxy's CA.
// - For passthrough requests, the client connects directly to the target server
// through a tunnel, so it needs to trust the target's self-signed certificate.
var certPool *x509.CertPool
if tt.passthrough {
if tt.tunneled {
certPool = x509.NewCertPool()
certPool.AddCert(targetServer.Certificate())
} else {
@@ -653,7 +653,7 @@ func TestProxy_CertCaching(t *testing.T) {
})
require.NoError(t, err)
if tt.passthrough {
if tt.tunneled {
// Certificate should NOT have been cached since request was tunneled.
require.Equal(t, 1, genCalls, "certificate should NOT have been cached for non-allowlisted domain")
} else {
@@ -803,8 +803,8 @@ func TestProxy_MITM(t *testing.T) {
name string
domainAllowlist []string
allowedPorts []string
buildTargetURL func(passthroughURL *url.URL) (string, error)
passthrough bool
buildTargetURL func(tunneledURL *url.URL) (string, error)
tunneled bool
noAIBridgeRouting bool
expectedPath string
}{
@@ -845,25 +845,25 @@ func TestProxy_MITM(t *testing.T) {
expectedPath: "/api/v2/aibridge/openai/v1/chat/completions",
},
{
name: "PassthroughUnknownHost",
name: "TunneledUnknownHost",
domainAllowlist: []string{"other.example.com"},
allowedPorts: nil, // will use passthroughURL.Port()
buildTargetURL: func(passthroughURL *url.URL) (string, error) {
return url.JoinPath(passthroughURL.String(), "/some/path")
allowedPorts: nil, // will use tunneledURL.Port()
buildTargetURL: func(tunneledURL *url.URL) (string, error) {
return url.JoinPath(tunneledURL.String(), "/some/path")
},
passthrough: true,
tunneled: true,
},
// The host is MITM'd but has no provider mapping.
// The request is decrypted but passed through to the original destination
// The request is decrypted but forwarded to the original destination
// instead of being routed to aibridge.
{
name: "MitmdWithoutAIBridgeRouting",
domainAllowlist: nil, // will use passthroughURL.Hostname()
allowedPorts: nil, // will use passthroughURL.Port()
buildTargetURL: func(passthroughURL *url.URL) (string, error) {
return url.JoinPath(passthroughURL.String(), "/some/path")
domainAllowlist: nil, // will use tunneledURL.Hostname()
allowedPorts: nil, // will use tunneledURL.Port()
buildTargetURL: func(tunneledURL *url.URL) (string, error) {
return url.JoinPath(tunneledURL.String(), "/some/path")
},
passthrough: false,
tunneled: false,
noAIBridgeRouting: true,
},
}
@@ -885,22 +885,22 @@ func TestProxy_MITM(t *testing.T) {
}))
t.Cleanup(func() { aibridgedServer.Close() })
// Create a mock target server for passthrough tests.
passthroughServer, passthroughURL := newTargetServer(t, func(w http.ResponseWriter, _ *http.Request) {
// Create a mock target server for tunneled tests.
tunneledServer, tunneledURL := newTargetServer(t, func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("hello from passthrough"))
_, _ = w.Write([]byte("hello from tunneled"))
})
// Configure allowed ports.
allowedPorts := tt.allowedPorts
if allowedPorts == nil {
allowedPorts = []string{passthroughURL.Port()}
allowedPorts = []string{tunneledURL.Port()}
}
// Configure domain allowlist.
domainAllowlist := tt.domainAllowlist
if domainAllowlist == nil {
domainAllowlist = []string{passthroughURL.Hostname()}
domainAllowlist = []string{tunneledURL.Hostname()}
}
// Start the proxy server pointing to our mock aibridged.
@@ -911,18 +911,18 @@ func TestProxy_MITM(t *testing.T) {
)
// Build the target URL:
targetURL, err := tt.buildTargetURL(passthroughURL)
targetURL, err := tt.buildTargetURL(tunneledURL)
require.NoError(t, err)
// Build the cert pool for the client to trust.
// Build the cert pool for the client to trust:
// - For tunneled requests, the client connects directly to the target server
// through a tunnel, so it needs to trust the target's self-signed certificate.
// - For MITM'd requests, the client connects through the proxy which generates
// certificates signed by our test CA, so it needs to trust the proxy's CA.
// - For passthrough requests, the client connects directly to the target server
// through a tunnel, so it needs to trust the target's self-signed certificate.
var certPool *x509.CertPool
if tt.passthrough {
if tt.tunneled {
certPool = x509.NewCertPool()
certPool.AddCert(passthroughServer.Certificate())
certPool.AddCert(tunneledServer.Certificate())
} else {
certPool = getProxyCertPool(t)
}
@@ -941,11 +941,11 @@ func TestProxy_MITM(t *testing.T) {
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
if tt.passthrough || tt.noAIBridgeRouting {
if tt.tunneled || tt.noAIBridgeRouting {
// Verify request went to target server, not aibridged.
require.Equal(t, "hello from passthrough", string(body))
require.Empty(t, receivedPath, "aibridged should not receive passthrough requests")
require.Empty(t, receivedAuth, "passthrough requests are not authenticated by the proxy")
require.Equal(t, "hello from tunneled", string(body))
require.Empty(t, receivedPath, "aibridged should not receive tunneled requests")
require.Empty(t, receivedAuth, "tunneled requests are not authenticated by the proxy")
} else {
// Verify the request was routed to aibridged correctly.
require.Equal(t, "hello from aibridged", string(body))
@@ -1078,39 +1078,39 @@ func TestUpstreamProxy(t *testing.T) {
tests := []struct {
name string
// passthrough determines whether the request should be tunneled through
// tunneled determines whether the request should be tunneled through
// the upstream proxy (true) or MITM'd by aiproxy (false).
// When true, the target domain is NOT in the allowlist.
// When false, the target domain IS in the allowlist.
passthrough bool
tunneled bool
// upstreamProxyTLS determines whether the upstream proxy uses TLS.
// When true, aiproxy must be configured with the upstream proxy's CA.
upstreamProxyTLS bool
// buildTargetURL constructs the request URL. For passthrough, it uses
// buildTargetURL constructs the request URL. For tunneled requests, it uses
// the final destination URL. For MITM, it uses api.anthropic.com.
buildTargetURL func(finalDestinationURL *url.URL) string
// expectedAIBridgePath is the path aibridge should receive for MITM requests.
expectedAIBridgePath string
}{
{
name: "NonAllowlistedDomain_PassthroughToHTTPUpstreamProxy",
passthrough: true,
name: "NonAllowlistedDomain_TunneledToHTTPUpstreamProxy",
tunneled: true,
upstreamProxyTLS: false,
buildTargetURL: func(finalDestinationURL *url.URL) string {
return fmt.Sprintf("https://%s/passthrough-path", finalDestinationURL.Host)
return fmt.Sprintf("https://%s/tunneled-path", finalDestinationURL.Host)
},
},
{
name: "NonAllowlistedDomain_PassthroughToHTTPSUpstreamProxy",
passthrough: true,
name: "NonAllowlistedDomain_TunneledToHTTPSUpstreamProxy",
tunneled: true,
upstreamProxyTLS: true,
buildTargetURL: func(finalDestinationURL *url.URL) string {
return fmt.Sprintf("https://%s/passthrough-path", finalDestinationURL.Host)
return fmt.Sprintf("https://%s/tunneled-path", finalDestinationURL.Host)
},
},
{
name: "AllowlistedDomain_MITMByAIProxy",
passthrough: false,
tunneled: false,
upstreamProxyTLS: false,
buildTargetURL: func(_ *url.URL) string {
return "https://api.anthropic.com:443/v1/messages"
@@ -1137,7 +1137,7 @@ func TestUpstreamProxy(t *testing.T) {
)
// Create mock final destination server representing the actual target:
// - For passthrough requests, traffic should reach this server.
// - For tunneled requests, traffic should reach this server.
// - For MITM requests, traffic should NOT reach this server.
finalDestination, finalDestinationURL := newTargetServer(t, func(w http.ResponseWriter, r *http.Request) {
finalDestinationReceived = true
@@ -1223,7 +1223,7 @@ func TestUpstreamProxy(t *testing.T) {
t.Cleanup(upstreamProxy.Close)
// Create a mock aibridged server:
// - For passthrough requests, traffic should NOT reach this server.
// - For tunneled requests, traffic should NOT reach this server.
// - For MITM requests, aiproxy rewrites the URL and forwards here.
aibridgeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
aibridgeReceived = true
@@ -1246,7 +1246,7 @@ func TestUpstreamProxy(t *testing.T) {
require.NoError(t, err)
// Configure allowlist based on test case:
// - For passthrough, api.anthropic.com is in allowlist, but we target a different host.
// - For tunneled requests, api.anthropic.com is in allowlist, but we target a different host.
// - For MITM, api.anthropic.com must be in the allowlist.
domainAllowlist := []string{"api.anthropic.com"}
@@ -1263,10 +1263,10 @@ func TestUpstreamProxy(t *testing.T) {
srv := newTestProxy(t, proxyOpts...)
// Configure certificate trust based on test case:
// - For passthrough: client trusts final destination's CA.
// - For tunneled requests: client trusts final destination's CA.
// - For MITM: client trusts aiproxy's CA (fake certs).
var certPool *x509.CertPool
if tt.passthrough {
if tt.tunneled {
certPool = x509.NewCertPool()
certPool.AddCert(finalDestination.Certificate())
} else {
@@ -1289,13 +1289,13 @@ func TestUpstreamProxy(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode)
// Verify the request flow based on test case.
if tt.passthrough {
if tt.tunneled {
require.True(t, upstreamProxyCONNECTReceived,
"upstream proxy should receive CONNECT for non-allowlisted domain")
require.Equal(t, finalDestinationURL.Host, upstreamProxyCONNECTHost,
"upstream proxy should receive CONNECT to correct host")
require.True(t, finalDestinationReceived,
"final destination should receive the passthrough request")
"final destination should receive the tunneled request")
require.Equal(t, parsedTargetURL.Path, finalDestinationPath,
"final destination should receive correct path")
require.Equal(t, requestBody, finalDestinationBody,
+1 -1
View File
@@ -167,7 +167,7 @@ AI BRIDGE PROXY OPTIONS:
The address the AI Bridge Proxy will listen on.
--aibridge-proxy-upstream string, $CODER_AIBRIDGE_PROXY_UPSTREAM
URL of an upstream HTTP proxy to chain passthrough (non-allowlisted)
URL of an upstream HTTP proxy to chain tunneled (non-allowlisted)
requests through. Format: http://[user:pass@]host:port or
https://[user:pass@]host:port.