/*
 *
 * Copyright 2026 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package xds_test

import (
	"context"
	"crypto/tls"
	"fmt"
	"strings"
	"testing"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	xdscreds "google.golang.org/grpc/credentials/xds"
	"google.golang.org/grpc/internal/envconfig"
	"google.golang.org/grpc/internal/stubserver"
	"google.golang.org/grpc/internal/testutils"
	"google.golang.org/grpc/internal/testutils/xds/e2e"
	"google.golang.org/grpc/internal/testutils/xds/e2e/setup"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/resolver"
	"google.golang.org/grpc/resolver/manual"
	"google.golang.org/grpc/status"

	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
	v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
	testgrpc "google.golang.org/grpc/interop/grpc_testing"
	testpb "google.golang.org/grpc/interop/grpc_testing"
)

// validSNI matches the wildcard SAN (*.test.example.com) in the test server
// certificate.
const validSNI = "x.test.example.com"

// Tests the SNI and SAN validation logic by verifying that RPCs succeed when
// AutoSNISANValidation is enabled and the SNI matches a server certificate DNS
// SAN. Also verifies that RPCs fail with an 'Unavailable' status if the SNI is
// present but does not match any DNS SAN in the certificate.
func (s) TestClientSideXDS_SNISANValidation(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, true)

	// Spin up an xDS management server.
	mgmtServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)

	// Create test backends for two clusters:
	// - backend1 configured with TLS creds, represents cluster1 (valid SNI)
	// - backend2 configured with TLS creds, represents cluster2 (invalid SNI)
	serverCreds := testutils.CreateServerTLSCredentials(t, tls.RequireAndVerifyClientCert)
	server1 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server1.Stop()
	server2 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server2.Stop()

	const serviceName = "my-service-client-side-xds"
	const routeConfigName = "route-" + serviceName
	const clusterName1 = "cluster1-" + serviceName
	const clusterName2 = "cluster2-" + serviceName
	const endpointName1 = "endpoint1-" + serviceName
	const endpointName2 = "endpoint2-" + serviceName

	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}

	// Route configuration:
	// - "/grpc.testing.TestService/EmptyCall" --> cluster1 (valid SNI)
	// - "/grpc.testing.TestService/UnaryCall" --> cluster2 (invalid SNI)
	routes := []*v3routepb.RouteConfiguration{{
		Name: routeConfigName,
		VirtualHosts: []*v3routepb.VirtualHost{{
			Domains: []string{serviceName},
			Routes: []*v3routepb.Route{
				{
					Match:  &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"}},
					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName1}}},
				},
				{
					Match:  &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/UnaryCall"}},
					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName2}}},
				},
			},
		}},
	}}

	// Configure cluster1 with valid SNI and AutoSniSanValidation set to true.
	cluster1 := e2e.DefaultCluster(clusterName1, endpointName1, e2e.SecurityLevelMTLS)
	cluster1.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				Sni:                  validSNI,
				AutoSniSanValidation: true,
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
						ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{InstanceName: e2e.ClientSideCertProviderInstance},
					},
					TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{InstanceName: e2e.ClientSideCertProviderInstance},
				},
			}),
		},
	}

	// Configure cluster2 with invalid SNI and AutoSniSanValidation set to true.
	cluster2 := e2e.DefaultCluster(clusterName2, endpointName2, e2e.SecurityLevelMTLS)
	cluster2.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				Sni:                  "wrong.sni.domain",
				AutoSniSanValidation: true,
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
						ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
							InstanceName:    e2e.ClientSideCertProviderInstance,
							CertificateName: "root",
						},
					},
					TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	clusters := []*v3clusterpb.Cluster{cluster1, cluster2}
	endpoints := []*v3endpointpb.ClusterLoadAssignment{
		e2e.DefaultEndpoint(endpointName1, "localhost", []uint32{testutils.ParsePort(t, server1.Address)}),
		e2e.DefaultEndpoint(endpointName2, "localhost", []uint32{testutils.ParsePort(t, server2.Address)}),
	}

	resources := e2e.UpdateOptions{
		NodeID:    nodeID,
		Listeners: listeners,
		Routes:    routes,
		Clusters:  clusters,
		Endpoints: endpoints,
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	clientCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
	if err != nil {
		t.Fatal(err)
	}
	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(xdsResolver))
	if err != nil {
		t.Fatalf("failed to dial local test server: %v", err)
	}
	defer cc.Close()

	client := testgrpc.NewTestServiceClient(cc)
	// RPC to cluster1 should succeed because auto_sni_san_validation is true
	// and sni matches server cert SAN.
	peerInfo := &peer.Peer{}
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peerInfo)); err != nil {
		t.Fatalf("EmptyCall() failed: %v", err)
	}
	if got, want := peerInfo.Addr.String(), server1.Address; got != want {
		t.Fatalf("EmptyCall() routed to %q, want to be routed to: %q", got, want)
	}

	// RPC to cluster2 should fail because even though auto_sni_san_validation
	// is true, sni doesn't match server cert SAN.
	const wantErr = "do not match the SNI"
	if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) {
		t.Fatalf("UnaryCall() failed: %v, wantCode: %s, wantErr: %s", err, codes.Unavailable, wantErr)
	}
}

// Tests that when AutoHostSNI is enabled, the endpoint's hostname is used for
// the ServerName even though SNI is specified. It verifies that the TLS
// handshake and subsequent RPC succeed because the resolved SNI i.e. the
// hostname matches the server's certificate SAN.
func (s) TestClientSideXDS_AutoHostSNI(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, true)

	// Spin up an xDS management server.
	mgmtServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)

	// Create test backend
	serverCreds := testutils.CreateServerTLSCredentials(t, tls.RequireAndVerifyClientCert)
	server := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server.Stop()

	// Configure client side xDS resources on the management server.
	const serviceName = "my-service-client-side-xds"
	const routeConfigName = "route-" + serviceName
	const clusterName = "cluster-" + serviceName
	const endpointName = "endpoint-" + serviceName

	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}
	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)}

	// Configure the cluster with AutoHostSni and AutoSniSanValidation set to
	// true with a invalid SNI.
	cluster := e2e.DefaultCluster(clusterName, endpointName, e2e.SecurityLevelMTLS)
	cluster.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				AutoHostSni:          true,
				AutoSniSanValidation: true,
				Sni:                  "wrong.sni.domain",
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
						ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
							InstanceName:    e2e.ClientSideCertProviderInstance,
							CertificateName: "root",
						},
					},
					TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	// Endpoints configuring Hostname to the defaultTestCertSAN to verify
	// AutoHostSni usage.
	endpoints := []*v3endpointpb.ClusterLoadAssignment{
		e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
			ClusterName: endpointName,
			Host:        "localhost",
			Localities: []e2e.LocalityOptions{{
				Weight: 1,
				Backends: []e2e.BackendOptions{{
					Ports:    []uint32{testutils.ParsePort(t, server.Address)},
					Hostname: validSNI,
				}},
			}},
		}),
	}

	resources := e2e.UpdateOptions{
		NodeID:    nodeID,
		Listeners: listeners,
		Routes:    routes,
		Clusters:  []*v3clusterpb.Cluster{cluster},
		Endpoints: endpoints,
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	clientCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
	if err != nil {
		t.Fatal(err)
	}
	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(xdsResolver))
	if err != nil {
		t.Fatalf("failed to dial local test server: %v", err)
	}
	defer cc.Close()

	client := testgrpc.NewTestServiceClient(cc)
	// RPC should succeed because auto_host_sni sets SNI from the endpoint
	// hostname and auto_sni_san_validation validates that the SNI matches
	// server cert SAN.
	peerInfo := &peer.Peer{}
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peerInfo)); err != nil {
		t.Fatalf("EmptyCall() failed: %v", err)
	}
	if got, want := peerInfo.Addr.String(), server.Address; got != want {
		t.Fatalf("EmptyCall() routed to %q, want to be routed to: %q", got, want)
	}
}

// Tests the scenario where multiple endpoints are configured with distinct
// hostnames. It verifies that the client uses the specific hostname associated
// with each endpoint during the TLS handshake by simulating an EDS failover and
// confirming that the SNI is dynamically updated to match the active endpoint.
func (s) TestClientSideXDS_AutoHostSNI_MultipleEndpoints(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, true)

	// Spin up an xDS management server.
	mgmtServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)

	// Create test backends.
	serverCreds := testutils.CreateServerTLSCredentials(t, tls.RequireAndVerifyClientCert)
	server1 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server1.Stop()
	server2 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server2.Stop()

	// Configure client side xDS resources on the management server.
	const serviceName = "my-service-client-side-xds"
	const routeConfigName = "route-" + serviceName
	const clusterName = "cluster-" + serviceName
	const endpointName = "endpoint-" + serviceName

	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}
	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)}

	clusters := []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, endpointName, e2e.SecurityLevelMTLS)}
	clusters[0].TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				AutoHostSni:          true,
				AutoSniSanValidation: true,
				Sni:                  "wrong.sni.domain",
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
						ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
							InstanceName:    e2e.ClientSideCertProviderInstance,
							CertificateName: "root",
						},
					},
					TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	// Configure only Priority 0 with an invalid hostname. RPC should fail due
	// to SAN mismatch.
	endpoints := []*v3endpointpb.ClusterLoadAssignment{
		e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
			ClusterName: endpointName,
			Host:        "localhost",
			Localities: []e2e.LocalityOptions{
				{
					Name:     "locality-0",
					Weight:   1,
					Priority: 0,
					Backends: []e2e.BackendOptions{{
						Ports:    []uint32{testutils.ParsePort(t, server1.Address)},
						Hostname: "wrong.sni.domain",
					}},
				},
			},
		}),
	}

	resources := e2e.UpdateOptions{
		NodeID:    nodeID,
		Listeners: listeners,
		Routes:    routes,
		Clusters:  clusters,
		Endpoints: endpoints,
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	clientCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
	if err != nil {
		t.Fatal(err)
	}
	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(xdsResolver))
	if err != nil {
		t.Fatalf("failed to dial local test server: %v", err)
	}
	defer cc.Close()

	client := testgrpc.NewTestServiceClient(cc)

	// RPC should fail because hostname for endpoints in Priority 0 is invalid.
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}); err == nil {
		t.Fatalf("EmptyCall() unexpectedly succeeded with invalid SNI and no failover")
	} else if !strings.Contains(err.Error(), "authentication handshake failed") {
		t.Fatalf("EmptyCall() failed with %v, want error containing 'authentication handshake failed'", err)
	}

	// Update the same EDS resource to add Priority 1 with an endpoint with
	// valid hostname.
	endpoints = []*v3endpointpb.ClusterLoadAssignment{
		e2e.EndpointResourceWithOptions(e2e.EndpointOptions{
			ClusterName: endpointName,
			Host:        "localhost",
			Localities: []e2e.LocalityOptions{
				{
					Name:     "locality-0",
					Weight:   1,
					Priority: 0,
					Backends: []e2e.BackendOptions{{
						Ports:    []uint32{testutils.ParsePort(t, server1.Address)},
						Hostname: "wrong.sni.domain",
					}},
				},
				{
					Name:     "locality-1",
					Weight:   1,
					Priority: 1,
					Backends: []e2e.BackendOptions{{
						Ports:    []uint32{testutils.ParsePort(t, server2.Address)},
						Hostname: validSNI,
					}},
				},
			},
		}),
	}
	resources.Endpoints = endpoints
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	// RPC should succeed because it automatically fails over to Priority 1. Id
	// only one hostname would have been used per leaf cluster , this RPC should
	// have failed due to invalid SNI in Priority 0.
	peerInfo := &peer.Peer{}
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peerInfo), grpc.WaitForReady(true)); err != nil {
		t.Fatalf("EmptyCall() failed after adding Priority 1: %v", err)
	}
	if got, want := peerInfo.Addr.String(), server2.Address; got != want {
		t.Fatalf("EmptyCall() successes routed to %q, want %q", got, want)
	}
}

// TestClientSideXDS_FallbackSANMatchers tests that when AutoSniSanValidation is
// true, and no SNI is provided by the control plane and AutoHostSNI is not set,
// the validation falls back to using the explicit SAN matchers specified in the
// configuration. It verifies that RPCs succeed when the fallback matchers match
// the server certificate SAN and fail when they do not.
func (s) TestClientSideXDS_FallbackSANMatchers(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, true)

	// Spin up an xDS management server.
	mgmtServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)

	// Create test backends.
	serverCreds := testutils.CreateServerTLSCredentials(t, tls.RequireAndVerifyClientCert)
	server1 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server1.Stop()
	server2 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server2.Stop()

	// Configure client side xDS resources on the management server.
	const serviceName = "my-service-client-side-xds"
	const routeConfigName = "route-" + serviceName
	const clusterName1 = "cluster1-" + serviceName
	const clusterName2 = "cluster2-" + serviceName
	const endpointName1 = "endpoint1-" + serviceName
	const endpointName2 = "endpoint2-" + serviceName

	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}

	routes := []*v3routepb.RouteConfiguration{{
		Name: routeConfigName,
		VirtualHosts: []*v3routepb.VirtualHost{{
			Domains: []string{serviceName},
			Routes: []*v3routepb.Route{
				{
					Match:  &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"}},
					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName1}}},
				},
				{
					Match:  &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/UnaryCall"}},
					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName2}}},
				},
			},
		}},
	}}

	// Configure cluster1 with AutoSniSanValidation set to true and no SNI
	// provided for the handshake. The validation falls back to using the explicit
	// SAN matchers specified in the configuration which matches the server1's
	// certificate SAN.
	cluster1 := e2e.DefaultCluster(clusterName1, endpointName1, e2e.SecurityLevelMTLS)
	cluster1.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				AutoSniSanValidation: true,
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
						CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
							DefaultValidationContext: &v3tlspb.CertificateValidationContext{
								MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
									{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "*.test.example.com"}},
								},
								CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
									InstanceName:    e2e.ClientSideCertProviderInstance,
									CertificateName: "root",
								},
							},
						},
					},
					TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	// Configure cluster2 with AutoSniSanValidation set to true and no SNI
	// provided for the handshake. The validation falls back to using the explicit
	// SAN matchers specified in the configuration which does not match the
	// server2's certificate SAN.
	cluster2 := e2e.DefaultCluster(clusterName2, endpointName2, e2e.SecurityLevelMTLS)
	cluster2.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				AutoSniSanValidation: true,
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
						CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
							DefaultValidationContext: &v3tlspb.CertificateValidationContext{
								MatchSubjectAltNames: []*v3matcherpb.StringMatcher{{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "wrong.san.domain"}}},
								CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
									InstanceName:    e2e.ClientSideCertProviderInstance,
									CertificateName: "root",
								},
							},
						},
					},
					TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	clusters := []*v3clusterpb.Cluster{cluster1, cluster2}
	endpoints := []*v3endpointpb.ClusterLoadAssignment{
		e2e.DefaultEndpoint(endpointName1, "localhost", []uint32{testutils.ParsePort(t, server1.Address)}),
		e2e.DefaultEndpoint(endpointName2, "localhost", []uint32{testutils.ParsePort(t, server2.Address)}),
	}

	resources := e2e.UpdateOptions{
		NodeID:    nodeID,
		Listeners: listeners,
		Routes:    routes,
		Clusters:  clusters,
		Endpoints: endpoints,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 50*defaultTestTimeout)
	defer cancel()
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	clientCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
	if err != nil {
		t.Fatal(err)
	}
	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(xdsResolver))
	if err != nil {
		t.Fatalf("failed to dial local test server: %v", err)
	}
	defer cc.Close()

	client := testgrpc.NewTestServiceClient(cc)

	// RPC to cluster1 should succeed because fallback SAN mathchers are used
	// and they match server cert SAN.
	peerInfo := &peer.Peer{}
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peerInfo)); err != nil {
		t.Fatalf("EmptyCall() failed: %v", err)
	}
	if got, want := peerInfo.Addr.String(), server1.Address; got != want {
		t.Fatalf("EmptyCall() routed to %q, want to be routed to: %q", got, want)
	}

	// RPC to cluster2 should fail because fallback SAN matchers are used
	// but they don't match server cert SAN.
	const wantErr = "do not match any of the accepted SANs"
	if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) {
		t.Fatalf("UnaryCall() failed: %v, wantCode: %s, wantErr: %s", err, codes.Unavailable, wantErr)
	}
}

// Tests that when the XDSSNIEnabled environment variable is set to false, SNI
// is not used for validation even if AutoSniSanValidation is true and SNI is
// set. It verifies that the system falls back to using explicit SAN matchers if
// provided, and the TLS handshake succeeds when they match the server
// certificate SAN.
func (s) TestClientSideXDS_SNIEnvVarDisabled(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, false)

	// Spin up an xDS management server.
	mgmtServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)

	// Create test backends.
	serverCreds := testutils.CreateServerTLSCredentials(t, tls.RequireAndVerifyClientCert)
	server1 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server1.Stop()
	server2 := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server2.Stop()

	// Configure client side xDS resources on the management server.
	const serviceName = "my-service-client-side-xds"
	const routeConfigName = "route-" + serviceName
	const clusterName1 = "cluster1-" + serviceName
	const clusterName2 = "cluster2-" + serviceName
	const endpointName1 = "endpoint1-" + serviceName
	const endpointName2 = "endpoint2-" + serviceName

	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}

	routes := []*v3routepb.RouteConfiguration{{
		Name: routeConfigName,
		VirtualHosts: []*v3routepb.VirtualHost{{
			Domains: []string{serviceName},
			Routes: []*v3routepb.Route{
				{
					Match:  &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"}},
					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName1}}},
				},
				{
					Match:  &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/UnaryCall"}},
					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName2}}},
				},
			},
		}},
	}}

	// cluster1 configuration with AutoSniSanValidation set to true and wrong SNI
	// provided for the handshake. The validation falls back to using the explicit
	// SAN matchers specified in the configuration which matches the server1's
	// certificate SAN.
	cluster1 := e2e.DefaultCluster(clusterName1, endpointName1, e2e.SecurityLevelMTLS)
	cluster1.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				Sni:                  "incorrect.sni",
				AutoSniSanValidation: true,
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
						CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
							DefaultValidationContext: &v3tlspb.CertificateValidationContext{
								MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
									{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "*.test.example.com"}},
								},
								CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
									InstanceName:    e2e.ClientSideCertProviderInstance,
									CertificateName: "root",
								},
							},
						},
					},
					TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	// Configure cluster2 with AutoSniSanValidation set to true and correct SNI
	// provided for the handshake. The validation falls back to using the
	// explicit SAN matchers specified in the configuration which does not match
	// the server2's certificate SAN.
	cluster2 := e2e.DefaultCluster(clusterName2, endpointName2, e2e.SecurityLevelMTLS)
	cluster2.TransportSocket = &v3corepb.TransportSocket{
		Name: "envoy.transport_sockets.tls",
		ConfigType: &v3corepb.TransportSocket_TypedConfig{
			TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
				Sni:                  validSNI,
				AutoSniSanValidation: true,
				CommonTlsContext: &v3tlspb.CommonTlsContext{
					ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
						CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
							DefaultValidationContext: &v3tlspb.CertificateValidationContext{
								MatchSubjectAltNames: []*v3matcherpb.StringMatcher{{MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "wrong.san.domain"}}},
								CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
									InstanceName:    e2e.ClientSideCertProviderInstance,
									CertificateName: "root",
								},
							},
						},
					},
					TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
						InstanceName:    e2e.ClientSideCertProviderInstance,
						CertificateName: "identity",
					},
				},
			}),
		},
	}

	clusters := []*v3clusterpb.Cluster{cluster1, cluster2}
	endpoints := []*v3endpointpb.ClusterLoadAssignment{
		e2e.DefaultEndpoint(endpointName1, "localhost", []uint32{testutils.ParsePort(t, server1.Address)}),
		e2e.DefaultEndpoint(endpointName2, "localhost", []uint32{testutils.ParsePort(t, server2.Address)}),
	}

	resources := e2e.UpdateOptions{
		NodeID:    nodeID,
		Listeners: listeners,
		Routes:    routes,
		Clusters:  clusters,
		Endpoints: endpoints,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 50*defaultTestTimeout)
	defer cancel()
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	clientCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
	if err != nil {
		t.Fatal(err)
	}
	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(xdsResolver))
	if err != nil {
		t.Fatalf("failed to dial local test server: %v", err)
	}
	defer cc.Close()

	client := testgrpc.NewTestServiceClient(cc)

	// RPC to cluster1 should succeed because fallback SAN mathchers are used
	// and they match server cert SAN.
	peerInfo := &peer.Peer{}
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peerInfo)); err != nil {
		t.Fatalf("EmptyCall() failed: %v", err)
	}
	if got, want := peerInfo.Addr.String(), server1.Address; got != want {
		t.Fatalf("EmptyCall() routed to %q, want to be routed to: %q", got, want)
	}

	// RPC to cluster2 should fail because fallback SAN matchers are used but they
	// don't match server cert SAN.
	const wantErr = "do not match any of the accepted SANs"
	if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) {
		t.Fatalf("UnaryCall() failed: %v, wantCode: %s, wantErr: %s", err, codes.Unavailable, wantErr)
	}
}

// Tests that when AutoHostSNI is enabled for a Logical DNS cluster, the SNI is
// resolved from the DNSHostName in the cluster configuration. It verifies that
// the TLS handshake succeeds when the DNSHostName matches the server's
// certificate SAN.
func (s) TestClientSideXDS_AutoHostSNI_LogicalDNS(t *testing.T) {
	testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, true)

	// Spin up an xDS management server.
	mgmtServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t)

	// Create test backend
	serverCreds := testutils.CreateServerTLSCredentials(t, tls.RequireAndVerifyClientCert)
	server := stubserver.StartTestService(t, nil, grpc.Creds(serverCreds))
	defer server.Stop()

	// Replace DNS resolver with a manual resolver.
	dnsR := manual.NewBuilderWithScheme("dns")
	originalDNS := resolver.Get("dns")
	resolver.Register(dnsR)
	t.Cleanup(func() { resolver.Register(originalDNS) })

	dnsR.UpdateState(resolver.State{Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: server.Address}}}}})

	// Configure client side xDS resources on the management server.
	const serviceName = "my-service-client-side-xds"
	const routeConfigName = "route-" + serviceName
	const clusterName = "cluster-" + serviceName

	listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)}
	routes := []*v3routepb.RouteConfiguration{e2e.DefaultRouteConfig(routeConfigName, serviceName, clusterName)}

	// Cluster of Type LogicalDNS. with DNSHostName set to match the server's cert
	// SAN.
	cluster := e2e.ClusterResourceWithOptions(e2e.ClusterOptions{
		Type:          e2e.ClusterTypeLogicalDNS,
		ClusterName:   clusterName,
		DNSHostName:   validSNI,
		DNSPort:       uint32(testutils.ParsePort(t, server.Address)),
		SecurityLevel: e2e.SecurityLevelMTLS,
	})
	cluster.TransportSocket.ConfigType.(*v3corepb.TransportSocket_TypedConfig).TypedConfig = testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
		AutoHostSni:          true,
		AutoSniSanValidation: true,
		CommonTlsContext: &v3tlspb.CommonTlsContext{
			ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
				ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
					InstanceName: e2e.ClientSideCertProviderInstance,
				},
			},
			TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
				InstanceName: e2e.ClientSideCertProviderInstance,
			},
		},
	})

	resources := e2e.UpdateOptions{
		NodeID:    nodeID,
		Listeners: listeners,
		Routes:    routes,
		Clusters:  []*v3clusterpb.Cluster{cluster},
	}

	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
	defer cancel()
	if err := mgmtServer.Update(ctx, resources); err != nil {
		t.Fatal(err)
	}

	clientCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
	if err != nil {
		t.Fatal(err)
	}
	cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(xdsResolver))
	if err != nil {
		t.Fatalf("failed to dial local test server: %v", err)
	}
	defer cc.Close()

	client := testgrpc.NewTestServiceClient(cc)
	// RPC should succeed because DNSHostName matches the server's certificate
	// SAN.
	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil {
		t.Fatalf("EmptyCall() failed: %v", err)
	}
}
