google.golang.org/grpc@v1.72.2/internal/transport/proxy_ext_test.go (about)

     1  /*
     2   *
     3   * Copyright 2024 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package transport_test
    20  
    21  import (
    22  	"context"
    23  	"encoding/base64"
    24  	"fmt"
    25  	"net"
    26  	"net/http"
    27  	"net/netip"
    28  	"net/url"
    29  	"testing"
    30  	"time"
    31  
    32  	"golang.org/x/net/http/httpproxy"
    33  	"google.golang.org/grpc"
    34  	"google.golang.org/grpc/credentials/insecure"
    35  	"google.golang.org/grpc/internal/grpctest"
    36  	"google.golang.org/grpc/internal/resolver/delegatingresolver"
    37  	"google.golang.org/grpc/internal/stubserver"
    38  	"google.golang.org/grpc/internal/testutils"
    39  	"google.golang.org/grpc/internal/testutils/proxyserver"
    40  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    41  	testpb "google.golang.org/grpc/interop/grpc_testing"
    42  	"google.golang.org/grpc/resolver"
    43  	"google.golang.org/grpc/resolver/manual"
    44  )
    45  
    46  const defaultTestTimeout = 10 * time.Second
    47  
    48  type s struct {
    49  	grpctest.Tester
    50  }
    51  
    52  func Test(t *testing.T) {
    53  	grpctest.RunSubTests(t, s{})
    54  }
    55  
    56  func startBackendServer(t *testing.T) *stubserver.StubServer {
    57  	t.Helper()
    58  	backend := &stubserver.StubServer{
    59  		EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) { return &testpb.Empty{}, nil },
    60  	}
    61  	if err := backend.StartServer(); err != nil {
    62  		t.Fatalf("failed to start backend: %v", err)
    63  	}
    64  	t.Logf("Started TestService backend at: %q", backend.Address)
    65  	t.Cleanup(backend.Stop)
    66  	return backend
    67  }
    68  
    69  func isIPAddr(addr string) bool {
    70  	_, err := netip.ParseAddr(addr)
    71  	return err == nil
    72  }
    73  
    74  func overrideTestHTTPSProxy(t *testing.T, proxyAddr string) {
    75  	t.Helper()
    76  	hpfe := func(req *http.Request) (*url.URL, error) {
    77  		return &url.URL{
    78  			Scheme: "https",
    79  			Host:   proxyAddr,
    80  		}, nil
    81  	}
    82  	originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
    83  	delegatingresolver.HTTPSProxyFromEnvironment = hpfe
    84  	t.Cleanup(func() { delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe })
    85  }
    86  
    87  // Tests the scenario where grpc.Dial is performed using a proxy with the
    88  // default resolver in the target URI. The test verifies that the connection is
    89  // established to the proxy server, sends the unresolved target URI in the HTTP
    90  // CONNECT request and is successfully connected to the backend server.
    91  func (s) TestGRPCDialWithProxy(t *testing.T) {
    92  	backend := startBackendServer(t)
    93  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
    94  	proxyCalled := false
    95  	reqCheck := func(req *http.Request) {
    96  		proxyCalled = true
    97  		host, _, err := net.SplitHostPort(req.URL.Host)
    98  		if err != nil {
    99  			t.Error(err)
   100  		}
   101  		if got, want := host, "localhost"; got != want {
   102  			t.Errorf(" Unexpected request host: %s, want = %s ", got, want)
   103  		}
   104  	}
   105  	pServer := proxyserver.New(t, reqCheck, false)
   106  	// Use "localhost:<port>" to verify the proxy address is handled
   107  	// correctly by the delegating resolver and connects to the proxy server
   108  	// correctly even when unresolved.
   109  	pAddr := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, pServer.Addr))
   110  
   111  	overrideTestHTTPSProxy(t, pAddr)
   112  
   113  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   114  	defer cancel()
   115  	conn, err := grpc.Dial(unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials()))
   116  	if err != nil {
   117  		t.Fatalf("grpc.Dial(%s) failed: %v", unresolvedTargetURI, err)
   118  	}
   119  	defer conn.Close()
   120  
   121  	// Send an empty RPC to the backend through the proxy.
   122  	client := testgrpc.NewTestServiceClient(conn)
   123  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   124  		t.Fatalf("EmptyCall failed: %v", err)
   125  	}
   126  
   127  	if !proxyCalled {
   128  		t.Fatalf("Proxy not connected")
   129  	}
   130  }
   131  
   132  // Tests the scenario where `grpc.Dial` is performed with a proxy and the "dns"
   133  // scheme for the target. The test verifies that the proxy URI is correctly
   134  // resolved and that the target URI resolution on the client preserves the
   135  // original behavior of `grpc.Dial`. It also ensures that a connection is
   136  // established to the proxy server, with the resolved target URI sent in the
   137  // HTTP CONNECT request, successfully connecting to the backend server.
   138  func (s) TestGRPCDialWithDNSAndProxy(t *testing.T) {
   139  	backend := startBackendServer(t)
   140  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
   141  	proxyCalled := false
   142  	reqCheck := func(req *http.Request) {
   143  		proxyCalled = true
   144  
   145  		host, _, err := net.SplitHostPort(req.URL.Host)
   146  		if err != nil {
   147  			t.Error(err)
   148  		}
   149  		if got, want := isIPAddr(host), true; got != want {
   150  			t.Errorf("isIPAddr(%q) = %t, want = %t", host, got, want)
   151  		}
   152  	}
   153  	pServer := proxyserver.New(t, reqCheck, false)
   154  
   155  	overrideTestHTTPSProxy(t, pServer.Addr)
   156  
   157  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   158  	defer cancel()
   159  	conn, err := grpc.Dial("dns:///"+unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials()))
   160  	if err != nil {
   161  		t.Fatalf("grpc.Dial(%s) failed: %v", "dns:///"+unresolvedTargetURI, err)
   162  	}
   163  	defer conn.Close()
   164  
   165  	// Send an empty RPC to the backend through the proxy.
   166  	client := testgrpc.NewTestServiceClient(conn)
   167  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   168  		t.Fatalf("EmptyCall failed: %v", err)
   169  	}
   170  
   171  	if !proxyCalled {
   172  		t.Fatalf("Proxy not connected")
   173  	}
   174  }
   175  
   176  // Tests the scenario where `grpc.NewClient` is used with the default DNS
   177  // resolver for the target URI and a proxy is configured. The test verifies
   178  // that the client resolves proxy URI, connects to the proxy server, sends the
   179  // unresolved target URI in the HTTP CONNECT request, and successfully
   180  // establishes a connection to the backend server.
   181  func (s) TestNewClientWithProxy(t *testing.T) {
   182  	backend := startBackendServer(t)
   183  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
   184  	proxyCalled := false
   185  	reqCheck := func(req *http.Request) {
   186  		proxyCalled = true
   187  		host, _, err := net.SplitHostPort(req.URL.Host)
   188  		if err != nil {
   189  			t.Error(err)
   190  		}
   191  		if got, want := host, "localhost"; got != want {
   192  			t.Errorf(" Unexpected request host: %s, want = %s ", got, want)
   193  		}
   194  	}
   195  	pServer := proxyserver.New(t, reqCheck, false)
   196  	// Use "localhost:<port>" to verify the proxy address is handled
   197  	// correctly by the delegating resolver and connects to the proxy server
   198  	// correctly even when unresolved.
   199  	pAddr := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, pServer.Addr))
   200  
   201  	overrideTestHTTPSProxy(t, pAddr)
   202  
   203  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   204  	defer cancel()
   205  	conn, err := grpc.NewClient(unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials()))
   206  	if err != nil {
   207  		t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err)
   208  	}
   209  	defer conn.Close()
   210  
   211  	// Send an empty RPC to the backend through the proxy.
   212  	client := testgrpc.NewTestServiceClient(conn)
   213  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   214  		t.Fatalf("EmptyCall failed: %v", err)
   215  	}
   216  	if !proxyCalled {
   217  		t.Fatalf("Proxy not connected")
   218  	}
   219  }
   220  
   221  // Tests the scenario where grpc.NewClient is used with a custom target URI
   222  // scheme and a proxy is configured. The test verifies that the client
   223  // successfully connects to the proxy server, resolves the proxy URI correctly,
   224  // includes the resolved target URI in the HTTP CONNECT request, and
   225  // establishes a connection to the backend server.
   226  func (s) TestNewClientWithProxyAndCustomResolver(t *testing.T) {
   227  	backend := startBackendServer(t)
   228  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
   229  	proxyCalled := false
   230  	reqCheck := func(req *http.Request) {
   231  		proxyCalled = true
   232  		host, _, err := net.SplitHostPort(req.URL.Host)
   233  		if err != nil {
   234  			t.Error(err)
   235  		}
   236  		if got, want := isIPAddr(host), true; got != want {
   237  			t.Errorf("isIPAddr(%q) = %t, want = %t", host, got, want)
   238  		}
   239  	}
   240  	pServer := proxyserver.New(t, reqCheck, false)
   241  
   242  	overrideTestHTTPSProxy(t, pServer.Addr)
   243  
   244  	// Create and update a custom resolver for target URI.
   245  	targetResolver := manual.NewBuilderWithScheme("test")
   246  	resolver.Register(targetResolver)
   247  	targetResolver.InitialState(resolver.State{Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: backend.Address}}}}})
   248  
   249  	// Dial to the proxy server.
   250  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   251  	defer cancel()
   252  	conn, err := grpc.NewClient(targetResolver.Scheme()+":///"+unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials()))
   253  	if err != nil {
   254  		t.Fatalf("grpc.NewClient(%s) failed: %v", targetResolver.Scheme()+":///"+unresolvedTargetURI, err)
   255  	}
   256  	defer conn.Close()
   257  
   258  	// Send an empty RPC to the backend through the proxy.
   259  	client := testgrpc.NewTestServiceClient(conn)
   260  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   261  		t.Fatalf("EmptyCall() failed: %v", err)
   262  	}
   263  
   264  	if !proxyCalled {
   265  		t.Fatalf("Proxy not connected")
   266  	}
   267  }
   268  
   269  // Tests the scenario where grpc.NewClient is used with the default "dns"
   270  // resolver and the dial option grpc.WithLocalDNSResolution() is set,
   271  // enabling target resolution on the client. The test verifies that target
   272  // resolution happens on the client by sending resolved target URI in HTTP
   273  // CONNECT request, the proxy URI is resolved correctly, and the connection is
   274  // successfully established with the backend server through the proxy.
   275  func (s) TestNewClientWithProxyAndTargetResolutionEnabled(t *testing.T) {
   276  	backend := startBackendServer(t)
   277  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
   278  	proxyCalled := false
   279  	reqCheck := func(req *http.Request) {
   280  		proxyCalled = true
   281  		host, _, err := net.SplitHostPort(req.URL.Host)
   282  		if err != nil {
   283  			t.Error(err)
   284  		}
   285  		if got, want := isIPAddr(host), true; got != want {
   286  			t.Errorf("isIPAddr(%q) = %t, want = %t", host, got, want)
   287  		}
   288  	}
   289  	pServer := proxyserver.New(t, reqCheck, false)
   290  
   291  	overrideTestHTTPSProxy(t, pServer.Addr)
   292  
   293  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   294  	defer cancel()
   295  	conn, err := grpc.NewClient(unresolvedTargetURI, grpc.WithLocalDNSResolution(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   296  	if err != nil {
   297  		t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err)
   298  	}
   299  	defer conn.Close()
   300  
   301  	// Send an empty RPC to the backend through the proxy.
   302  	client := testgrpc.NewTestServiceClient(conn)
   303  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   304  		t.Fatalf("EmptyCall failed: %v", err)
   305  	}
   306  
   307  	if !proxyCalled {
   308  		t.Fatalf("Proxy not connected")
   309  	}
   310  }
   311  
   312  // Tests the scenario where grpc.NewClient is used with grpc.WithNoProxy() set,
   313  // explicitly disabling proxy usage. The test verifies that the client does not
   314  // dial the proxy but directly connects to the backend server. It also checks
   315  // that the proxy resolution function is not called and that the proxy server
   316  // never receives a connection request.
   317  func (s) TestNewClientWithNoProxy(t *testing.T) {
   318  	backend := startBackendServer(t)
   319  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
   320  	reqCheck := func(_ *http.Request) { t.Error("proxy server should not have received a Connect request") }
   321  	pServer := proxyserver.New(t, reqCheck, false)
   322  
   323  	overrideTestHTTPSProxy(t, pServer.Addr)
   324  
   325  	dopts := []grpc.DialOption{
   326  		grpc.WithTransportCredentials(insecure.NewCredentials()),
   327  		grpc.WithNoProxy(), // Disable proxy.
   328  	}
   329  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   330  	defer cancel()
   331  	conn, err := grpc.NewClient(unresolvedTargetURI, dopts...)
   332  	if err != nil {
   333  		t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err)
   334  	}
   335  	defer conn.Close()
   336  
   337  	// Create a test service client and make an RPC call.
   338  	client := testgrpc.NewTestServiceClient(conn)
   339  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   340  		t.Fatalf("EmptyCall() failed: %v", err)
   341  	}
   342  }
   343  
   344  // Tests the scenario where grpc.NewClient is used with grpc.WithContextDialer()
   345  // set. The test verifies that the client bypasses proxy dialing and uses the
   346  // custom dialer instead. It ensures that the proxy server is never dialed, the
   347  // proxy resolution function is not triggered, and the custom dialer is invoked
   348  // as expected.
   349  func (s) TestNewClientWithContextDialer(t *testing.T) {
   350  	backend := startBackendServer(t)
   351  	unresolvedTargetURI := fmt.Sprintf("localhost:%d", testutils.ParsePort(t, backend.Address))
   352  	reqCheck := func(_ *http.Request) { t.Error("proxy server should not have received a Connect request") }
   353  	pServer := proxyserver.New(t, reqCheck, false)
   354  
   355  	overrideTestHTTPSProxy(t, pServer.Addr)
   356  
   357  	// Create a custom dialer that directly dials the backend.
   358  	customDialer := func(_ context.Context, unresolvedTargetURI string) (net.Conn, error) {
   359  		return net.Dial("tcp", unresolvedTargetURI)
   360  	}
   361  
   362  	dopts := []grpc.DialOption{
   363  		grpc.WithTransportCredentials(insecure.NewCredentials()),
   364  		grpc.WithContextDialer(customDialer), // Use a custom dialer.
   365  	}
   366  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   367  	defer cancel()
   368  	conn, err := grpc.NewClient(unresolvedTargetURI, dopts...)
   369  	if err != nil {
   370  		t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err)
   371  	}
   372  	defer conn.Close()
   373  
   374  	client := testgrpc.NewTestServiceClient(conn)
   375  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
   376  		t.Fatalf("EmptyCall() failed: %v", err)
   377  	}
   378  }
   379  
   380  // Tests the scenario where grpc.NewClient is used with the default DNS resolver
   381  // for targetURI and a proxy. The test verifies that the client connects to the
   382  // proxy server, sends the unresolved target URI in the HTTP CONNECT request,
   383  // and successfully connects to the backend. Additionally, it checks that the
   384  // correct user information is included in the Proxy-Authorization header of
   385  // the CONNECT request. The test also ensures that target resolution does not
   386  // happen on the client.
   387  func (s) TestBasicAuthInNewClientWithProxy(t *testing.T) {
   388  	unresolvedTargetURI := "example.test"
   389  	const (
   390  		user     = "notAUser"
   391  		password = "notAPassword"
   392  	)
   393  	proxyCalled := false
   394  	reqCheck := func(req *http.Request) {
   395  		proxyCalled = true
   396  		if got, want := req.URL.Host, "example.test"; got != want {
   397  			t.Errorf(" Unexpected request host: %s, want = %s ", got, want)
   398  		}
   399  		wantProxyAuthStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password))
   400  		if got := req.Header.Get("Proxy-Authorization"); got != wantProxyAuthStr {
   401  			gotDecoded, err := base64.StdEncoding.DecodeString(got)
   402  			if err != nil {
   403  				t.Errorf("failed to decode Proxy-Authorization header: %v", err)
   404  			}
   405  			wantDecoded, _ := base64.StdEncoding.DecodeString(wantProxyAuthStr)
   406  			t.Errorf("unexpected auth %q (%q), want %q (%q)", got, gotDecoded, wantProxyAuthStr, wantDecoded)
   407  		}
   408  	}
   409  	pServer := proxyserver.New(t, reqCheck, false)
   410  
   411  	t.Setenv("HTTPS_PROXY", user+":"+password+"@"+pServer.Addr)
   412  
   413  	// Use the httpproxy package functions instead of `http.ProxyFromEnvironment`
   414  	// because the latter reads proxy-related environment variables only once at
   415  	// initialization. This behavior causes issues when running test multiple
   416  	// times, as changes to environment variables during tests would be ignored.
   417  	// By using `httpproxy.FromEnvironment()`, we ensure proxy settings are read dynamically.
   418  	origHTTPSProxyFromEnvironment := delegatingresolver.HTTPSProxyFromEnvironment
   419  	delegatingresolver.HTTPSProxyFromEnvironment = func(req *http.Request) (*url.URL, error) {
   420  		return httpproxy.FromEnvironment().ProxyFunc()(req.URL)
   421  	}
   422  	defer func() {
   423  		delegatingresolver.HTTPSProxyFromEnvironment = origHTTPSProxyFromEnvironment
   424  	}()
   425  
   426  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   427  	defer cancel()
   428  	conn, err := grpc.NewClient(unresolvedTargetURI, grpc.WithTransportCredentials(insecure.NewCredentials()))
   429  	if err != nil {
   430  		t.Fatalf("grpc.NewClient(%s) failed: %v", unresolvedTargetURI, err)
   431  	}
   432  	defer conn.Close()
   433  
   434  	// Send an empty RPC to the backend through the proxy.
   435  	client := testgrpc.NewTestServiceClient(conn)
   436  	client.EmptyCall(ctx, &testpb.Empty{})
   437  
   438  	if !proxyCalled {
   439  		t.Fatalf("Proxy not connected")
   440  	}
   441  }