istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/sidecar_simulation_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package core_test
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"reflect"
    21  	"sort"
    22  	"strings"
    23  	"testing"
    24  
    25  	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    26  	endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    27  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    28  	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    29  	"k8s.io/apimachinery/pkg/types"
    30  
    31  	meshconfig "istio.io/api/mesh/v1alpha1"
    32  	networking "istio.io/api/networking/v1alpha3"
    33  	"istio.io/istio/pilot/pkg/config/kube/crd"
    34  	"istio.io/istio/pilot/pkg/features"
    35  	"istio.io/istio/pilot/pkg/model"
    36  	"istio.io/istio/pilot/pkg/networking/core"
    37  	"istio.io/istio/pilot/pkg/networking/util"
    38  	"istio.io/istio/pilot/pkg/simulation"
    39  	"istio.io/istio/pilot/test/xds"
    40  	"istio.io/istio/pilot/test/xdstest"
    41  	"istio.io/istio/pkg/config"
    42  	"istio.io/istio/pkg/config/host"
    43  	"istio.io/istio/pkg/config/mesh"
    44  	"istio.io/istio/pkg/config/protocol"
    45  	"istio.io/istio/pkg/config/schema/gvk"
    46  	"istio.io/istio/pkg/kube"
    47  	"istio.io/istio/pkg/test"
    48  	"istio.io/istio/pkg/test/util/tmpl"
    49  	"istio.io/istio/pkg/util/protomarshal"
    50  )
    51  
    52  func flattenInstances(il ...[]*model.ServiceInstance) []*model.ServiceInstance {
    53  	ret := []*model.ServiceInstance{}
    54  	for _, i := range il {
    55  		ret = append(ret, i...)
    56  	}
    57  	return ret
    58  }
    59  
    60  func makeInstances(proxy *model.Proxy, svc *model.Service, servicePort int, targetPort int) []*model.ServiceInstance {
    61  	ret := []*model.ServiceInstance{}
    62  	for _, p := range svc.Ports {
    63  		if p.Port != servicePort {
    64  			continue
    65  		}
    66  		ret = append(ret, &model.ServiceInstance{
    67  			Service:     svc,
    68  			ServicePort: p,
    69  			Endpoint: &model.IstioEndpoint{
    70  				Address:         proxy.IPAddresses[0],
    71  				ServicePortName: p.Name,
    72  				EndpointPort:    uint32(targetPort),
    73  			},
    74  		})
    75  	}
    76  	return ret
    77  }
    78  
    79  func TestInboundClusters(t *testing.T) {
    80  	proxy := &model.Proxy{
    81  		IPAddresses: []string{"1.2.3.4"},
    82  		Metadata:    &model.NodeMetadata{},
    83  	}
    84  	service := &model.Service{
    85  		Hostname:       host.Name("backend.default.svc.cluster.local"),
    86  		DefaultAddress: "1.1.1.1",
    87  		Ports: model.PortList{&model.Port{
    88  			Name:     "default",
    89  			Port:     80,
    90  			Protocol: protocol.HTTP,
    91  		}, &model.Port{
    92  			Name:     "other",
    93  			Port:     81,
    94  			Protocol: protocol.HTTP,
    95  		}},
    96  		Resolution: model.ClientSideLB,
    97  	}
    98  	serviceAlt := &model.Service{
    99  		Hostname:       host.Name("backend-alt.default.svc.cluster.local"),
   100  		DefaultAddress: "1.1.1.2",
   101  		Ports: model.PortList{&model.Port{
   102  			Name:     "default",
   103  			Port:     80,
   104  			Protocol: protocol.HTTP,
   105  		}, &model.Port{
   106  			Name:     "other",
   107  			Port:     81,
   108  			Protocol: protocol.HTTP,
   109  		}},
   110  		Resolution: model.ClientSideLB,
   111  	}
   112  
   113  	cases := []struct {
   114  		name      string
   115  		configs   []config.Config
   116  		services  []*model.Service
   117  		instances []*model.ServiceInstance
   118  		// Assertions
   119  		clusters                  map[string][]string
   120  		telemetry                 map[string][]string
   121  		proxy                     *model.Proxy
   122  		disableInboundPassthrough bool
   123  	}{
   124  		// Proxy 1.8.1+ tests
   125  		{name: "empty"},
   126  		{name: "empty service", services: []*model.Service{service}},
   127  		{
   128  			name:      "single service, partial instance",
   129  			services:  []*model.Service{service},
   130  			instances: makeInstances(proxy, service, 80, 8080),
   131  			clusters: map[string][]string{
   132  				"inbound|8080||": nil,
   133  			},
   134  			telemetry: map[string][]string{
   135  				"inbound|8080||": {string(service.Hostname)},
   136  			},
   137  		},
   138  		{
   139  			name:     "single service, multiple instance",
   140  			services: []*model.Service{service},
   141  			instances: flattenInstances(
   142  				makeInstances(proxy, service, 80, 8080),
   143  				makeInstances(proxy, service, 81, 8081)),
   144  			clusters: map[string][]string{
   145  				"inbound|8080||": nil,
   146  				"inbound|8081||": nil,
   147  			},
   148  			telemetry: map[string][]string{
   149  				"inbound|8080||": {string(service.Hostname)},
   150  				"inbound|8081||": {string(service.Hostname)},
   151  			},
   152  		},
   153  		{
   154  			name:     "multiple services with same service port, different target",
   155  			services: []*model.Service{service, serviceAlt},
   156  			instances: flattenInstances(
   157  				makeInstances(proxy, service, 80, 8080),
   158  				makeInstances(proxy, service, 81, 8081),
   159  				makeInstances(proxy, serviceAlt, 80, 8082),
   160  				makeInstances(proxy, serviceAlt, 81, 8083)),
   161  			clusters: map[string][]string{
   162  				"inbound|8080||": nil,
   163  				"inbound|8081||": nil,
   164  				"inbound|8082||": nil,
   165  				"inbound|8083||": nil,
   166  			},
   167  			telemetry: map[string][]string{
   168  				"inbound|8080||": {string(service.Hostname)},
   169  				"inbound|8081||": {string(service.Hostname)},
   170  				"inbound|8082||": {string(serviceAlt.Hostname)},
   171  				"inbound|8083||": {string(serviceAlt.Hostname)},
   172  			},
   173  		},
   174  		{
   175  			name:     "multiple services with same service port and target",
   176  			services: []*model.Service{service, serviceAlt},
   177  			instances: flattenInstances(
   178  				makeInstances(proxy, service, 80, 8080),
   179  				makeInstances(proxy, service, 81, 8081),
   180  				makeInstances(proxy, serviceAlt, 80, 8080),
   181  				makeInstances(proxy, serviceAlt, 81, 8081)),
   182  			clusters: map[string][]string{
   183  				"inbound|8080||": nil,
   184  				"inbound|8081||": nil,
   185  			},
   186  			telemetry: map[string][]string{
   187  				"inbound|8080||": {string(serviceAlt.Hostname), string(service.Hostname)},
   188  				"inbound|8081||": {string(serviceAlt.Hostname), string(service.Hostname)},
   189  			},
   190  		},
   191  		{
   192  			name: "ingress to same port",
   193  			configs: []config.Config{
   194  				{
   195  					Meta: config.Meta{GroupVersionKind: gvk.Sidecar, Namespace: "default", Name: "sidecar"},
   196  					Spec: &networking.Sidecar{Ingress: []*networking.IstioIngressListener{{
   197  						Port: &networking.SidecarPort{
   198  							Number:   80,
   199  							Protocol: "HTTP",
   200  							Name:     "http",
   201  						},
   202  						DefaultEndpoint: "127.0.0.1:80",
   203  					}}},
   204  				},
   205  			},
   206  			clusters: map[string][]string{
   207  				"inbound|80||": {"127.0.0.1:80"},
   208  			},
   209  		},
   210  		{
   211  			name: "ingress to different port",
   212  			configs: []config.Config{
   213  				{
   214  					Meta: config.Meta{GroupVersionKind: gvk.Sidecar, Namespace: "default", Name: "sidecar"},
   215  					Spec: &networking.Sidecar{Ingress: []*networking.IstioIngressListener{{
   216  						Port: &networking.SidecarPort{
   217  							Number:   80,
   218  							Protocol: "HTTP",
   219  							Name:     "http",
   220  						},
   221  						DefaultEndpoint: "127.0.0.1:8080",
   222  					}}},
   223  				},
   224  			},
   225  			clusters: map[string][]string{
   226  				"inbound|80||": {"127.0.0.1:8080"},
   227  			},
   228  		},
   229  		{
   230  			name: "ingress to instance IP",
   231  			configs: []config.Config{
   232  				{
   233  					Meta: config.Meta{GroupVersionKind: gvk.Sidecar, Namespace: "default", Name: "sidecar"},
   234  					Spec: &networking.Sidecar{Ingress: []*networking.IstioIngressListener{{
   235  						Port: &networking.SidecarPort{
   236  							Number:   80,
   237  							Protocol: "HTTP",
   238  							Name:     "http",
   239  						},
   240  						DefaultEndpoint: "0.0.0.0:8080",
   241  					}}},
   242  				},
   243  			},
   244  			clusters: map[string][]string{
   245  				"inbound|80||": {"1.2.3.4:8080"},
   246  			},
   247  		},
   248  		{
   249  			name: "ingress without default endpoint",
   250  			configs: []config.Config{
   251  				{
   252  					Meta: config.Meta{GroupVersionKind: gvk.Sidecar, Namespace: "default", Name: "sidecar"},
   253  					Spec: &networking.Sidecar{Ingress: []*networking.IstioIngressListener{{
   254  						Port: &networking.SidecarPort{
   255  							Number:   80,
   256  							Protocol: "HTTP",
   257  							Name:     "http",
   258  						},
   259  					}}},
   260  				},
   261  			},
   262  			clusters: map[string][]string{
   263  				"inbound|80||": nil,
   264  			},
   265  		},
   266  		{
   267  			name: "ingress to socket",
   268  			configs: []config.Config{
   269  				{
   270  					Meta: config.Meta{GroupVersionKind: gvk.Sidecar, Namespace: "default", Name: "sidecar"},
   271  					Spec: &networking.Sidecar{Ingress: []*networking.IstioIngressListener{{
   272  						Port: &networking.SidecarPort{
   273  							Number:   80,
   274  							Protocol: "HTTP",
   275  							Name:     "http",
   276  						},
   277  						DefaultEndpoint: "unix:///socket",
   278  					}}},
   279  				},
   280  			},
   281  			clusters: map[string][]string{
   282  				"inbound|80||": {"/socket"},
   283  			},
   284  		},
   285  		{
   286  			name: "multiple ingress",
   287  			configs: []config.Config{
   288  				{
   289  					Meta: config.Meta{GroupVersionKind: gvk.Sidecar, Namespace: "default", Name: "sidecar"},
   290  					Spec: &networking.Sidecar{Ingress: []*networking.IstioIngressListener{
   291  						{
   292  							Port: &networking.SidecarPort{
   293  								Number:   80,
   294  								Protocol: "HTTP",
   295  								Name:     "http",
   296  							},
   297  							DefaultEndpoint: "127.0.0.1:8080",
   298  						},
   299  						{
   300  							Port: &networking.SidecarPort{
   301  								Number:   81,
   302  								Protocol: "HTTP",
   303  								Name:     "http",
   304  							},
   305  							DefaultEndpoint: "127.0.0.1:8080",
   306  						},
   307  					}},
   308  				},
   309  			},
   310  			clusters: map[string][]string{
   311  				"inbound|80||": {"127.0.0.1:8080"},
   312  				"inbound|81||": {"127.0.0.1:8080"},
   313  			},
   314  		},
   315  		// Disable inbound passthrough
   316  		{
   317  			name:      "single service, partial instance",
   318  			services:  []*model.Service{service},
   319  			instances: makeInstances(proxy, service, 80, 8080),
   320  			clusters: map[string][]string{
   321  				"inbound|8080||": {"127.0.0.1:8080"},
   322  			},
   323  			telemetry: map[string][]string{
   324  				"inbound|8080||": {string(service.Hostname)},
   325  			},
   326  			disableInboundPassthrough: true,
   327  		},
   328  		{
   329  			name:     "single service, multiple instance",
   330  			services: []*model.Service{service},
   331  			instances: flattenInstances(
   332  				makeInstances(proxy, service, 80, 8080),
   333  				makeInstances(proxy, service, 81, 8081)),
   334  			clusters: map[string][]string{
   335  				"inbound|8080||": {"127.0.0.1:8080"},
   336  				"inbound|8081||": {"127.0.0.1:8081"},
   337  			},
   338  			telemetry: map[string][]string{
   339  				"inbound|8080||": {string(service.Hostname)},
   340  				"inbound|8081||": {string(service.Hostname)},
   341  			},
   342  			disableInboundPassthrough: true,
   343  		},
   344  		{
   345  			name:     "multiple services with same service port, different target",
   346  			services: []*model.Service{service, serviceAlt},
   347  			instances: flattenInstances(
   348  				makeInstances(proxy, service, 80, 8080),
   349  				makeInstances(proxy, service, 81, 8081),
   350  				makeInstances(proxy, serviceAlt, 80, 8082),
   351  				makeInstances(proxy, serviceAlt, 81, 8083)),
   352  			clusters: map[string][]string{
   353  				"inbound|8080||": {"127.0.0.1:8080"},
   354  				"inbound|8081||": {"127.0.0.1:8081"},
   355  				"inbound|8082||": {"127.0.0.1:8082"},
   356  				"inbound|8083||": {"127.0.0.1:8083"},
   357  			},
   358  			telemetry: map[string][]string{
   359  				"inbound|8080||": {string(service.Hostname)},
   360  				"inbound|8081||": {string(service.Hostname)},
   361  				"inbound|8082||": {string(serviceAlt.Hostname)},
   362  				"inbound|8083||": {string(serviceAlt.Hostname)},
   363  			},
   364  			disableInboundPassthrough: true,
   365  		},
   366  		{
   367  			name:     "multiple services with same service port and target",
   368  			services: []*model.Service{service, serviceAlt},
   369  			instances: flattenInstances(
   370  				makeInstances(proxy, service, 80, 8080),
   371  				makeInstances(proxy, service, 81, 8081),
   372  				makeInstances(proxy, serviceAlt, 80, 8080),
   373  				makeInstances(proxy, serviceAlt, 81, 8081)),
   374  			clusters: map[string][]string{
   375  				"inbound|8080||": {"127.0.0.1:8080"},
   376  				"inbound|8081||": {"127.0.0.1:8081"},
   377  			},
   378  			telemetry: map[string][]string{
   379  				"inbound|8080||": {string(serviceAlt.Hostname), string(service.Hostname)},
   380  				"inbound|8081||": {string(serviceAlt.Hostname), string(service.Hostname)},
   381  			},
   382  			disableInboundPassthrough: true,
   383  		},
   384  	}
   385  	for _, tt := range cases {
   386  		name := tt.name
   387  		if tt.proxy == nil {
   388  			tt.proxy = proxy
   389  		} else {
   390  			name += "-" + tt.proxy.Metadata.IstioVersion
   391  		}
   392  
   393  		t.Run(name, func(t *testing.T) {
   394  			s := core.NewConfigGenTest(t, core.TestOptions{
   395  				Services:  tt.services,
   396  				Instances: tt.instances,
   397  				Configs:   tt.configs,
   398  				MeshConfig: func() *meshconfig.MeshConfig {
   399  					m := mesh.DefaultMeshConfig()
   400  					if tt.disableInboundPassthrough {
   401  						m.InboundTrafficPolicy.Mode = meshconfig.MeshConfig_InboundTrafficPolicy_LOCALHOST
   402  					}
   403  					return m
   404  				}(),
   405  			})
   406  			sim := simulation.NewSimulationFromConfigGen(t, s, s.SetupProxy(tt.proxy))
   407  
   408  			clusters := xdstest.FilterClusters(sim.Clusters, func(c *cluster.Cluster) bool {
   409  				return strings.HasPrefix(c.Name, "inbound")
   410  			})
   411  			if len(s.PushContext().ProxyStatus) != 0 {
   412  				// TODO make this fatal, once inbound conflict is silenced
   413  				t.Logf("got unexpected error: %+v", s.PushContext().ProxyStatus)
   414  			}
   415  			cmap := xdstest.ExtractClusters(clusters)
   416  			got := xdstest.MapKeys(cmap)
   417  
   418  			// Check we have all expected clusters
   419  			if !reflect.DeepEqual(xdstest.MapKeys(tt.clusters), got) {
   420  				t.Errorf("expected clusters: %v, got: %v", xdstest.MapKeys(tt.clusters), got)
   421  			}
   422  
   423  			for cname, c := range cmap {
   424  				// Check the upstream endpoints match
   425  				got := xdstest.ExtractLoadAssignments([]*endpoint.ClusterLoadAssignment{c.GetLoadAssignment()})[cname]
   426  				if !reflect.DeepEqual(tt.clusters[cname], got) {
   427  					t.Errorf("%v: expected endpoints %v, got %v", cname, tt.clusters[cname], got)
   428  				}
   429  				gotTelemetry := extractClusterMetadataServices(t, c)
   430  				if !reflect.DeepEqual(tt.telemetry[cname], gotTelemetry) {
   431  					t.Errorf("%v: expected telemetry services %v, got %v", cname, tt.telemetry[cname], gotTelemetry)
   432  				}
   433  
   434  				// simulate an actual call, this ensures we are aligned with the inbound listener configuration
   435  				_, _, hostname, port := model.ParseSubsetKey(cname)
   436  				if tt.proxy.Metadata.IstioVersion != "" {
   437  					// This doesn't work with the legacy proxies which have issues (https://github.com/istio/istio/issues/29199)
   438  					for _, i := range tt.instances {
   439  						if len(hostname) > 0 && i.Service.Hostname != hostname {
   440  							continue
   441  						}
   442  						if i.ServicePort.Port == port {
   443  							port = int(i.Endpoint.EndpointPort)
   444  						}
   445  					}
   446  				}
   447  				sim.Run(simulation.Call{
   448  					Port:     port,
   449  					Protocol: simulation.HTTP,
   450  					Address:  "1.2.3.4",
   451  					CallMode: simulation.CallModeInbound,
   452  				}).Matches(t, simulation.Result{
   453  					ClusterMatched: cname,
   454  				})
   455  			}
   456  		})
   457  	}
   458  }
   459  
   460  type clusterServicesMetadata struct {
   461  	Services []struct {
   462  		Host      string
   463  		Name      string
   464  		Namespace string
   465  	}
   466  }
   467  
   468  func extractClusterMetadataServices(t test.Failer, c *cluster.Cluster) []string {
   469  	got := c.GetMetadata().GetFilterMetadata()[util.IstioMetadataKey]
   470  	if got == nil {
   471  		return nil
   472  	}
   473  	s, err := protomarshal.Marshal(got)
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  	meta := clusterServicesMetadata{}
   478  	if err := json.Unmarshal(s, &meta); err != nil {
   479  		t.Fatal(err)
   480  	}
   481  	res := []string{}
   482  	for _, m := range meta.Services {
   483  		res = append(res, m.Host)
   484  	}
   485  	return res
   486  }
   487  
   488  func mtlsMode(m string) string {
   489  	return fmt.Sprintf(`apiVersion: security.istio.io/v1beta1
   490  kind: PeerAuthentication
   491  metadata:
   492    name: default
   493    namespace: istio-system
   494  spec:
   495    mtls:
   496      mode: %s
   497  `, m)
   498  }
   499  
   500  func TestInbound(t *testing.T) {
   501  	svc := `
   502  apiVersion: networking.istio.io/v1alpha3
   503  kind: ServiceEntry
   504  metadata:
   505    name: se
   506  spec:
   507    hosts:
   508    - foo.bar
   509    endpoints:
   510    - address: 1.1.1.1
   511    location: MESH_INTERNAL
   512    resolution: STATIC
   513    ports:
   514    - name: tcp
   515      number: 70
   516      protocol: TCP
   517    - name: http
   518      number: 80
   519      protocol: HTTP
   520    - name: auto
   521      number: 81
   522  ---
   523  `
   524  	cases := []struct {
   525  		Name       string
   526  		Call       simulation.Call
   527  		Disabled   simulation.Result
   528  		Permissive simulation.Result
   529  		Strict     simulation.Result
   530  	}{
   531  		{
   532  			Name: "tcp",
   533  			Call: simulation.Call{
   534  				Port:     70,
   535  				Protocol: simulation.TCP,
   536  				CallMode: simulation.CallModeInbound,
   537  			},
   538  			Disabled: simulation.Result{
   539  				ClusterMatched: "inbound|70||",
   540  			},
   541  			Permissive: simulation.Result{
   542  				ClusterMatched: "inbound|70||",
   543  			},
   544  			Strict: simulation.Result{
   545  				// Plaintext to strict, should fail
   546  				Error: simulation.ErrNoFilterChain,
   547  			},
   548  		},
   549  		{
   550  			Name: "http to tcp",
   551  			Call: simulation.Call{
   552  				Port:     70,
   553  				Protocol: simulation.HTTP,
   554  				CallMode: simulation.CallModeInbound,
   555  			},
   556  			Disabled: simulation.Result{
   557  				ClusterMatched: "inbound|70||",
   558  			},
   559  			Permissive: simulation.Result{
   560  				ClusterMatched: "inbound|70||",
   561  			},
   562  			Strict: simulation.Result{
   563  				// Plaintext to strict, should fail
   564  				Error: simulation.ErrNoFilterChain,
   565  			},
   566  		},
   567  		{
   568  			Name: "tls to tcp",
   569  			Call: simulation.Call{
   570  				Port:     70,
   571  				Protocol: simulation.TCP,
   572  				TLS:      simulation.TLS,
   573  				CallMode: simulation.CallModeInbound,
   574  			},
   575  			Disabled: simulation.Result{
   576  				ClusterMatched: "inbound|70||",
   577  			},
   578  			Permissive: simulation.Result{
   579  				ClusterMatched: "inbound|70||",
   580  			},
   581  			Strict: simulation.Result{
   582  				// TLS, but not mTLS
   583  				Error: simulation.ErrMTLSError,
   584  			},
   585  		},
   586  		{
   587  			Name: "https to tcp",
   588  			Call: simulation.Call{
   589  				Port:     70,
   590  				Protocol: simulation.HTTP,
   591  				TLS:      simulation.TLS,
   592  				CallMode: simulation.CallModeInbound,
   593  			},
   594  			Disabled: simulation.Result{
   595  				ClusterMatched: "inbound|70||",
   596  			},
   597  			Permissive: simulation.Result{
   598  				ClusterMatched: "inbound|70||",
   599  			},
   600  			Strict: simulation.Result{
   601  				// TLS, but not mTLS
   602  				Error: simulation.ErrMTLSError,
   603  			},
   604  		},
   605  		{
   606  			Name: "mtls tcp to tcp",
   607  			Call: simulation.Call{
   608  				Port:     70,
   609  				Protocol: simulation.TCP,
   610  				TLS:      simulation.MTLS,
   611  				CallMode: simulation.CallModeInbound,
   612  			},
   613  			Disabled: simulation.Result{
   614  				// This is probably a user error, but there is no reason we should block mTLS traffic
   615  				// we just will not terminate it
   616  				ClusterMatched: "inbound|70||",
   617  			},
   618  			Permissive: simulation.Result{
   619  				ClusterMatched: "inbound|70||",
   620  			},
   621  			Strict: simulation.Result{
   622  				ClusterMatched: "inbound|70||",
   623  			},
   624  		},
   625  		{
   626  			Name: "mtls http to tcp",
   627  			Call: simulation.Call{
   628  				Port:     70,
   629  				Protocol: simulation.HTTP,
   630  				TLS:      simulation.MTLS,
   631  				CallMode: simulation.CallModeInbound,
   632  			},
   633  			Disabled: simulation.Result{
   634  				// This is probably a user error, but there is no reason we should block mTLS traffic
   635  				// we just will not terminate it
   636  				ClusterMatched: "inbound|70||",
   637  			},
   638  			Permissive: simulation.Result{
   639  				ClusterMatched: "inbound|70||",
   640  			},
   641  			Strict: simulation.Result{
   642  				ClusterMatched: "inbound|70||",
   643  			},
   644  		},
   645  		{
   646  			Name: "http",
   647  			Call: simulation.Call{
   648  				Port:     80,
   649  				Protocol: simulation.HTTP,
   650  				CallMode: simulation.CallModeInbound,
   651  			},
   652  			Disabled: simulation.Result{
   653  				VirtualHostMatched: "inbound|http|80",
   654  				ClusterMatched:     "inbound|80||",
   655  			},
   656  			Permissive: simulation.Result{
   657  				VirtualHostMatched: "inbound|http|80",
   658  				ClusterMatched:     "inbound|80||",
   659  			},
   660  			Strict: simulation.Result{
   661  				// Plaintext to strict, should fail
   662  				Error: simulation.ErrNoFilterChain,
   663  			},
   664  		},
   665  		{
   666  			Name: "tls to http",
   667  			Call: simulation.Call{
   668  				Port:     80,
   669  				Protocol: simulation.TCP,
   670  				TLS:      simulation.TLS,
   671  				CallMode: simulation.CallModeInbound,
   672  			},
   673  			Disabled: simulation.Result{
   674  				// TLS is not terminated, so we will attempt to decode as HTTP and fail
   675  				Error: simulation.ErrProtocolError,
   676  			},
   677  			Permissive: simulation.Result{
   678  				// This could also be a protocol error. In the current implementation, we choose not
   679  				// to create a match since if we did it would just be rejected in HCM; no match
   680  				// is more performant
   681  				Error: simulation.ErrNoFilterChain,
   682  			},
   683  			Strict: simulation.Result{
   684  				// TLS, but not mTLS
   685  				Error: simulation.ErrMTLSError,
   686  			},
   687  		},
   688  		{
   689  			Name: "https to http",
   690  			Call: simulation.Call{
   691  				Port:     80,
   692  				Protocol: simulation.HTTP,
   693  				TLS:      simulation.TLS,
   694  				CallMode: simulation.CallModeInbound,
   695  			},
   696  			Disabled: simulation.Result{
   697  				// TLS is not terminated, so we will attempt to decode as HTTP and fail
   698  				Error: simulation.ErrProtocolError,
   699  			},
   700  			Permissive: simulation.Result{
   701  				// This could also be a protocol error. In the current implementation, we choose not
   702  				// to create a match since if we did it would just be rejected in HCM; no match
   703  				// is more performant
   704  				Error: simulation.ErrNoFilterChain,
   705  			},
   706  			Strict: simulation.Result{
   707  				// TLS, but not mTLS
   708  				Error: simulation.ErrMTLSError,
   709  			},
   710  		},
   711  		{
   712  			Name: "mtls to http",
   713  			Call: simulation.Call{
   714  				Port:     80,
   715  				Protocol: simulation.HTTP,
   716  				TLS:      simulation.MTLS,
   717  				CallMode: simulation.CallModeInbound,
   718  			},
   719  			Disabled: simulation.Result{
   720  				// TLS is not terminated, so we will attempt to decode as HTTP and fail
   721  				Error: simulation.ErrProtocolError,
   722  			},
   723  			Permissive: simulation.Result{
   724  				VirtualHostMatched: "inbound|http|80",
   725  				ClusterMatched:     "inbound|80||",
   726  			},
   727  			Strict: simulation.Result{
   728  				VirtualHostMatched: "inbound|http|80",
   729  				ClusterMatched:     "inbound|80||",
   730  			},
   731  		},
   732  		{
   733  			Name: "tcp to http",
   734  			Call: simulation.Call{
   735  				Port:     80,
   736  				Protocol: simulation.TCP,
   737  				CallMode: simulation.CallModeInbound,
   738  			},
   739  			Disabled: simulation.Result{
   740  				// Expected, the port only supports HTTP
   741  				Error: simulation.ErrProtocolError,
   742  			},
   743  			Permissive: simulation.Result{
   744  				// Expected, the port only supports HTTP
   745  				Error: simulation.ErrProtocolError,
   746  			},
   747  			Strict: simulation.Result{
   748  				// Plaintext to strict fails
   749  				Error: simulation.ErrNoFilterChain,
   750  			},
   751  		},
   752  		{
   753  			Name: "auto port http",
   754  			Call: simulation.Call{
   755  				Port:     81,
   756  				Protocol: simulation.HTTP,
   757  				CallMode: simulation.CallModeInbound,
   758  			},
   759  			Disabled: simulation.Result{
   760  				VirtualHostMatched: "inbound|http|81",
   761  				ClusterMatched:     "inbound|81||",
   762  			},
   763  			Permissive: simulation.Result{
   764  				VirtualHostMatched: "inbound|http|81",
   765  				ClusterMatched:     "inbound|81||",
   766  			},
   767  			Strict: simulation.Result{
   768  				// Plaintext to strict fails
   769  				Error: simulation.ErrNoFilterChain,
   770  			},
   771  		},
   772  		{
   773  			Name: "auto port http2",
   774  			Call: simulation.Call{
   775  				Port:     81,
   776  				Protocol: simulation.HTTP2,
   777  				CallMode: simulation.CallModeInbound,
   778  			},
   779  			Disabled: simulation.Result{
   780  				VirtualHostMatched: "inbound|http|81",
   781  				ClusterMatched:     "inbound|81||",
   782  			},
   783  			Permissive: simulation.Result{
   784  				VirtualHostMatched: "inbound|http|81",
   785  				ClusterMatched:     "inbound|81||",
   786  			},
   787  			Strict: simulation.Result{
   788  				// Plaintext to strict fails
   789  				Error: simulation.ErrNoFilterChain,
   790  			},
   791  		},
   792  		{
   793  			Name: "auto port tcp",
   794  			Call: simulation.Call{
   795  				Port:     81,
   796  				Protocol: simulation.TCP,
   797  				CallMode: simulation.CallModeInbound,
   798  			},
   799  			Disabled: simulation.Result{
   800  				ListenerMatched:    "virtualInbound",
   801  				FilterChainMatched: "0.0.0.0_81",
   802  				ClusterMatched:     "inbound|81||",
   803  				StrictMatch:        true,
   804  			},
   805  			Permissive: simulation.Result{
   806  				ListenerMatched:    "virtualInbound",
   807  				FilterChainMatched: "0.0.0.0_81",
   808  				ClusterMatched:     "inbound|81||",
   809  				StrictMatch:        true,
   810  			},
   811  			Strict: simulation.Result{
   812  				// Plaintext to strict fails
   813  				Error: simulation.ErrNoFilterChain,
   814  			},
   815  		},
   816  		{
   817  			Name: "tls to auto port",
   818  			Call: simulation.Call{
   819  				Port:     81,
   820  				Protocol: simulation.TCP,
   821  				TLS:      simulation.TLS,
   822  				CallMode: simulation.CallModeInbound,
   823  			},
   824  			Disabled: simulation.Result{
   825  				// Should go through the TCP chains
   826  				ListenerMatched:    "virtualInbound",
   827  				FilterChainMatched: "0.0.0.0_81",
   828  				ClusterMatched:     "inbound|81||",
   829  				StrictMatch:        true,
   830  			},
   831  			Permissive: simulation.Result{
   832  				// Should go through the TCP chains
   833  				ListenerMatched:    "virtualInbound",
   834  				FilterChainMatched: "0.0.0.0_81",
   835  				ClusterMatched:     "inbound|81||",
   836  				StrictMatch:        true,
   837  			},
   838  			Strict: simulation.Result{
   839  				// Tls, but not mTLS
   840  				Error: simulation.ErrMTLSError,
   841  			},
   842  		},
   843  		{
   844  			Name: "https to auto port",
   845  			Call: simulation.Call{
   846  				Port:     81,
   847  				Protocol: simulation.HTTP,
   848  				TLS:      simulation.TLS,
   849  				CallMode: simulation.CallModeInbound,
   850  			},
   851  			Disabled: simulation.Result{
   852  				// Should go through the TCP chains
   853  				ListenerMatched:    "virtualInbound",
   854  				FilterChainMatched: "0.0.0.0_81",
   855  				ClusterMatched:     "inbound|81||",
   856  				StrictMatch:        true,
   857  			},
   858  			Permissive: simulation.Result{
   859  				// Should go through the TCP chains
   860  				ListenerMatched:    "virtualInbound",
   861  				FilterChainMatched: "0.0.0.0_81",
   862  				ClusterMatched:     "inbound|81||",
   863  				StrictMatch:        true,
   864  			},
   865  			Strict: simulation.Result{
   866  				// Tls, but not mTLS
   867  				Error: simulation.ErrMTLSError,
   868  			},
   869  		},
   870  		{
   871  			Name: "mtls tcp to auto port",
   872  			Call: simulation.Call{
   873  				Port:     81,
   874  				Protocol: simulation.TCP,
   875  				TLS:      simulation.MTLS,
   876  				CallMode: simulation.CallModeInbound,
   877  			},
   878  			Disabled: simulation.Result{
   879  				// This is probably a user error, but there is no reason we should block mTLS traffic
   880  				// we just will not terminate it
   881  				ClusterMatched: "inbound|81||",
   882  			},
   883  			Permissive: simulation.Result{
   884  				// Should go through the TCP chains
   885  				ListenerMatched:    "virtualInbound",
   886  				FilterChainMatched: "0.0.0.0_81",
   887  				ClusterMatched:     "inbound|81||",
   888  				StrictMatch:        true,
   889  			},
   890  			Strict: simulation.Result{
   891  				// Should go through the TCP chains
   892  				ListenerMatched:    "virtualInbound",
   893  				FilterChainMatched: "0.0.0.0_81",
   894  				ClusterMatched:     "inbound|81||",
   895  				StrictMatch:        true,
   896  			},
   897  		},
   898  		{
   899  			Name: "mtls http to auto port",
   900  			Call: simulation.Call{
   901  				Port:     81,
   902  				Protocol: simulation.HTTP,
   903  				TLS:      simulation.MTLS,
   904  				CallMode: simulation.CallModeInbound,
   905  			},
   906  			Disabled: simulation.Result{
   907  				// This is probably a user error, but there is no reason we should block mTLS traffic
   908  				// we just will not terminate it
   909  				ClusterMatched: "inbound|81||",
   910  			},
   911  			Permissive: simulation.Result{
   912  				// Should go through the HTTP chains
   913  				VirtualHostMatched: "inbound|http|81",
   914  				ClusterMatched:     "inbound|81||",
   915  			},
   916  			Strict: simulation.Result{
   917  				// Should go through the HTTP chains
   918  				VirtualHostMatched: "inbound|http|81",
   919  				ClusterMatched:     "inbound|81||",
   920  			},
   921  		},
   922  		{
   923  			Name: "passthrough http",
   924  			Call: simulation.Call{
   925  				Address:  "1.2.3.4",
   926  				Port:     82,
   927  				Protocol: simulation.HTTP,
   928  				CallMode: simulation.CallModeInbound,
   929  			},
   930  			Disabled: simulation.Result{
   931  				ClusterMatched:     "InboundPassthroughClusterIpv4",
   932  				FilterChainMatched: "virtualInbound-catchall-http",
   933  			},
   934  			Permissive: simulation.Result{
   935  				ClusterMatched:     "InboundPassthroughClusterIpv4",
   936  				FilterChainMatched: "virtualInbound-catchall-http",
   937  			},
   938  			Strict: simulation.Result{
   939  				// Plaintext to strict fails
   940  				Error: simulation.ErrNoFilterChain,
   941  			},
   942  		},
   943  		{
   944  			Name: "passthrough tcp",
   945  			Call: simulation.Call{
   946  				Address:  "1.2.3.4",
   947  				Port:     82,
   948  				Protocol: simulation.TCP,
   949  				CallMode: simulation.CallModeInbound,
   950  			},
   951  			Disabled: simulation.Result{
   952  				ClusterMatched:     "InboundPassthroughClusterIpv4",
   953  				FilterChainMatched: "virtualInbound",
   954  			},
   955  			Permissive: simulation.Result{
   956  				ClusterMatched:     "InboundPassthroughClusterIpv4",
   957  				FilterChainMatched: "virtualInbound",
   958  			},
   959  			Strict: simulation.Result{
   960  				// Plaintext to strict fails
   961  				Error: simulation.ErrNoFilterChain,
   962  			},
   963  		},
   964  		{
   965  			Name: "passthrough tls",
   966  			Call: simulation.Call{
   967  				Address:  "1.2.3.4",
   968  				Port:     82,
   969  				Protocol: simulation.TCP,
   970  				TLS:      simulation.TLS,
   971  				CallMode: simulation.CallModeInbound,
   972  			},
   973  			Disabled: simulation.Result{
   974  				ClusterMatched:     "InboundPassthroughClusterIpv4",
   975  				FilterChainMatched: "virtualInbound",
   976  			},
   977  			Permissive: simulation.Result{
   978  				ClusterMatched: "InboundPassthroughClusterIpv4",
   979  			},
   980  			Strict: simulation.Result{
   981  				// tls, but not mTLS
   982  				Error: simulation.ErrMTLSError,
   983  			},
   984  		},
   985  		{
   986  			Name: "passthrough https",
   987  			Call: simulation.Call{
   988  				Address:  "1.2.3.4",
   989  				Port:     82,
   990  				Protocol: simulation.HTTP,
   991  				TLS:      simulation.TLS,
   992  				CallMode: simulation.CallModeInbound,
   993  			},
   994  			Disabled: simulation.Result{
   995  				ClusterMatched: "InboundPassthroughClusterIpv4",
   996  			},
   997  			Permissive: simulation.Result{
   998  				ClusterMatched: "InboundPassthroughClusterIpv4",
   999  			},
  1000  			Strict: simulation.Result{
  1001  				// tls, but not mTLS
  1002  				Error: simulation.ErrMTLSError,
  1003  			},
  1004  		},
  1005  		{
  1006  			Name: "passthrough mtls",
  1007  			Call: simulation.Call{
  1008  				Address:  "1.2.3.4",
  1009  				Port:     82,
  1010  				Protocol: simulation.HTTP,
  1011  				TLS:      simulation.MTLS,
  1012  				CallMode: simulation.CallModeInbound,
  1013  			},
  1014  			Disabled: simulation.Result{
  1015  				ClusterMatched: "InboundPassthroughClusterIpv4",
  1016  			},
  1017  			Permissive: simulation.Result{
  1018  				ClusterMatched: "InboundPassthroughClusterIpv4",
  1019  			},
  1020  			Strict: simulation.Result{
  1021  				ClusterMatched: "InboundPassthroughClusterIpv4",
  1022  			},
  1023  		},
  1024  	}
  1025  	t.Run("Disable", func(t *testing.T) {
  1026  		calls := []simulation.Expect{}
  1027  		for _, c := range cases {
  1028  			calls = append(calls, simulation.Expect{
  1029  				Name:   c.Name,
  1030  				Call:   c.Call,
  1031  				Result: c.Disabled,
  1032  			})
  1033  		}
  1034  		runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1035  			config: svc + mtlsMode("DISABLE"),
  1036  			calls:  calls,
  1037  		})
  1038  	})
  1039  
  1040  	t.Run("Permissive", func(t *testing.T) {
  1041  		calls := []simulation.Expect{}
  1042  		for _, c := range cases {
  1043  			calls = append(calls, simulation.Expect{
  1044  				Name:   c.Name,
  1045  				Call:   c.Call,
  1046  				Result: c.Permissive,
  1047  			})
  1048  		}
  1049  		runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1050  			config: svc + mtlsMode("PERMISSIVE"),
  1051  			calls:  calls,
  1052  		})
  1053  	})
  1054  
  1055  	t.Run("Strict", func(t *testing.T) {
  1056  		calls := []simulation.Expect{}
  1057  		for _, c := range cases {
  1058  			calls = append(calls, simulation.Expect{
  1059  				Name:   c.Name,
  1060  				Call:   c.Call,
  1061  				Result: c.Strict,
  1062  			})
  1063  		}
  1064  		runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1065  			config: svc + mtlsMode("STRICT"),
  1066  			calls:  calls,
  1067  		})
  1068  	})
  1069  }
  1070  
  1071  func TestHeadlessServices(t *testing.T) {
  1072  	ports := `
  1073    - name: http
  1074      port: 80
  1075    - name: auto
  1076      port: 81
  1077    - name: tcp
  1078      port: 82
  1079    - name: tls
  1080      port: 83
  1081    - name: https
  1082      port: 84`
  1083  
  1084  	calls := []simulation.Expect{}
  1085  	for _, call := range []simulation.Call{
  1086  		{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "headless.default.svc.cluster.local"},
  1087  
  1088  		// Auto port should support any protocol
  1089  		{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "headless.default.svc.cluster.local"},
  1090  		{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "headless.default.svc.cluster.local"},
  1091  		{Address: "1.2.3.4", Port: 81, Protocol: simulation.TCP, HostHeader: "headless.default.svc.cluster.local"},
  1092  
  1093  		{Address: "1.2.3.4", Port: 82, Protocol: simulation.TCP, HostHeader: "headless.default.svc.cluster.local"},
  1094  
  1095  		// Use short host name
  1096  		{Address: "1.2.3.4", Port: 83, Protocol: simulation.TCP, TLS: simulation.TLS, HostHeader: "headless.default"},
  1097  		{Address: "1.2.3.4", Port: 84, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "headless.default"},
  1098  	} {
  1099  		calls = append(calls, simulation.Expect{
  1100  			Name: fmt.Sprintf("%s-%d", call.Protocol, call.Port),
  1101  			Call: call,
  1102  			Result: simulation.Result{
  1103  				ClusterMatched: fmt.Sprintf("outbound|%d||headless.default.svc.cluster.local", call.Port),
  1104  			},
  1105  		})
  1106  	}
  1107  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1108  		kubeConfig: `apiVersion: v1
  1109  kind: Service
  1110  metadata:
  1111    name: headless
  1112    namespace: default
  1113  spec:
  1114    clusterIP: None
  1115    selector:
  1116      app: headless
  1117    ports:` + ports + `
  1118  ---
  1119  apiVersion: discovery.k8s.io/v1
  1120  kind: EndpointSlice
  1121  metadata:
  1122    name: headless
  1123    namespace: default
  1124    labels:
  1125      kubernetes.io/service-name: headless
  1126  endpoints:
  1127  - addresses:
  1128    - 1.2.3.4
  1129  ports:
  1130  ` + ports,
  1131  		calls: calls,
  1132  	},
  1133  	)
  1134  }
  1135  
  1136  func TestExternalNameServices(t *testing.T) {
  1137  	test.SetForTest(t, &features.EnableExternalNameAlias, true)
  1138  	ports := `
  1139    - name: http
  1140      port: 80
  1141    - name: auto
  1142      port: 81
  1143    - name: tcp
  1144      port: 82
  1145    - name: tls
  1146      port: 83
  1147    - name: https
  1148      port: 84`
  1149  
  1150  	calls := []simulation.Expect{}
  1151  	for _, call := range []simulation.Call{
  1152  		{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local"},
  1153  
  1154  		// Auto port should support any protocol
  1155  		{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local"},
  1156  		{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "alias.default.svc.cluster.local"},
  1157  		{Address: "1.2.3.4", Port: 81, Protocol: simulation.TCP},
  1158  
  1159  		{Address: "1.2.3.4", Port: 82, Protocol: simulation.TCP},
  1160  
  1161  		// Use short host name
  1162  		{Address: "1.2.3.4", Port: 83, Protocol: simulation.TCP, TLS: simulation.TLS, HostHeader: "alias.default"},
  1163  		{Address: "1.2.3.4", Port: 84, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "alias.default"},
  1164  	} {
  1165  		calls = append(calls, simulation.Expect{
  1166  			Name: fmt.Sprintf("%s-%d", call.Protocol, call.Port),
  1167  			Call: call,
  1168  			Result: simulation.Result{
  1169  				ClusterMatched: fmt.Sprintf("outbound|%d||concrete.default.svc.cluster.local", call.Port),
  1170  			},
  1171  		})
  1172  	}
  1173  	service := `apiVersion: v1
  1174  kind: Service
  1175  metadata:
  1176    name: alias
  1177    namespace: default
  1178  spec:
  1179    type: ExternalName
  1180    externalName: concrete.default.svc.cluster.local
  1181  ` + `---
  1182  apiVersion: v1
  1183  kind: Service
  1184  metadata:
  1185    name: concrete
  1186    namespace: default
  1187  spec:
  1188    clusterIP: 1.2.3.4
  1189    ports:` + ports
  1190  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1191  		kubeConfig: service,
  1192  		calls:      calls,
  1193  	})
  1194  
  1195  	// HTTP Routes
  1196  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1197  		config: `apiVersion: networking.istio.io/v1alpha3
  1198  kind: VirtualService
  1199  metadata:
  1200    name: alias
  1201  spec:
  1202    hosts:
  1203    - alias.default.svc.cluster.local
  1204    http:
  1205    - name: "route1"
  1206      match:
  1207      - uri:
  1208          prefix: "/one"
  1209      route:
  1210      - destination:
  1211          host: concrete.default.svc.cluster.local`,
  1212  		kubeConfig: service,
  1213  		calls: []simulation.Expect{
  1214  			{
  1215  				// This work, Host is just an opaque hostname match
  1216  				Name: "HTTP virtual service applies to alias fqdn",
  1217  				Call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local", Path: "/one"},
  1218  				Result: simulation.Result{
  1219  					RouteMatched:   "route1",
  1220  					ClusterMatched: "outbound|80||concrete.default.svc.cluster.local",
  1221  				},
  1222  			},
  1223  			{
  1224  				// Host is opaque, so no expansion
  1225  				Name: "HTTP virtual service does not apply to alias without exact match",
  1226  				Call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "alias.default", Path: "/one"},
  1227  				Result: simulation.Result{
  1228  					RouteMatched:   "default",
  1229  					ClusterMatched: "outbound|80||concrete.default.svc.cluster.local",
  1230  				},
  1231  			},
  1232  			{
  1233  				Name: "HTTP virtual service of alias does not apply to concrete",
  1234  				Call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "concrete.default.svc.cluster.local", Path: "/one"},
  1235  				Result: simulation.Result{
  1236  					RouteMatched:   "default",
  1237  					ClusterMatched: "outbound|80||concrete.default.svc.cluster.local",
  1238  				},
  1239  			},
  1240  			// Auto
  1241  			{
  1242  				// No opaque host match for auto
  1243  				Name: "Auto virtual service applies to alias fqdn",
  1244  				Call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local", Path: "/one"},
  1245  				Result: simulation.Result{
  1246  					RouteMatched:   "default",
  1247  					ClusterMatched: "outbound|81||concrete.default.svc.cluster.local",
  1248  				},
  1249  			},
  1250  			{
  1251  				// Host is opaque, so no expansion
  1252  				Name: "Auto virtual service does not apply to alias without exact match",
  1253  				Call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default", Path: "/one"},
  1254  				Result: simulation.Result{
  1255  					RouteMatched:   "default",
  1256  					ClusterMatched: "outbound|81||concrete.default.svc.cluster.local",
  1257  				},
  1258  			},
  1259  			{
  1260  				Name: "Auto virtual service of alias does not apply to concrete",
  1261  				Call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "concrete.default.svc.cluster.local", Path: "/one"},
  1262  				Result: simulation.Result{
  1263  					RouteMatched:   "default",
  1264  					ClusterMatched: "outbound|81||concrete.default.svc.cluster.local",
  1265  				},
  1266  			},
  1267  		},
  1268  	})
  1269  
  1270  	// TCP Routes
  1271  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1272  		config: `apiVersion: networking.istio.io/v1alpha3
  1273  kind: VirtualService
  1274  metadata:
  1275    name: alias
  1276  spec:
  1277    hosts:
  1278    - alias.default.svc.cluster.local
  1279    tcp:
  1280    - name: "route1"
  1281      route:
  1282      - destination:
  1283          host: concrete.default.svc.cluster.local
  1284          port:
  1285            number: 80`,
  1286  		kubeConfig: service,
  1287  		calls: []simulation.Expect{
  1288  			{
  1289  				Name: "TCP virtual services do not apply",
  1290  				Call: simulation.Call{Address: "1.2.3.4", Port: 82, Protocol: simulation.TCP, Path: "/one"},
  1291  				Result: simulation.Result{
  1292  					ClusterMatched: "outbound|82||concrete.default.svc.cluster.local",
  1293  				},
  1294  			},
  1295  		},
  1296  	})
  1297  }
  1298  
  1299  func TestExternalNameServicesWithoutAliases(t *testing.T) {
  1300  	test.SetForTest(t, &features.EnableExternalNameAlias, false)
  1301  	ports := `
  1302    - name: http
  1303      port: 80
  1304    - name: auto
  1305      port: 81
  1306    - name: tcp
  1307      port: 82
  1308    - name: tls
  1309      port: 83
  1310    - name: https
  1311      port: 84`
  1312  
  1313  	type tc struct {
  1314  		call     simulation.Call
  1315  		expected string
  1316  	}
  1317  	calls := []simulation.Expect{}
  1318  	for _, call := range []tc{
  1319  		{call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local"}, expected: "alias"},
  1320  
  1321  		// Auto port should support any protocol
  1322  		{call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local"}, expected: "concrete"},
  1323  		{call: simulation.Call{Address: "1.1.1.1", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local"}, expected: "alias"},
  1324  		{
  1325  			call:     simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "alias.default.svc.cluster.local"},
  1326  			expected: "concrete",
  1327  		},
  1328  		{call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.TCP}, expected: "concrete"},
  1329  
  1330  		{call: simulation.Call{Address: "1.2.3.4", Port: 82, Protocol: simulation.TCP}, expected: "concrete"},
  1331  
  1332  		// Use short host name
  1333  		{call: simulation.Call{Address: "1.2.3.4", Port: 83, Protocol: simulation.TCP, TLS: simulation.TLS, HostHeader: "alias.default"}, expected: "concrete"},
  1334  		{call: simulation.Call{Address: "1.2.3.4", Port: 84, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "alias.default"}, expected: "concrete"},
  1335  	} {
  1336  		calls = append(calls, simulation.Expect{
  1337  			Name: fmt.Sprintf("%s-%d", call.call.Protocol, call.call.Port),
  1338  			Call: call.call,
  1339  			Result: simulation.Result{
  1340  				ClusterMatched: fmt.Sprintf("outbound|%d||%s.default.svc.cluster.local", call.call.Port, call.expected),
  1341  			},
  1342  		})
  1343  	}
  1344  	service := `apiVersion: v1
  1345  kind: Service
  1346  metadata:
  1347    name: alias
  1348    namespace: default
  1349  spec:
  1350    type: ExternalName
  1351    externalName: concrete.default.svc.cluster.local
  1352    ports:` + ports + `
  1353  ---
  1354  apiVersion: v1
  1355  kind: Service
  1356  metadata:
  1357    name: concrete
  1358    namespace: default
  1359  spec:
  1360    clusterIP: 1.2.3.4
  1361    ports:` + ports
  1362  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1363  		kubeConfig: service,
  1364  		calls:      calls,
  1365  	})
  1366  
  1367  	// HTTP Routes
  1368  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1369  		config: `apiVersion: networking.istio.io/v1alpha3
  1370  kind: VirtualService
  1371  metadata:
  1372    name: alias
  1373  spec:
  1374    hosts:
  1375    - alias.default.svc.cluster.local
  1376    http:
  1377    - name: "route1"
  1378      match:
  1379      - uri:
  1380          prefix: "/one"
  1381      route:
  1382      - destination:
  1383          host: concrete.default.svc.cluster.local`,
  1384  		kubeConfig: service,
  1385  		calls: []simulation.Expect{
  1386  			{
  1387  				// This work, Host is just an opaque hostname match
  1388  				Name: "HTTP virtual service applies to alias fqdn",
  1389  				Call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local", Path: "/one"},
  1390  				Result: simulation.Result{
  1391  					RouteMatched:   "route1",
  1392  					ClusterMatched: "outbound|80||concrete.default.svc.cluster.local",
  1393  				},
  1394  			},
  1395  			{
  1396  				// Host is expanded
  1397  				Name: "HTTP virtual service does apply to alias without exact match",
  1398  				Call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "alias.default", Path: "/one"},
  1399  				Result: simulation.Result{
  1400  					RouteMatched:   "route1",
  1401  					ClusterMatched: "outbound|80||concrete.default.svc.cluster.local",
  1402  				},
  1403  			},
  1404  			{
  1405  				Name: "HTTP virtual service of alias does not apply to concrete",
  1406  				Call: simulation.Call{Address: "1.2.3.4", Port: 80, Protocol: simulation.HTTP, HostHeader: "concrete.default.svc.cluster.local", Path: "/one"},
  1407  				Result: simulation.Result{
  1408  					RouteMatched:   "default",
  1409  					ClusterMatched: "outbound|80||concrete.default.svc.cluster.local",
  1410  				},
  1411  			},
  1412  			// Auto
  1413  			{
  1414  				// No opaque host match for auto
  1415  				Name: "Auto virtual service applies to alias fqdn",
  1416  				Call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default.svc.cluster.local", Path: "/one"},
  1417  				Result: simulation.Result{
  1418  					RouteMatched:   "default",
  1419  					ClusterMatched: "outbound|81||concrete.default.svc.cluster.local",
  1420  				},
  1421  			},
  1422  			{
  1423  				// Host is opaque, so no expansion
  1424  				Name: "Auto virtual service does not apply to alias without exact match",
  1425  				Call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "alias.default", Path: "/one"},
  1426  				Result: simulation.Result{
  1427  					RouteMatched:   "default",
  1428  					ClusterMatched: "outbound|81||concrete.default.svc.cluster.local",
  1429  				},
  1430  			},
  1431  			{
  1432  				Name: "Auto virtual service of alias does not apply to concrete",
  1433  				Call: simulation.Call{Address: "1.2.3.4", Port: 81, Protocol: simulation.HTTP, HostHeader: "concrete.default.svc.cluster.local", Path: "/one"},
  1434  				Result: simulation.Result{
  1435  					RouteMatched:   "default",
  1436  					ClusterMatched: "outbound|81||concrete.default.svc.cluster.local",
  1437  				},
  1438  			},
  1439  		},
  1440  	})
  1441  
  1442  	// TCP Routes
  1443  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1444  		config: `apiVersion: networking.istio.io/v1alpha3
  1445  kind: VirtualService
  1446  metadata:
  1447    name: alias
  1448  spec:
  1449    hosts:
  1450    - alias.default.svc.cluster.local
  1451    tcp:
  1452    - name: "route1"
  1453      route:
  1454      - destination:
  1455          host: concrete.default.svc.cluster.local
  1456          port:
  1457            number: 80`,
  1458  		kubeConfig: service,
  1459  		calls: []simulation.Expect{
  1460  			{
  1461  				Name: "TCP virtual services do not apply",
  1462  				Call: simulation.Call{Address: "1.2.3.4", Port: 82, Protocol: simulation.TCP, Path: "/one"},
  1463  				Result: simulation.Result{
  1464  					ClusterMatched: "outbound|82||concrete.default.svc.cluster.local",
  1465  				},
  1466  			},
  1467  		},
  1468  	})
  1469  
  1470  	// HTTP Routes to alias
  1471  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1472  		config: `apiVersion: networking.istio.io/v1alpha3
  1473  kind: VirtualService
  1474  metadata:
  1475    name: alias
  1476  spec:
  1477    hosts:
  1478    - example.com
  1479    http:
  1480    - name: "route1"
  1481      match:
  1482      - uri:
  1483          prefix: "/one"
  1484      route:
  1485      - destination:
  1486          host: alias.default.svc.cluster.local`,
  1487  		kubeConfig: service,
  1488  		calls: []simulation.Expect{
  1489  			{
  1490  				// This work, Host is just an opaque hostname match
  1491  				Name: "HTTP route to alias",
  1492  				Call: simulation.Call{Port: 80, Protocol: simulation.HTTP, HostHeader: "example.com", Path: "/one"},
  1493  				Result: simulation.Result{
  1494  					RouteMatched:   "route1",
  1495  					ClusterMatched: "outbound|80||alias.default.svc.cluster.local",
  1496  				},
  1497  			},
  1498  		},
  1499  	})
  1500  }
  1501  
  1502  func TestPassthroughTraffic(t *testing.T) {
  1503  	calls := map[string]simulation.Call{}
  1504  	for port := 80; port < 87; port++ {
  1505  		for _, call := range []simulation.Call{
  1506  			{Port: port, Protocol: simulation.HTTP, TLS: simulation.Plaintext, HostHeader: "foo"},
  1507  			{Port: port, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "foo"},
  1508  			{Port: port, Protocol: simulation.HTTP, TLS: simulation.TLS, HostHeader: "foo", Alpn: "http/1.1"},
  1509  			{Port: port, Protocol: simulation.TCP, TLS: simulation.Plaintext, HostHeader: "foo"},
  1510  			{Port: port, Protocol: simulation.HTTP2, TLS: simulation.TLS, HostHeader: "foo"},
  1511  		} {
  1512  			suffix := ""
  1513  			if call.Alpn != "" {
  1514  				suffix = "-" + call.Alpn
  1515  			}
  1516  			calls[fmt.Sprintf("%v-%v-%v%v", call.Protocol, call.TLS, port, suffix)] = call
  1517  		}
  1518  	}
  1519  	ports := `
  1520    ports:
  1521    - name: http
  1522      number: 80
  1523      protocol: HTTP
  1524    - name: auto
  1525      number: 81
  1526    - name: tcp
  1527      number: 82
  1528      protocol: TCP
  1529    - name: tls
  1530      number: 83
  1531      protocol: TLS
  1532    - name: https
  1533      number: 84
  1534      protocol: HTTPS
  1535    - name: grpc
  1536      number: 85
  1537      protocol: GRPC
  1538    - name: h2
  1539      number: 86
  1540      protocol: HTTP2`
  1541  
  1542  	isHTTPPort := func(p int) bool {
  1543  		switch p {
  1544  		case 80, 85, 86:
  1545  			return true
  1546  		default:
  1547  			return false
  1548  		}
  1549  	}
  1550  	isAutoPort := func(p int) bool {
  1551  		switch p {
  1552  		case 81:
  1553  			return true
  1554  		default:
  1555  			return false
  1556  		}
  1557  	}
  1558  	for _, tp := range []meshconfig.MeshConfig_OutboundTrafficPolicy_Mode{
  1559  		meshconfig.MeshConfig_OutboundTrafficPolicy_REGISTRY_ONLY,
  1560  		meshconfig.MeshConfig_OutboundTrafficPolicy_ALLOW_ANY,
  1561  	} {
  1562  		t.Run(tp.String(), func(t *testing.T) {
  1563  			o := xds.FakeOptions{
  1564  				MeshConfig: func() *meshconfig.MeshConfig {
  1565  					m := mesh.DefaultMeshConfig()
  1566  					m.OutboundTrafficPolicy.Mode = tp
  1567  					return m
  1568  				}(),
  1569  			}
  1570  			expectedCluster := map[meshconfig.MeshConfig_OutboundTrafficPolicy_Mode]string{
  1571  				meshconfig.MeshConfig_OutboundTrafficPolicy_REGISTRY_ONLY: util.BlackHoleCluster,
  1572  				meshconfig.MeshConfig_OutboundTrafficPolicy_ALLOW_ANY:     util.PassthroughCluster,
  1573  			}[tp]
  1574  			t.Run("with VIP", func(t *testing.T) {
  1575  				testCalls := []simulation.Expect{}
  1576  				for name, call := range calls {
  1577  					e := simulation.Expect{
  1578  						Name: name,
  1579  						Call: call,
  1580  						Result: simulation.Result{
  1581  							ClusterMatched: expectedCluster,
  1582  						},
  1583  					}
  1584  					// For blackhole, we will 502 where possible instead of blackhole cluster
  1585  					// This only works for HTTP on HTTP
  1586  					if expectedCluster == util.BlackHoleCluster && call.IsHTTP() && isHTTPPort(call.Port) {
  1587  						e.Result.ClusterMatched = ""
  1588  						e.Result.VirtualHostMatched = util.BlackHole
  1589  					}
  1590  					testCalls = append(testCalls, e)
  1591  				}
  1592  				sort.Slice(testCalls, func(i, j int) bool {
  1593  					return testCalls[i].Name < testCalls[j].Name
  1594  				})
  1595  				runSimulationTest(t, nil, o,
  1596  					simulationTest{
  1597  						config: `
  1598  apiVersion: networking.istio.io/v1alpha3
  1599  kind: ServiceEntry
  1600  metadata:
  1601    name: se
  1602  spec:
  1603    hosts:
  1604    - istio.io
  1605    addresses: [1.2.3.4]
  1606    location: MESH_EXTERNAL
  1607    resolution: DNS` + ports,
  1608  						calls: testCalls,
  1609  					})
  1610  			})
  1611  			t.Run("without VIP", func(t *testing.T) {
  1612  				testCalls := []simulation.Expect{}
  1613  				for name, call := range calls {
  1614  					e := simulation.Expect{
  1615  						Name: name,
  1616  						Call: call,
  1617  						Result: simulation.Result{
  1618  							ClusterMatched: expectedCluster,
  1619  						},
  1620  					}
  1621  					// For blackhole, we will 502 where possible instead of blackhole cluster
  1622  					// This only works for HTTP on HTTP
  1623  					if expectedCluster == util.BlackHoleCluster && call.IsHTTP() && (isHTTPPort(call.Port) || isAutoPort(call.Port)) {
  1624  						e.Result.ClusterMatched = ""
  1625  						e.Result.VirtualHostMatched = util.BlackHole
  1626  					}
  1627  					// TCP without a VIP will capture everything.
  1628  					// Auto without a VIP is similar, but HTTP happens to work because routing is done on header
  1629  					if call.Port == 82 || (call.Port == 81 && !call.IsHTTP()) {
  1630  						e.Result.Error = nil
  1631  						e.Result.ClusterMatched = ""
  1632  					}
  1633  					testCalls = append(testCalls, e)
  1634  				}
  1635  				sort.Slice(testCalls, func(i, j int) bool {
  1636  					return testCalls[i].Name < testCalls[j].Name
  1637  				})
  1638  				runSimulationTest(t, nil, o,
  1639  					simulationTest{
  1640  						config: `
  1641  apiVersion: networking.istio.io/v1alpha3
  1642  kind: ServiceEntry
  1643  metadata:
  1644    name: se
  1645  spec:
  1646    hosts:
  1647    - istio.io
  1648    location: MESH_EXTERNAL
  1649    resolution: DNS` + ports,
  1650  						calls: testCalls,
  1651  					})
  1652  			})
  1653  		})
  1654  	}
  1655  }
  1656  
  1657  func TestLoop(t *testing.T) {
  1658  	runSimulationTest(t, nil, xds.FakeOptions{}, simulationTest{
  1659  		calls: []simulation.Expect{
  1660  			{
  1661  				Name: "direct request to outbound port",
  1662  				Call: simulation.Call{
  1663  					Port:     15001,
  1664  					Protocol: simulation.TCP,
  1665  				},
  1666  				Result: simulation.Result{
  1667  					// This request should be blocked
  1668  					ClusterMatched: "BlackHoleCluster",
  1669  				},
  1670  			},
  1671  			{
  1672  				Name: "direct request to inbound port",
  1673  				Call: simulation.Call{
  1674  					Port:     15006,
  1675  					Protocol: simulation.TCP,
  1676  				},
  1677  				Result: simulation.Result{
  1678  					// This request should be blocked
  1679  					ClusterMatched: "BlackHoleCluster",
  1680  				},
  1681  			},
  1682  		},
  1683  	})
  1684  }
  1685  
  1686  func TestInboundSidecarTLSModes(t *testing.T) {
  1687  	peerAuthConfig := func(m string) string {
  1688  		return fmt.Sprintf(`apiVersion: security.istio.io/v1beta1
  1689  kind: PeerAuthentication
  1690  metadata:
  1691    name: peer-auth
  1692    namespace: default
  1693  spec:
  1694    selector:
  1695      matchLabels:
  1696        app: foo
  1697    mtls:
  1698      mode: STRICT
  1699    portLevelMtls:
  1700      9080:
  1701        mode: %s
  1702  ---
  1703  `, m)
  1704  	}
  1705  	sidecarSimple := func(protocol string) string {
  1706  		return fmt.Sprintf(`
  1707  apiVersion: networking.istio.io/v1alpha3
  1708  kind: Sidecar
  1709  metadata:
  1710    labels:
  1711      app: foo
  1712    name: sidecar
  1713    namespace: default
  1714  spec:
  1715    ingress:
  1716      - defaultEndpoint: 0.0.0.0:9080
  1717        port:
  1718          name: tls
  1719          number: 9080
  1720          protocol: %s
  1721        tls:
  1722          mode: SIMPLE
  1723          privateKey: "httpbinkey.pem"
  1724          serverCertificate: "httpbin.pem"
  1725    workloadSelector:
  1726      labels:
  1727        app: foo
  1728  ---
  1729  `, protocol)
  1730  	}
  1731  	sidecarMutual := func(protocol string) string {
  1732  		return fmt.Sprintf(`
  1733  apiVersion: networking.istio.io/v1alpha3
  1734  kind: Sidecar
  1735  metadata:
  1736    labels:
  1737      app: foo
  1738    name: sidecar
  1739    namespace: default
  1740  spec:
  1741    ingress:
  1742      - defaultEndpoint: 0.0.0.0:9080
  1743        port:
  1744          name: tls
  1745          number: 9080
  1746          protocol: %s
  1747        tls:
  1748          mode: MUTUAL
  1749          privateKey: "httpbinkey.pem"
  1750          serverCertificate: "httpbin.pem"
  1751          caCertificates: "rootCA.pem"
  1752    workloadSelector:
  1753      labels:
  1754        app: foo
  1755  ---
  1756  `, protocol)
  1757  	}
  1758  	expectedTLSContext := func(filterChain *listener.FilterChain) error {
  1759  		tlsContext := &tls.DownstreamTlsContext{}
  1760  		ts := filterChain.GetTransportSocket().GetTypedConfig()
  1761  		if ts == nil {
  1762  			return fmt.Errorf("expected transport socket for chain %v", filterChain.GetName())
  1763  		}
  1764  		if err := ts.UnmarshalTo(tlsContext); err != nil {
  1765  			return err
  1766  		}
  1767  		commonTLSContext := tlsContext.CommonTlsContext
  1768  		if len(commonTLSContext.TlsCertificateSdsSecretConfigs) == 0 {
  1769  			return fmt.Errorf("expected tls certificates")
  1770  		}
  1771  		if commonTLSContext.TlsCertificateSdsSecretConfigs[0].Name != "file-cert:httpbin.pem~httpbinkey.pem" {
  1772  			return fmt.Errorf("expected certificate httpbin.pem, actual %s", commonTLSContext.TlsCertificates[0].CertificateChain.String())
  1773  		}
  1774  		if tlsContext.RequireClientCertificate.Value {
  1775  			return fmt.Errorf("expected RequireClientCertificate to be false")
  1776  		}
  1777  		return nil
  1778  	}
  1779  
  1780  	mkCall := func(port int, protocol simulation.Protocol,
  1781  		tls simulation.TLSMode, validations []simulation.CustomFilterChainValidation,
  1782  		mTLSSecretConfigName string,
  1783  	) simulation.Call {
  1784  		return simulation.Call{
  1785  			Protocol:                  protocol,
  1786  			Port:                      port,
  1787  			CallMode:                  simulation.CallModeInbound,
  1788  			TLS:                       tls,
  1789  			CustomListenerValidations: validations,
  1790  			MtlsSecretConfigName:      mTLSSecretConfigName,
  1791  		}
  1792  	}
  1793  	cases := []struct {
  1794  		name   string
  1795  		config string
  1796  		calls  []simulation.Expect
  1797  	}{
  1798  		{
  1799  			name:   "sidecar http over TLS simple mode with peer auth on port disabled",
  1800  			config: peerAuthConfig("DISABLE") + sidecarSimple("HTTPS"),
  1801  			calls: []simulation.Expect{
  1802  				{
  1803  					Name: "http over tls",
  1804  					Call: mkCall(9080, simulation.HTTP, simulation.TLS, []simulation.CustomFilterChainValidation{expectedTLSContext}, ""),
  1805  					Result: simulation.Result{
  1806  						FilterChainMatched: "1.1.1.1_9080",
  1807  						ClusterMatched:     "inbound|9080||",
  1808  						VirtualHostMatched: "inbound|http|9080",
  1809  						RouteMatched:       "default",
  1810  						ListenerMatched:    "virtualInbound",
  1811  					},
  1812  				},
  1813  				{
  1814  					Name: "plaintext",
  1815  					Call: mkCall(9080, simulation.HTTP, simulation.Plaintext, nil, ""),
  1816  					Result: simulation.Result{
  1817  						Error: simulation.ErrNoFilterChain,
  1818  					},
  1819  				},
  1820  				{
  1821  					Name: "http over mTLS",
  1822  					Call: mkCall(9080, simulation.HTTP, simulation.MTLS, nil, "file-cert:httpbin.pem~httpbinkey.pem"),
  1823  					Result: simulation.Result{
  1824  						Error: simulation.ErrMTLSError,
  1825  					},
  1826  				},
  1827  			},
  1828  		},
  1829  		{
  1830  			name:   "sidecar TCP over TLS simple mode with peer auth on port disabled",
  1831  			config: peerAuthConfig("DISABLE") + sidecarSimple("TLS"),
  1832  			calls: []simulation.Expect{
  1833  				{
  1834  					Name: "tcp over tls",
  1835  					Call: mkCall(9080, simulation.TCP, simulation.TLS, []simulation.CustomFilterChainValidation{expectedTLSContext}, ""),
  1836  					Result: simulation.Result{
  1837  						FilterChainMatched: "1.1.1.1_9080",
  1838  						ClusterMatched:     "inbound|9080||",
  1839  						ListenerMatched:    "virtualInbound",
  1840  					},
  1841  				},
  1842  				{
  1843  					Name: "plaintext",
  1844  					Call: mkCall(9080, simulation.TCP, simulation.Plaintext, nil, ""),
  1845  					Result: simulation.Result{
  1846  						Error: simulation.ErrNoFilterChain,
  1847  					},
  1848  				},
  1849  				{
  1850  					Name: "tcp over mTLS",
  1851  					Call: mkCall(9080, simulation.TCP, simulation.MTLS, nil, "file-cert:httpbin.pem~httpbinkey.pem"),
  1852  					Result: simulation.Result{
  1853  						Error: simulation.ErrMTLSError,
  1854  					},
  1855  				},
  1856  			},
  1857  		},
  1858  		{
  1859  			name:   "sidecar http over mTLS mutual mode with peer auth on port disabled",
  1860  			config: peerAuthConfig("DISABLE") + sidecarMutual("HTTPS"),
  1861  			calls: []simulation.Expect{
  1862  				{
  1863  					Name: "http over mtls",
  1864  					Call: mkCall(9080, simulation.HTTP, simulation.MTLS, nil, "file-cert:httpbin.pem~httpbinkey.pem"),
  1865  					Result: simulation.Result{
  1866  						FilterChainMatched: "1.1.1.1_9080",
  1867  						ClusterMatched:     "inbound|9080||",
  1868  						ListenerMatched:    "virtualInbound",
  1869  					},
  1870  				},
  1871  				{
  1872  					Name: "plaintext",
  1873  					Call: mkCall(9080, simulation.HTTP, simulation.Plaintext, nil, ""),
  1874  					Result: simulation.Result{
  1875  						Error: simulation.ErrNoFilterChain,
  1876  					},
  1877  				},
  1878  				{
  1879  					Name: "http over tls",
  1880  					Call: mkCall(9080, simulation.HTTP, simulation.TLS, nil, "file-cert:httpbin.pem~httpbinkey.pem"),
  1881  					Result: simulation.Result{
  1882  						Error: simulation.ErrMTLSError,
  1883  					},
  1884  				},
  1885  			},
  1886  		},
  1887  		{
  1888  			name:   "sidecar tcp over mTLS mutual mode with peer auth on port disabled",
  1889  			config: peerAuthConfig("DISABLE") + sidecarMutual("TLS"),
  1890  			calls: []simulation.Expect{
  1891  				{
  1892  					Name: "tcp over mtls",
  1893  					Call: mkCall(9080, simulation.TCP, simulation.MTLS, nil, "file-cert:httpbin.pem~httpbinkey.pem"),
  1894  					Result: simulation.Result{
  1895  						FilterChainMatched: "1.1.1.1_9080",
  1896  						ClusterMatched:     "inbound|9080||",
  1897  						ListenerMatched:    "virtualInbound",
  1898  					},
  1899  				},
  1900  				{
  1901  					Name: "plaintext",
  1902  					Call: mkCall(9080, simulation.TCP, simulation.Plaintext, nil, ""),
  1903  					Result: simulation.Result{
  1904  						Error: simulation.ErrNoFilterChain,
  1905  					},
  1906  				},
  1907  				{
  1908  					Name: "http over tls",
  1909  					Call: mkCall(9080, simulation.TCP, simulation.TLS, nil, "file-cert:httpbin.pem~httpbinkey.pem"),
  1910  					Result: simulation.Result{
  1911  						Error: simulation.ErrMTLSError,
  1912  					},
  1913  				},
  1914  			},
  1915  		},
  1916  		{
  1917  			name:   "sidecar http over TLS SIMPLE mode with peer auth on port STRICT",
  1918  			config: peerAuthConfig("STRICT") + sidecarMutual("TLS"),
  1919  			calls: []simulation.Expect{
  1920  				{
  1921  					Name: "http over tls",
  1922  					Call: mkCall(9080, simulation.HTTP, simulation.TLS, nil, ""),
  1923  					Result: simulation.Result{
  1924  						Error: simulation.ErrMTLSError,
  1925  					},
  1926  				},
  1927  				{
  1928  					Name: "plaintext",
  1929  					Call: mkCall(9080, simulation.HTTP, simulation.Plaintext, nil, ""),
  1930  					Result: simulation.Result{
  1931  						Error: simulation.ErrNoFilterChain,
  1932  					},
  1933  				},
  1934  				{
  1935  					Name: "http over mtls",
  1936  					Call: mkCall(9080, simulation.HTTP, simulation.MTLS, nil, ""),
  1937  					Result: simulation.Result{
  1938  						FilterChainMatched: "1.1.1.1_9080",
  1939  						ClusterMatched:     "inbound|9080||",
  1940  						ListenerMatched:    "virtualInbound",
  1941  					},
  1942  				},
  1943  			},
  1944  		},
  1945  	}
  1946  	proxy := &model.Proxy{
  1947  		Labels:   map[string]string{"app": "foo"},
  1948  		Metadata: &model.NodeMetadata{Labels: map[string]string{"app": "foo"}},
  1949  	}
  1950  	test.SetForTest(t, &features.EnableTLSOnSidecarIngress, true)
  1951  	for _, tt := range cases {
  1952  		runSimulationTest(t, proxy, xds.FakeOptions{}, simulationTest{
  1953  			name:   tt.name,
  1954  			config: tt.config,
  1955  			calls:  tt.calls,
  1956  		})
  1957  	}
  1958  }
  1959  
  1960  const (
  1961  	TimeOlder = "2019-01-01T00:00:00Z"
  1962  	TimeBase  = "2020-01-01T00:00:00Z"
  1963  	TimeNewer = "2021-01-01T00:00:00Z"
  1964  )
  1965  
  1966  type Configer interface {
  1967  	Config(t *testing.T, variant string) string
  1968  }
  1969  
  1970  type vsArgs struct {
  1971  	Namespace string
  1972  	Match     string
  1973  	Matches   []string
  1974  	GwMatches []types.NamespacedName
  1975  	Dest      string
  1976  	Port      int
  1977  	PortMatch int
  1978  	Time      string
  1979  }
  1980  
  1981  func (args vsArgs) Config(t *testing.T, variant string) string {
  1982  	if args.Time == "" {
  1983  		args.Time = TimeBase
  1984  	}
  1985  
  1986  	if args.Matches == nil {
  1987  		args.Matches = []string{args.Match}
  1988  	}
  1989  	if variant == "httproute" {
  1990  		if args.GwMatches == nil {
  1991  			args.GwMatches = make([]types.NamespacedName, 0, len(args.Matches))
  1992  			for _, m := range args.Matches {
  1993  				spl := strings.Split(m, ".")
  1994  				if len(spl) != 5 {
  1995  					t.Skipf("unsupported match: %v", spl)
  1996  				}
  1997  				if spl[0] == "*" {
  1998  					t.Skipf("unsupported match: %v", spl)
  1999  				}
  2000  				args.GwMatches = append(args.GwMatches, types.NamespacedName{
  2001  					Namespace: spl[1],
  2002  					Name:      spl[0],
  2003  				})
  2004  			}
  2005  		}
  2006  	}
  2007  	switch variant {
  2008  	case "httproute":
  2009  		return tmpl.MustEvaluate(`apiVersion: gateway.networking.k8s.io/v1beta1
  2010  kind: HTTPRoute
  2011  metadata:
  2012    name: "{{.Namespace}}{{.Match | replace "*" "wild"}}{{.Dest}}"
  2013    namespace: {{.Namespace}}
  2014    creationTimestamp: "{{.Time}}"
  2015  spec:
  2016    parentRefs:
  2017  {{- range $val := .GwMatches }}
  2018    - group: ""
  2019      kind: Service
  2020      name: "{{$val.Name}}"
  2021      namespace: "{{$val.Namespace}}"
  2022  {{ with $.PortMatch }}
  2023      port: {{.}}
  2024  {{ end }}
  2025  {{ end }}
  2026    rules:
  2027    - backendRefs:
  2028      - kind: Hostname
  2029        group: networking.istio.io
  2030        name: {{.Dest}}
  2031        port: {{.Port | default 80}}
  2032  `, args)
  2033  	case "virtualservice":
  2034  		return tmpl.MustEvaluate(`apiVersion: networking.istio.io/v1alpha3
  2035  kind: VirtualService
  2036  metadata:
  2037    name: "{{.Namespace}}{{.Match | replace "*" "wild"}}{{.Dest}}"
  2038    namespace: {{.Namespace}}
  2039    creationTimestamp: "{{.Time}}"
  2040  spec:
  2041    hosts:
  2042  {{- range $val := .Matches }}
  2043    - "{{$val}}"
  2044  {{ end }}
  2045    http:
  2046    - route:
  2047      - destination:
  2048          host: {{.Dest}}
  2049  {{ with .Port }}
  2050          port:
  2051            number: {{.}}
  2052  {{ end }}
  2053  {{ with .PortMatch }}
  2054      match:
  2055      - port: {{.}}
  2056  {{ end }}
  2057  `, args)
  2058  	default:
  2059  		panic(variant + " unknown")
  2060  	}
  2061  }
  2062  
  2063  type scArgs struct {
  2064  	Namespace string
  2065  	Egress    []string
  2066  }
  2067  
  2068  func (args scArgs) Config(t *testing.T, variant string) string {
  2069  	return tmpl.MustEvaluate(`apiVersion: networking.istio.io/v1alpha3
  2070  kind: Sidecar
  2071  metadata:
  2072    name: "{{.Namespace}}"
  2073    namespace: "{{.Namespace}}"
  2074  spec:
  2075    egress:
  2076    - hosts:
  2077  {{- range $val := .Egress }}
  2078      - "{{$val}}"
  2079  {{- end }}
  2080  `, args)
  2081  }
  2082  
  2083  func TestSidecarRoutes(t *testing.T) {
  2084  	knownServices := `
  2085  apiVersion: v1
  2086  kind: Service
  2087  metadata:
  2088    name: known
  2089    namespace: default
  2090  spec:
  2091    clusterIP: 2.0.0.0
  2092    ports:
  2093    - port: 80
  2094      name: http
  2095    - port: 8080
  2096      name: http
  2097  ---
  2098  apiVersion: v1
  2099  kind: Service
  2100  metadata:
  2101    name: alt-known
  2102    namespace: default
  2103  spec:
  2104    clusterIP: 2.0.0.1
  2105    ports:
  2106    - port: 80
  2107      name: http
  2108    - port: 8080
  2109      name: http
  2110  ---
  2111  apiVersion: v1
  2112  kind: Service
  2113  metadata:
  2114    name: not-default
  2115    namespace: not-default
  2116  spec:
  2117    clusterIP: 2.0.0.2
  2118    ports:
  2119    - port: 80
  2120      name: http
  2121    - port: 8080
  2122      name: http
  2123  `
  2124  	proxy := func(ns string) *model.Proxy {
  2125  		return &model.Proxy{ConfigNamespace: ns}
  2126  	}
  2127  	cases := []struct {
  2128  		name            string
  2129  		cfg             []Configer
  2130  		proxy           *model.Proxy
  2131  		routeName       string
  2132  		expected        map[string][]string
  2133  		expectedGateway map[string][]string
  2134  		oldestWins      bool
  2135  	}{
  2136  		// Port 80 has special cases as there is defaulting logic around this port
  2137  		{
  2138  			name: "simple port 80",
  2139  			cfg: []Configer{vsArgs{
  2140  				Namespace: "default",
  2141  				Match:     "known.default.svc.cluster.local",
  2142  				Dest:      "alt-known.default.svc.cluster.local",
  2143  			}},
  2144  			proxy:     proxy("default"),
  2145  			routeName: "80",
  2146  			expected: map[string][]string{
  2147  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2148  			},
  2149  		},
  2150  		{
  2151  			name: "simple port 8080",
  2152  			cfg: []Configer{vsArgs{
  2153  				Namespace: "default",
  2154  				Match:     "known.default.svc.cluster.local",
  2155  				Dest:      "alt-known.default.svc.cluster.local",
  2156  			}},
  2157  			proxy:     proxy("default"),
  2158  			routeName: "8080",
  2159  			expected: map[string][]string{
  2160  				"known.default.svc.cluster.local": {"outbound|8080||alt-known.default.svc.cluster.local"},
  2161  			},
  2162  			expectedGateway: map[string][]string{
  2163  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2164  			},
  2165  		},
  2166  		{
  2167  			name: "unknown port 80",
  2168  			cfg: []Configer{vsArgs{
  2169  				Namespace: "default",
  2170  				Match:     "foo.default.svc.cluster.local",
  2171  				Dest:      "foo.default.svc.cluster.local",
  2172  			}},
  2173  			proxy:     proxy("default"),
  2174  			routeName: "80",
  2175  			expected: map[string][]string{
  2176  				"foo.default.svc.cluster.local": {"outbound|80||foo.default.svc.cluster.local"},
  2177  			},
  2178  			expectedGateway: map[string][]string{
  2179  				"foo.default.svc.cluster.local": nil,
  2180  			},
  2181  		},
  2182  		{
  2183  			name: "unknown port 8080",
  2184  			cfg: []Configer{vsArgs{
  2185  				Namespace: "default",
  2186  				Match:     "foo.default.svc.cluster.local",
  2187  				Dest:      "foo.default.svc.cluster.local",
  2188  			}},
  2189  			proxy:     proxy("default"),
  2190  			routeName: "8080",
  2191  			// For unknown services, we only will add a route to the port 80
  2192  			expected: map[string][]string{
  2193  				"foo.default.svc.cluster.local": nil,
  2194  			},
  2195  		},
  2196  		{
  2197  			name: "unknown port 8080 match 8080",
  2198  			cfg: []Configer{vsArgs{
  2199  				Namespace: "default",
  2200  				Match:     "foo.default.svc.cluster.local",
  2201  				Dest:      "foo.default.svc.cluster.local",
  2202  				PortMatch: 8080,
  2203  			}},
  2204  			proxy:     proxy("default"),
  2205  			routeName: "8080",
  2206  			// For unknown services, we only will add a route to the port 80
  2207  			expected: map[string][]string{
  2208  				"foo.default.svc.cluster.local": nil,
  2209  			},
  2210  		},
  2211  		{
  2212  			name: "unknown port 8080 dest 8080 ",
  2213  			cfg: []Configer{vsArgs{
  2214  				Namespace: "default",
  2215  				Match:     "foo.default.svc.cluster.local",
  2216  				Dest:      "foo.default.svc.cluster.local",
  2217  				Port:      8080,
  2218  			}},
  2219  			proxy:     proxy("default"),
  2220  			routeName: "8080",
  2221  			// For unknown services, we only will add a route to the port 80
  2222  			expected: map[string][]string{
  2223  				"foo.default.svc.cluster.local": nil,
  2224  			},
  2225  		},
  2226  		{
  2227  			name: "producer rule port 80",
  2228  			cfg: []Configer{vsArgs{
  2229  				Namespace: "default",
  2230  				Match:     "known.default.svc.cluster.local",
  2231  				Dest:      "alt-known.default.svc.cluster.local",
  2232  			}},
  2233  			proxy:     proxy("not-default"),
  2234  			routeName: "80",
  2235  			expected: map[string][]string{
  2236  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2237  			},
  2238  		},
  2239  		{
  2240  			name: "producer rule port 8080",
  2241  			cfg: []Configer{vsArgs{
  2242  				Namespace: "default",
  2243  				Match:     "known.default.svc.cluster.local",
  2244  				Dest:      "alt-known.default.svc.cluster.local",
  2245  			}},
  2246  			proxy:     proxy("not-default"),
  2247  			routeName: "8080",
  2248  			expected: map[string][]string{
  2249  				"known.default.svc.cluster.local": {"outbound|8080||alt-known.default.svc.cluster.local"},
  2250  			},
  2251  			expectedGateway: map[string][]string{ // No implicit port matching for gateway
  2252  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2253  			},
  2254  		},
  2255  		{
  2256  			name: "consumer rule port 80",
  2257  			cfg: []Configer{vsArgs{
  2258  				Namespace: "not-default",
  2259  				Match:     "known.default.svc.cluster.local",
  2260  				Dest:      "alt-known.default.svc.cluster.local",
  2261  			}},
  2262  			proxy:     proxy("not-default"),
  2263  			routeName: "80",
  2264  			expected: map[string][]string{
  2265  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2266  			},
  2267  		},
  2268  		{
  2269  			name: "consumer rule port 8080",
  2270  			cfg: []Configer{vsArgs{
  2271  				Namespace: "not-default",
  2272  				Match:     "known.default.svc.cluster.local",
  2273  				Dest:      "alt-known.default.svc.cluster.local",
  2274  			}},
  2275  			proxy:     proxy("not-default"),
  2276  			routeName: "8080",
  2277  			expected: map[string][]string{
  2278  				"known.default.svc.cluster.local": {"outbound|8080||alt-known.default.svc.cluster.local"},
  2279  			},
  2280  			expectedGateway: map[string][]string{ // No implicit port matching for gateway
  2281  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2282  			},
  2283  		},
  2284  		{
  2285  			name: "arbitrary rule port 80",
  2286  			cfg: []Configer{vsArgs{
  2287  				Namespace: "arbitrary",
  2288  				Match:     "known.default.svc.cluster.local",
  2289  				Dest:      "alt-known.default.svc.cluster.local",
  2290  			}},
  2291  			proxy:     proxy("not-default"),
  2292  			routeName: "80",
  2293  			expected: map[string][]string{
  2294  				"known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2295  			},
  2296  			expectedGateway: map[string][]string{
  2297  				"known.default.svc.cluster.local": {"outbound|80||known.default.svc.cluster.local"},
  2298  			},
  2299  		},
  2300  		{
  2301  			name: "arbitrary rule port 8080",
  2302  			cfg: []Configer{vsArgs{
  2303  				Namespace: "arbitrary",
  2304  				Match:     "known.default.svc.cluster.local",
  2305  				Dest:      "alt-known.default.svc.cluster.local",
  2306  			}},
  2307  			proxy:     proxy("not-default"),
  2308  			routeName: "8080",
  2309  			expected: map[string][]string{
  2310  				"known.default.svc.cluster.local": {"outbound|8080||alt-known.default.svc.cluster.local"},
  2311  			},
  2312  			expectedGateway: map[string][]string{
  2313  				"known.default.svc.cluster.local": {"outbound|8080||known.default.svc.cluster.local"},
  2314  			},
  2315  		},
  2316  		{
  2317  			name: "multiple rules 80",
  2318  			cfg: []Configer{
  2319  				vsArgs{
  2320  					Namespace: "arbitrary",
  2321  					Match:     "known.default.svc.cluster.local",
  2322  					Dest:      "arbitrary.example.com",
  2323  					Time:      TimeOlder,
  2324  				},
  2325  				vsArgs{
  2326  					Namespace: "default",
  2327  					Match:     "known.default.svc.cluster.local",
  2328  					Dest:      "default.example.com",
  2329  					Time:      TimeBase,
  2330  				},
  2331  				vsArgs{
  2332  					Namespace: "not-default",
  2333  					Match:     "known.default.svc.cluster.local",
  2334  					Dest:      "not-default.example.com",
  2335  					Time:      TimeNewer,
  2336  				},
  2337  			},
  2338  			proxy:     proxy("not-default"),
  2339  			routeName: "80",
  2340  			expected: map[string][]string{
  2341  				// Oldest wins
  2342  				"known.default.svc.cluster.local": {"outbound|80||arbitrary.example.com"},
  2343  			},
  2344  			expectedGateway: map[string][]string{
  2345  				"known.default.svc.cluster.local": {"outbound|80||not-default.example.com"},
  2346  			},
  2347  		},
  2348  		{
  2349  			name: "multiple rules 8080",
  2350  			cfg: []Configer{
  2351  				vsArgs{
  2352  					Namespace: "arbitrary",
  2353  					Match:     "known.default.svc.cluster.local",
  2354  					Dest:      "arbitrary.example.com",
  2355  					Time:      TimeOlder,
  2356  				},
  2357  				vsArgs{
  2358  					Namespace: "default",
  2359  					Match:     "known.default.svc.cluster.local",
  2360  					Dest:      "default.example.com",
  2361  					Time:      TimeBase,
  2362  				},
  2363  				vsArgs{
  2364  					Namespace: "not-default",
  2365  					Match:     "known.default.svc.cluster.local",
  2366  					Dest:      "not-default.example.com",
  2367  					Time:      TimeNewer,
  2368  				},
  2369  			},
  2370  			proxy:     proxy("not-default"),
  2371  			routeName: "8080",
  2372  			expected: map[string][]string{
  2373  				// Oldest wins
  2374  				"known.default.svc.cluster.local": {"outbound|8080||arbitrary.example.com"},
  2375  			},
  2376  			expectedGateway: map[string][]string{
  2377  				"known.default.svc.cluster.local": {"outbound|80||not-default.example.com"},
  2378  			},
  2379  		},
  2380  		{
  2381  			name: "wildcard random",
  2382  			cfg: []Configer{vsArgs{
  2383  				Namespace: "default",
  2384  				Match:     "*.unknown.example.com",
  2385  				Dest:      "arbitrary.example.com",
  2386  			}},
  2387  			proxy:     proxy("default"),
  2388  			routeName: "80",
  2389  			expected: map[string][]string{
  2390  				// match no VS, get default config
  2391  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2392  				"known.default.svc.cluster.local":     {"outbound|80||known.default.svc.cluster.local"},
  2393  				// Wildcard doesn't match any known services, insert it as-is
  2394  				"*.unknown.example.com": {"outbound|80||arbitrary.example.com"},
  2395  			},
  2396  		},
  2397  		{
  2398  			name: "wildcard match with sidecar",
  2399  			cfg: []Configer{
  2400  				vsArgs{
  2401  					Namespace: "default",
  2402  					Match:     "*.cluster.local",
  2403  					Dest:      "arbitrary.example.com",
  2404  				},
  2405  				scArgs{
  2406  					Namespace: "default",
  2407  					Egress:    []string{"*/*.cluster.local"},
  2408  				},
  2409  			},
  2410  			proxy:     proxy("default"),
  2411  			routeName: "80",
  2412  			expected: map[string][]string{
  2413  				"alt-known.default.svc.cluster.local": {"outbound|80||arbitrary.example.com"},
  2414  				"known.default.svc.cluster.local":     {"outbound|80||arbitrary.example.com"},
  2415  				// Matched an exact service, so we have no route for the wildcard
  2416  				"*.cluster.local": nil,
  2417  			},
  2418  			expectedGateway: map[string][]string{
  2419  				// Exact service matches do not get the wildcard applied
  2420  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2421  				"known.default.svc.cluster.local":     {"outbound|80||known.default.svc.cluster.local"},
  2422  				// The wildcard
  2423  				"*.cluster.local": {"outbound|80||arbitrary.example.com"},
  2424  			},
  2425  		},
  2426  		{
  2427  			name: "wildcard first then explicit (oldest wins feature flag)",
  2428  			cfg: []Configer{
  2429  				vsArgs{
  2430  					Namespace: "default",
  2431  					Match:     "*.cluster.local",
  2432  					Dest:      "wild.example.com",
  2433  					Time:      TimeOlder,
  2434  				},
  2435  				vsArgs{
  2436  					Namespace: "default",
  2437  					Match:     "known.default.svc.cluster.local",
  2438  					Dest:      "explicit.example.com",
  2439  					Time:      TimeNewer,
  2440  				},
  2441  			},
  2442  			proxy:     proxy("default"),
  2443  			routeName: "80",
  2444  			expected: map[string][]string{
  2445  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2446  				"known.default.svc.cluster.local":     {"outbound|80||wild.example.com"}, // oldest wins
  2447  				// Matched an exact service, so we have no route for the wildcard
  2448  				"*.cluster.local": nil,
  2449  			},
  2450  			expectedGateway: map[string][]string{
  2451  				// No overrides, use default
  2452  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2453  				// Explicit has precedence
  2454  				"known.default.svc.cluster.local": {"outbound|80||explicit.example.com"},
  2455  				// Last is our wildcard
  2456  				"*.cluster.local": {"outbound|80||wild.example.com"},
  2457  			},
  2458  			oldestWins: true,
  2459  		},
  2460  		{
  2461  			name: "wildcard first then explicit",
  2462  			cfg: []Configer{
  2463  				vsArgs{
  2464  					Namespace: "default",
  2465  					Match:     "*.cluster.local",
  2466  					Dest:      "wild.example.com",
  2467  					Time:      TimeOlder,
  2468  				},
  2469  				vsArgs{
  2470  					Namespace: "default",
  2471  					Match:     "known.default.svc.cluster.local",
  2472  					Dest:      "explicit.example.com",
  2473  					Time:      TimeNewer,
  2474  				},
  2475  			},
  2476  			proxy:     proxy("default"),
  2477  			routeName: "80",
  2478  			expected: map[string][]string{
  2479  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2480  				"known.default.svc.cluster.local":     {"outbound|80||explicit.example.com"},
  2481  				// Matched an exact service, so we have no route for the wildcard
  2482  				"*.cluster.local": nil,
  2483  			},
  2484  			expectedGateway: map[string][]string{
  2485  				// No overrides, use default
  2486  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2487  				// Explicit has precedence
  2488  				"known.default.svc.cluster.local": {"outbound|80||explicit.example.com"},
  2489  				// Last is our wildcard
  2490  				"*.cluster.local": {"outbound|80||wild.example.com"},
  2491  			},
  2492  		},
  2493  		{
  2494  			name: "explicit first then wildcard",
  2495  			cfg: []Configer{
  2496  				vsArgs{
  2497  					Namespace: "default",
  2498  					Match:     "*.cluster.local",
  2499  					Dest:      "wild.example.com",
  2500  					Time:      TimeNewer,
  2501  				},
  2502  				vsArgs{
  2503  					Namespace: "default",
  2504  					Match:     "known.default.svc.cluster.local",
  2505  					Dest:      "explicit.example.com",
  2506  					Time:      TimeOlder,
  2507  				},
  2508  			},
  2509  			proxy:     proxy("default"),
  2510  			routeName: "80",
  2511  			expected: map[string][]string{
  2512  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2513  				"known.default.svc.cluster.local":     {"outbound|80||explicit.example.com"},
  2514  				// Matched an exact service, so we have no route for the wildcard
  2515  				"*.cluster.local": nil,
  2516  			},
  2517  			expectedGateway: map[string][]string{
  2518  				// No overrides, use default
  2519  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2520  				// Explicit has precedence
  2521  				"known.default.svc.cluster.local": {"outbound|80||explicit.example.com"},
  2522  				// Last is our wildcard
  2523  				"*.cluster.local": {"outbound|80||wild.example.com"},
  2524  			},
  2525  		},
  2526  		{
  2527  			name: "wildcard and explicit with sidecar (oldest wins feature flag)",
  2528  			cfg: []Configer{
  2529  				vsArgs{
  2530  					Namespace: "default",
  2531  					Match:     "*.cluster.local",
  2532  					Dest:      "wild.example.com",
  2533  					Time:      TimeOlder,
  2534  				},
  2535  				vsArgs{
  2536  					Namespace: "default",
  2537  					Match:     "known.default.svc.cluster.local",
  2538  					Dest:      "explicit.example.com",
  2539  					Time:      TimeNewer,
  2540  				},
  2541  				scArgs{
  2542  					Namespace: "default",
  2543  					Egress:    []string{"default/known.default.svc.cluster.local", "default/alt-known.default.svc.cluster.local"},
  2544  				},
  2545  			},
  2546  			proxy:     proxy("default"),
  2547  			routeName: "80",
  2548  			expected: map[string][]string{
  2549  				// Even though we did not import `*.cluster.local`, the VS attaches
  2550  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2551  				// Oldest wins
  2552  				"known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2553  				// Matched an exact service, so we have no route for the wildcard
  2554  				"*.cluster.local": nil,
  2555  			},
  2556  			expectedGateway: map[string][]string{
  2557  				// No rule imported
  2558  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2559  				// Imported rule
  2560  				"known.default.svc.cluster.local": {"outbound|80||explicit.example.com"},
  2561  				// Not imported
  2562  				"*.cluster.local": nil,
  2563  			},
  2564  			oldestWins: true,
  2565  		},
  2566  		{
  2567  			name: "wildcard and explicit with sidecar",
  2568  			cfg: []Configer{
  2569  				vsArgs{
  2570  					Namespace: "default",
  2571  					Match:     "*.cluster.local",
  2572  					Dest:      "wild.example.com",
  2573  					Time:      TimeOlder,
  2574  				},
  2575  				vsArgs{
  2576  					Namespace: "default",
  2577  					Match:     "known.default.svc.cluster.local",
  2578  					Dest:      "explicit.example.com",
  2579  					Time:      TimeNewer,
  2580  				},
  2581  				scArgs{
  2582  					Namespace: "default",
  2583  					Egress:    []string{"default/known.default.svc.cluster.local", "default/alt-known.default.svc.cluster.local"},
  2584  				},
  2585  			},
  2586  			proxy:     proxy("default"),
  2587  			routeName: "80",
  2588  			expected: map[string][]string{
  2589  				// Even though we did not import `*.cluster.local`, the VS attaches
  2590  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2591  				// Most exact match wins
  2592  				"known.default.svc.cluster.local": {"outbound|80||explicit.example.com"},
  2593  				// Matched an exact service, so we have no route for the wildcard
  2594  				"*.cluster.local": nil,
  2595  			},
  2596  			expectedGateway: map[string][]string{
  2597  				// No rule imported
  2598  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2599  				// Imported rule
  2600  				"known.default.svc.cluster.local": {"outbound|80||explicit.example.com"},
  2601  				// Not imported
  2602  				"*.cluster.local": nil,
  2603  			},
  2604  		},
  2605  		{
  2606  			name: "explicit first then wildcard with sidecar cross namespace",
  2607  			cfg: []Configer{
  2608  				vsArgs{
  2609  					Namespace: "not-default",
  2610  					Match:     "*.cluster.local",
  2611  					Dest:      "wild.example.com",
  2612  					Time:      TimeOlder,
  2613  				},
  2614  				vsArgs{
  2615  					Namespace: "default",
  2616  					Match:     "known.default.svc.cluster.local",
  2617  					Dest:      "explicit.example.com",
  2618  					Time:      TimeNewer,
  2619  				},
  2620  				scArgs{
  2621  					Namespace: "default",
  2622  					Egress:    []string{"default/known.default.svc.cluster.local", "default/alt-known.default.svc.cluster.local"},
  2623  				},
  2624  			},
  2625  			proxy:     proxy("default"),
  2626  			routeName: "80",
  2627  			expected: map[string][]string{
  2628  				// Similar to above, but now the older wildcard VS is in a complete different namespace which we don't import
  2629  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2630  				"known.default.svc.cluster.local":     {"outbound|80||explicit.example.com"},
  2631  				// Matched an exact service, so we have no route for the wildcard
  2632  				"*.cluster.local": nil,
  2633  			},
  2634  		},
  2635  		{
  2636  			name: "wildcard and explicit cross namespace (oldest wins feature flag)",
  2637  			cfg: []Configer{
  2638  				vsArgs{
  2639  					Namespace: "not-default",
  2640  					Match:     "*.cluster.local",
  2641  					Dest:      "wild.example.com",
  2642  					Time:      TimeOlder,
  2643  				},
  2644  				vsArgs{
  2645  					Namespace: "default",
  2646  					Match:     "known.default.svc.cluster.local",
  2647  					Dest:      "explicit.example.com",
  2648  					Time:      TimeNewer,
  2649  				},
  2650  			},
  2651  			proxy:     proxy("default"),
  2652  			routeName: "80",
  2653  			expected: map[string][]string{
  2654  				// Wildcard is older, so it wins, even though it is cross namespace
  2655  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2656  				"known.default.svc.cluster.local":     {"outbound|80||wild.example.com"},
  2657  				// Matched an exact service, so we have no route for the wildcard
  2658  				"*.cluster.local": nil,
  2659  			},
  2660  			expectedGateway: map[string][]string{
  2661  				// Exact match wins
  2662  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2663  				"known.default.svc.cluster.local":     {"outbound|80||explicit.example.com"},
  2664  				// Wildcard last
  2665  				"*.cluster.local": {"outbound|80||wild.example.com"},
  2666  			},
  2667  			oldestWins: true,
  2668  		},
  2669  		{
  2670  			name: "wildcard and explicit cross namespace",
  2671  			cfg: []Configer{
  2672  				vsArgs{
  2673  					Namespace: "not-default",
  2674  					Match:     "*.cluster.local",
  2675  					Dest:      "wild.example.com",
  2676  					Time:      TimeOlder,
  2677  				},
  2678  				vsArgs{
  2679  					Namespace: "default",
  2680  					Match:     "known.default.svc.cluster.local",
  2681  					Dest:      "explicit.example.com",
  2682  					Time:      TimeNewer,
  2683  				},
  2684  			},
  2685  			proxy:     proxy("default"),
  2686  			routeName: "80",
  2687  			expected: map[string][]string{
  2688  				// Exact match wins
  2689  				"alt-known.default.svc.cluster.local": {"outbound|80||wild.example.com"},
  2690  				"known.default.svc.cluster.local":     {"outbound|80||explicit.example.com"},
  2691  				// Matched an exact service, so we have no route for the wildcard
  2692  				"*.cluster.local": nil,
  2693  			},
  2694  			expectedGateway: map[string][]string{
  2695  				// Exact match wins
  2696  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"},
  2697  				"known.default.svc.cluster.local":     {"outbound|80||explicit.example.com"},
  2698  				// Wildcard last
  2699  				"*.cluster.local": {"outbound|80||wild.example.com"},
  2700  			},
  2701  		},
  2702  		{
  2703  			name: "wildcard and explicit unknown",
  2704  			cfg: []Configer{
  2705  				vsArgs{
  2706  					Namespace: "default",
  2707  					Match:     "*.tld",
  2708  					Dest:      "wild.example.com",
  2709  					Time:      TimeOlder,
  2710  				},
  2711  				vsArgs{
  2712  					Namespace: "default",
  2713  					Match:     "example.tld",
  2714  					Dest:      "explicit.example.com",
  2715  					Time:      TimeNewer,
  2716  				},
  2717  			},
  2718  			proxy:     proxy("default"),
  2719  			routeName: "80",
  2720  			expected: map[string][]string{
  2721  				// wildcard does not match
  2722  				"known.default.svc.cluster.local": {"outbound|80||known.default.svc.cluster.local"},
  2723  				// Even though its less exact, this wildcard wins
  2724  				"*.tld":         {"outbound|80||wild.example.com"},
  2725  				"*.example.tld": nil,
  2726  			},
  2727  		},
  2728  		{
  2729  			name: "explicit match with wildcard sidecar",
  2730  			cfg: []Configer{
  2731  				vsArgs{
  2732  					Namespace: "default",
  2733  					Match:     "arbitrary.svc.cluster.local",
  2734  					Dest:      "arbitrary.svc.cluster.local",
  2735  				},
  2736  				scArgs{
  2737  					Namespace: "default",
  2738  					Egress:    []string{"*/*.cluster.local"},
  2739  				},
  2740  			},
  2741  			proxy:     proxy("default"),
  2742  			routeName: "80",
  2743  			expected: map[string][]string{
  2744  				"arbitrary.svc.cluster.local": {"outbound|80||arbitrary.svc.cluster.local"},
  2745  			},
  2746  		},
  2747  		{
  2748  			name: "wildcard match with explicit sidecar",
  2749  			cfg: []Configer{
  2750  				vsArgs{
  2751  					Namespace: "default",
  2752  					Match:     "*.cluster.local",
  2753  					Dest:      "arbitrary.example.com",
  2754  				},
  2755  				scArgs{
  2756  					Namespace: "default",
  2757  					Egress:    []string{"*/known.default.svc.cluster.local"},
  2758  				},
  2759  			},
  2760  			proxy:     proxy("default"),
  2761  			routeName: "80",
  2762  			expected: map[string][]string{
  2763  				"known.default.svc.cluster.local": {"outbound|80||arbitrary.example.com"},
  2764  				"*.cluster.local":                 nil,
  2765  			},
  2766  			expectedGateway: map[string][]string{
  2767  				"known.default.svc.cluster.local": {"outbound|80||known.default.svc.cluster.local"},
  2768  				"*.cluster.local":                 nil,
  2769  			},
  2770  		},
  2771  		{
  2772  			name: "non-service wildcard match with explicit sidecar",
  2773  			cfg: []Configer{
  2774  				vsArgs{
  2775  					Namespace: "default",
  2776  					Match:     "*.example.org",
  2777  					Dest:      "arbitrary.example.com",
  2778  				},
  2779  				scArgs{
  2780  					Namespace: "default",
  2781  					Egress:    []string{"*/explicit.example.org", "*/alt-known.default.svc.cluster.local"},
  2782  				},
  2783  			},
  2784  			proxy:     proxy("default"),
  2785  			routeName: "80",
  2786  			expected: map[string][]string{
  2787  				"known.default.svc.cluster.local":     nil,                                                  // Not imported
  2788  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"}, // No change
  2789  				"*.example.org":                       {"outbound|80||arbitrary.example.com"},
  2790  			},
  2791  			expectedGateway: map[string][]string{
  2792  				"known.default.svc.cluster.local":     nil,                                                  // Not imported
  2793  				"alt-known.default.svc.cluster.local": {"outbound|80||alt-known.default.svc.cluster.local"}, // No change
  2794  				"*.example.org":                       nil,                                                  // Not imported
  2795  			},
  2796  		},
  2797  		{
  2798  			name: "sidecar filter",
  2799  			cfg: []Configer{
  2800  				vsArgs{
  2801  					Namespace: "not-default",
  2802  					Match:     "*.default.svc.cluster.local",
  2803  					Dest:      "arbitrary.example.com",
  2804  				},
  2805  				vsArgs{
  2806  					Namespace: "default",
  2807  					Match:     "explicit.default.svc.cluster.local",
  2808  					Dest:      "explicit.default.svc.cluster.local",
  2809  				},
  2810  				scArgs{
  2811  					Namespace: "not-default",
  2812  					Egress:    []string{"not-default/*.default.svc.cluster.local", "not-default/not-default.not-default.svc.cluster.local"},
  2813  				},
  2814  			},
  2815  			proxy:     proxy("not-default"),
  2816  			routeName: "80",
  2817  			expected: map[string][]string{
  2818  				// even though there is an *.svc.cluster.local, since we do not import it we should create a wildcard matcher
  2819  				"*.default.svc.cluster.local": {"outbound|80||arbitrary.example.com"},
  2820  				// We did not import this, shouldn't show up
  2821  				"explicit.default.svc.cluster.local":        nil,
  2822  				"not-default.not-default.svc.cluster.local": {"outbound|80||not-default.not-default.svc.cluster.local"},
  2823  			},
  2824  		},
  2825  		{
  2826  			name: "same namespace conflict",
  2827  			cfg: []Configer{
  2828  				vsArgs{
  2829  					Namespace: "default",
  2830  					Match:     "known.default.svc.cluster.local",
  2831  					Dest:      "old.example.com",
  2832  					Time:      TimeOlder,
  2833  				},
  2834  				vsArgs{
  2835  					Namespace: "default",
  2836  					Match:     "known.default.svc.cluster.local",
  2837  					Dest:      "new.example.com",
  2838  					Time:      TimeNewer,
  2839  				},
  2840  			},
  2841  			proxy:     proxy("default"),
  2842  			routeName: "80",
  2843  			expected: map[string][]string{
  2844  				"known.default.svc.cluster.local": {"outbound|80||old.example.com"}, // oldest wins
  2845  			},
  2846  		},
  2847  		{
  2848  			name: "cross namespace conflict",
  2849  			cfg: []Configer{
  2850  				vsArgs{
  2851  					Namespace: "not-default",
  2852  					Match:     "known.default.svc.cluster.local",
  2853  					Dest:      "producer.example.com",
  2854  					Time:      TimeOlder,
  2855  				},
  2856  				vsArgs{
  2857  					Namespace: "default",
  2858  					Match:     "known.default.svc.cluster.local",
  2859  					Dest:      "consumer.example.com",
  2860  					Time:      TimeNewer,
  2861  				},
  2862  			},
  2863  			proxy:     proxy("default"),
  2864  			routeName: "80",
  2865  			expected: map[string][]string{
  2866  				// oldest wins
  2867  				"known.default.svc.cluster.local": {"outbound|80||producer.example.com"},
  2868  			},
  2869  			expectedGateway: map[string][]string{
  2870  				// consumer wins
  2871  				"known.default.svc.cluster.local": {"outbound|80||consumer.example.com"},
  2872  			},
  2873  		},
  2874  		{
  2875  			name: "import only a unknown service route",
  2876  			cfg: []Configer{
  2877  				vsArgs{
  2878  					Namespace: "default",
  2879  					Match:     "foo.default.svc.cluster.local",
  2880  					Dest:      "example.com",
  2881  				},
  2882  				scArgs{
  2883  					Namespace: "default",
  2884  					Egress:    []string{"*/foo.default.svc.cluster.local"},
  2885  				},
  2886  			},
  2887  			proxy:     proxy("default"),
  2888  			routeName: "80",
  2889  			expected:  nil, // We do not even get a route as there is no service on the port
  2890  		},
  2891  		{
  2892  			// https://github.com/istio/istio/issues/37087
  2893  			name: "multi-host import single",
  2894  			cfg: []Configer{
  2895  				vsArgs{
  2896  					Namespace: "default",
  2897  					Matches:   []string{"known.default.svc.cluster.local", "alt-known.default.svc.cluster.local"},
  2898  					Dest:      "example.com",
  2899  				},
  2900  				scArgs{
  2901  					Namespace: "default",
  2902  					Egress:    []string{"*/known.default.svc.cluster.local"},
  2903  				},
  2904  			},
  2905  			proxy:     proxy("default"),
  2906  			routeName: "80",
  2907  			expected: map[string][]string{
  2908  				// imported
  2909  				"known.default.svc.cluster.local": {"outbound|80||example.com"},
  2910  				// Not imported but we include it anyway
  2911  				"alt-known.default.svc.cluster.local": {"outbound|80||example.com"},
  2912  			},
  2913  			expectedGateway: map[string][]string{
  2914  				// imported
  2915  				"known.default.svc.cluster.local": {"outbound|80||example.com"},
  2916  				// Not imported
  2917  				"alt-known.default.svc.cluster.local": nil,
  2918  			},
  2919  		},
  2920  	}
  2921  	for _, variant := range []string{"httproute", "virtualservice"} {
  2922  		t.Run(variant, func(t *testing.T) {
  2923  			for _, tt := range cases {
  2924  				tt := tt
  2925  				t.Run(tt.name, func(t *testing.T) {
  2926  					if tt.oldestWins {
  2927  						test.SetForTest(t, &features.PersistOldestWinsHeuristicForVirtualServiceHostMatching, true)
  2928  					} else {
  2929  						t.Parallel() // feature flags and parallel tests don't mix
  2930  					}
  2931  					cfg := knownServices
  2932  					for _, tc := range tt.cfg {
  2933  						cfg = cfg + "\n---\n" + tc.Config(t, variant)
  2934  					}
  2935  					istio, k, err := crd.ParseInputs(cfg)
  2936  					if err != nil {
  2937  						t.Fatal(err)
  2938  					}
  2939  					kubeo, err := kube.SlowConvertKindsToRuntimeObjects(k)
  2940  					if err != nil {
  2941  						t.Fatal(err)
  2942  					}
  2943  					s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{
  2944  						Configs:           istio,
  2945  						KubernetesObjects: kubeo,
  2946  					})
  2947  					sim := simulation.NewSimulation(t, s, s.SetupProxy(tt.proxy))
  2948  					xdstest.ValidateListeners(t, sim.Listeners)
  2949  					xdstest.ValidateRouteConfigurations(t, sim.Routes)
  2950  					r := xdstest.ExtractRouteConfigurations(sim.Routes)
  2951  					vh := r[tt.routeName]
  2952  					exp := tt.expected
  2953  					if variant == "httproute" && tt.expectedGateway != nil {
  2954  						exp = tt.expectedGateway
  2955  					}
  2956  					if vh == nil && exp != nil {
  2957  						t.Fatalf("route %q not found, have %v", tt.routeName, xdstest.MapKeys(r))
  2958  					}
  2959  					gotHosts := xdstest.ExtractVirtualHosts(vh)
  2960  					for wk, wv := range exp {
  2961  						got := gotHosts[wk]
  2962  						if !reflect.DeepEqual(wv, got) {
  2963  							t.Errorf("%q: wanted %v, got %v (had %v)", wk, wv, got, xdstest.MapKeys(gotHosts))
  2964  						}
  2965  					}
  2966  				})
  2967  			}
  2968  		})
  2969  	}
  2970  }