diff --git a/enterprise/aibridgeproxyd/aibridgeproxyd.go b/enterprise/aibridgeproxyd/aibridgeproxyd.go index bf4b8375a58..d9eec0a7d03 100644 --- a/enterprise/aibridgeproxyd/aibridgeproxyd.go +++ b/enterprise/aibridgeproxyd/aibridgeproxyd.go @@ -113,13 +113,15 @@ var blockedIPRanges = func() []net.IPNet { // - decrypting requests using the configured MITM CA certificate // - forwarding requests to aibridged for processing type Server struct { - ctx context.Context - logger slog.Logger - proxy *goproxy.ProxyHttpServer - httpServer *http.Server - listener net.Listener - tlsEnabled bool - coderAccessURL *url.URL + ctx context.Context + logger slog.Logger + proxy *goproxy.ProxyHttpServer + httpServer *http.Server + listener net.Listener + tlsEnabled bool + coderAccessURL *url.URL + // coderAccessPort is the resolved port for the Coder access URL. + coderAccessPort string aibridgeProviderFromHost func(host string) string // caCert is the PEM-encoded MITM CA certificate loaded during initialization. // This is served to clients who need to trust the proxy's generated certificates. @@ -228,7 +230,6 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error) coderAccessPort = "80" } } - coderAccessURL.Host = net.JoinHostPort(coderAccessURL.Hostname(), coderAccessPort) // MITM cert and key are required to intercept and decrypt HTTPS traffic. if opts.MITMCertFile == "" || opts.MITMKeyFile == "" { @@ -312,6 +313,7 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error) proxy: proxy, tlsEnabled: opts.TLSCertFile != "", coderAccessURL: coderAccessURL, + coderAccessPort: coderAccessPort, aibridgeProviderFromHost: aibridgeProviderFromHost, caCert: certPEM, allowedPrivateRanges: allowedPrivateRanges, @@ -806,7 +808,7 @@ func (s *Server) isBlockedIP(ip net.IP, hostname string, port string) bool { // block connections to its own deployment. Hostname-based (not IP-based) // to handle dynamic IPs (DNS changes, load balancers, k8s rescheduling). // The port is normalized at startup to handle URLs without explicit ports. - if strings.EqualFold(hostname, s.coderAccessURL.Hostname()) && port == s.coderAccessURL.Port() { + if strings.EqualFold(hostname, s.coderAccessURL.Hostname()) && port == s.coderAccessPort { return false } diff --git a/enterprise/aibridgeproxyd/aibridgeproxyd_test.go b/enterprise/aibridgeproxyd/aibridgeproxyd_test.go index e8465f174a8..73a0445978e 100644 --- a/enterprise/aibridgeproxyd/aibridgeproxyd_test.go +++ b/enterprise/aibridgeproxyd/aibridgeproxyd_test.go @@ -550,8 +550,7 @@ func TestNew(t *testing.T) { DomainAllowlist: []string{aibridgeproxyd.HostAnthropic}, }) require.NoError(t, err) - require.Equal(t, "localhost", srv.CoderAccessURL().Hostname()) - require.Equal(t, "80", srv.CoderAccessURL().Port()) + require.Equal(t, "localhost", srv.CoderAccessURL().Host) }) t.Run("CoderAccessURLDefaultHTTPSPort", func(t *testing.T) { @@ -568,8 +567,7 @@ func TestNew(t *testing.T) { DomainAllowlist: []string{aibridgeproxyd.HostAnthropic}, }) require.NoError(t, err) - require.Equal(t, "localhost", srv.CoderAccessURL().Hostname()) - require.Equal(t, "443", srv.CoderAccessURL().Port()) + require.Equal(t, "localhost", srv.CoderAccessURL().Host) }) t.Run("CoderAccessURLExplicitPort", func(t *testing.T) { @@ -980,6 +978,49 @@ func TestNew(t *testing.T) { require.NoError(t, err) require.NotNil(t, srv) }) + + t.Run("CoderAccessURLHostPreserved", func(t *testing.T) { + t.Parallel() + + mitmCertFile, mitmKeyFile := getSharedTestMITMCert(t) + logger := slogtest.Make(t, nil) + + srv, err := aibridgeproxyd.New(t.Context(), logger, aibridgeproxyd.Options{ + ListenAddr: "127.0.0.1:0", + CoderAccessURL: "https://coder.example.com", + MITMCertFile: mitmCertFile, + MITMKeyFile: mitmKeyFile, + DomainAllowlist: []string{aibridgeproxyd.HostAnthropic}, + AIBridgeProviderFromHost: func(string) string { return "test-provider" }, + AllowedPrivateCIDRs: []string{"127.0.0.1/32"}, + }) + require.NoError(t, err) + t.Cleanup(func() { _ = srv.Close() }) + + require.Equal(t, "coder.example.com", srv.CoderAccessURL().Host, + "Host must not have :443 appended") + }) + + t.Run("CoderAccessURLExplicitPortPreserved", func(t *testing.T) { + t.Parallel() + + mitmCertFile, mitmKeyFile := getSharedTestMITMCert(t) + logger := slogtest.Make(t, nil) + + srv, err := aibridgeproxyd.New(t.Context(), logger, aibridgeproxyd.Options{ + ListenAddr: "127.0.0.1:0", + CoderAccessURL: "https://coder.example.com:8443", + MITMCertFile: mitmCertFile, + MITMKeyFile: mitmKeyFile, + DomainAllowlist: []string{aibridgeproxyd.HostAnthropic}, + AIBridgeProviderFromHost: func(string) string { return "test-provider" }, + AllowedPrivateCIDRs: []string{"127.0.0.1/32"}, + }) + require.NoError(t, err) + t.Cleanup(func() { _ = srv.Close() }) + + require.Equal(t, "coder.example.com:8443", srv.CoderAccessURL().Host) + }) } func TestClose(t *testing.T) {