istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/common/routing.go (about)

     1  //go:build integ
     2  // +build integ
     3  
     4  // Copyright Istio Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package common
    19  
    20  import (
    21  	"fmt"
    22  	"net/http"
    23  	"net/netip"
    24  	"net/url"
    25  	"reflect"
    26  	"sort"
    27  	"strings"
    28  	"time"
    29  
    30  	"google.golang.org/grpc/codes"
    31  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    32  
    33  	"istio.io/istio/pilot/pkg/model"
    34  	"istio.io/istio/pkg/config/host"
    35  	"istio.io/istio/pkg/config/protocol"
    36  	"istio.io/istio/pkg/config/security"
    37  	"istio.io/istio/pkg/http/headers"
    38  	echoClient "istio.io/istio/pkg/test/echo"
    39  	"istio.io/istio/pkg/test/echo/common/scheme"
    40  	"istio.io/istio/pkg/test/framework/components/echo"
    41  	"istio.io/istio/pkg/test/framework/components/echo/check"
    42  	"istio.io/istio/pkg/test/framework/components/echo/common/deployment"
    43  	"istio.io/istio/pkg/test/framework/components/echo/common/ports"
    44  	"istio.io/istio/pkg/test/framework/components/echo/echotest"
    45  	"istio.io/istio/pkg/test/framework/components/echo/match"
    46  	"istio.io/istio/pkg/test/framework/components/istio"
    47  	"istio.io/istio/pkg/test/framework/components/istio/ingress"
    48  	"istio.io/istio/pkg/test/framework/label"
    49  	"istio.io/istio/pkg/test/scopes"
    50  	"istio.io/istio/pkg/test/util/tmpl"
    51  	"istio.io/istio/pkg/util/sets"
    52  	"istio.io/istio/tests/common/jwt"
    53  	ingressutil "istio.io/istio/tests/integration/security/sds_ingress/util"
    54  )
    55  
    56  const originateTLSTmpl = `
    57  apiVersion: networking.istio.io/v1alpha3
    58  kind: DestinationRule
    59  metadata:
    60    name: "{{.VirtualServiceHost|replace "*" "wild"}}"
    61    namespace: "{{.IngressNamespace}}"
    62  spec:
    63    host: "{{.VirtualServiceHost}}"
    64    trafficPolicy:
    65      tls:
    66        mode: SIMPLE
    67        insecureSkipVerify: true
    68  ---
    69  `
    70  
    71  const httpVirtualServiceTmpl = `
    72  apiVersion: networking.istio.io/v1alpha3
    73  kind: VirtualService
    74  metadata:
    75    name: "{{.VirtualServiceHost|replace "*" "wild"}}"
    76  spec:
    77    gateways:
    78    - {{.Gateway}}
    79    hosts:
    80    - "{{.VirtualServiceHost}}"
    81    http:
    82    - route:
    83      - destination:
    84          host: "{{.DestinationHost | default .VirtualServiceHost}}"
    85          port:
    86            number: {{.Port}}
    87  {{- if .MatchScheme }}
    88      match:
    89      - scheme:
    90          exact: {{.MatchScheme}}
    91      headers:
    92        request:
    93          add:
    94            istio-custom-header: user-defined-value
    95  {{- end }}
    96  ---
    97  `
    98  
    99  func httpVirtualService(gateway, host string, port int) string {
   100  	return tmpl.MustEvaluate(httpVirtualServiceTmpl, struct {
   101  		Gateway            string
   102  		VirtualServiceHost string
   103  		DestinationHost    string
   104  		Port               int
   105  		MatchScheme        string
   106  	}{gateway, host, "", port, ""})
   107  }
   108  
   109  const tcpVirtualServiceTmpl = `
   110  apiVersion: networking.istio.io/v1alpha3
   111  kind: VirtualService
   112  metadata:
   113    name: "{{.VirtualServiceHost|replace "*" "wild"}}"
   114  spec:
   115    gateways:
   116    - {{.Gateway}}
   117    hosts:
   118    - "{{.VirtualServiceHost}}"
   119    tcp:
   120    - match:
   121      - port: {{.SourcePort}}
   122      route:
   123      - destination:
   124          host: "{{.DestinationHost | default .VirtualServiceHost}}"
   125          port:
   126            number: {{.TargetPort}}
   127  ---
   128  `
   129  
   130  func tcpVirtualService(gateway, host, destHost string, sourcePort, targetPort int) string {
   131  	return tmpl.MustEvaluate(tcpVirtualServiceTmpl, struct {
   132  		Gateway            string
   133  		VirtualServiceHost string
   134  		DestinationHost    string
   135  		SourcePort         int
   136  		TargetPort         int
   137  	}{gateway, host, destHost, sourcePort, targetPort})
   138  }
   139  
   140  const gatewayTmpl = `
   141  apiVersion: networking.istio.io/v1alpha3
   142  kind: Gateway
   143  metadata:
   144    name: gateway
   145  spec:
   146    selector:
   147      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
   148    servers:
   149    - port:
   150        number: {{.GatewayPort}}
   151        name: {{.GatewayPortName}}
   152        protocol: {{.GatewayProtocol}}
   153  {{- if .Credential }}
   154      tls:
   155        mode: {{.TLSMode}}
   156        credentialName: {{.Credential}}
   157  {{- if .Ciphers }}
   158        cipherSuites:
   159  {{- range $cipher := .Ciphers }}
   160        - "{{$cipher}}"
   161  {{- end }}
   162  {{- end }}
   163  {{- end }}
   164      hosts:
   165      - "{{.GatewayHost}}"
   166  ---
   167  `
   168  
   169  func httpGateway(host string, port int, portName, protocol string, gatewayIstioLabel string) string { //nolint: unparam
   170  	return tmpl.MustEvaluate(gatewayTmpl, struct {
   171  		GatewayHost       string
   172  		GatewayPort       int
   173  		GatewayPortName   string
   174  		GatewayProtocol   string
   175  		Credential        string
   176  		GatewayIstioLabel string
   177  		TLSMode           string
   178  	}{
   179  		host, port, portName, protocol, "", gatewayIstioLabel, "SIMPLE",
   180  	})
   181  }
   182  
   183  func virtualServiceCases(t TrafficContext) {
   184  	// reduce the total # of subtests that don't give valuable coverage or just don't work
   185  	// TODO include proxyless as different features become supported
   186  	t.SetDefaultSourceMatchers(match.NotNaked, match.NotHeadless, match.NotProxylessGRPC)
   187  	t.SetDefaultTargetMatchers(match.NotNaked, match.NotHeadless, match.NotProxylessGRPC)
   188  	includeProxyless := []match.Matcher{match.NotNaked, match.NotHeadless}
   189  
   190  	skipVM := t.Settings().Skip(echo.VM)
   191  	t.RunTraffic(TrafficTestCase{
   192  		name: "added header",
   193  		config: `
   194  apiVersion: networking.istio.io/v1alpha3
   195  kind: VirtualService
   196  metadata:
   197    name: default
   198  spec:
   199    hosts:
   200    - {{ .dstSvc }}
   201    http:
   202    - route:
   203      - destination:
   204          host: {{ .dstSvc }}
   205      headers:
   206        request:
   207          add:
   208            istio-custom-header: user-defined-value`,
   209  		opts: echo.CallOptions{
   210  			Port: echo.Port{
   211  				Name: "http",
   212  			},
   213  			Count: 1,
   214  			Check: check.And(
   215  				check.OK(),
   216  				check.RequestHeader("Istio-Custom-Header", "user-defined-value")),
   217  		},
   218  		workloadAgnostic: true,
   219  	})
   220  	t.RunTraffic(TrafficTestCase{
   221  		name: "set header",
   222  		config: `
   223  apiVersion: networking.istio.io/v1alpha3
   224  kind: VirtualService
   225  metadata:
   226    name: default
   227  spec:
   228    hosts:
   229    - {{ (index .dst 0).Config.Service }}
   230    http:
   231    - route:
   232      - destination:
   233          host: {{ (index .dst 0).Config.Service }}
   234      headers:
   235        request:
   236          set:
   237            x-custom: some-value`,
   238  		opts: echo.CallOptions{
   239  			Port: echo.Port{
   240  				Name: "http",
   241  			},
   242  			Count: 1,
   243  			Check: check.And(
   244  				check.OK(),
   245  				check.RequestHeader("X-Custom", "some-value")),
   246  		},
   247  		workloadAgnostic: true,
   248  	})
   249  	t.RunTraffic(TrafficTestCase{
   250  		name: "set authority header",
   251  		config: `
   252  apiVersion: networking.istio.io/v1alpha3
   253  kind: VirtualService
   254  metadata:
   255    name: default
   256  spec:
   257    hosts:
   258    - {{ (index .dst 0).Config.Service }}
   259    http:
   260    - route:
   261      - destination:
   262          host: {{ (index .dst 0).Config.Service }}
   263      headers:
   264        request:
   265          set:
   266            :authority: my-custom-authority`,
   267  		opts: echo.CallOptions{
   268  			Port: echo.Port{
   269  				Name: "http",
   270  			},
   271  			Count: 1,
   272  			Check: check.And(
   273  				check.OK(),
   274  				check.Host("my-custom-authority")),
   275  		},
   276  		workloadAgnostic: true,
   277  	})
   278  	t.RunTraffic(TrafficTestCase{
   279  		name: "set host header in destination",
   280  		config: `
   281  apiVersion: networking.istio.io/v1alpha3
   282  kind: VirtualService
   283  metadata:
   284    name: default
   285  spec:
   286    hosts:
   287    - {{ (index .dst 0).Config.Service }}
   288    http:
   289    - route:
   290      - destination:
   291          host: {{ (index .dst 0).Config.Service }}
   292        headers:
   293          request:
   294            set:
   295              Host: my-custom-authority`,
   296  		opts: echo.CallOptions{
   297  			Port: echo.Port{
   298  				Name: "http",
   299  			},
   300  			Count: 1,
   301  			Check: check.And(
   302  				check.OK(),
   303  				check.Host("my-custom-authority")),
   304  		},
   305  		workloadAgnostic: true,
   306  	})
   307  	t.RunTraffic(TrafficTestCase{
   308  		name: "set authority header in destination",
   309  		config: `
   310  apiVersion: networking.istio.io/v1alpha3
   311  kind: VirtualService
   312  metadata:
   313    name: default
   314  spec:
   315    hosts:
   316    - {{ (index .dst 0).Config.Service }}
   317    http:
   318    - route:
   319      - destination:
   320          host: {{ (index .dst 0).Config.Service }}
   321        headers:
   322          request:
   323            set:
   324              :authority: my-custom-authority`,
   325  		opts: echo.CallOptions{
   326  			Port: echo.Port{
   327  				Name: "http",
   328  			},
   329  			Count: 1,
   330  			Check: check.And(
   331  				check.OK(),
   332  				check.Host("my-custom-authority")),
   333  		},
   334  		workloadAgnostic: true,
   335  	})
   336  	t.RunTraffic(TrafficTestCase{
   337  		name: "set host header in route and destination",
   338  		config: `
   339  apiVersion: networking.istio.io/v1alpha3
   340  kind: VirtualService
   341  metadata:
   342    name: default
   343  spec:
   344    hosts:
   345    - {{ (index .dst 0).Config.Service }}
   346    http:
   347    - route:
   348      - destination:
   349          host: {{ (index .dst 0).Config.Service }}
   350        headers:
   351          request:
   352            set:
   353              Host: dest-authority
   354      headers:
   355        request:
   356          set:
   357            :authority: route-authority`,
   358  		opts: echo.CallOptions{
   359  			Port: echo.Port{
   360  				Name: "http",
   361  			},
   362  			Count: 1,
   363  			Check: check.And(
   364  				check.OK(),
   365  				check.Host("route-authority")),
   366  		},
   367  		workloadAgnostic: true,
   368  	})
   369  	t.RunTraffic(TrafficTestCase{
   370  		name: "set host header in route and multi destination",
   371  		config: `
   372  apiVersion: networking.istio.io/v1alpha3
   373  kind: VirtualService
   374  metadata:
   375    name: default
   376  spec:
   377    hosts:
   378    - {{ (index .dst 0).Config.Service }}
   379    http:
   380    - route:
   381      - destination:
   382          host: {{ (index .dst 0).Config.Service }}
   383        headers:
   384          request:
   385            set:
   386              Host: dest-authority
   387        weight: 50
   388      - destination:
   389          host: {{ (index .dst 0).Config.Service }}
   390        weight: 50
   391      headers:
   392        request:
   393          set:
   394            :authority: route-authority`,
   395  		opts: echo.CallOptions{
   396  			Port: echo.Port{
   397  				Name: "http",
   398  			},
   399  			Count: 1,
   400  			Check: check.And(
   401  				check.OK(),
   402  				check.Host("route-authority")),
   403  		},
   404  		workloadAgnostic: true,
   405  	})
   406  	t.RunTraffic(TrafficTestCase{
   407  		name: "set host header multi destination",
   408  		config: `
   409  apiVersion: networking.istio.io/v1alpha3
   410  kind: VirtualService
   411  metadata:
   412    name: default
   413  spec:
   414    hosts:
   415    - {{ (index .dst 0).Config.Service }}
   416    http:
   417    - route:
   418      - destination:
   419          host: {{ (index .dst 0).Config.Service }}
   420        headers:
   421          request:
   422            set:
   423              Host: dest-authority
   424        weight: 50
   425      - destination:
   426          host: {{ (index .dst 0).Config.Service }}
   427        headers:
   428          request:
   429            set:
   430              Host: dest-authority
   431        weight: 50`,
   432  		opts: echo.CallOptions{
   433  			Port: echo.Port{
   434  				Name: "http",
   435  			},
   436  			Count: 1,
   437  			Check: check.And(
   438  				check.OK(),
   439  				check.Host("dest-authority")),
   440  		},
   441  		workloadAgnostic: true,
   442  	})
   443  	t.RunTraffic(TrafficTestCase{
   444  		name: "redirect",
   445  		config: `
   446  apiVersion: networking.istio.io/v1alpha3
   447  kind: VirtualService
   448  metadata:
   449    name: default
   450  spec:
   451    hosts:
   452      - {{ .dstSvc }}
   453    http:
   454    - match:
   455      - uri:
   456          exact: /foo
   457      redirect:
   458        uri: /new/path
   459    - match:
   460      - uri:
   461          exact: /new/path
   462      route:
   463      - destination:
   464          host: {{ .dstSvc }}`,
   465  		opts: echo.CallOptions{
   466  			Port: echo.Port{
   467  				Name: "http",
   468  			},
   469  			HTTP: echo.HTTP{
   470  				Path:            "/foo?key=value",
   471  				FollowRedirects: true,
   472  			},
   473  			Count: 1,
   474  			Check: check.And(
   475  				check.OK(),
   476  				check.URL("/new/path?key=value")),
   477  		},
   478  		workloadAgnostic: true,
   479  	})
   480  	t.RunTraffic(TrafficTestCase{
   481  		name: "redirect port and scheme",
   482  		config: `
   483  apiVersion: networking.istio.io/v1alpha3
   484  kind: VirtualService
   485  metadata:
   486    name: default
   487  spec:
   488    hosts:
   489      - {{ .dstSvc }}
   490    http:
   491    - match:
   492      - uri:
   493          exact: /foo
   494      redirect:
   495        derivePort: FROM_REQUEST_PORT
   496        scheme: https
   497  `,
   498  		opts: echo.CallOptions{
   499  			Port: echo.Port{
   500  				Name: "http",
   501  			},
   502  			HTTP: echo.HTTP{
   503  				Path:            "/foo",
   504  				FollowRedirects: false,
   505  			},
   506  			Count: 1,
   507  			Check: check.And(
   508  				check.Status(http.StatusMovedPermanently),
   509  				LocationHeader("https://{{.Hostname}}:80/foo")),
   510  		},
   511  		workloadAgnostic: true,
   512  	})
   513  	t.RunTraffic(TrafficTestCase{
   514  		name: "redirect request port",
   515  		config: `
   516  apiVersion: networking.istio.io/v1alpha3
   517  kind: VirtualService
   518  metadata:
   519    name: default
   520  spec:
   521    hosts:
   522      - b
   523    http:
   524    - match:
   525      - uri:
   526          exact: /foo
   527      redirect:
   528        derivePort: FROM_REQUEST_PORT
   529  `,
   530  		children: []TrafficCall{
   531  			{
   532  				name: "to default",
   533  				call: t.Apps.A[0].CallOrFail,
   534  				opts: echo.CallOptions{
   535  					To: t.Apps.B,
   536  					Port: echo.Port{
   537  						Name: "http",
   538  					},
   539  					HTTP: echo.HTTP{
   540  						Path:            "/foo",
   541  						Headers:         HostHeader("b"),
   542  						FollowRedirects: false,
   543  					},
   544  
   545  					Count: 1,
   546  					Check: check.And(
   547  						check.Status(http.StatusMovedPermanently),
   548  						// Note: there is no "80" added, as its already the protocol default
   549  						LocationHeader("http://b/foo")),
   550  				},
   551  			},
   552  			{
   553  				name: "to default with host port",
   554  				call: t.Apps.A[0].CallOrFail,
   555  				opts: echo.CallOptions{
   556  					To: t.Apps.B,
   557  					Port: echo.Port{
   558  						Name: "http",
   559  					},
   560  					HTTP: echo.HTTP{
   561  						Path:            "/foo",
   562  						Headers:         HostHeader("b:80"),
   563  						FollowRedirects: false,
   564  					},
   565  					Count: 1,
   566  					Check: check.And(
   567  						check.Status(http.StatusMovedPermanently),
   568  						// Note: 80 is set as it was explicit in the host header
   569  						LocationHeader("http://b:80/foo")),
   570  				},
   571  			},
   572  			{
   573  				name: "non-default",
   574  				call: t.Apps.A[0].CallOrFail,
   575  				opts: echo.CallOptions{
   576  					To: t.Apps.B,
   577  					Port: echo.Port{
   578  						Name: "http2",
   579  					},
   580  					HTTP: echo.HTTP{
   581  						Path:            "/foo",
   582  						Headers:         HostHeader("b"),
   583  						FollowRedirects: false,
   584  					},
   585  					Count: 1,
   586  					Check: check.And(
   587  						check.Status(http.StatusMovedPermanently),
   588  						// Note: there is "85" added, as its already NOT the protocol default
   589  						LocationHeader("http://b:85/foo")),
   590  				},
   591  			},
   592  			{
   593  				name: "non-default with host port",
   594  				call: t.Apps.A[0].CallOrFail,
   595  				opts: echo.CallOptions{
   596  					To: t.Apps.B,
   597  					Port: echo.Port{
   598  						Name: "http2",
   599  					},
   600  					HTTP: echo.HTTP{
   601  						Path:            "/foo",
   602  						Headers:         HostHeader("b:85"),
   603  						FollowRedirects: false,
   604  					},
   605  					Count: 1,
   606  					Check: check.And(
   607  						check.Status(http.StatusMovedPermanently),
   608  						// Note: there is "85" added, as its already NOT the protocol default
   609  						LocationHeader("http://b:85/foo")),
   610  				},
   611  			},
   612  		},
   613  	})
   614  	t.RunTraffic(TrafficTestCase{
   615  		name: "rewrite uri",
   616  		config: `
   617  apiVersion: networking.istio.io/v1alpha3
   618  kind: VirtualService
   619  metadata:
   620    name: default
   621  spec:
   622    hosts:
   623      - {{ .dstSvc }}
   624    http:
   625    - match:
   626      - uri:
   627          exact: /foo
   628      rewrite:
   629        uri: /new/path
   630      route:
   631      - destination:
   632          host: {{ .dstSvc }}`,
   633  		opts: echo.CallOptions{
   634  			Port: echo.Port{
   635  				Name: "http",
   636  			},
   637  			HTTP: echo.HTTP{
   638  				Path: "/foo?key=value#hash",
   639  			},
   640  			Count: 1,
   641  			Check: check.And(
   642  				check.OK(),
   643  				check.URL("/new/path?key=value")),
   644  		},
   645  		workloadAgnostic: true,
   646  	})
   647  	t.RunTraffic(TrafficTestCase{
   648  		name: "rewrite authority",
   649  		config: `
   650  apiVersion: networking.istio.io/v1alpha3
   651  kind: VirtualService
   652  metadata:
   653    name: default
   654  spec:
   655    hosts:
   656      - {{ .dstSvc }}
   657    http:
   658    - match:
   659      - uri:
   660          exact: /foo
   661      rewrite:
   662        authority: new-authority
   663      route:
   664      - destination:
   665          host: {{ .dstSvc }}`,
   666  		opts: echo.CallOptions{
   667  			Port: echo.Port{
   668  				Name: "http",
   669  			},
   670  			HTTP: echo.HTTP{
   671  				Path: "/foo",
   672  			},
   673  			Count: 1,
   674  			Check: check.And(
   675  				check.OK(),
   676  				check.Host("new-authority")),
   677  		},
   678  		workloadAgnostic: true,
   679  	})
   680  	t.RunTraffic(TrafficTestCase{
   681  		name: "cors",
   682  		// TODO https://github.com/istio/istio/issues/31532
   683  		targetMatchers: []match.Matcher{match.NotTProxy, match.NotVM, match.NotNaked, match.NotHeadless, match.NotProxylessGRPC},
   684  
   685  		config: `
   686  apiVersion: networking.istio.io/v1alpha3
   687  kind: VirtualService
   688  metadata:
   689    name: default
   690  spec:
   691    hosts:
   692      - {{ .dstSvc }}
   693    http:
   694    - corsPolicy:
   695        allowOrigins:
   696        - exact: cors.com
   697        allowMethods:
   698        - POST
   699        - GET
   700        allowCredentials: false
   701        allowHeaders:
   702        - X-Foo-Bar
   703        - X-Foo-Baz
   704        maxAge: "24h"
   705      route:
   706      - destination:
   707          host: {{ .dstSvc }}
   708  `,
   709  		children: []TrafficCall{
   710  			{
   711  				name: "preflight",
   712  				opts: func() echo.CallOptions {
   713  					return echo.CallOptions{
   714  						Port: echo.Port{
   715  							Name: "http",
   716  						},
   717  						HTTP: echo.HTTP{
   718  							Method: "OPTIONS",
   719  							Headers: headers.New().
   720  								With(headers.Origin, "cors.com").
   721  								With(headers.AccessControlRequestMethod, "DELETE").
   722  								Build(),
   723  						},
   724  						Count: 1,
   725  						Check: check.And(
   726  							check.OK(),
   727  							check.ResponseHeaders(map[string]string{
   728  								"Access-Control-Allow-Origin":  "cors.com",
   729  								"Access-Control-Allow-Methods": "POST,GET",
   730  								"Access-Control-Allow-Headers": "X-Foo-Bar,X-Foo-Baz",
   731  								"Access-Control-Max-Age":       "86400",
   732  							})),
   733  					}
   734  				}(),
   735  			},
   736  			{
   737  				name: "get",
   738  				opts: func() echo.CallOptions {
   739  					return echo.CallOptions{
   740  						Port: echo.Port{
   741  							Name: "http",
   742  						},
   743  						HTTP: echo.HTTP{
   744  							Headers: headers.New().With(headers.Origin, "cors.com").Build(),
   745  						},
   746  						Count: 1,
   747  						Check: check.And(
   748  							check.OK(),
   749  							check.ResponseHeader("Access-Control-Allow-Origin", "cors.com")),
   750  					}
   751  				}(),
   752  			},
   753  			{
   754  				// GET without matching origin
   755  				name: "get no origin match",
   756  				opts: echo.CallOptions{
   757  					Port: echo.Port{
   758  						Name: "http",
   759  					},
   760  					Count: 1,
   761  					Check: check.And(
   762  						check.OK(),
   763  						check.ResponseHeader("Access-Control-Allow-Origin", "")),
   764  				},
   765  			},
   766  		},
   767  		workloadAgnostic: true,
   768  	})
   769  	// Retry conditions have been added to just check that config is correct.
   770  	// Retries are not specifically tested. TODO if we actually test retries, include proxyless
   771  	t.RunTraffic(TrafficTestCase{
   772  		name: "retry conditions",
   773  		config: `
   774  apiVersion: networking.istio.io/v1alpha3
   775  kind: VirtualService
   776  metadata:
   777    name: default
   778  spec:
   779    hosts:
   780    - {{ .dstSvc }}
   781    http:
   782    - route:
   783      - destination:
   784          host: {{ .dstSvc }}
   785      retries:
   786        attempts: 3
   787        perTryTimeout: 2s
   788        retryOn: gateway-error,connect-failure,refused-stream
   789        retryRemoteLocalities: true`,
   790  		opts: echo.CallOptions{
   791  			Port: echo.Port{
   792  				Name: "http",
   793  			},
   794  			Count: 1,
   795  			Check: check.OK(),
   796  		},
   797  		workloadAgnostic: true,
   798  	})
   799  	t.RunTraffic(TrafficTestCase{
   800  		name: "fault abort",
   801  		config: `
   802  apiVersion: networking.istio.io/v1alpha3
   803  kind: VirtualService
   804  metadata:
   805    name: default
   806  spec:
   807    hosts:
   808    - {{ (index .dst 0).Config.Service }}
   809    http:
   810    - route:
   811      - destination:
   812          host: {{ (index .dst 0).Config.Service }}
   813      fault:
   814        abort:
   815          percentage:
   816            value: 100
   817          httpStatus: 418`,
   818  		opts: echo.CallOptions{
   819  			Port: echo.Port{
   820  				Name: "http",
   821  			},
   822  			Count: 1,
   823  			Check: check.Status(http.StatusTeapot),
   824  		},
   825  		workloadAgnostic: true,
   826  	})
   827  	t.RunTraffic(TrafficTestCase{
   828  		name: "fault abort gRPC",
   829  		config: `
   830  apiVersion: networking.istio.io/v1alpha3
   831  kind: VirtualService
   832  metadata:
   833    name: default
   834  spec:
   835    hosts:
   836    - {{ (index .dst 0).Config.Service }}
   837    http:
   838    - route:
   839      - destination:
   840          host: {{ (index .dst 0).Config.Service }}
   841      fault:
   842        abort:
   843          percentage:
   844            value: 100
   845          grpcStatus: "UNAVAILABLE"`,
   846  		opts: echo.CallOptions{
   847  			Port: echo.Port{
   848  				Name: "grpc",
   849  			},
   850  			Scheme: scheme.GRPC,
   851  			Count:  1,
   852  			Check:  check.GRPCStatus(codes.Unavailable),
   853  		},
   854  		workloadAgnostic: true,
   855  		sourceMatchers:   includeProxyless,
   856  	})
   857  	t.RunTraffic(TrafficTestCase{
   858  		name: "catch all route short circuit the other routes",
   859  		config: `
   860  apiVersion: networking.istio.io/v1alpha3
   861  kind: VirtualService
   862  metadata:
   863    name: default
   864  spec:
   865    hosts:
   866    - {{ (index .dst 0).Config.Service }}
   867    http:
   868    - match:
   869      - uri:
   870          regex: .*
   871      route:
   872      - destination:
   873          host: {{ (index .dst 0).Config.Service }}
   874      fault:
   875        abort:
   876          percentage:
   877            value: 100
   878          httpStatus: 418
   879    - route:
   880      - destination:
   881          host: {{ .dstSvc }}`,
   882  		opts: echo.CallOptions{
   883  			Port: echo.Port{
   884  				Name: "http",
   885  			},
   886  			Count: 1,
   887  			Check: check.Status(http.StatusTeapot),
   888  		},
   889  		workloadAgnostic: true,
   890  	})
   891  
   892  	splits := [][]int{
   893  		{50, 25, 25},
   894  		{80, 10, 10},
   895  	}
   896  	if skipVM {
   897  		splits = [][]int{
   898  			{50, 50},
   899  			{80, 20},
   900  		}
   901  	}
   902  	for _, split := range splits {
   903  		split := split
   904  		t.RunTraffic(TrafficTestCase{
   905  			name:           fmt.Sprintf("shifting-%d", split[0]),
   906  			skip:           skipAmbient(t, "https://github.com/istio/istio/issues/44948"),
   907  			toN:            len(split),
   908  			sourceMatchers: []match.Matcher{match.NotHeadless, match.NotNaked},
   909  			targetMatchers: []match.Matcher{match.NotHeadless, match.NotNaked, match.NotExternal, match.NotProxylessGRPC},
   910  			templateVars: func(_ echo.Callers, _ echo.Instances) map[string]any {
   911  				return map[string]any{
   912  					"split": split,
   913  				}
   914  			},
   915  			config: `
   916  {{ $split := .split }} 
   917  apiVersion: networking.istio.io/v1alpha3
   918  kind: VirtualService
   919  metadata:
   920    name: default
   921  spec:
   922    hosts:
   923      - {{ ( index .dstSvcs 0) }}
   924    http:
   925    - route:
   926  {{- range $idx, $svc := .dstSvcs }}
   927      - destination:
   928          host: {{ $svc }}
   929        weight: {{ ( index $split $idx ) }}
   930  {{- end }}
   931  `,
   932  			checkForN: func(src echo.Caller, dests echo.Services, opts *echo.CallOptions) echo.Checker {
   933  				return check.And(
   934  					check.OK(),
   935  					func(result echo.CallResult, err error) error {
   936  						errorThreshold := 10
   937  						if len(split) != len(dests) {
   938  							// shouldn't happen
   939  							return fmt.Errorf("split configured for %d destinations, but framework gives %d", len(split), len(dests))
   940  						}
   941  						splitPerHost := map[echo.NamespacedName]int{}
   942  						destNames := dests.NamespacedNames()
   943  						for i, pct := range split {
   944  							splitPerHost[destNames[i]] = pct
   945  						}
   946  						for serviceName, exp := range splitPerHost {
   947  							hostResponses := result.Responses.Match(func(r echoClient.Response) bool {
   948  								return strings.HasPrefix(r.Hostname, serviceName.Name)
   949  							})
   950  							if !AlmostEquals(len(hostResponses), exp, errorThreshold) {
   951  								return fmt.Errorf("expected %v calls to %s, got %v", exp, serviceName, len(hostResponses))
   952  							}
   953  							// echotest should have filtered the deployment to only contain reachable clusters
   954  							to := match.ServiceName(serviceName).GetMatches(dests.Instances())
   955  							fromCluster := src.(echo.Instance).Config().Cluster
   956  							toClusters := to.Clusters()
   957  							// don't check headless since lb is unpredictable
   958  							headlessTarget := match.Headless.Any(to)
   959  							if !headlessTarget && len(toClusters.ByNetwork()[fromCluster.NetworkName()]) > 1 {
   960  								// Conditionally check reached clusters to work around connection load balancing issues
   961  								// See https://github.com/istio/istio/issues/32208 for details
   962  								// We want to skip this for requests from the cross-network pod
   963  								if err := check.ReachedClusters(t.AllClusters(), toClusters).Check(echo.CallResult{
   964  									From:      result.From,
   965  									Opts:      result.Opts,
   966  									Responses: hostResponses,
   967  								}, nil); err != nil {
   968  									return fmt.Errorf("did not reach all clusters for %s: %v", serviceName, err)
   969  								}
   970  							}
   971  						}
   972  						return nil
   973  					})
   974  			},
   975  			setupOpts: func(src echo.Caller, opts *echo.CallOptions) {
   976  				// TODO force this globally in echotest?
   977  				if src, ok := src.(echo.Instance); ok && src.Config().IsProxylessGRPC() {
   978  					opts.Port.Name = "grpc"
   979  				}
   980  			},
   981  			opts: echo.CallOptions{
   982  				Port: echo.Port{
   983  					Name: "http",
   984  				},
   985  				Count: 100,
   986  			},
   987  			workloadAgnostic: true,
   988  		})
   989  
   990  		// access is denied when the request header `end-user` is not jason.
   991  		t.RunTraffic(TrafficTestCase{
   992  			name: "without headers",
   993  			config: `
   994  apiVersion: networking.istio.io/v1alpha3
   995  kind: VirtualService
   996  metadata:
   997    name: vs
   998  spec:
   999    hosts:
  1000    - {{ .dstSvc }}
  1001    http:
  1002    - match:
  1003      - withoutHeaders:
  1004          end-user:
  1005            exact: jason
  1006      route:
  1007      - destination:
  1008          host: {{ .dstSvc }}
  1009      fault:
  1010        abort:
  1011          percentage:
  1012            value: 100
  1013          httpStatus: 403
  1014    - route:
  1015      - destination:
  1016          host: {{ .dstSvc }}
  1017  `,
  1018  			children: []TrafficCall{
  1019  				{
  1020  					name: "end-user is jason",
  1021  					opts: func() echo.CallOptions {
  1022  						return echo.CallOptions{
  1023  							Port: echo.Port{
  1024  								Name: "http",
  1025  							},
  1026  							HTTP: echo.HTTP{
  1027  								Path: "/foo",
  1028  								Headers: headers.New().
  1029  									With("end-user", "jason").
  1030  									Build(),
  1031  							},
  1032  							Count: 1,
  1033  							Check: check.Status(http.StatusOK),
  1034  						}
  1035  					}(),
  1036  				},
  1037  				{
  1038  					name: "end-user is not jason",
  1039  					opts: func() echo.CallOptions {
  1040  						return echo.CallOptions{
  1041  							Port: echo.Port{
  1042  								Name: "http",
  1043  							},
  1044  							HTTP: echo.HTTP{
  1045  								Path: "/foo",
  1046  								Headers: headers.New().
  1047  									With("end-user", "not-jason").
  1048  									Build(),
  1049  							},
  1050  							Count: 1,
  1051  							Check: check.Status(http.StatusForbidden),
  1052  						}
  1053  					}(),
  1054  				},
  1055  				{
  1056  					name: "do not have end-user header",
  1057  					opts: func() echo.CallOptions {
  1058  						return echo.CallOptions{
  1059  							Port: echo.Port{
  1060  								Name: "http",
  1061  							},
  1062  							HTTP: echo.HTTP{
  1063  								Path: "/foo",
  1064  							},
  1065  							Count: 1,
  1066  							Check: check.Status(http.StatusForbidden),
  1067  						}
  1068  					}(),
  1069  				},
  1070  			},
  1071  			workloadAgnostic: true,
  1072  		})
  1073  
  1074  		// allow only when `end-user` header present.
  1075  		t.RunTraffic(TrafficTestCase{
  1076  			name: "without headers regex convert to present_match",
  1077  			config: `
  1078  apiVersion: networking.istio.io/v1alpha3
  1079  kind: VirtualService
  1080  metadata:
  1081    name: vs
  1082  spec:
  1083    hosts:
  1084    - {{ .dstSvc }}
  1085    http:
  1086    - match:
  1087      - withoutHeaders:
  1088          end-user:
  1089            regex: "*"
  1090      route:
  1091      - destination:
  1092          host: {{ .dstSvc }}
  1093      fault:
  1094        abort:
  1095          percentage:
  1096            value: 100
  1097          httpStatus: 403
  1098    - route:
  1099      - destination:
  1100          host: {{ .dstSvc }}
  1101  `,
  1102  			children: []TrafficCall{
  1103  				{
  1104  					name: "have end-user header and value",
  1105  					opts: func() echo.CallOptions {
  1106  						return echo.CallOptions{
  1107  							Port: echo.Port{
  1108  								Name: "http",
  1109  							},
  1110  							HTTP: echo.HTTP{
  1111  								Path: "/foo",
  1112  								Headers: headers.New().
  1113  									With("end-user", "jason").
  1114  									Build(),
  1115  							},
  1116  							Count: 1,
  1117  							Check: check.Status(http.StatusOK),
  1118  						}
  1119  					}(),
  1120  				},
  1121  				{
  1122  					name: "have end-user header but value is empty",
  1123  					opts: func() echo.CallOptions {
  1124  						return echo.CallOptions{
  1125  							Port: echo.Port{
  1126  								Name: "http",
  1127  							},
  1128  							HTTP: echo.HTTP{
  1129  								Path: "/foo",
  1130  								Headers: headers.New().
  1131  									With("end-user", "").
  1132  									Build(),
  1133  							},
  1134  							Count: 1,
  1135  							Check: check.Status(http.StatusOK),
  1136  						}
  1137  					}(),
  1138  				},
  1139  				{
  1140  					name: "do not have end-user header",
  1141  					opts: func() echo.CallOptions {
  1142  						return echo.CallOptions{
  1143  							Port: echo.Port{
  1144  								Name: "http",
  1145  							},
  1146  							HTTP: echo.HTTP{
  1147  								Path: "/foo",
  1148  							},
  1149  							Count: 1,
  1150  							Check: check.Status(http.StatusForbidden),
  1151  						}
  1152  					}(),
  1153  				},
  1154  			},
  1155  			workloadAgnostic: true,
  1156  		})
  1157  
  1158  		// allow all access.
  1159  		t.RunTraffic(TrafficTestCase{
  1160  			name: "without headers regex match any string",
  1161  			config: `
  1162  apiVersion: networking.istio.io/v1alpha3
  1163  kind: VirtualService
  1164  metadata:
  1165    name: vs
  1166  spec:
  1167    hosts:
  1168    - {{ .dstSvc }}
  1169    http:
  1170    - match:
  1171      - withoutHeaders:
  1172          end-user:
  1173            regex: .*
  1174      route:
  1175      - destination:
  1176          host: {{ .dstSvc }}
  1177      fault:
  1178        abort:
  1179          percentage:
  1180            value: 100
  1181          httpStatus: 403
  1182    - route:
  1183      - destination:
  1184          host: {{ .dstSvc }}
  1185  `,
  1186  			children: []TrafficCall{
  1187  				{
  1188  					name: "have end-user header and value",
  1189  					opts: func() echo.CallOptions {
  1190  						return echo.CallOptions{
  1191  							Port: echo.Port{
  1192  								Name: "http",
  1193  							},
  1194  							HTTP: echo.HTTP{
  1195  								Path: "/foo",
  1196  								Headers: headers.New().
  1197  									With("end-user", "jason").
  1198  									Build(),
  1199  							},
  1200  							Count: 1,
  1201  							Check: check.Status(http.StatusOK),
  1202  						}
  1203  					}(),
  1204  				},
  1205  				{
  1206  					name: "have end-user header but value is empty",
  1207  					opts: func() echo.CallOptions {
  1208  						return echo.CallOptions{
  1209  							Port: echo.Port{
  1210  								Name: "http",
  1211  							},
  1212  							HTTP: echo.HTTP{
  1213  								Path: "/foo",
  1214  								Headers: headers.New().
  1215  									With("end-user", "").
  1216  									Build(),
  1217  							},
  1218  							Count: 1,
  1219  							Check: check.Status(http.StatusOK),
  1220  						}
  1221  					}(),
  1222  				},
  1223  				{
  1224  					name: "do not have end-user header",
  1225  					opts: func() echo.CallOptions {
  1226  						return echo.CallOptions{
  1227  							Port: echo.Port{
  1228  								Name: "http",
  1229  							},
  1230  							HTTP: echo.HTTP{
  1231  								Path: "/foo",
  1232  							},
  1233  							Count: 1,
  1234  							Check: check.Status(http.StatusOK),
  1235  						}
  1236  					}(),
  1237  				},
  1238  			},
  1239  			workloadAgnostic: true,
  1240  		})
  1241  
  1242  	}
  1243  }
  1244  
  1245  func HostHeader(header string) http.Header {
  1246  	return headers.New().WithHost(header).Build()
  1247  }
  1248  
  1249  // tlsOriginationCases contains tests TLS origination from DestinationRule
  1250  func tlsOriginationCases(t TrafficContext) {
  1251  	tc := TrafficTestCase{
  1252  		name: "DNS",
  1253  		config: fmt.Sprintf(`
  1254  apiVersion: networking.istio.io/v1alpha3
  1255  kind: DestinationRule
  1256  metadata:
  1257    name: external
  1258  spec:
  1259    host: %s
  1260    trafficPolicy:
  1261      tls:
  1262        mode: SIMPLE
  1263        insecureSkipVerify: true
  1264  `, t.Apps.External.All.Config().DefaultHostHeader),
  1265  		children: []TrafficCall{},
  1266  	}
  1267  	expects := []struct {
  1268  		port int
  1269  		alpn string
  1270  	}{
  1271  		{8888, "http/1.1"},
  1272  		{8882, "h2"},
  1273  	}
  1274  	for _, c := range t.Apps.A {
  1275  		for _, e := range expects {
  1276  			c := c
  1277  			e := e
  1278  
  1279  			tc.children = append(tc.children, TrafficCall{
  1280  				name: fmt.Sprintf("%s: %s", c.Config().Cluster.StableName(), e.alpn),
  1281  				opts: echo.CallOptions{
  1282  					Port:  echo.Port{ServicePort: e.port, Protocol: protocol.HTTP},
  1283  					Count: 1,
  1284  					// Failed requests will go to non-existent port which hangs forever
  1285  					// Set a low timeout to fail faster
  1286  					Timeout: time.Millisecond * 500,
  1287  					Address: t.Apps.External.All[0].Address(),
  1288  					HTTP: echo.HTTP{
  1289  						Headers: HostHeader(t.Apps.External.All[0].Config().DefaultHostHeader),
  1290  					},
  1291  					Scheme: scheme.HTTP,
  1292  					Check: check.And(
  1293  						check.OK(),
  1294  						check.Alpn(e.alpn)),
  1295  				},
  1296  				call: c.CallOrFail,
  1297  			})
  1298  		}
  1299  	}
  1300  	t.RunTraffic(tc)
  1301  
  1302  	tc = TrafficTestCase{
  1303  		name: "NONE",
  1304  		config: fmt.Sprintf(`
  1305  apiVersion: networking.istio.io/v1alpha3
  1306  kind: DestinationRule
  1307  metadata:
  1308    name: external
  1309  spec:
  1310    host: %s
  1311    trafficPolicy:
  1312      tls:
  1313        mode: SIMPLE
  1314        insecureSkipVerify: true
  1315  ---
  1316  apiVersion: networking.istio.io/v1alpha3
  1317  kind: ServiceEntry
  1318  metadata:
  1319    name: alt-external-service
  1320  spec:
  1321    exportTo: [.]
  1322    hosts:
  1323    - %s
  1324    resolution: NONE
  1325    ports:
  1326    - name: http-tls-origination
  1327      number: 8888
  1328      protocol: http
  1329      targetPort: 443
  1330    - name: http2-tls-origination
  1331      number: 8882
  1332      protocol: http2
  1333      targetPort: 443`,
  1334  			"external."+t.Apps.External.Namespace.Name()+".svc.cluster.local",
  1335  			"external."+t.Apps.External.Namespace.Name()+".svc.cluster.local",
  1336  		),
  1337  		children: []TrafficCall{},
  1338  	}
  1339  	for _, c := range t.Apps.A {
  1340  		for _, e := range expects {
  1341  			c := c
  1342  			e := e
  1343  
  1344  			tc.children = append(tc.children, TrafficCall{
  1345  				name: fmt.Sprintf("%s: %s", c.Config().Cluster.StableName(), e.alpn),
  1346  				opts: echo.CallOptions{
  1347  					Port:  echo.Port{ServicePort: e.port, Protocol: protocol.HTTP},
  1348  					Count: 1,
  1349  					// Failed requests will go to non-existent port which hangs forever
  1350  					// Set a low timeout to fail faster
  1351  					Timeout: time.Millisecond * 500,
  1352  					Address: t.Apps.External.All.ForCluster(c.Config().Cluster.Name())[0].Address(),
  1353  					HTTP: echo.HTTP{
  1354  						Headers: HostHeader(t.Apps.External.All[0].Config().ClusterLocalFQDN()),
  1355  					},
  1356  					Scheme: scheme.HTTP,
  1357  					Check: check.And(
  1358  						check.OK(),
  1359  						check.Alpn(e.alpn)),
  1360  				},
  1361  				call: c.CallOrFail,
  1362  			})
  1363  		}
  1364  	}
  1365  	t.RunTraffic(tc)
  1366  
  1367  	tc = TrafficTestCase{
  1368  		name: "Redirect NONE",
  1369  		config: tmpl.MustEvaluate(`
  1370  apiVersion: networking.istio.io/v1alpha3
  1371  kind: DestinationRule
  1372  metadata:
  1373    name: external
  1374  spec:
  1375    host: {{.}}
  1376    trafficPolicy:
  1377      tls:
  1378        mode: SIMPLE
  1379        insecureSkipVerify: true
  1380  ---
  1381  apiVersion: networking.istio.io/v1beta1
  1382  kind: VirtualService
  1383  metadata:
  1384    name: httpbin.org
  1385  spec:
  1386    hosts:
  1387    - {{.}}
  1388    http:
  1389    - route:
  1390      - destination:
  1391          host: {{.}}
  1392          port:
  1393            number: 443
  1394  ---
  1395  apiVersion: networking.istio.io/v1alpha3
  1396  kind: ServiceEntry
  1397  metadata:
  1398    name: alt-external-service
  1399  spec:
  1400    exportTo: [.]
  1401    hosts:
  1402    - {{.}}
  1403    resolution: NONE
  1404    ports:
  1405    - name: http
  1406      number: 8888
  1407      protocol: http
  1408    - name: http2
  1409      number: 8882
  1410      protocol: http2
  1411    - name: https
  1412      number: 443
  1413      targetPort: 443
  1414      protocol: https
  1415      `,
  1416  			"external."+t.Apps.External.Namespace.Name()+".svc.cluster.local",
  1417  		),
  1418  		children: []TrafficCall{},
  1419  	}
  1420  	expects = []struct {
  1421  		port int
  1422  		alpn string
  1423  	}{
  1424  		{8888, "http/1.1"},
  1425  		// Note: here we expect HTTP/1.1, because the 443 port is not configured to be HTTP2!
  1426  		{8882, "http/1.1"},
  1427  	}
  1428  	for _, c := range t.Apps.A {
  1429  		for _, e := range expects {
  1430  			c := c
  1431  			e := e
  1432  
  1433  			tc.children = append(tc.children, TrafficCall{
  1434  				name: fmt.Sprintf("%s: %s", c.Config().Cluster.StableName(), e.alpn),
  1435  				opts: echo.CallOptions{
  1436  					Port:  echo.Port{ServicePort: e.port, Protocol: protocol.HTTP},
  1437  					Count: 1,
  1438  					// Failed requests will go to non-existent port which hangs forever
  1439  					// Set a low timeout to fail faster
  1440  					Timeout: time.Millisecond * 500,
  1441  					Address: t.Apps.External.All.ForCluster(c.Config().Cluster.Name())[0].Address(),
  1442  					HTTP: echo.HTTP{
  1443  						Headers: HostHeader(t.Apps.External.All[0].Config().ClusterLocalFQDN()),
  1444  					},
  1445  					Scheme: scheme.HTTP,
  1446  					Check: check.And(
  1447  						check.OK(),
  1448  						check.Alpn(e.alpn)),
  1449  				},
  1450  				call: c.CallOrFail,
  1451  			})
  1452  		}
  1453  	}
  1454  	t.RunTraffic(tc)
  1455  }
  1456  
  1457  // useClientProtocolCases contains tests use_client_protocol from DestinationRule
  1458  func useClientProtocolCases(t TrafficContext) {
  1459  	client := t.Apps.A
  1460  	to := t.Apps.C
  1461  	t.RunTraffic(TrafficTestCase{
  1462  		name:   "use client protocol with h2",
  1463  		config: useClientProtocolDestinationRule(to.Config().Service),
  1464  		call:   client[0].CallOrFail,
  1465  		opts: echo.CallOptions{
  1466  			To: to,
  1467  			Port: echo.Port{
  1468  				Name: "http",
  1469  			},
  1470  			Count: 1,
  1471  			HTTP: echo.HTTP{
  1472  				HTTP2: true,
  1473  			},
  1474  			Check: check.And(
  1475  				check.OK(),
  1476  				check.Protocol("HTTP/2.0"),
  1477  			),
  1478  		},
  1479  	})
  1480  	t.RunTraffic(TrafficTestCase{
  1481  		name:   "use client protocol with h1",
  1482  		config: useClientProtocolDestinationRule(to.Config().Service),
  1483  		call:   client[0].CallOrFail,
  1484  		opts: echo.CallOptions{
  1485  			Port: echo.Port{
  1486  				Name: "http",
  1487  			},
  1488  			Count: 1,
  1489  			To:    to,
  1490  			HTTP: echo.HTTP{
  1491  				HTTP2: false,
  1492  			},
  1493  			Check: check.And(
  1494  				check.OK(),
  1495  				check.Protocol("HTTP/1.1"),
  1496  			),
  1497  		},
  1498  	})
  1499  }
  1500  
  1501  // destinationRuleCases contains tests some specific DestinationRule tests.
  1502  func destinationRuleCases(t TrafficContext) {
  1503  	from := t.Apps.A
  1504  	to := t.Apps.C
  1505  	// Validates the config is generated correctly when only idletimeout is specified in DR.
  1506  	t.RunTraffic(TrafficTestCase{
  1507  		name:   "only idletimeout specified in DR",
  1508  		config: idletimeoutDestinationRule("idletimeout-dr", to.Config().Service),
  1509  		call:   from[0].CallOrFail,
  1510  		opts: echo.CallOptions{
  1511  			To: to,
  1512  			Port: echo.Port{
  1513  				Name: "http",
  1514  			},
  1515  			Count: 1,
  1516  			HTTP: echo.HTTP{
  1517  				HTTP2: true,
  1518  			},
  1519  			Check: check.OK(),
  1520  		},
  1521  	})
  1522  }
  1523  
  1524  // trafficLoopCases contains tests to ensure traffic does not loop through the sidecar
  1525  func trafficLoopCases(t TrafficContext) {
  1526  	for _, c := range t.Apps.A {
  1527  		for _, d := range t.Apps.B {
  1528  			for _, port := range []int{15001, 15006} {
  1529  				c, d, port := c, d, port
  1530  				t.RunTraffic(TrafficTestCase{
  1531  					name: fmt.Sprint(port),
  1532  					call: c.CallOrFail,
  1533  					opts: echo.CallOptions{
  1534  						ToWorkload: d,
  1535  						Port:       echo.Port{ServicePort: port, Protocol: protocol.HTTP},
  1536  						// Ideally we would actually check to make sure we do not blow up the pod,
  1537  						// but I couldn't find a way to reliably detect this.
  1538  						Check: check.Error(),
  1539  					},
  1540  				})
  1541  			}
  1542  		}
  1543  	}
  1544  }
  1545  
  1546  // autoPassthroughCases tests that we cannot hit unexpected destinations when using AUTO_PASSTHROUGH
  1547  func autoPassthroughCases(t TrafficContext) {
  1548  	// We test the cross product of all Istio ALPNs (or no ALPN), all mTLS modes, and various backends
  1549  	alpns := []string{"istio", "istio-peer-exchange", "istio-http/1.0", "istio-http/1.1", "istio-h2", ""}
  1550  	modes := []string{"STRICT", "PERMISSIVE", "DISABLE"}
  1551  
  1552  	mtlsHost := host.Name(t.Apps.A.Config().ClusterLocalFQDN())
  1553  	nakedHost := host.Name(t.Apps.Naked.Config().ClusterLocalFQDN())
  1554  	httpsPort := ports.HTTP.ServicePort
  1555  	httpsAutoPort := ports.AutoHTTPS.ServicePort
  1556  	snis := []string{
  1557  		model.BuildSubsetKey(model.TrafficDirectionOutbound, "", mtlsHost, httpsPort),
  1558  		model.BuildDNSSrvSubsetKey(model.TrafficDirectionOutbound, "", mtlsHost, httpsPort),
  1559  		model.BuildSubsetKey(model.TrafficDirectionOutbound, "", nakedHost, httpsPort),
  1560  		model.BuildDNSSrvSubsetKey(model.TrafficDirectionOutbound, "", nakedHost, httpsPort),
  1561  		model.BuildSubsetKey(model.TrafficDirectionOutbound, "", mtlsHost, httpsAutoPort),
  1562  		model.BuildDNSSrvSubsetKey(model.TrafficDirectionOutbound, "", mtlsHost, httpsAutoPort),
  1563  		model.BuildSubsetKey(model.TrafficDirectionOutbound, "", nakedHost, httpsAutoPort),
  1564  		model.BuildDNSSrvSubsetKey(model.TrafficDirectionOutbound, "", nakedHost, httpsAutoPort),
  1565  	}
  1566  	defaultIngress := istio.DefaultIngressOrFail(t, t)
  1567  	for _, mode := range modes {
  1568  		var childs []TrafficCall
  1569  		for _, sni := range snis {
  1570  			for _, alpn := range alpns {
  1571  				alpn, sni, mode := alpn, sni, mode
  1572  				al := []string{alpn}
  1573  				if alpn == "" {
  1574  					al = nil
  1575  				}
  1576  				childs = append(childs, TrafficCall{
  1577  					name: fmt.Sprintf("mode:%v,sni:%v,alpn:%v", mode, sni, alpn),
  1578  					call: defaultIngress.CallOrFail,
  1579  					opts: echo.CallOptions{
  1580  						Port: echo.Port{
  1581  							ServicePort: 443,
  1582  							Protocol:    protocol.HTTPS,
  1583  						},
  1584  						TLS: echo.TLS{
  1585  							ServerName: sni,
  1586  							Alpn:       al,
  1587  						},
  1588  						Check:   check.Error(),
  1589  						Timeout: 5 * time.Second,
  1590  					},
  1591  				},
  1592  				)
  1593  			}
  1594  		}
  1595  		t.RunTraffic(TrafficTestCase{
  1596  			config: globalPeerAuthentication(mode) + `
  1597  ---
  1598  apiVersion: networking.istio.io/v1alpha3
  1599  kind: Gateway
  1600  metadata:
  1601    name: cross-network-gateway-test
  1602    namespace: {{.SystemNamespace | default "istio-system"}}
  1603  spec:
  1604    selector:
  1605      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1606    servers:
  1607      - port:
  1608          number: 443
  1609          name: tls
  1610          protocol: TLS
  1611        tls:
  1612          mode: AUTO_PASSTHROUGH
  1613        hosts:
  1614          - "*.local"
  1615  `,
  1616  			children: childs,
  1617  			templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  1618  				systemNamespace := "istio-system"
  1619  				if t.Istio.Settings().SystemNamespace != "" {
  1620  					systemNamespace = t.Istio.Settings().SystemNamespace
  1621  				}
  1622  				return map[string]any{
  1623  					"SystemNamespace":   systemNamespace,
  1624  					"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  1625  				}
  1626  			},
  1627  		})
  1628  	}
  1629  }
  1630  
  1631  func gatewayCases(t TrafficContext) {
  1632  	// TODO fix for ambient
  1633  	skipEnvoyPeerMeta := skipAmbient(t, "X-Envoy-Peer-Metadata present in response")
  1634  	templateParams := func(protocol protocol.Instance, src echo.Callers, dests echo.Instances, ciphers []string, port string) map[string]any {
  1635  		hostName, dest, portN, cred := "*", dests[0], 80, ""
  1636  		if protocol.IsTLS() {
  1637  			hostName, portN, cred = dest.Config().ClusterLocalFQDN(), 443, "cred"
  1638  		}
  1639  		return map[string]any{
  1640  			"IngressNamespace":   src[0].(ingress.Instance).Namespace(),
  1641  			"GatewayHost":        hostName,
  1642  			"GatewayPort":        portN,
  1643  			"GatewayPortName":    strings.ToLower(string(protocol)),
  1644  			"GatewayProtocol":    string(protocol),
  1645  			"Gateway":            "gateway",
  1646  			"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  1647  			"Port":               dest.PortForName(port).ServicePort,
  1648  			"Credential":         cred,
  1649  			"Ciphers":            ciphers,
  1650  			"TLSMode":            "SIMPLE",
  1651  			"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  1652  		}
  1653  	}
  1654  
  1655  	// clears the To to avoid echo internals trying to match the protocol with the port on echo.Config
  1656  	noTarget := func(_ echo.Caller, opts *echo.CallOptions) {
  1657  		opts.To = nil
  1658  	}
  1659  	// allows setting the target indirectly via the host header
  1660  	fqdnHostHeader := func(src echo.Caller, opts *echo.CallOptions) {
  1661  		if opts.HTTP.Headers == nil {
  1662  			opts.HTTP.Headers = make(http.Header)
  1663  		}
  1664  		opts.HTTP.Headers.Set(headers.Host, opts.To.Config().ClusterLocalFQDN())
  1665  		noTarget(src, opts)
  1666  	}
  1667  
  1668  	// SingleRegualrPod is already applied leaving one regular pod, to only regular pods should leave a single workload.
  1669  	// the following cases don't actually target workloads, we use the singleTarget filter to avoid duplicate cases
  1670  	// Gateways don't support talking directly to waypoint, so we skip that here as well.
  1671  	matchers := []match.Matcher{match.RegularPod, match.NotWaypoint}
  1672  
  1673  	gatewayListenPort := 80
  1674  	gatewayListenPortName := "http"
  1675  	t.RunTraffic(TrafficTestCase{
  1676  		name:             "404",
  1677  		targetMatchers:   matchers,
  1678  		workloadAgnostic: true,
  1679  		viaIngress:       true,
  1680  		config:           httpGateway("*", gatewayListenPort, gatewayListenPortName, "HTTP", t.Istio.Settings().IngressGatewayIstioLabel),
  1681  		opts: echo.CallOptions{
  1682  			Count: 1,
  1683  			Port: echo.Port{
  1684  				Protocol: protocol.HTTP,
  1685  			},
  1686  			HTTP: echo.HTTP{
  1687  				Headers: headers.New().WithHost("foo.bar").Build(),
  1688  			},
  1689  			Check: check.Status(http.StatusNotFound),
  1690  		},
  1691  		setupOpts: noTarget,
  1692  	})
  1693  	t.RunTraffic(TrafficTestCase{
  1694  		name:             "https redirect",
  1695  		targetMatchers:   matchers,
  1696  		workloadAgnostic: true,
  1697  		viaIngress:       true,
  1698  		config: `apiVersion: networking.istio.io/v1alpha3
  1699  kind: Gateway
  1700  metadata:
  1701    name: gateway
  1702  spec:
  1703    selector:
  1704      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1705    servers:
  1706    - port:
  1707        number: 80
  1708        name: http
  1709        protocol: HTTP
  1710      hosts:
  1711      - "*"
  1712      tls:
  1713        httpsRedirect: true
  1714  ---
  1715  `,
  1716  		opts: echo.CallOptions{
  1717  			Count: 1,
  1718  			Port: echo.Port{
  1719  				Protocol: protocol.HTTP,
  1720  			},
  1721  			Check: check.Status(http.StatusMovedPermanently),
  1722  		},
  1723  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  1724  			return map[string]any{
  1725  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  1726  			}
  1727  		},
  1728  		setupOpts: fqdnHostHeader,
  1729  	})
  1730  	t.RunTraffic(TrafficTestCase{
  1731  		// See https://github.com/istio/istio/issues/27315
  1732  		name:             "https with x-forwarded-proto",
  1733  		targetMatchers:   matchers,
  1734  		workloadAgnostic: true,
  1735  		viaIngress:       true,
  1736  		config: `apiVersion: networking.istio.io/v1alpha3
  1737  kind: Gateway
  1738  metadata:
  1739    name: gateway
  1740  spec:
  1741    selector:
  1742      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1743    servers:
  1744    - port:
  1745        number: 80
  1746        name: http
  1747        protocol: HTTP
  1748      hosts:
  1749      - "*"
  1750      tls:
  1751        httpsRedirect: true
  1752  ---
  1753  apiVersion: networking.istio.io/v1alpha3
  1754  kind: EnvoyFilter
  1755  metadata:
  1756    name: ingressgateway-redirect-config
  1757    namespace: {{.SystemNamespace | default "istio-system"}}
  1758  spec:
  1759    configPatches:
  1760    - applyTo: NETWORK_FILTER
  1761      match:
  1762        context: GATEWAY
  1763        listener:
  1764          filterChain:
  1765            filter:
  1766              name: envoy.filters.network.http_connection_manager
  1767      patch:
  1768        operation: MERGE
  1769        value:
  1770          typed_config:
  1771            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  1772            xff_num_trusted_hops: 1
  1773            normalize_path: true
  1774    workloadSelector:
  1775      labels:
  1776        istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1777  ---
  1778  ` + httpVirtualServiceTmpl,
  1779  		opts: echo.CallOptions{
  1780  			Count: 1,
  1781  			Port: echo.Port{
  1782  				Protocol: protocol.HTTP,
  1783  			},
  1784  			HTTP: echo.HTTP{
  1785  				// In real world, this may be set by a downstream LB that terminates the TLS
  1786  				Headers: headers.New().With(headers.XForwardedProto, "https").Build(),
  1787  			},
  1788  			Check: check.OK(),
  1789  		},
  1790  		setupOpts: fqdnHostHeader,
  1791  		templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  1792  			dest := dests[0]
  1793  			systemNamespace := "istio-system"
  1794  			if t.Istio.Settings().SystemNamespace != "" {
  1795  				systemNamespace = t.Istio.Settings().SystemNamespace
  1796  			}
  1797  			return map[string]any{
  1798  				"Gateway":            "gateway",
  1799  				"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  1800  				"Port":               dest.PortForName(ports.HTTP.Name).ServicePort,
  1801  				"SystemNamespace":    systemNamespace,
  1802  				"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  1803  			}
  1804  		},
  1805  	})
  1806  	t.RunTraffic(TrafficTestCase{
  1807  		name:           "cipher suite",
  1808  		targetMatchers: []match.Matcher{match.NotWaypoint},
  1809  		config: gatewayTmpl + httpVirtualServiceTmpl +
  1810  			ingressutil.IngressKubeSecretYAML("cred", "{{.IngressNamespace}}", ingressutil.TLS, ingressutil.IngressCredentialA),
  1811  		templateVars: func(src echo.Callers, dests echo.Instances) map[string]any {
  1812  			// Test all cipher suites, including a fake one. Envoy should accept all of the ones on the "valid" list,
  1813  			// and control plane should filter our invalid one.
  1814  
  1815  			params := templateParams(protocol.HTTPS, src, dests, append(sets.SortedList(security.ValidCipherSuites), "fake"), ports.HTTP.Name)
  1816  			params["GatewayIstioLabel"] = t.Istio.Settings().IngressGatewayIstioLabel
  1817  			return params
  1818  		},
  1819  		setupOpts: fqdnHostHeader,
  1820  		opts: echo.CallOptions{
  1821  			Count: 1,
  1822  			Port: echo.Port{
  1823  				Protocol: protocol.HTTPS,
  1824  			},
  1825  		},
  1826  		viaIngress:       true,
  1827  		workloadAgnostic: true,
  1828  	})
  1829  	t.RunTraffic(TrafficTestCase{
  1830  		name:           "optional mTLS",
  1831  		targetMatchers: []match.Matcher{match.NotWaypoint},
  1832  		config: gatewayTmpl + httpVirtualServiceTmpl +
  1833  			ingressutil.IngressKubeSecretYAML("cred", "{{.IngressNamespace}}", ingressutil.TLS, ingressutil.IngressCredentialA),
  1834  		templateVars: func(src echo.Callers, dests echo.Instances) map[string]any {
  1835  			params := templateParams(protocol.HTTPS, src, dests, nil, ports.HTTP.Name)
  1836  			params["GatewayIstioLabel"] = t.Istio.Settings().IngressGatewayIstioLabel
  1837  			params["TLSMode"] = "OPTIONAL_MUTUAL"
  1838  			return params
  1839  		},
  1840  		setupOpts: fqdnHostHeader,
  1841  		opts: echo.CallOptions{
  1842  			Count: 1,
  1843  			Port: echo.Port{
  1844  				Protocol: protocol.HTTPS,
  1845  			},
  1846  		},
  1847  		viaIngress:       true,
  1848  		workloadAgnostic: true,
  1849  	})
  1850  	t.RunTraffic(TrafficTestCase{
  1851  		// See https://github.com/istio/istio/issues/34609
  1852  		name:             "http redirect when vs port specify https",
  1853  		targetMatchers:   matchers,
  1854  		workloadAgnostic: true,
  1855  		viaIngress:       true,
  1856  		config: `apiVersion: networking.istio.io/v1alpha3
  1857  kind: Gateway
  1858  metadata:
  1859    name: gateway
  1860  spec:
  1861    selector:
  1862      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1863    servers:
  1864    - port:
  1865        number: 80
  1866        name: http
  1867        protocol: HTTP
  1868      hosts:
  1869      - "*"
  1870      tls:
  1871        httpsRedirect: true
  1872  ---
  1873  ` + httpVirtualServiceTmpl,
  1874  		opts: echo.CallOptions{
  1875  			Count: 1,
  1876  			Port: echo.Port{
  1877  				Protocol: protocol.HTTP,
  1878  			},
  1879  			Check: check.Status(http.StatusMovedPermanently),
  1880  		},
  1881  		setupOpts: fqdnHostHeader,
  1882  		templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  1883  			dest := dests[0]
  1884  			return map[string]any{
  1885  				"Gateway":            "gateway",
  1886  				"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  1887  				"Port":               443,
  1888  				"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  1889  			}
  1890  		},
  1891  	})
  1892  	t.RunTraffic(TrafficTestCase{
  1893  		// See https://github.com/istio/istio/issues/27315
  1894  		// See https://github.com/istio/istio/issues/34609
  1895  		name:             "http return 400 with with x-forwarded-proto https when vs port specify https",
  1896  		targetMatchers:   matchers,
  1897  		workloadAgnostic: true,
  1898  		viaIngress:       true,
  1899  		config: `apiVersion: networking.istio.io/v1alpha3
  1900  kind: Gateway
  1901  metadata:
  1902    name: gateway
  1903  spec:
  1904    selector:
  1905      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1906    servers:
  1907    - port:
  1908        number: 80
  1909        name: http
  1910        protocol: HTTP
  1911      hosts:
  1912      - "*"
  1913      tls:
  1914        httpsRedirect: true
  1915  ---
  1916  apiVersion: networking.istio.io/v1alpha3
  1917  kind: EnvoyFilter
  1918  metadata:
  1919    name: ingressgateway-redirect-config
  1920    namespace: {{.SystemNamespace | default "istio-system"}}
  1921  spec:
  1922    configPatches:
  1923    - applyTo: NETWORK_FILTER
  1924      match:
  1925        context: GATEWAY
  1926        listener:
  1927          filterChain:
  1928            filter:
  1929              name: envoy.filters.network.http_connection_manager
  1930      patch:
  1931        operation: MERGE
  1932        value:
  1933          typed_config:
  1934            '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  1935            xff_num_trusted_hops: 1
  1936            normalize_path: true
  1937    workloadSelector:
  1938      labels:
  1939        istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1940  ---
  1941  ` + httpVirtualServiceTmpl,
  1942  		opts: echo.CallOptions{
  1943  			Count: 1,
  1944  			Port: echo.Port{
  1945  				Protocol: protocol.HTTP,
  1946  			},
  1947  			HTTP: echo.HTTP{
  1948  				// In real world, this may be set by a downstream LB that terminates the TLS
  1949  				Headers: headers.New().With(headers.XForwardedProto, "https").Build(),
  1950  			},
  1951  			Check: check.Status(http.StatusBadRequest),
  1952  		},
  1953  		setupOpts: fqdnHostHeader,
  1954  		templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  1955  			systemNamespace := "istio-system"
  1956  			if t.Istio.Settings().SystemNamespace != "" {
  1957  				systemNamespace = t.Istio.Settings().SystemNamespace
  1958  			}
  1959  			dest := dests[0]
  1960  			return map[string]any{
  1961  				"Gateway":            "gateway",
  1962  				"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  1963  				"Port":               443,
  1964  				"SystemNamespace":    systemNamespace,
  1965  				"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  1966  			}
  1967  		},
  1968  	})
  1969  	t.RunTraffic(TrafficTestCase{
  1970  		// https://github.com/istio/istio/issues/37196
  1971  		name:             "client protocol - http1",
  1972  		targetMatchers:   matchers,
  1973  		workloadAgnostic: true,
  1974  		viaIngress:       true,
  1975  		config: `apiVersion: networking.istio.io/v1alpha3
  1976  kind: Gateway
  1977  metadata:
  1978    name: gateway
  1979  spec:
  1980    selector:
  1981      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  1982    servers:
  1983    - port:
  1984        number: 80
  1985        name: http
  1986        protocol: HTTP
  1987      hosts:
  1988      - "*"
  1989  ---
  1990  ` + httpVirtualServiceTmpl,
  1991  		opts: echo.CallOptions{
  1992  			Count: 1,
  1993  			Port: echo.Port{
  1994  				Protocol: protocol.HTTP,
  1995  			},
  1996  			Check: check.And(
  1997  				check.OK(),
  1998  				check.Protocol("HTTP/1.1")),
  1999  		},
  2000  		setupOpts: fqdnHostHeader,
  2001  		templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  2002  			dest := dests[0]
  2003  			return map[string]any{
  2004  				"Gateway":            "gateway",
  2005  				"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  2006  				"Port":               dest.PortForName(ports.AutoHTTP.Name).ServicePort,
  2007  				"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  2008  			}
  2009  		},
  2010  	})
  2011  	t.RunTraffic(TrafficTestCase{
  2012  		// https://github.com/istio/istio/issues/37196
  2013  		name:             "client protocol - http2",
  2014  		skip:             skipEnvoyPeerMeta,
  2015  		targetMatchers:   matchers,
  2016  		workloadAgnostic: true,
  2017  		viaIngress:       true,
  2018  		config: `apiVersion: networking.istio.io/v1alpha3
  2019  kind: Gateway
  2020  metadata:
  2021    name: gateway
  2022  spec:
  2023    selector:
  2024      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  2025    servers:
  2026    - port:
  2027        number: 80
  2028        name: http
  2029        protocol: HTTP
  2030      hosts:
  2031      - "*"
  2032  ---
  2033  ` + httpVirtualServiceTmpl,
  2034  		opts: echo.CallOptions{
  2035  			HTTP: echo.HTTP{
  2036  				HTTP2: true,
  2037  			},
  2038  			Count: 1,
  2039  			Port: echo.Port{
  2040  				Protocol: protocol.HTTP,
  2041  			},
  2042  			Check: check.And(
  2043  				check.OK(),
  2044  				// Gateway doesn't implicitly use downstream
  2045  				check.Protocol("HTTP/1.1"),
  2046  				// Regression test; if this is set it means the inbound sidecar is treating it as TCP
  2047  				check.RequestHeader("X-Envoy-Peer-Metadata", "")),
  2048  		},
  2049  		setupOpts: fqdnHostHeader,
  2050  		templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  2051  			dest := dests[0]
  2052  			systemNamespace := "istio-system"
  2053  			if t.Istio.Settings().SystemNamespace != "" {
  2054  				systemNamespace = t.Istio.Settings().SystemNamespace
  2055  			}
  2056  			return map[string]any{
  2057  				"Gateway":            "gateway",
  2058  				"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  2059  				"Port":               dest.PortForName(ports.AutoHTTP.Name).ServicePort,
  2060  				"SystemNamespace":    systemNamespace,
  2061  				"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  2062  			}
  2063  		},
  2064  	})
  2065  	t.RunTraffic(TrafficTestCase{
  2066  		name:             "wildcard hostname",
  2067  		targetMatchers:   matchers,
  2068  		workloadAgnostic: true,
  2069  		viaIngress:       true,
  2070  		config: `apiVersion: networking.istio.io/v1alpha3
  2071  kind: Gateway
  2072  metadata:
  2073    name: gateway
  2074  spec:
  2075    selector:
  2076      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  2077    servers:
  2078    - port:
  2079        number: 80
  2080        name: http
  2081        protocol: HTTP
  2082      hosts:
  2083      - "*.example.com"
  2084  ---
  2085  ` + httpVirtualServiceTmpl,
  2086  		children: []TrafficCall{
  2087  			{
  2088  				name: "no port",
  2089  				call: nil,
  2090  				opts: echo.CallOptions{
  2091  					HTTP: echo.HTTP{
  2092  						HTTP2:   true,
  2093  						Headers: headers.New().WithHost("foo.example.com").Build(),
  2094  					},
  2095  					Port: echo.Port{
  2096  						Protocol: protocol.HTTP,
  2097  					},
  2098  					Check: check.OK(),
  2099  				},
  2100  			},
  2101  			{
  2102  				name: "correct port",
  2103  				call: nil,
  2104  				opts: echo.CallOptions{
  2105  					HTTP: echo.HTTP{
  2106  						HTTP2:   true,
  2107  						Headers: headers.New().WithHost("foo.example.com:80").Build(),
  2108  					},
  2109  					Port: echo.Port{
  2110  						Protocol: protocol.HTTP,
  2111  					},
  2112  					Check: check.OK(),
  2113  				},
  2114  			},
  2115  			{
  2116  				name: "random port",
  2117  				call: nil,
  2118  				opts: echo.CallOptions{
  2119  					HTTP: echo.HTTP{
  2120  						HTTP2:   true,
  2121  						Headers: headers.New().WithHost("foo.example.com:12345").Build(),
  2122  					},
  2123  					Port: echo.Port{
  2124  						Protocol: protocol.HTTP,
  2125  					},
  2126  					Check: check.OK(),
  2127  				},
  2128  			},
  2129  		},
  2130  		setupOpts: noTarget,
  2131  		templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  2132  			return map[string]any{
  2133  				"Gateway":            "gateway",
  2134  				"VirtualServiceHost": "*.example.com",
  2135  				"DestinationHost":    dests[0].Config().ClusterLocalFQDN(),
  2136  				"Port":               ports.HTTP.ServicePort,
  2137  				"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  2138  			}
  2139  		},
  2140  	})
  2141  
  2142  	for _, port := range []echo.Port{ports.AutoHTTP, ports.HTTP, ports.HTTP2} {
  2143  		for _, h2 := range []bool{true, false} {
  2144  			port, h2 := port, h2
  2145  			protoName := "http1"
  2146  			expectedProto := "HTTP/1.1"
  2147  			if h2 {
  2148  				protoName = "http2"
  2149  				expectedProto = "HTTP/2.0"
  2150  			}
  2151  
  2152  			t.RunTraffic(TrafficTestCase{
  2153  				// https://github.com/istio/istio/issues/37196
  2154  				name:             fmt.Sprintf("client protocol - %v use client with %v", protoName, port),
  2155  				skip:             skipEnvoyPeerMeta,
  2156  				targetMatchers:   matchers,
  2157  				workloadAgnostic: true,
  2158  				viaIngress:       true,
  2159  				config: `apiVersion: networking.istio.io/v1alpha3
  2160  kind: Gateway
  2161  metadata:
  2162    name: gateway
  2163  spec:
  2164    selector:
  2165      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  2166    servers:
  2167    - port:
  2168        number: 80
  2169        name: http
  2170        protocol: HTTP
  2171      hosts:
  2172      - "*"
  2173  ---
  2174  ` + httpVirtualServiceTmpl + useClientProtocolDestinationRuleTmpl,
  2175  				opts: echo.CallOptions{
  2176  					HTTP: echo.HTTP{
  2177  						HTTP2: h2,
  2178  					},
  2179  					Count: 1,
  2180  					Port: echo.Port{
  2181  						Protocol: protocol.HTTP,
  2182  					},
  2183  					Check: check.And(
  2184  						check.OK(),
  2185  						// We did configure to use client protocol
  2186  						check.Protocol(expectedProto),
  2187  						// Regression test; if this is set it means the inbound sidecar is treating it as TCP
  2188  						check.RequestHeader("X-Envoy-Peer-Metadata", "")),
  2189  				},
  2190  				setupOpts: fqdnHostHeader,
  2191  				templateVars: func(_ echo.Callers, dests echo.Instances) map[string]any {
  2192  					dest := dests[0]
  2193  					return map[string]any{
  2194  						"Gateway":            "gateway",
  2195  						"VirtualServiceHost": dest.Config().ClusterLocalFQDN(),
  2196  						"Port":               port.ServicePort,
  2197  						"GatewayIstioLabel":  t.Istio.Settings().IngressGatewayIstioLabel,
  2198  					}
  2199  				},
  2200  			})
  2201  		}
  2202  	}
  2203  
  2204  	for _, proto := range []protocol.Instance{protocol.HTTP, protocol.HTTPS} {
  2205  		proto, secret := proto, ""
  2206  		if proto.IsTLS() {
  2207  			secret = ingressutil.IngressKubeSecretYAML("cred", "{{.IngressNamespace}}", ingressutil.TLS, ingressutil.IngressCredentialA)
  2208  		}
  2209  		t.RunTraffic(TrafficTestCase{
  2210  			name:   string(proto),
  2211  			config: gatewayTmpl + httpVirtualServiceTmpl + secret,
  2212  			templateVars: func(src echo.Callers, dests echo.Instances) map[string]any {
  2213  				params := templateParams(proto, src, dests, nil, ports.HTTP.Name)
  2214  				params["GatewayIstioLabel"] = t.Istio.Settings().IngressGatewayIstioLabel
  2215  				return params
  2216  			},
  2217  			setupOpts: fqdnHostHeader,
  2218  			opts: echo.CallOptions{
  2219  				Count: 1,
  2220  				Port: echo.Port{
  2221  					Protocol: proto,
  2222  				},
  2223  			},
  2224  			viaIngress:       true,
  2225  			workloadAgnostic: true,
  2226  		})
  2227  		t.RunTraffic(TrafficTestCase{
  2228  			name:   fmt.Sprintf("%s scheme match", proto),
  2229  			config: gatewayTmpl + httpVirtualServiceTmpl + secret,
  2230  			templateVars: func(src echo.Callers, dests echo.Instances) map[string]any {
  2231  				params := templateParams(proto, src, dests, nil, ports.HTTP.Name)
  2232  				params["MatchScheme"] = strings.ToLower(string(proto))
  2233  				params["GatewayIstioLabel"] = t.Istio.Settings().IngressGatewayIstioLabel
  2234  				return params
  2235  			},
  2236  			setupOpts: fqdnHostHeader,
  2237  			opts: echo.CallOptions{
  2238  				Count: 1,
  2239  				Port: echo.Port{
  2240  					Protocol: proto,
  2241  				},
  2242  				Check: check.And(
  2243  					check.OK(),
  2244  					check.RequestHeader("Istio-Custom-Header", "user-defined-value")),
  2245  			},
  2246  			// to keep tests fast, we only run the basic protocol test per-workload and scheme match once (per cluster)
  2247  			targetMatchers:   matchers,
  2248  			viaIngress:       true,
  2249  			workloadAgnostic: true,
  2250  		})
  2251  	}
  2252  	secret := ingressutil.IngressKubeSecretYAML("cred", "{{.IngressNamespace}}", ingressutil.TLS, ingressutil.IngressCredentialA)
  2253  	t.RunTraffic(TrafficTestCase{
  2254  		name:   "HTTPS re-encrypt",
  2255  		config: gatewayTmpl + httpVirtualServiceTmpl + originateTLSTmpl + secret,
  2256  		templateVars: func(src echo.Callers, dests echo.Instances) map[string]any {
  2257  			return templateParams(protocol.HTTPS, src, dests, nil, ports.HTTPS.Name)
  2258  		},
  2259  		setupOpts: fqdnHostHeader,
  2260  		opts: echo.CallOptions{
  2261  			Port: echo.Port{
  2262  				Protocol: protocol.HTTPS,
  2263  			},
  2264  			Check: check.OK(),
  2265  		},
  2266  		viaIngress:       true,
  2267  		workloadAgnostic: true,
  2268  	})
  2269  }
  2270  
  2271  // 1. Creates a TCP Gateway and VirtualService listener
  2272  // 2. Configures the echoserver to call itself via the TCP gateway using PROXY protocol https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
  2273  // 3. Assumes that the proxy filter EnvoyFilter is applied
  2274  func ProxyProtocolFilterAppliedGatewayCase(apps *deployment.SingleNamespaceView, gateway string) []TrafficTestCase {
  2275  	var cases []TrafficTestCase
  2276  	gatewayListenPort := 80
  2277  	gatewayListenPortName := "tcp"
  2278  
  2279  	destinationSets := []echo.Instances{
  2280  		apps.A,
  2281  	}
  2282  
  2283  	for _, d := range destinationSets {
  2284  		d := d
  2285  		if len(d) == 0 {
  2286  			continue
  2287  		}
  2288  
  2289  		fqdn := d[0].Config().ClusterLocalFQDN()
  2290  		cases = append(cases, TrafficTestCase{
  2291  			name: d[0].Config().Service,
  2292  			// This creates a Gateway with a TCP listener that will accept TCP traffic from host
  2293  			// `fqdn` and forward that traffic back to `fqdn`, from srcPort to targetPort
  2294  			config: httpGateway("*", gatewayListenPort, gatewayListenPortName, "TCP", "") + // use the default label since this test creates its own gateway
  2295  				tcpVirtualService("gateway", fqdn, "", 80, ports.TCP.ServicePort),
  2296  			call: apps.Naked[0].CallOrFail,
  2297  			opts: echo.CallOptions{
  2298  				Count:   1,
  2299  				Port:    echo.Port{ServicePort: 80},
  2300  				Scheme:  scheme.TCP,
  2301  				Address: gateway,
  2302  				// Envoy requires PROXY protocol TCP payloads have a minimum size, see:
  2303  				// https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/listener/proxy_protocol/v3/proxy_protocol.proto
  2304  				// If the PROXY protocol filter is enabled, Envoy should parse and consume the header out of the TCP payload, otherwise echo it back as-is.
  2305  				Message:              "This is a test TCP message",
  2306  				ProxyProtocolVersion: 1,
  2307  				Check: check.Each(
  2308  					func(r echoClient.Response) error {
  2309  						body := r.RawContent
  2310  						ok := strings.Contains(body, "PROXY TCP4")
  2311  						if ok {
  2312  							return fmt.Errorf("sent proxy protocol header, and it was echoed back")
  2313  						}
  2314  						return nil
  2315  					}),
  2316  			},
  2317  		})
  2318  	}
  2319  	return cases
  2320  }
  2321  
  2322  func UpstreamProxyProtocolCase(apps *deployment.SingleNamespaceView, gateway string) []TrafficTestCase {
  2323  	var cases []TrafficTestCase
  2324  	gatewayListenPort := 80
  2325  	gatewayListenPortName := "tcp"
  2326  
  2327  	destinationSets := []echo.Instances{
  2328  		apps.A,
  2329  	}
  2330  
  2331  	destRule := fmt.Sprintf(`
  2332  apiVersion: networking.istio.io/v1alpha3
  2333  kind: DestinationRule
  2334  metadata:
  2335    name: custom-gateway
  2336  spec:
  2337    host: %s
  2338    trafficPolicy:
  2339      proxyProtocol:
  2340        version: V1
  2341  ---
  2342  `, gateway)
  2343  
  2344  	for _, d := range destinationSets {
  2345  		d := d
  2346  		if len(d) == 0 {
  2347  			continue
  2348  		}
  2349  
  2350  		fqdn := d[0].Config().ClusterLocalFQDN()
  2351  		cases = append(cases, TrafficTestCase{
  2352  			name: d[0].Config().Service,
  2353  			// This creates a Gateway with a TCP listener that will accept TCP traffic from host
  2354  			// `fqdn` and forward that traffic back to `fqdn`, from srcPort to targetPort
  2355  			config: httpGateway("*", gatewayListenPort, gatewayListenPortName, "TCP", "") + // use the default label since this test creates its own gateway
  2356  				tcpVirtualService("gateway", fqdn, "", 80, ports.TCP.ServicePort) +
  2357  				destRule,
  2358  			call: apps.A[0].CallOrFail,
  2359  			opts: echo.CallOptions{
  2360  				Count:   1,
  2361  				Port:    echo.Port{ServicePort: 80},
  2362  				Scheme:  scheme.TCP,
  2363  				Address: gateway,
  2364  				Message: "This is a test TCP message",
  2365  				Check: check.Each(
  2366  					func(r echoClient.Response) error {
  2367  						body := r.RawContent
  2368  						ok := strings.Contains(body, "PROXY TCP4")
  2369  						if ok {
  2370  							return fmt.Errorf("sent proxy protocol header, and it was echoed back")
  2371  						}
  2372  						return nil
  2373  					}),
  2374  			},
  2375  		})
  2376  	}
  2377  	return cases
  2378  }
  2379  
  2380  func XFFGatewayCase(apps *deployment.SingleNamespaceView, gateway string) []TrafficTestCase {
  2381  	var cases []TrafficTestCase
  2382  	gatewayListenPort := 80
  2383  
  2384  	destinationSets := []echo.Instances{
  2385  		apps.A,
  2386  	}
  2387  
  2388  	for _, d := range destinationSets {
  2389  		d := d
  2390  		if len(d) == 0 {
  2391  			continue
  2392  		}
  2393  		fqdn := d[0].Config().ClusterLocalFQDN()
  2394  		cases = append(cases, TrafficTestCase{
  2395  			name: d[0].Config().Service,
  2396  			config: httpGateway("*", gatewayListenPort, ports.HTTP.Name, "HTTP", "") +
  2397  				httpVirtualService("gateway", fqdn, ports.HTTP.ServicePort),
  2398  			call: apps.Naked[0].CallOrFail,
  2399  			opts: echo.CallOptions{
  2400  				Count:   1,
  2401  				Port:    echo.Port{ServicePort: gatewayListenPort},
  2402  				Scheme:  scheme.HTTP,
  2403  				Address: gateway,
  2404  				HTTP: echo.HTTP{
  2405  					Headers: headers.New().
  2406  						WithHost(fqdn).
  2407  						With(headers.XForwardedFor, "56.5.6.7, 72.9.5.6, 98.1.2.3").
  2408  						Build(),
  2409  				},
  2410  				Check: check.Each(
  2411  					func(r echoClient.Response) error {
  2412  						externalAddress, ok := r.RequestHeaders["X-Envoy-External-Address"]
  2413  						if !ok {
  2414  							return fmt.Errorf("missing X-Envoy-External-Address Header")
  2415  						}
  2416  						if err := ExpectString(externalAddress[0], "72.9.5.6", "envoy-external-address header"); err != nil {
  2417  							return err
  2418  						}
  2419  						xffHeader, ok := r.RequestHeaders["X-Forwarded-For"]
  2420  						if !ok {
  2421  							return fmt.Errorf("missing X-Forwarded-For Header")
  2422  						}
  2423  
  2424  						xffIPs := strings.Split(xffHeader[0], ",")
  2425  						if len(xffIPs) != 4 {
  2426  							return fmt.Errorf("did not receive expected 4 hosts in X-Forwarded-For header")
  2427  						}
  2428  
  2429  						return ExpectString(strings.TrimSpace(xffIPs[1]), "72.9.5.6", "ip in xff header")
  2430  					}),
  2431  			},
  2432  		})
  2433  	}
  2434  	return cases
  2435  }
  2436  
  2437  func envoyFilterCases(t TrafficContext) {
  2438  	// Test adding envoyfilter to inbound and outbound route/cluster/listeners
  2439  	cfg := `
  2440  apiVersion: networking.istio.io/v1alpha3
  2441  kind: EnvoyFilter
  2442  metadata:
  2443    name: outbound
  2444  spec:
  2445    workloadSelector:
  2446      labels:
  2447        app: a
  2448    configPatches:
  2449    - applyTo: HTTP_FILTER
  2450      match:
  2451        context: SIDECAR_OUTBOUND
  2452        listener:
  2453          filterChain:
  2454            filter:
  2455              name: "envoy.filters.network.http_connection_manager"
  2456              subFilter:
  2457                name: "envoy.filters.http.router"
  2458      patch:
  2459        operation: INSERT_BEFORE
  2460        value:
  2461         name: envoy.lua
  2462         typed_config:
  2463            "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
  2464            inlineCode: |
  2465              function envoy_on_request(request_handle)
  2466                request_handle:headers():add("x-lua-outbound", "hello world")
  2467              end
  2468    - applyTo: VIRTUAL_HOST
  2469      match:
  2470        context: SIDECAR_OUTBOUND
  2471      patch:
  2472        operation: MERGE
  2473        value:
  2474          request_headers_to_add:
  2475          - header:
  2476              key: x-vhost-outbound
  2477              value: "hello world"
  2478    - applyTo: CLUSTER
  2479      match:
  2480        context: SIDECAR_OUTBOUND
  2481        cluster: {}
  2482      patch:
  2483        operation: MERGE
  2484        value:
  2485          http2_protocol_options: {}
  2486  ---
  2487  apiVersion: networking.istio.io/v1alpha3
  2488  kind: EnvoyFilter
  2489  metadata:
  2490    name: inbound
  2491  spec:
  2492    workloadSelector:
  2493      labels:
  2494        app: b
  2495    configPatches:
  2496    - applyTo: HTTP_FILTER
  2497      match:
  2498        context: SIDECAR_INBOUND
  2499        listener:
  2500          filterChain:
  2501            filter:
  2502              name: "envoy.filters.network.http_connection_manager"
  2503              subFilter:
  2504                name: "envoy.filters.http.router"
  2505      patch:
  2506        operation: INSERT_BEFORE
  2507        value:
  2508         name: envoy.lua
  2509         typed_config:
  2510            "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
  2511            inlineCode: |
  2512              function envoy_on_request(request_handle)
  2513                request_handle:headers():add("x-lua-inbound", "hello world")
  2514              end
  2515    - applyTo: VIRTUAL_HOST
  2516      match:
  2517        context: SIDECAR_INBOUND
  2518      patch:
  2519        operation: MERGE
  2520        value:
  2521          request_headers_to_add:
  2522          - header:
  2523              key: x-vhost-inbound
  2524              value: "hello world"
  2525    - applyTo: CLUSTER
  2526      match:
  2527        context: SIDECAR_INBOUND
  2528        cluster: {}
  2529      patch:
  2530        operation: MERGE
  2531        value:
  2532          http2_protocol_options: {}
  2533  `
  2534  	for _, c := range t.Apps.A {
  2535  		t.RunTraffic(TrafficTestCase{
  2536  			config: cfg,
  2537  			call:   c.CallOrFail,
  2538  			opts: echo.CallOptions{
  2539  				To:    t.Apps.B,
  2540  				Count: 1,
  2541  				Port: echo.Port{
  2542  					Name: "http",
  2543  				},
  2544  				Check: check.And(
  2545  					check.OK(),
  2546  					check.Protocol("HTTP/2.0"),
  2547  					check.RequestHeaders(map[string]string{
  2548  						"X-Vhost-Inbound":  "hello world",
  2549  						"X-Vhost-Outbound": "hello world",
  2550  						"X-Lua-Inbound":    "hello world",
  2551  						"X-Lua-Outbound":   "hello world",
  2552  					}),
  2553  				),
  2554  			},
  2555  		})
  2556  	}
  2557  }
  2558  
  2559  // hostCases tests different forms of host header to use
  2560  func hostCases(t TrafficContext) {
  2561  	for _, c := range t.Apps.A {
  2562  		cfg := t.Apps.Headless.Config()
  2563  		port := ports.AutoHTTP.WorkloadPort
  2564  		wl := t.Apps.Headless[0].WorkloadsOrFail(t)
  2565  		if len(wl) == 0 {
  2566  			t.Fatalf("no workloads found")
  2567  		}
  2568  		address := wl[0].Address()
  2569  		// We test all variants with no port, the expected port, and a random port.
  2570  		hosts := []string{
  2571  			cfg.ClusterLocalFQDN(),
  2572  			fmt.Sprintf("%s:%d", cfg.ClusterLocalFQDN(), port),
  2573  			fmt.Sprintf("%s:12345", cfg.ClusterLocalFQDN()),
  2574  			fmt.Sprintf("%s.%s.svc", cfg.Service, cfg.Namespace.Name()),
  2575  			fmt.Sprintf("%s.%s.svc:%d", cfg.Service, cfg.Namespace.Name(), port),
  2576  			fmt.Sprintf("%s.%s.svc:12345", cfg.Service, cfg.Namespace.Name()),
  2577  			cfg.Service,
  2578  			fmt.Sprintf("%s:%d", cfg.Service, port),
  2579  			fmt.Sprintf("%s:12345", cfg.Service),
  2580  			fmt.Sprintf("some-instances.%s:%d", cfg.ClusterLocalFQDN(), port),
  2581  			fmt.Sprintf("some-instances.%s:12345", cfg.ClusterLocalFQDN()),
  2582  			fmt.Sprintf("some-instances.%s.%s.svc", cfg.Service, cfg.Namespace.Name()),
  2583  			fmt.Sprintf("some-instances.%s.%s.svc:12345", cfg.Service, cfg.Namespace.Name()),
  2584  			fmt.Sprintf("some-instances.%s", cfg.Service),
  2585  			fmt.Sprintf("some-instances.%s:%d", cfg.Service, port),
  2586  			fmt.Sprintf("some-instances.%s:12345", cfg.Service),
  2587  			address,
  2588  			fmt.Sprintf("%s:%d", address, port),
  2589  		}
  2590  		for _, h := range hosts {
  2591  			name := strings.Replace(h, address, "ip", -1) + "/auto-http"
  2592  			t.RunTraffic(TrafficTestCase{
  2593  				name: name,
  2594  				call: c.CallOrFail,
  2595  				opts: echo.CallOptions{
  2596  					To:    t.Apps.Headless,
  2597  					Count: 1,
  2598  					Port: echo.Port{
  2599  						Name: "auto-http",
  2600  					},
  2601  					HTTP: echo.HTTP{
  2602  						Headers: HostHeader(h),
  2603  					},
  2604  					// check mTLS to ensure we are not hitting pass-through cluster
  2605  					Check: check.And(check.OK(), check.MTLSForHTTP()),
  2606  				},
  2607  			})
  2608  		}
  2609  		port = ports.HTTP.WorkloadPort
  2610  		hosts = []string{
  2611  			cfg.ClusterLocalFQDN(),
  2612  			fmt.Sprintf("%s:%d", cfg.ClusterLocalFQDN(), port),
  2613  			fmt.Sprintf("%s:12345", cfg.ClusterLocalFQDN()),
  2614  			fmt.Sprintf("%s.%s.svc", cfg.Service, cfg.Namespace.Name()),
  2615  			fmt.Sprintf("%s.%s.svc:%d", cfg.Service, cfg.Namespace.Name(), port),
  2616  			fmt.Sprintf("%s.%s.svc:12345", cfg.Service, cfg.Namespace.Name()),
  2617  			cfg.Service,
  2618  			fmt.Sprintf("%s:%d", cfg.Service, port),
  2619  			fmt.Sprintf("%s:12345", cfg.Service),
  2620  			fmt.Sprintf("some-instances.%s:%d", cfg.ClusterLocalFQDN(), port),
  2621  			fmt.Sprintf("some-instances.%s:12345", cfg.ClusterLocalFQDN()),
  2622  			fmt.Sprintf("some-instances.%s.%s.svc", cfg.Service, cfg.Namespace.Name()),
  2623  			fmt.Sprintf("some-instances.%s.%s.svc:%d", cfg.Service, cfg.Namespace.Name(), port),
  2624  			fmt.Sprintf("some-instances.%s.%s.svc:12345", cfg.Service, cfg.Namespace.Name()),
  2625  			fmt.Sprintf("some-instances.%s", cfg.Service),
  2626  			fmt.Sprintf("some-instances.%s:%d", cfg.Service, port),
  2627  			fmt.Sprintf("some-instances.%s:12345", cfg.Service),
  2628  			address,
  2629  			fmt.Sprintf("%s:%d", address, port),
  2630  		}
  2631  		for _, h := range hosts {
  2632  			name := strings.Replace(h, address, "ip", -1) + "/http"
  2633  			assertion := check.And(check.OK(), check.MTLSForHTTP())
  2634  			if strings.Contains(name, "ip") {
  2635  				// we expect to actually do passthrough for the IP case
  2636  				assertion = check.OK()
  2637  			}
  2638  			t.RunTraffic(TrafficTestCase{
  2639  				name: name,
  2640  				call: c.CallOrFail,
  2641  				opts: echo.CallOptions{
  2642  					To: t.Apps.Headless,
  2643  					Port: echo.Port{
  2644  						Name: "http",
  2645  					},
  2646  					HTTP: echo.HTTP{
  2647  						Headers: HostHeader(h),
  2648  					},
  2649  					// check mTLS to ensure we are not hitting pass-through cluster
  2650  					Check: assertion,
  2651  				},
  2652  			})
  2653  		}
  2654  	}
  2655  }
  2656  
  2657  // serviceCases tests overlapping Services. There are a few cases.
  2658  // Consider we have our base service B, with service port P and target port T
  2659  //  1. Another service, B', with P -> T. In this case, both the listener and the cluster will conflict.
  2660  //     Because everything is workload oriented, this is not a problem unless they try to make them different
  2661  //     protocols (this is explicitly called out as "not supported") or control inbound connectionPool settings
  2662  //     (which is moving to Sidecar soon)
  2663  //  2. Another service, B', with P -> T'. In this case, the listener will be distinct, since its based on the target.
  2664  //     The cluster, however, will be shared, which is broken, because we should be forwarding to T when we call B, and T' when we call B'.
  2665  //  3. Another service, B', with P' -> T. In this case, the listener is shared. This is fine, with the exception of different protocols
  2666  //     The cluster is distinct.
  2667  //  4. Another service, B', with P' -> T'. There is no conflicts here at all.
  2668  func serviceCases(t TrafficContext) {
  2669  	for _, c := range t.Apps.A {
  2670  		c := c
  2671  
  2672  		// Case 1
  2673  		// Identical to port "http" or service B, just behind another service name
  2674  		svc := fmt.Sprintf(`apiVersion: v1
  2675  kind: Service
  2676  metadata:
  2677    name: b-alt-1
  2678    labels:
  2679      app: b
  2680  spec:
  2681    ports:
  2682    - name: http
  2683      port: %d
  2684      targetPort: %d
  2685    selector:
  2686      app: b`, ports.HTTP.ServicePort, ports.HTTP.WorkloadPort)
  2687  		t.RunTraffic(TrafficTestCase{
  2688  			name:   fmt.Sprintf("case 1 both match in cluster %v", c.Config().Cluster.StableName()),
  2689  			config: svc,
  2690  			call:   c.CallOrFail,
  2691  			opts: echo.CallOptions{
  2692  				Count:   1,
  2693  				Address: "b-alt-1",
  2694  				Port:    echo.Port{ServicePort: ports.HTTP.ServicePort, Protocol: protocol.HTTP},
  2695  				Timeout: time.Millisecond * 100,
  2696  				Check:   check.OK(),
  2697  			},
  2698  		})
  2699  
  2700  		// Case 2
  2701  		// We match the service port, but forward to a different port
  2702  		// Here we make the new target tcp so the test would fail if it went to the http port
  2703  		svc = fmt.Sprintf(`apiVersion: v1
  2704  kind: Service
  2705  metadata:
  2706    name: b-alt-2
  2707    labels:
  2708      app: b
  2709  spec:
  2710    ports:
  2711    - name: tcp
  2712      port: %d
  2713      targetPort: %d
  2714    selector:
  2715      app: b`, ports.HTTP.ServicePort, ports.All().GetWorkloadOnlyPorts()[0].WorkloadPort)
  2716  		t.RunTraffic(TrafficTestCase{
  2717  			name:   fmt.Sprintf("case 2 service port match in cluster %v", c.Config().Cluster.StableName()),
  2718  			config: svc,
  2719  			call:   c.CallOrFail,
  2720  			opts: echo.CallOptions{
  2721  				Count:   1,
  2722  				Address: "b-alt-2",
  2723  				Port:    echo.Port{ServicePort: ports.HTTP.ServicePort, Protocol: protocol.TCP},
  2724  				Scheme:  scheme.TCP,
  2725  				Timeout: time.Millisecond * 100,
  2726  				Check:   check.OK(),
  2727  			},
  2728  		})
  2729  
  2730  		// Case 3
  2731  		// We match the target port, but front with a different service port
  2732  		svc = fmt.Sprintf(`apiVersion: v1
  2733  kind: Service
  2734  metadata:
  2735    name: b-alt-3
  2736    labels:
  2737      app: b
  2738  spec:
  2739    ports:
  2740    - name: http
  2741      port: 12345
  2742      targetPort: %d
  2743    selector:
  2744      app: b`, ports.HTTP.WorkloadPort)
  2745  		t.RunTraffic(TrafficTestCase{
  2746  			name:   fmt.Sprintf("case 3 target port match in cluster %v", c.Config().Cluster.StableName()),
  2747  			config: svc,
  2748  			call:   c.CallOrFail,
  2749  			opts: echo.CallOptions{
  2750  				Count:   1,
  2751  				Address: "b-alt-3",
  2752  				Port:    echo.Port{ServicePort: 12345, Protocol: protocol.HTTP},
  2753  				Timeout: time.Millisecond * 100,
  2754  				Check:   check.OK(),
  2755  			},
  2756  		})
  2757  
  2758  		// Case 4
  2759  		// Completely new set of ports
  2760  		svc = fmt.Sprintf(`apiVersion: v1
  2761  kind: Service
  2762  metadata:
  2763    name: b-alt-4
  2764    labels:
  2765      app: b
  2766  spec:
  2767    ports:
  2768    - name: http
  2769      port: 12346
  2770      targetPort: %d
  2771    selector:
  2772      app: b`, ports.All().GetWorkloadOnlyPorts()[1].WorkloadPort)
  2773  		t.RunTraffic(TrafficTestCase{
  2774  			name:   fmt.Sprintf("case 4 no match in cluster %v", c.Config().Cluster.StableName()),
  2775  			config: svc,
  2776  			call:   c.CallOrFail,
  2777  			opts: echo.CallOptions{
  2778  				Count:   1,
  2779  				Address: "b-alt-4",
  2780  				Port:    echo.Port{ServicePort: 12346, Protocol: protocol.HTTP},
  2781  				Timeout: time.Millisecond * 100,
  2782  				Check:   check.OK(),
  2783  			},
  2784  		})
  2785  	}
  2786  }
  2787  
  2788  func externalNameCases(t TrafficContext) {
  2789  	calls := func(name string, checks ...echo.Checker) []TrafficCall {
  2790  		checks = append(checks, check.OK())
  2791  		ch := []TrafficCall{}
  2792  		for _, c := range t.Apps.A {
  2793  			for _, port := range []echo.Port{ports.HTTP, ports.AutoHTTP, ports.TCP, ports.HTTPS} {
  2794  				c, port := c, port
  2795  				ch = append(ch, TrafficCall{
  2796  					name: port.Name,
  2797  					call: c.CallOrFail,
  2798  					opts: echo.CallOptions{
  2799  						Address: name,
  2800  						Port:    port,
  2801  						Timeout: time.Millisecond * 250,
  2802  						Check:   check.And(checks...),
  2803  					},
  2804  				})
  2805  			}
  2806  		}
  2807  		return ch
  2808  	}
  2809  
  2810  	t.RunTraffic(TrafficTestCase{
  2811  		name:         "without port",
  2812  		globalConfig: true,
  2813  		config: fmt.Sprintf(`apiVersion: v1
  2814  kind: Service
  2815  metadata:
  2816    name: b-ext-no-port
  2817  spec:
  2818    type: ExternalName
  2819    externalName: b.%s.svc.cluster.local`, t.Apps.Namespace.Name()),
  2820  		children: calls("b-ext-no-port", check.MTLSForHTTP()),
  2821  	})
  2822  
  2823  	t.RunTraffic(TrafficTestCase{
  2824  		name:         "with port",
  2825  		globalConfig: true,
  2826  		config: fmt.Sprintf(`apiVersion: v1
  2827  kind: Service
  2828  metadata:
  2829    name: b-ext-port
  2830  spec:
  2831    ports:
  2832    - name: http
  2833      port: %d
  2834      protocol: TCP
  2835      targetPort: %d
  2836    type: ExternalName
  2837    externalName: b.%s.svc.cluster.local`,
  2838  			ports.HTTP.ServicePort, ports.HTTP.WorkloadPort, t.Apps.Namespace.Name()),
  2839  		children: calls("b-ext-port", check.MTLSForHTTP()),
  2840  	})
  2841  
  2842  	t.RunTraffic(TrafficTestCase{
  2843  		name: "service entry",
  2844  		skip: skip{
  2845  			skip:   true,
  2846  			reason: "not currently working, as SE doesn't have a VIP",
  2847  		},
  2848  		globalConfig: true,
  2849  		config: fmt.Sprintf(`apiVersion: v1
  2850  kind: Service
  2851  metadata:
  2852    name: b-ext-se
  2853  spec:
  2854    type: ExternalName
  2855    externalName: %s`,
  2856  			t.Apps.External.All.Config().HostHeader()),
  2857  		children: calls("b-ext-se"),
  2858  	})
  2859  
  2860  	gatewayListenPort := 80
  2861  	gatewayListenPortName := "http"
  2862  	t.RunTraffic(TrafficTestCase{
  2863  		name: "gateway",
  2864  		skip: skip{
  2865  			skip:   t.Clusters().IsMulticluster(),
  2866  			reason: "we need to apply service to all but Istio config to only Istio clusters, which we don't support",
  2867  		},
  2868  		globalConfig: true,
  2869  		config: fmt.Sprintf(`apiVersion: v1
  2870  kind: Service
  2871  metadata:
  2872    name: b-ext-se
  2873  spec:
  2874    type: ExternalName
  2875    externalName: b.%s.svc.cluster.local
  2876  ---`,
  2877  			t.Apps.Namespace.Name()) +
  2878  			httpGateway("*", gatewayListenPort, gatewayListenPortName, "HTTP", t.Istio.Settings().IngressGatewayIstioLabel) +
  2879  			httpVirtualService("gateway", fmt.Sprintf("b-ext-se.%s.svc.cluster.local", t.Apps.Namespace.Name()), ports.HTTP.ServicePort),
  2880  		call: t.Istio.Ingresses().Callers()[0].CallOrFail,
  2881  		opts: echo.CallOptions{
  2882  			Address: fmt.Sprintf("b-ext-se.%s.svc.cluster.local", t.Apps.Namespace.Name()),
  2883  			Port:    ports.HTTP,
  2884  			Check:   check.OK(),
  2885  		},
  2886  	})
  2887  }
  2888  
  2889  // consistentHashCases tests destination rule's consistent hashing mechanism
  2890  func consistentHashCases(t TrafficContext) {
  2891  	if len(t.Clusters().ByNetwork()) != 1 {
  2892  		// Consistent hashing does not work for multinetwork. The first request will consistently go to a
  2893  		// gateway, but that gateway will tcp_proxy it to a random pod.
  2894  		t.Skipf("multi-network is not supported")
  2895  	}
  2896  	for _, app := range []echo.Instances{t.Apps.A, t.Apps.B} {
  2897  		app := app
  2898  		for _, c := range app {
  2899  			c := c
  2900  
  2901  			// First setup a service selecting a few services. This is needed to ensure we can load balance across many pods.
  2902  			svcName := "consistent-hash"
  2903  			if nw := c.Config().Cluster.NetworkName(); nw != "" {
  2904  				svcName += "-" + nw
  2905  			}
  2906  			svc := tmpl.MustEvaluate(`apiVersion: v1
  2907  kind: Service
  2908  metadata:
  2909    name: {{.Service}}
  2910  spec:
  2911    ports:
  2912    - name: http
  2913      port: {{.Port}}
  2914      targetPort: {{.TargetPort}}
  2915    - name: tcp
  2916      port: {{.TcpPort}}
  2917      targetPort: {{.TcpTargetPort}}
  2918    selector:
  2919      test.istio.io/class: standard
  2920      {{- if .Network }}
  2921      topology.istio.io/network: {{.Network}}
  2922  	{{- end }}
  2923  `, map[string]any{
  2924  				"Service":        svcName,
  2925  				"Network":        c.Config().Cluster.NetworkName(),
  2926  				"Port":           ports.HTTP.ServicePort,
  2927  				"TargetPort":     ports.HTTP.WorkloadPort,
  2928  				"TcpPort":        ports.TCP.ServicePort,
  2929  				"TcpTargetPort":  ports.TCP.WorkloadPort,
  2930  				"GrpcPort":       ports.GRPC.ServicePort,
  2931  				"GrpcTargetPort": ports.GRPC.WorkloadPort,
  2932  			})
  2933  
  2934  			destRule := fmt.Sprintf(`
  2935  ---
  2936  apiVersion: networking.istio.io/v1beta1
  2937  kind: DestinationRule
  2938  metadata:
  2939    name: %s
  2940  spec:
  2941    host: %s
  2942    trafficPolicy:
  2943      loadBalancer:
  2944        consistentHash:
  2945          {{. | indent 8}}
  2946  `, svcName, svcName)
  2947  
  2948  			cookieWithTTLDest := fmt.Sprintf(`
  2949  ---
  2950  apiVersion: networking.istio.io/v1beta1
  2951  kind: DestinationRule
  2952  metadata:
  2953    name: %s
  2954  spec:
  2955    host: %s
  2956    trafficPolicy:
  2957      loadBalancer:
  2958        consistentHash:
  2959          httpCookie:
  2960            name: session-cookie
  2961            ttl: 3600s
  2962  `, svcName, svcName)
  2963  
  2964  			cookieWithoutTTLDest := fmt.Sprintf(`
  2965  ---
  2966  apiVersion: networking.istio.io/v1beta1
  2967  kind: DestinationRule
  2968  metadata:
  2969    name: %s
  2970  spec:
  2971    host: %s
  2972    trafficPolicy:
  2973      loadBalancer:
  2974        consistentHash:
  2975          httpCookie:
  2976            name: session-cookie
  2977  `, svcName, svcName)
  2978  			// Add a negative test case. This ensures that the test is actually valid; its not a super trivial check
  2979  			// and could be broken by having only 1 pod so its good to have this check in place
  2980  			t.RunTraffic(TrafficTestCase{
  2981  				name:   "no consistent",
  2982  				config: svc,
  2983  				call:   c.CallOrFail,
  2984  				opts: echo.CallOptions{
  2985  					Count:   10,
  2986  					Address: svcName,
  2987  					Port:    echo.Port{ServicePort: ports.HTTP.ServicePort, Protocol: protocol.HTTP},
  2988  					Check: check.And(
  2989  						check.OK(),
  2990  						func(result echo.CallResult, rerr error) error {
  2991  							err := ConsistentHostChecker.Check(result, rerr)
  2992  							if err == nil {
  2993  								return fmt.Errorf("expected inconsistent hash, but it was consistent")
  2994  							}
  2995  							return nil
  2996  						},
  2997  					),
  2998  				},
  2999  			})
  3000  			callOpts := echo.CallOptions{
  3001  				Count:   10,
  3002  				Address: svcName,
  3003  				HTTP: echo.HTTP{
  3004  					Path:    "/?some-query-param=bar",
  3005  					Headers: headers.New().With("x-some-header", "baz").Build(),
  3006  				},
  3007  				Port: echo.Port{ServicePort: ports.HTTP.ServicePort, Protocol: protocol.HTTP},
  3008  				Check: check.And(
  3009  					check.OK(),
  3010  					ConsistentHostChecker,
  3011  				),
  3012  			}
  3013  			cookieCallOpts := echo.CallOptions{
  3014  				Count:   10,
  3015  				Address: svcName,
  3016  				HTTP: echo.HTTP{
  3017  					Path:    "/?some-query-param=bar",
  3018  					Headers: headers.New().With("x-some-header", "baz").Build(),
  3019  				},
  3020  				Port: echo.Port{ServicePort: ports.HTTP.ServicePort, Protocol: protocol.HTTP},
  3021  				Check: check.And(
  3022  					check.OK(),
  3023  					ConsistentHostChecker,
  3024  				),
  3025  				PropagateResponse: func(req *http.Request, res *http.Response) {
  3026  					scopes.Framework.Infof("invoking propagate response")
  3027  					if res == nil {
  3028  						scopes.Framework.Infof("no response")
  3029  						return
  3030  					}
  3031  					if res.Cookies() == nil {
  3032  						scopes.Framework.Infof("no cookies")
  3033  						return
  3034  					}
  3035  					var sessionCookie *http.Cookie
  3036  					for _, cookie := range res.Cookies() {
  3037  						if cookie.Name == "session-cookie" {
  3038  							sessionCookie = cookie
  3039  							break
  3040  						}
  3041  					}
  3042  					if sessionCookie != nil {
  3043  						scopes.Framework.Infof("setting the request cookie back in the request: %v %b",
  3044  							sessionCookie.Value, sessionCookie.Expires)
  3045  						req.AddCookie(sessionCookie)
  3046  					} else {
  3047  						scopes.Framework.Infof("no session cookie found in the response")
  3048  					}
  3049  				},
  3050  			}
  3051  			cookieWithoutTTLCallOpts := cookieCallOpts
  3052  			cookieWithoutTTLCallOpts.HTTP.Headers = headers.New().With("Cookie", "session-cookie=somecookie").Build()
  3053  			tcpCallopts := echo.CallOptions{
  3054  				Count:   10,
  3055  				Address: svcName,
  3056  				Port:    echo.Port{ServicePort: ports.TCP.ServicePort, Protocol: protocol.TCP},
  3057  				Check: check.And(
  3058  					check.OK(),
  3059  					ConsistentHostChecker,
  3060  				),
  3061  			}
  3062  			if c.Config().WorkloadClass() == echo.Proxyless {
  3063  				callOpts.Port = echo.Port{ServicePort: ports.GRPC.ServicePort, Protocol: protocol.GRPC}
  3064  			}
  3065  			// Setup tests for various forms of the API
  3066  			// TODO: it may be necessary to vary the inputs of the hash and ensure we get a different backend
  3067  			// But its pretty hard to test that, so for now just ensure we hit the same one.
  3068  			t.RunTraffic(TrafficTestCase{
  3069  				name:   "source ip " + c.Config().Service,
  3070  				config: svc + tmpl.MustEvaluate(destRule, "useSourceIp: true"),
  3071  				call:   c.CallOrFail,
  3072  				opts:   callOpts,
  3073  			})
  3074  			t.RunTraffic(TrafficTestCase{
  3075  				name:   "query param" + c.Config().Service,
  3076  				config: svc + tmpl.MustEvaluate(destRule, "httpQueryParameterName: some-query-param"),
  3077  				call:   c.CallOrFail,
  3078  				opts:   callOpts,
  3079  			})
  3080  			t.RunTraffic(TrafficTestCase{
  3081  				name:   "http header" + c.Config().Service,
  3082  				config: svc + tmpl.MustEvaluate(destRule, "httpHeaderName: x-some-header"),
  3083  				call:   c.CallOrFail,
  3084  				opts:   callOpts,
  3085  			})
  3086  			t.RunTraffic(TrafficTestCase{
  3087  				name:   "tcp source ip " + c.Config().Service,
  3088  				config: svc + tmpl.MustEvaluate(destRule, "useSourceIp: true"),
  3089  				call:   c.CallOrFail,
  3090  				opts:   tcpCallopts,
  3091  				skip: skip{
  3092  					skip:   c.Config().WorkloadClass() == echo.Proxyless,
  3093  					reason: "", // TODO: is this a bug or WAI?
  3094  				},
  3095  			})
  3096  			t.RunTraffic(TrafficTestCase{
  3097  				name:   "http cookie with ttl" + c.Config().Service,
  3098  				config: svc + tmpl.MustEvaluate(cookieWithTTLDest, ""),
  3099  				call:   c.CallOrFail,
  3100  				opts:   cookieCallOpts,
  3101  				skip: skip{
  3102  					skip:   true,
  3103  					reason: "https://github.com/istio/istio/issues/48156: not currently working, as test framework is not passing the cookies back",
  3104  				},
  3105  			})
  3106  			t.RunTraffic(TrafficTestCase{
  3107  				name:   "http cookie without ttl" + c.Config().Service,
  3108  				config: svc + tmpl.MustEvaluate(cookieWithoutTTLDest, ""),
  3109  				call:   c.CallOrFail,
  3110  				opts:   cookieWithoutTTLCallOpts,
  3111  				skip: skip{
  3112  					skip:   true,
  3113  					reason: "https://github.com/istio/istio/issues/48156: not currently working, as test framework is not passing the cookies back",
  3114  				},
  3115  			})
  3116  		}
  3117  	}
  3118  }
  3119  
  3120  var ConsistentHostChecker echo.Checker = func(result echo.CallResult, _ error) error {
  3121  	hostnames := make([]string, len(result.Responses))
  3122  	for i, r := range result.Responses {
  3123  		hostnames[i] = r.Hostname
  3124  	}
  3125  	scopes.Framework.Infof("requests landed on hostnames: %v", hostnames)
  3126  	unique := sets.SortedList(sets.New(hostnames...))
  3127  	if len(unique) != 1 {
  3128  		return fmt.Errorf("expected only one destination, got: %v", unique)
  3129  	}
  3130  	return nil
  3131  }
  3132  
  3133  func flatten(clients ...[]echo.Instance) []echo.Instance {
  3134  	var instances []echo.Instance
  3135  	for _, c := range clients {
  3136  		instances = append(instances, c...)
  3137  	}
  3138  	return instances
  3139  }
  3140  
  3141  // selfCallsCases checks that pods can call themselves
  3142  func selfCallsCases(t TrafficContext) {
  3143  	t.SetDefaultSourceMatchers(match.NotExternal, match.NotNaked, match.NotHeadless, match.NotProxylessGRPC)
  3144  	t.SetDefaultComboFilter(func(from echo.Instance, to echo.Instances) echo.Instances {
  3145  		return match.ServiceName(from.NamespacedName()).GetMatches(to)
  3146  	})
  3147  	// Calls to the Service will go through envoy outbound and inbound, so we get envoy headers added
  3148  	t.RunTraffic(TrafficTestCase{
  3149  		name:             "to service",
  3150  		workloadAgnostic: true,
  3151  
  3152  		opts: echo.CallOptions{
  3153  			Count: 1,
  3154  			Port: echo.Port{
  3155  				Name: "http",
  3156  			},
  3157  			Check: check.And(
  3158  				check.OK(),
  3159  				check.RequestHeader("X-Envoy-Attempt-Count", "1")),
  3160  		},
  3161  	})
  3162  	// Localhost calls will go directly to localhost, bypassing Envoy. No envoy headers added.
  3163  	t.RunTraffic(TrafficTestCase{
  3164  		name:             "to localhost",
  3165  		workloadAgnostic: true,
  3166  		setupOpts: func(_ echo.Caller, opts *echo.CallOptions) {
  3167  			// the framework will try to set this when enumerating test cases
  3168  			opts.To = nil
  3169  		},
  3170  		opts: echo.CallOptions{
  3171  			Count:   1,
  3172  			Address: "localhost",
  3173  			Port:    echo.Port{ServicePort: 8080},
  3174  			Scheme:  scheme.HTTP,
  3175  			Check: check.And(
  3176  				check.OK(),
  3177  				check.RequestHeader("X-Envoy-Attempt-Count", "")),
  3178  		},
  3179  	})
  3180  	// PodIP calls will go directly to podIP, bypassing Envoy. No envoy headers added.
  3181  	t.RunTraffic(TrafficTestCase{
  3182  		name:             "to podIP",
  3183  		workloadAgnostic: true,
  3184  		setupOpts: func(srcCaller echo.Caller, opts *echo.CallOptions) {
  3185  			src := srcCaller.(echo.Instance)
  3186  			workloads, _ := src.Workloads()
  3187  			opts.Address = workloads[0].Address()
  3188  			// the framework will try to set this when enumerating test cases
  3189  			opts.To = nil
  3190  		},
  3191  		opts: echo.CallOptions{
  3192  			Count:  1,
  3193  			Scheme: scheme.HTTP,
  3194  			Port:   echo.Port{ServicePort: 8080},
  3195  			Check: check.And(
  3196  				check.OK(),
  3197  				check.RequestHeader("X-Envoy-Attempt-Count", "")),
  3198  		},
  3199  	})
  3200  }
  3201  
  3202  // TODO: merge with security TestReachability code
  3203  func protocolSniffingCases(t TrafficContext) {
  3204  	type protocolCase struct {
  3205  		// The port we call
  3206  		port string
  3207  		// The actual type of traffic we send to the port
  3208  		scheme scheme.Instance
  3209  	}
  3210  	protocols := []protocolCase{
  3211  		{"http", scheme.HTTP},
  3212  		{"auto-http", scheme.HTTP},
  3213  		{"tcp", scheme.TCP},
  3214  		{"auto-tcp", scheme.TCP},
  3215  		{"grpc", scheme.GRPC},
  3216  		{"auto-grpc", scheme.GRPC},
  3217  	}
  3218  
  3219  	// so we can check all clusters are hit
  3220  	for _, call := range protocols {
  3221  		call := call
  3222  		t.RunTraffic(TrafficTestCase{
  3223  			skip: skip{
  3224  				skip:   call.scheme == scheme.TCP,
  3225  				reason: "https://github.com/istio/istio/issues/26798: enable sniffing tcp",
  3226  			},
  3227  			name: call.port,
  3228  			opts: echo.CallOptions{
  3229  				Count: 1,
  3230  				Port: echo.Port{
  3231  					Name: call.port,
  3232  				},
  3233  				Scheme:  call.scheme,
  3234  				Timeout: time.Second * 5,
  3235  			},
  3236  			check: func(src echo.Caller, opts *echo.CallOptions) echo.Checker {
  3237  				if call.scheme == scheme.TCP || src.(echo.Instance).Config().IsProxylessGRPC() {
  3238  					// no host header for TCP
  3239  					// TODO understand why proxyless adds the port to :authority md
  3240  					return check.OK()
  3241  				}
  3242  				return check.And(
  3243  					check.OK(),
  3244  					check.Host(opts.GetHost()))
  3245  			},
  3246  			comboFilters: func() []echotest.CombinationFilter {
  3247  				if call.scheme != scheme.GRPC {
  3248  					return []echotest.CombinationFilter{func(from echo.Instance, to echo.Instances) echo.Instances {
  3249  						if from.Config().IsProxylessGRPC() && match.VM.Any(to) {
  3250  							return nil
  3251  						}
  3252  						return to
  3253  					}}
  3254  				}
  3255  				return nil
  3256  			}(),
  3257  			workloadAgnostic: true,
  3258  		})
  3259  	}
  3260  
  3261  	autoPort := ports.AutoHTTP
  3262  	httpPort := ports.HTTP
  3263  	// Tests for http1.0. Golang does not support 1.0 client requests at all
  3264  	// To simulate these, we use TCP and hand-craft the requests.
  3265  	t.RunTraffic(TrafficTestCase{
  3266  		name: "http10 to http",
  3267  		call: t.Apps.A[0].CallOrFail,
  3268  		opts: echo.CallOptions{
  3269  			To:    t.Apps.B,
  3270  			Count: 1,
  3271  			Port: echo.Port{
  3272  				Name: "http",
  3273  			},
  3274  			Scheme: scheme.TCP,
  3275  			Message: `GET / HTTP/1.0
  3276  `,
  3277  			Timeout: time.Second * 5,
  3278  			TCP: echo.TCP{
  3279  				// Explicitly declared as HTTP, so we always go through http filter which fails
  3280  				ExpectedResponse: &wrappers.StringValue{Value: `HTTP/1.1 426 Upgrade Required`},
  3281  			},
  3282  		},
  3283  	})
  3284  	t.RunTraffic(TrafficTestCase{
  3285  		name: "http10 to auto",
  3286  		call: t.Apps.A[0].CallOrFail,
  3287  		opts: echo.CallOptions{
  3288  			To:    t.Apps.B,
  3289  			Count: 1,
  3290  			Port: echo.Port{
  3291  				Name: "auto-http",
  3292  			},
  3293  			Scheme: scheme.TCP,
  3294  			Message: `GET / HTTP/1.0
  3295  `,
  3296  			Timeout: time.Second * 5,
  3297  			TCP: echo.TCP{
  3298  				// Auto should be detected as TCP
  3299  				ExpectedResponse: &wrappers.StringValue{Value: `HTTP/1.0 200 OK`},
  3300  			},
  3301  		},
  3302  	})
  3303  	t.RunTraffic(TrafficTestCase{
  3304  		name: "http10 to external",
  3305  		call: t.Apps.A[0].CallOrFail,
  3306  		opts: echo.CallOptions{
  3307  			Address: t.Apps.External.All[0].Address(),
  3308  			HTTP: echo.HTTP{
  3309  				Headers: HostHeader(t.Apps.External.All.Config().DefaultHostHeader),
  3310  			},
  3311  			Port:   httpPort,
  3312  			Count:  1,
  3313  			Scheme: scheme.TCP,
  3314  			Message: `GET / HTTP/1.0
  3315  `,
  3316  			Timeout: time.Second * 5,
  3317  			TCP: echo.TCP{
  3318  				// There is no VIP so we fall back to 0.0.0.0 listener which sniffs
  3319  				ExpectedResponse: &wrappers.StringValue{Value: `HTTP/1.0 200 OK`},
  3320  			},
  3321  		},
  3322  	})
  3323  	t.RunTraffic(TrafficTestCase{
  3324  		name: "http10 to external auto",
  3325  		call: t.Apps.A[0].CallOrFail,
  3326  		opts: echo.CallOptions{
  3327  			Address: t.Apps.External.All[0].Address(),
  3328  			HTTP: echo.HTTP{
  3329  				Headers: HostHeader(t.Apps.External.All.Config().DefaultHostHeader),
  3330  			},
  3331  			Port:   autoPort,
  3332  			Count:  1,
  3333  			Scheme: scheme.TCP,
  3334  			Message: `GET / HTTP/1.0
  3335  `,
  3336  			Timeout: time.Second * 5,
  3337  			TCP: echo.TCP{
  3338  				// Auto should be detected as TCP
  3339  				ExpectedResponse: &wrappers.StringValue{Value: `HTTP/1.0 200 OK`},
  3340  			},
  3341  		},
  3342  	},
  3343  	)
  3344  }
  3345  
  3346  // Todo merge with security TestReachability code
  3347  func instanceIPTests(t TrafficContext) {
  3348  	// proxyless doesn't get valuable coverage here
  3349  	t.SetDefaultTargetMatchers(match.NotProxylessGRPC)
  3350  	t.SetDefaultSourceMatchers(match.NotProxylessGRPC)
  3351  
  3352  	ipCases := []struct {
  3353  		name            string
  3354  		endpoint        string
  3355  		disableSidecar  bool
  3356  		port            echo.Port
  3357  		code            int
  3358  		minIstioVersion string
  3359  	}{
  3360  		// instance IP bind
  3361  		{
  3362  			name:           "instance IP without sidecar",
  3363  			disableSidecar: true,
  3364  			port:           ports.HTTPInstance,
  3365  			code:           http.StatusOK,
  3366  		},
  3367  		{
  3368  			name:     "instance IP with wildcard sidecar",
  3369  			endpoint: "0.0.0.0",
  3370  			port:     ports.HTTPInstance,
  3371  			code:     http.StatusOK,
  3372  		},
  3373  		{
  3374  			name:     "instance IP with localhost sidecar",
  3375  			endpoint: "127.0.0.1",
  3376  			port:     ports.HTTPInstance,
  3377  			code:     http.StatusServiceUnavailable,
  3378  		},
  3379  		{
  3380  			name:     "instance IP with empty sidecar",
  3381  			endpoint: "",
  3382  			port:     ports.HTTPInstance,
  3383  			code:     http.StatusOK,
  3384  		},
  3385  
  3386  		// Localhost bind
  3387  		{
  3388  			name:           "localhost IP without sidecar",
  3389  			disableSidecar: true,
  3390  			port:           ports.HTTPLocalHost,
  3391  			code:           http.StatusServiceUnavailable,
  3392  		},
  3393  		{
  3394  			name:     "localhost IP with wildcard sidecar",
  3395  			endpoint: "0.0.0.0",
  3396  			port:     ports.HTTPLocalHost,
  3397  			code:     http.StatusServiceUnavailable,
  3398  		},
  3399  		{
  3400  			name:     "localhost IP with localhost sidecar",
  3401  			endpoint: "127.0.0.1",
  3402  			port:     ports.HTTPLocalHost,
  3403  			code:     http.StatusOK,
  3404  		},
  3405  		{
  3406  			name:     "localhost IP with empty sidecar",
  3407  			endpoint: "",
  3408  			port:     ports.HTTPLocalHost,
  3409  			code:     http.StatusServiceUnavailable,
  3410  		},
  3411  
  3412  		// Wildcard bind
  3413  		{
  3414  			name:           "wildcard IP without sidecar",
  3415  			disableSidecar: true,
  3416  			port:           ports.HTTP,
  3417  			code:           http.StatusOK,
  3418  		},
  3419  		{
  3420  			name:     "wildcard IP with wildcard sidecar",
  3421  			endpoint: "0.0.0.0",
  3422  			port:     ports.HTTP,
  3423  			code:     http.StatusOK,
  3424  		},
  3425  		{
  3426  			name:     "wildcard IP with localhost sidecar",
  3427  			endpoint: "127.0.0.1",
  3428  			port:     ports.HTTP,
  3429  			code:     http.StatusOK,
  3430  		},
  3431  		{
  3432  			name:     "wildcard IP with empty sidecar",
  3433  			endpoint: "",
  3434  			port:     ports.HTTP,
  3435  			code:     http.StatusOK,
  3436  		},
  3437  	}
  3438  	for _, ipCase := range ipCases {
  3439  		for _, client := range t.Apps.A {
  3440  			ipCase := ipCase
  3441  			client := client
  3442  			to := t.Apps.B
  3443  			var config string
  3444  			if !ipCase.disableSidecar {
  3445  				config = fmt.Sprintf(`
  3446  apiVersion: networking.istio.io/v1alpha3
  3447  kind: Sidecar
  3448  metadata:
  3449    name: sidecar
  3450  spec:
  3451    workloadSelector:
  3452      labels:
  3453        app: b
  3454    egress:
  3455    - hosts:
  3456      - "./*"
  3457    ingress:
  3458    - port:
  3459        number: %d
  3460        protocol: HTTP
  3461      defaultEndpoint: %s:%d
  3462  `, ipCase.port.WorkloadPort, ipCase.endpoint, ipCase.port.WorkloadPort)
  3463  			}
  3464  			t.RunTraffic(TrafficTestCase{
  3465  				name:   ipCase.name,
  3466  				call:   client.CallOrFail,
  3467  				config: config,
  3468  				opts: echo.CallOptions{
  3469  					Count:   1,
  3470  					To:      to,
  3471  					Port:    ipCase.port,
  3472  					Scheme:  scheme.HTTP,
  3473  					Timeout: time.Second * 5,
  3474  					Check:   check.Status(ipCase.code),
  3475  				},
  3476  				minIstioVersion: ipCase.minIstioVersion,
  3477  			})
  3478  		}
  3479  	}
  3480  }
  3481  
  3482  type vmCase struct {
  3483  	name string
  3484  	from echo.Instance
  3485  	to   echo.Instances
  3486  	host string
  3487  }
  3488  
  3489  func DNSTestCases(t TrafficContext) {
  3490  	makeSE := func(ips ...string) string {
  3491  		return tmpl.MustEvaluate(`
  3492  apiVersion: networking.istio.io/v1alpha3
  3493  kind: ServiceEntry
  3494  metadata:
  3495    name: dns
  3496  spec:
  3497    hosts:
  3498    - "fake.service.local"
  3499    addresses:
  3500  {{ range $ip := .IPs }}
  3501    - "{{$ip}}"
  3502  {{ end }}
  3503    resolution: STATIC
  3504    endpoints: []
  3505    ports:
  3506    - number: 80
  3507      name: http
  3508      protocol: HTTP
  3509  `, map[string]any{"IPs": ips})
  3510  	}
  3511  	ipv4 := "1.2.3.4"
  3512  	ipv6 := "1234:1234:1234::1234:1234:1234"
  3513  	dummyLocalhostServer := "127.0.0.1"
  3514  	cases := []struct {
  3515  		name string
  3516  		// TODO(https://github.com/istio/istio/issues/30282) support multiple vips
  3517  		ips      string
  3518  		protocol string
  3519  		server   string
  3520  		skipCNI  bool
  3521  		expected []string
  3522  	}{
  3523  		{
  3524  			name:     "tcp ipv4",
  3525  			ips:      ipv4,
  3526  			expected: []string{ipv4},
  3527  			protocol: "tcp",
  3528  		},
  3529  		{
  3530  			name:     "udp ipv4",
  3531  			ips:      ipv4,
  3532  			expected: []string{ipv4},
  3533  			protocol: "udp",
  3534  		},
  3535  		{
  3536  			name:     "tcp ipv6",
  3537  			ips:      ipv6,
  3538  			expected: []string{ipv6},
  3539  			protocol: "tcp",
  3540  		},
  3541  		{
  3542  			name:     "udp ipv6",
  3543  			ips:      ipv6,
  3544  			expected: []string{ipv6},
  3545  			protocol: "udp",
  3546  		},
  3547  		{
  3548  			// We should only capture traffic to servers in /etc/resolv.conf nameservers
  3549  			// This checks we do not capture traffic to other servers.
  3550  			// This is important for cases like app -> istio dns server -> dnsmasq -> upstream
  3551  			// If we captured all DNS traffic, we would loop dnsmasq traffic back to our server.
  3552  			name:     "tcp localhost server",
  3553  			ips:      ipv4,
  3554  			expected: nil,
  3555  			protocol: "tcp",
  3556  			skipCNI:  true,
  3557  			server:   dummyLocalhostServer,
  3558  		},
  3559  		{
  3560  			name:     "udp localhost server",
  3561  			ips:      ipv4,
  3562  			expected: nil,
  3563  			protocol: "udp",
  3564  			skipCNI:  true,
  3565  			server:   dummyLocalhostServer,
  3566  		},
  3567  	}
  3568  	for _, client := range flatten(t.Apps.VM, t.Apps.A, t.Apps.Tproxy) {
  3569  		for _, tt := range cases {
  3570  			if tt.skipCNI && t.Istio.Settings().EnableCNI {
  3571  				continue
  3572  			}
  3573  			tt, client := tt, client
  3574  			address := "fake.service.local?"
  3575  			if tt.protocol != "" {
  3576  				address += "&protocol=" + tt.protocol
  3577  			}
  3578  			if tt.server != "" {
  3579  				address += "&server=" + tt.server
  3580  			}
  3581  			var checker echo.Checker = func(result echo.CallResult, _ error) error {
  3582  				for _, r := range result.Responses {
  3583  					if !reflect.DeepEqual(r.Body(), tt.expected) {
  3584  						return fmt.Errorf("unexpected dns response: wanted %v, got %v", tt.expected, r.Body())
  3585  					}
  3586  				}
  3587  				return nil
  3588  			}
  3589  			if tt.expected == nil {
  3590  				checker = check.Error()
  3591  			}
  3592  			t.RunTraffic(TrafficTestCase{
  3593  				name:   fmt.Sprintf("%s/%s", client.Config().Service, tt.name),
  3594  				config: makeSE(tt.ips),
  3595  				call:   client.CallOrFail,
  3596  				opts: echo.CallOptions{
  3597  					Scheme:  scheme.DNS,
  3598  					Count:   1,
  3599  					Address: address,
  3600  					Check:   checker,
  3601  				},
  3602  			})
  3603  		}
  3604  	}
  3605  	svcCases := []struct {
  3606  		name     string
  3607  		protocol string
  3608  		server   string
  3609  	}{
  3610  		{
  3611  			name:     "tcp",
  3612  			protocol: "tcp",
  3613  		},
  3614  		{
  3615  			name:     "udp",
  3616  			protocol: "udp",
  3617  		},
  3618  	}
  3619  	for _, client := range flatten(t.Apps.VM, t.Apps.A, t.Apps.Tproxy) {
  3620  		for _, tt := range svcCases {
  3621  			tt, client := tt, client
  3622  			aInCluster := match.Cluster(client.Config().Cluster).GetMatches(t.Apps.A)
  3623  			if len(aInCluster) == 0 {
  3624  				// The cluster doesn't contain A, but connects to a cluster containing A
  3625  				aInCluster = match.Cluster(client.Config().Cluster.Config()).GetMatches(t.Apps.A)
  3626  			}
  3627  			address := aInCluster[0].Config().ClusterLocalFQDN() + "?"
  3628  			if tt.protocol != "" {
  3629  				address += "&protocol=" + tt.protocol
  3630  			}
  3631  			if tt.server != "" {
  3632  				address += "&server=" + tt.server
  3633  			}
  3634  			expected := aInCluster[0].Address()
  3635  			t.RunTraffic(TrafficTestCase{
  3636  				name: fmt.Sprintf("svc/%s/%s/%s", client.Config().Service, client.Config().Cluster.StableName(), tt.name),
  3637  				call: client.CallOrFail,
  3638  				opts: echo.CallOptions{
  3639  					Count:   1,
  3640  					Scheme:  scheme.DNS,
  3641  					Address: address,
  3642  					Check: func(result echo.CallResult, _ error) error {
  3643  						for _, r := range result.Responses {
  3644  							ips := r.Body()
  3645  							sort.Strings(ips)
  3646  							exp := []string{expected}
  3647  							if !reflect.DeepEqual(ips, exp) {
  3648  								return fmt.Errorf("unexpected dns response: wanted %v, got %v", exp, ips)
  3649  							}
  3650  						}
  3651  						return nil
  3652  					},
  3653  				},
  3654  			})
  3655  		}
  3656  	}
  3657  }
  3658  
  3659  func VMTestCases(vms echo.Instances) func(t TrafficContext) {
  3660  	return func(t TrafficContext) {
  3661  		if t.Settings().Skip(echo.VM) {
  3662  			t.Skipf("VMs are disabled")
  3663  		}
  3664  		var testCases []vmCase
  3665  
  3666  		for _, vm := range vms {
  3667  			testCases = append(testCases,
  3668  				vmCase{
  3669  					name: "dns: VM to k8s cluster IP service name.namespace host",
  3670  					from: vm,
  3671  					to:   t.Apps.A,
  3672  					host: deployment.ASvc + "." + t.Apps.Namespace.Name(),
  3673  				},
  3674  				vmCase{
  3675  					name: "dns: VM to k8s cluster IP service fqdn host",
  3676  					from: vm,
  3677  					to:   t.Apps.A,
  3678  					host: t.Apps.A[0].Config().ClusterLocalFQDN(),
  3679  				},
  3680  				vmCase{
  3681  					name: "dns: VM to k8s cluster IP service short name host",
  3682  					from: vm,
  3683  					to:   t.Apps.A,
  3684  					host: deployment.ASvc,
  3685  				},
  3686  				vmCase{
  3687  					name: "dns: VM to k8s headless service",
  3688  					from: vm,
  3689  					to:   match.Cluster(vm.Config().Cluster.Config()).GetMatches(t.Apps.Headless),
  3690  					host: t.Apps.Headless.Config().ClusterLocalFQDN(),
  3691  				},
  3692  				vmCase{
  3693  					name: "dns: VM to k8s statefulset service",
  3694  					from: vm,
  3695  					to:   match.Cluster(vm.Config().Cluster.Config()).GetMatches(t.Apps.StatefulSet),
  3696  					host: t.Apps.StatefulSet.Config().ClusterLocalFQDN(),
  3697  				},
  3698  				// TODO(https://github.com/istio/istio/issues/32552) re-enable
  3699  				//vmCase{
  3700  				//	name: "dns: VM to k8s statefulset instance.service",
  3701  				//	from: vm,
  3702  				//	to:   apps.StatefulSet.Match(echo.Cluster(vm.Config().Cluster.Config())),
  3703  				//	host: fmt.Sprintf("%s-v1-0.%s", StatefulSetSvc, StatefulSetSvc),
  3704  				//},
  3705  				//vmCase{
  3706  				//	name: "dns: VM to k8s statefulset instance.service.namespace",
  3707  				//	from: vm,
  3708  				//	to:   apps.StatefulSet.Match(echo.Cluster(vm.Config().Cluster.Config())),
  3709  				//	host: fmt.Sprintf("%s-v1-0.%s.%s", StatefulSetSvc, StatefulSetSvc, apps.Namespace.Name()),
  3710  				//},
  3711  				//vmCase{
  3712  				//	name: "dns: VM to k8s statefulset instance.service.namespace.svc",
  3713  				//	from: vm,
  3714  				//	to:   apps.StatefulSet.Match(echo.Cluster(vm.Config().Cluster.Config())),
  3715  				//	host: fmt.Sprintf("%s-v1-0.%s.%s.svc", StatefulSetSvc, StatefulSetSvc, apps.Namespace.Name()),
  3716  				//},
  3717  				//vmCase{
  3718  				//	name: "dns: VM to k8s statefulset instance FQDN",
  3719  				//	from: vm,
  3720  				//	to:   apps.StatefulSet.Match(echo.Cluster(vm.Config().Cluster.Config())),
  3721  				//	host: fmt.Sprintf("%s-v1-0.%s", StatefulSetSvc, apps.StatefulSet[0].Config().ClusterLocalFQDN()),
  3722  				//},
  3723  			)
  3724  		}
  3725  		for _, podA := range t.Apps.A {
  3726  			testCases = append(testCases, vmCase{
  3727  				name: "k8s to vm",
  3728  				from: podA,
  3729  				to:   vms,
  3730  			})
  3731  		}
  3732  		for _, c := range testCases {
  3733  			c := c
  3734  			checker := check.OK()
  3735  			if !match.Headless.Any(c.to) {
  3736  				// headless load-balancing can be inconsistent
  3737  				checker = check.And(checker, check.ReachedTargetClusters(t))
  3738  			}
  3739  			t.RunTraffic(TrafficTestCase{
  3740  				name: fmt.Sprintf("%s from %s", c.name, c.from.Config().Cluster.StableName()),
  3741  				call: c.from.CallOrFail,
  3742  				opts: echo.CallOptions{
  3743  					// assume that all echos in `to` only differ in which cluster they're deployed in
  3744  					To: c.to,
  3745  					Port: echo.Port{
  3746  						Name: "http",
  3747  					},
  3748  					Address: c.host,
  3749  					Check:   checker,
  3750  				},
  3751  			})
  3752  		}
  3753  	}
  3754  }
  3755  
  3756  func TestExternalService(t TrafficContext) {
  3757  	// Let us enable outboundTrafficPolicy REGISTRY_ONLY
  3758  	// on one of the workloads, to verify selective external connectivity
  3759  	SidecarScope := fmt.Sprintf(`apiVersion: networking.istio.io/v1alpha3
  3760  kind: Sidecar
  3761  metadata:
  3762    name: restrict-external-service
  3763    namespace: %s
  3764  spec:
  3765    workloadSelector:
  3766      labels:
  3767        app: a
  3768    outboundTrafficPolicy:
  3769      mode: "REGISTRY_ONLY"
  3770  `, t.Apps.EchoNamespace.Namespace.Name())
  3771  
  3772  	if len(t.Apps.External.All) == 0 {
  3773  		t.Skip("no external service instances")
  3774  	}
  3775  	fakeExternalAddress := t.Apps.External.All[0].Address()
  3776  	parsedIP, err := netip.ParseAddr(fakeExternalAddress)
  3777  	if err == nil {
  3778  		if parsedIP.Is6() {
  3779  			// CI has some issues with ipv6 DNS resolution, due to which
  3780  			// we are not able to directly use the host name in the connectivity tests.
  3781  			// Hence using a bogus IPv6 address with -HHost option as a workaround to
  3782  			// simulate ServiceEntry based wildcard listener scenarios.
  3783  			fakeExternalAddress = "2002::1"
  3784  		} else {
  3785  			fakeExternalAddress = "1.1.1.1"
  3786  		}
  3787  	}
  3788  
  3789  	testCases := []struct {
  3790  		name       string
  3791  		statusCode int
  3792  		from       echo.Instances
  3793  		to         string
  3794  		protocol   protocol.Instance
  3795  		port       int
  3796  	}{
  3797  		// TC1: Test connectivity to external service from outboundTrafficPolicy restricted pod.
  3798  		// The external service is exposed through a ServiceEntry, so the traffic should go through
  3799  		{
  3800  			name:       "traffic from outboundTrafficPolicy REGISTRY_ONLY to allowed host",
  3801  			statusCode: http.StatusOK,
  3802  			from:       t.Apps.A,
  3803  			to:         t.Apps.External.All[0].Address(),
  3804  			protocol:   protocol.HTTPS,
  3805  			port:       443,
  3806  		},
  3807  		// TC2: Same test as TC1, but use a fake external ip in destination for connectivity.
  3808  		{
  3809  			name:       "traffic from outboundTrafficPolicy REGISTRY_ONLY to allowed host",
  3810  			statusCode: http.StatusOK,
  3811  			from:       t.Apps.A,
  3812  			to:         fakeExternalAddress,
  3813  			protocol:   protocol.HTTPS,
  3814  			port:       443,
  3815  		},
  3816  		// TC3: Test connectivity to external service from outboundTrafficPolicy=PASS_THROUGH pod.
  3817  		// Traffic should go through without the need for any explicit ServiceEntry
  3818  		{
  3819  			name:       "traffic from outboundTrafficPolicy PASS_THROUGH to any host",
  3820  			statusCode: http.StatusOK,
  3821  			from:       t.Apps.B,
  3822  			to:         t.Apps.External.All[0].Address(),
  3823  			protocol:   protocol.HTTP,
  3824  			port:       80,
  3825  		},
  3826  		// TC4: Same test as TC3, but use a fake external ip in destination for connectivity.
  3827  		{
  3828  			name:       "traffic from outboundTrafficPolicy PASS_THROUGH to any host",
  3829  			statusCode: http.StatusOK,
  3830  			from:       t.Apps.B,
  3831  			to:         fakeExternalAddress,
  3832  			protocol:   protocol.HTTP,
  3833  			port:       80,
  3834  		},
  3835  	}
  3836  
  3837  	for _, tc := range testCases {
  3838  		t.RunTraffic(TrafficTestCase{
  3839  			name:   fmt.Sprintf("%v to external service %v", tc.from[0].NamespacedName(), tc.to),
  3840  			config: SidecarScope,
  3841  			opts: echo.CallOptions{
  3842  				Address: tc.to,
  3843  				HTTP: echo.HTTP{
  3844  					Headers: HostHeader(t.Apps.External.All[0].Config().DefaultHostHeader),
  3845  				},
  3846  				Port: echo.Port{Protocol: tc.protocol, ServicePort: tc.port},
  3847  				Check: check.And(
  3848  					check.Status(tc.statusCode),
  3849  				),
  3850  			},
  3851  			call: tc.from[0].CallOrFail,
  3852  		})
  3853  	}
  3854  }
  3855  
  3856  func destinationRule(app, mode string) string {
  3857  	return fmt.Sprintf(`apiVersion: networking.istio.io/v1beta1
  3858  kind: DestinationRule
  3859  metadata:
  3860    name: %s
  3861  spec:
  3862    host: %s
  3863    trafficPolicy:
  3864      tls:
  3865        mode: %s
  3866  ---
  3867  `, app, app, mode)
  3868  }
  3869  
  3870  const useClientProtocolDestinationRuleTmpl = `apiVersion: networking.istio.io/v1beta1
  3871  kind: DestinationRule
  3872  metadata:
  3873    name: use-client-protocol
  3874  spec:
  3875    host: {{.VirtualServiceHost}}
  3876    trafficPolicy:
  3877      tls:
  3878        mode: DISABLE
  3879      connectionPool:
  3880        http:
  3881          useClientProtocol: true
  3882  ---
  3883  `
  3884  
  3885  func useClientProtocolDestinationRule(app string) string {
  3886  	return tmpl.MustEvaluate(useClientProtocolDestinationRuleTmpl, map[string]string{"VirtualServiceHost": app})
  3887  }
  3888  
  3889  func idletimeoutDestinationRule(name, app string) string {
  3890  	return fmt.Sprintf(`apiVersion: networking.istio.io/v1beta1
  3891  kind: DestinationRule
  3892  metadata:
  3893    name: %s
  3894  spec:
  3895    host: %s
  3896    trafficPolicy:
  3897      tls:
  3898        mode: DISABLE
  3899      connectionPool:
  3900        http:
  3901          idleTimeout: 100s
  3902  ---
  3903  `, name, app)
  3904  }
  3905  
  3906  func peerAuthentication(app, mode string) string {
  3907  	return fmt.Sprintf(`apiVersion: security.istio.io/v1beta1
  3908  kind: PeerAuthentication
  3909  metadata:
  3910    name: %s
  3911  spec:
  3912    selector:
  3913      matchLabels:
  3914        app: %s
  3915    mtls:
  3916      mode: %s
  3917  ---
  3918  `, app, app, mode)
  3919  }
  3920  
  3921  func globalPeerAuthentication(mode string) string {
  3922  	return fmt.Sprintf(`apiVersion: security.istio.io/v1beta1
  3923  kind: PeerAuthentication
  3924  metadata:
  3925    name: default
  3926  spec:
  3927    mtls:
  3928      mode: %s
  3929  ---
  3930  `, mode)
  3931  }
  3932  
  3933  func serverFirstTestCases(t TrafficContext) {
  3934  	from := t.Apps.A
  3935  	to := t.Apps.C
  3936  	configs := []struct {
  3937  		port    string
  3938  		dest    string
  3939  		auth    string
  3940  		checker echo.Checker
  3941  	}{
  3942  		// TODO: All these cases *should* succeed (except the TLS mismatch cases) - but don't due to issues in our implementation
  3943  
  3944  		// For auto port, outbound request will be delayed by the protocol sniffer, regardless of configuration
  3945  		{"auto-tcp-server", "DISABLE", "DISABLE", check.Error()},
  3946  		{"auto-tcp-server", "DISABLE", "PERMISSIVE", check.Error()},
  3947  		{"auto-tcp-server", "DISABLE", "STRICT", check.Error()},
  3948  		{"auto-tcp-server", "ISTIO_MUTUAL", "DISABLE", check.Error()},
  3949  		{"auto-tcp-server", "ISTIO_MUTUAL", "PERMISSIVE", check.Error()},
  3950  		{"auto-tcp-server", "ISTIO_MUTUAL", "STRICT", check.Error()},
  3951  
  3952  		// These is broken because we will still enable inbound sniffing for the port. Since there is no tls,
  3953  		// there is no server-first "upgrading" to client-first
  3954  		{"tcp-server", "DISABLE", "DISABLE", check.OK()},
  3955  		{"tcp-server", "DISABLE", "PERMISSIVE", check.Error()},
  3956  
  3957  		// Expected to fail, incompatible configuration
  3958  		{"tcp-server", "DISABLE", "STRICT", check.Error()},
  3959  		{"tcp-server", "ISTIO_MUTUAL", "DISABLE", check.Error()},
  3960  
  3961  		// In these cases, we expect success
  3962  		// There is no sniffer on either side
  3963  		{"tcp-server", "DISABLE", "DISABLE", check.OK()},
  3964  
  3965  		// On outbound, we have no sniffer involved
  3966  		// On inbound, the request is TLS, so its not server first
  3967  		{"tcp-server", "ISTIO_MUTUAL", "PERMISSIVE", check.OK()},
  3968  		{"tcp-server", "ISTIO_MUTUAL", "STRICT", check.OK()},
  3969  	}
  3970  	for _, client := range from {
  3971  		for _, c := range configs {
  3972  			client, c := client, c
  3973  			t.RunTraffic(TrafficTestCase{
  3974  				name: fmt.Sprintf("%v:%v/%v", c.port, c.dest, c.auth),
  3975  				skip: skip{
  3976  					skip:   t.Apps.All.Instances().Clusters().IsMulticluster(),
  3977  					reason: "https://github.com/istio/istio/issues/37305: stabilize tcp connection breaks",
  3978  				},
  3979  				config: destinationRule(to.Config().Service, c.dest) + peerAuthentication(to.Config().Service, c.auth),
  3980  				call:   client.CallOrFail,
  3981  				opts: echo.CallOptions{
  3982  					To: to,
  3983  					Port: echo.Port{
  3984  						Name: c.port,
  3985  					},
  3986  					Scheme: scheme.TCP,
  3987  					// Inbound timeout is 1s. We want to test this does not hit the listener filter timeout
  3988  					Timeout: time.Millisecond * 100,
  3989  					Count:   1,
  3990  					Check:   c.checker,
  3991  				},
  3992  			})
  3993  		}
  3994  	}
  3995  }
  3996  
  3997  func jwtClaimRoute(t TrafficContext) {
  3998  	if t.Settings().Selector.Excludes(label.NewSet(label.IPv4)) {
  3999  		t.Skipf("https://github.com/istio/istio/issues/35835")
  4000  	}
  4001  	configRoute := `
  4002  apiVersion: networking.istio.io/v1alpha3
  4003  kind: Gateway
  4004  metadata:
  4005    name: gateway
  4006  spec:
  4007    selector:
  4008      istio: {{.GatewayIstioLabel | default "ingressgateway"}}
  4009    servers:
  4010    - port:
  4011        number: 80
  4012        name: http
  4013        protocol: HTTP
  4014      hosts:
  4015      - "*"
  4016  ---
  4017  apiVersion: networking.istio.io/v1alpha3
  4018  kind: VirtualService
  4019  metadata:
  4020    name: default
  4021  spec:
  4022    hosts:
  4023    - {{ .dstSvc }}.foo.bar
  4024    gateways:
  4025    - gateway
  4026    http:
  4027    - match:
  4028      - uri:
  4029          prefix: /
  4030        {{- if .Headers }}
  4031        headers:
  4032          {{- range $data := .Headers }}
  4033            "{{$data.Name}}":
  4034              {{$data.Match}}: {{$data.Value}}
  4035          {{- end }}
  4036        {{- end }}
  4037        {{- if .WithoutHeaders }}
  4038        withoutHeaders:
  4039          {{- range $data := .WithoutHeaders }}
  4040            "{{$data.Name}}":
  4041              {{$data.Match}}: {{$data.Value}}
  4042          {{- end }}
  4043        {{- end }}
  4044      route:
  4045      - destination:
  4046          host: {{ .dstSvc }}
  4047  ---
  4048  `
  4049  	configAll := configRoute + `
  4050  apiVersion: security.istio.io/v1beta1
  4051  kind: RequestAuthentication
  4052  metadata:
  4053    name: default
  4054    namespace: {{.SystemNamespace | default "istio-system"}}
  4055  spec:
  4056    jwtRules:
  4057    - issuer: "test-issuer-1@istio.io"
  4058      jwksUri: "https://raw.githubusercontent.com/istio/istio/master/tests/common/jwt/jwks.json"
  4059      outputClaimToHeaders:
  4060      - header: "x-jwt-nested-key"
  4061        claim: "nested.nested-2.key2"
  4062      - header: "x-jwt-iss"
  4063        claim: "iss"
  4064      - header: "x-jwt-wrong-header"
  4065        claim: "wrong_claim"
  4066  ---
  4067  `
  4068  	matchers := []match.Matcher{match.And(
  4069  		// No waypoint here, these are all via ingress which doesn't forward to waypoint
  4070  		match.NotWaypoint,
  4071  		match.Or(match.ServiceName(t.Apps.B.NamespacedName()), match.AmbientCaptured()),
  4072  	)}
  4073  	headersWithToken := map[string][]string{
  4074  		"Authorization": {"Bearer " + jwt.TokenIssuer1WithNestedClaims1},
  4075  	}
  4076  	headersWithInvalidToken := map[string][]string{
  4077  		"Authorization": {"Bearer " + jwt.TokenExpired},
  4078  	}
  4079  	headersWithNoToken := map[string][]string{"Host": {"foo.bar"}}
  4080  	headersWithNoTokenButSameHeader := map[string][]string{
  4081  		"request.auth.claims.nested.key1": {"valueA"},
  4082  	}
  4083  	headersWithToken2 := map[string][]string{
  4084  		"Authorization":    {"Bearer " + jwt.TokenIssuer1WithNestedClaims2},
  4085  		"X-Jwt-Nested-Key": {"value_to_be_replaced"},
  4086  	}
  4087  	headersWithToken2WithAddedHeader := map[string][]string{
  4088  		"Authorization":      {"Bearer " + jwt.TokenIssuer1WithNestedClaims2},
  4089  		"x-jwt-wrong-header": {"header_to_be_deleted"},
  4090  	}
  4091  	headersWithToken3 := map[string][]string{
  4092  		"Authorization": {"Bearer " + jwt.TokenIssuer1WithCollisionResistantName},
  4093  	}
  4094  	// the VirtualService for each test should be unique to avoid
  4095  	// one test passing because it's new config hasn't kicked in yet
  4096  	// and we're still testing the previous destination
  4097  	setHostHeader := func(src echo.Caller, opts *echo.CallOptions) {
  4098  		opts.HTTP.Headers["Host"] = []string{opts.To.ServiceName() + ".foo.bar"}
  4099  	}
  4100  
  4101  	type configData struct {
  4102  		Name, Match, Value string
  4103  	}
  4104  
  4105  	t.RunTraffic(TrafficTestCase{
  4106  		name:             "matched with nested claim using claim to header:200",
  4107  		targetMatchers:   matchers,
  4108  		workloadAgnostic: true,
  4109  		viaIngress:       true,
  4110  		config:           configAll,
  4111  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4112  			return map[string]any{
  4113  				"Headers":           []configData{{"X-Jwt-Nested-Key", "exact", "valueC"}},
  4114  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4115  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4116  			}
  4117  		},
  4118  		opts: echo.CallOptions{
  4119  			Count: 1,
  4120  			Port: echo.Port{
  4121  				Name:     "http",
  4122  				Protocol: protocol.HTTP,
  4123  			},
  4124  			HTTP: echo.HTTP{
  4125  				Headers: headersWithToken2,
  4126  			},
  4127  			Check: check.Status(http.StatusOK),
  4128  		},
  4129  		setupOpts: setHostHeader,
  4130  	})
  4131  	t.RunTraffic(TrafficTestCase{
  4132  		name:             "matched with nested claim and single claim using claim to header:200",
  4133  		targetMatchers:   matchers,
  4134  		workloadAgnostic: true,
  4135  		viaIngress:       true,
  4136  		config:           configAll,
  4137  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4138  			return map[string]any{
  4139  				"Headers": []configData{
  4140  					{"X-Jwt-Nested-Key", "exact", "valueC"},
  4141  					{"X-Jwt-Iss", "exact", "test-issuer-1@istio.io"},
  4142  				},
  4143  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4144  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4145  			}
  4146  		},
  4147  		opts: echo.CallOptions{
  4148  			Count: 1,
  4149  			Port: echo.Port{
  4150  				Name:     "http",
  4151  				Protocol: protocol.HTTP,
  4152  			},
  4153  			HTTP: echo.HTTP{
  4154  				Headers: headersWithToken2,
  4155  			},
  4156  			Check: check.Status(http.StatusOK),
  4157  		},
  4158  		setupOpts: setHostHeader,
  4159  	})
  4160  	t.RunTraffic(TrafficTestCase{
  4161  		name:             "unmatched with wrong claim and added header:404",
  4162  		targetMatchers:   matchers,
  4163  		workloadAgnostic: true,
  4164  		viaIngress:       true,
  4165  		config:           configAll,
  4166  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4167  			return map[string]any{
  4168  				"Headers":           []configData{{"x-jwt-wrong-header", "exact", "header_to_be_deleted"}},
  4169  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4170  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4171  			}
  4172  		},
  4173  		opts: echo.CallOptions{
  4174  			Count: 1,
  4175  			Port: echo.Port{
  4176  				Name:     "http",
  4177  				Protocol: protocol.HTTP,
  4178  			},
  4179  			HTTP: echo.HTTP{
  4180  				Headers: headersWithToken2WithAddedHeader,
  4181  			},
  4182  			Check: check.Status(http.StatusNotFound),
  4183  		},
  4184  		setupOpts: setHostHeader,
  4185  	})
  4186  
  4187  	// ---------------------------------------------
  4188  	// Usage 1: using `.` as a separator test cases
  4189  	// ---------------------------------------------
  4190  
  4191  	t.RunTraffic(TrafficTestCase{
  4192  		name:             "matched with nested claims:200",
  4193  		targetMatchers:   matchers,
  4194  		workloadAgnostic: true,
  4195  		viaIngress:       true,
  4196  		config:           configAll,
  4197  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4198  			return map[string]any{
  4199  				"Headers":           []configData{{"@request.auth.claims.nested.key1", "exact", "valueA"}},
  4200  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4201  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4202  			}
  4203  		},
  4204  		opts: echo.CallOptions{
  4205  			Count: 1,
  4206  			Port: echo.Port{
  4207  				Name:     "http",
  4208  				Protocol: protocol.HTTP,
  4209  			},
  4210  			HTTP: echo.HTTP{
  4211  				Headers: headersWithToken,
  4212  			},
  4213  			Check: check.Status(http.StatusOK),
  4214  		},
  4215  		setupOpts: setHostHeader,
  4216  	})
  4217  	t.RunTraffic(TrafficTestCase{
  4218  		name:             "matched with single claim:200",
  4219  		targetMatchers:   matchers,
  4220  		workloadAgnostic: true,
  4221  		viaIngress:       true,
  4222  		config:           configAll,
  4223  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4224  			return map[string]any{
  4225  				"Headers":           []configData{{"@request.auth.claims.sub", "prefix", "sub"}},
  4226  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4227  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4228  			}
  4229  		},
  4230  		opts: echo.CallOptions{
  4231  			Count: 1,
  4232  			Port: echo.Port{
  4233  				Name:     "http",
  4234  				Protocol: protocol.HTTP,
  4235  			},
  4236  			HTTP: echo.HTTP{
  4237  				Headers: headersWithToken,
  4238  			},
  4239  			Check: check.Status(http.StatusOK),
  4240  		},
  4241  		setupOpts: setHostHeader,
  4242  	})
  4243  	t.RunTraffic(TrafficTestCase{
  4244  		name:             "matched multiple claims with regex:200",
  4245  		targetMatchers:   matchers,
  4246  		workloadAgnostic: true,
  4247  		viaIngress:       true,
  4248  		config:           configAll,
  4249  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4250  			return map[string]any{
  4251  				"Headers": []configData{
  4252  					{"@request.auth.claims.sub", "regex", "(\\W|^)(sub-1|sub-2)(\\W|$)"},
  4253  					{"@request.auth.claims.nested.key1", "regex", "(\\W|^)value[AB](\\W|$)"},
  4254  				},
  4255  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4256  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4257  			}
  4258  		},
  4259  		opts: echo.CallOptions{
  4260  			Count: 1,
  4261  			Port: echo.Port{
  4262  				Name:     "http",
  4263  				Protocol: protocol.HTTP,
  4264  			},
  4265  			HTTP: echo.HTTP{
  4266  				Headers: headersWithToken,
  4267  			},
  4268  			Check: check.Status(http.StatusOK),
  4269  		},
  4270  		setupOpts: setHostHeader,
  4271  	})
  4272  	t.RunTraffic(TrafficTestCase{
  4273  		name:             "matched multiple claims:200",
  4274  		targetMatchers:   matchers,
  4275  		workloadAgnostic: true,
  4276  		viaIngress:       true,
  4277  		config:           configAll,
  4278  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4279  			return map[string]any{
  4280  				"Headers": []configData{
  4281  					{"@request.auth.claims.nested.key1", "exact", "valueA"},
  4282  					{"@request.auth.claims.sub", "prefix", "sub"},
  4283  				},
  4284  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4285  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4286  			}
  4287  		},
  4288  		opts: echo.CallOptions{
  4289  			Count: 1,
  4290  			Port: echo.Port{
  4291  				Name:     "http",
  4292  				Protocol: protocol.HTTP,
  4293  			},
  4294  			HTTP: echo.HTTP{
  4295  				Headers: headersWithToken,
  4296  			},
  4297  			Check: check.Status(http.StatusOK),
  4298  		},
  4299  		setupOpts: setHostHeader,
  4300  	})
  4301  	t.RunTraffic(TrafficTestCase{
  4302  		name:             "matched without claim:200",
  4303  		targetMatchers:   matchers,
  4304  		workloadAgnostic: true,
  4305  		viaIngress:       true,
  4306  		config:           configAll,
  4307  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4308  			return map[string]any{
  4309  				"WithoutHeaders":    []configData{{"@request.auth.claims.nested.key1", "exact", "value-not-matched"}},
  4310  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4311  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4312  			}
  4313  		},
  4314  		opts: echo.CallOptions{
  4315  			Count: 1,
  4316  			Port: echo.Port{
  4317  				Name:     "http",
  4318  				Protocol: protocol.HTTP,
  4319  			},
  4320  			HTTP: echo.HTTP{
  4321  				Headers: headersWithToken,
  4322  			},
  4323  			Check: check.Status(http.StatusOK),
  4324  		},
  4325  		setupOpts: setHostHeader,
  4326  	})
  4327  	t.RunTraffic(TrafficTestCase{
  4328  		name:             "unmatched without claim:404",
  4329  		targetMatchers:   matchers,
  4330  		workloadAgnostic: true,
  4331  		viaIngress:       true,
  4332  		config:           configAll,
  4333  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4334  			return map[string]any{
  4335  				"WithoutHeaders":    []configData{{"@request.auth.claims.nested.key1", "exact", "valueA"}},
  4336  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4337  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4338  			}
  4339  		},
  4340  		opts: echo.CallOptions{
  4341  			Count: 1,
  4342  			Port: echo.Port{
  4343  				Name:     "http",
  4344  				Protocol: protocol.HTTP,
  4345  			},
  4346  			HTTP: echo.HTTP{
  4347  				Headers: headersWithToken,
  4348  			},
  4349  			Check: check.Status(http.StatusNotFound),
  4350  		},
  4351  		setupOpts: setHostHeader,
  4352  	})
  4353  	t.RunTraffic(TrafficTestCase{
  4354  		name:             "matched both with and without claims with regex:200",
  4355  		targetMatchers:   matchers,
  4356  		workloadAgnostic: true,
  4357  		viaIngress:       true,
  4358  		config:           configAll,
  4359  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4360  			return map[string]any{
  4361  				"Headers": []configData{{"@request.auth.claims.sub", "prefix", "sub"}},
  4362  				"WithoutHeaders": []configData{
  4363  					{"@request.auth.claims.nested.key1", "exact", "value-not-matched"},
  4364  					{"@request.auth.claims.nested.key1", "regex", "(\\W|^)value\\s{0,3}not{0,1}\\s{0,3}matched(\\W|$)"},
  4365  				},
  4366  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4367  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4368  			}
  4369  		},
  4370  		opts: echo.CallOptions{
  4371  			Count: 1,
  4372  			Port: echo.Port{
  4373  				Name:     "http",
  4374  				Protocol: protocol.HTTP,
  4375  			},
  4376  			HTTP: echo.HTTP{
  4377  				Headers: headersWithToken,
  4378  			},
  4379  			Check: check.Status(http.StatusOK),
  4380  		},
  4381  		setupOpts: setHostHeader,
  4382  	})
  4383  	t.RunTraffic(TrafficTestCase{
  4384  		name:             "unmatched multiple claims:404",
  4385  		targetMatchers:   matchers,
  4386  		workloadAgnostic: true,
  4387  		viaIngress:       true,
  4388  		config:           configAll,
  4389  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4390  			return map[string]any{
  4391  				"Headers": []configData{
  4392  					{"@request.auth.claims.nested.key1", "exact", "valueA"},
  4393  					{"@request.auth.claims.sub", "prefix", "value-not-matched"},
  4394  				},
  4395  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4396  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4397  			}
  4398  		},
  4399  		opts: echo.CallOptions{
  4400  			Count: 1,
  4401  			Port: echo.Port{
  4402  				Name:     "http",
  4403  				Protocol: protocol.HTTP,
  4404  			},
  4405  			HTTP: echo.HTTP{
  4406  				Headers: headersWithToken,
  4407  			},
  4408  			Check: check.Status(http.StatusNotFound),
  4409  		},
  4410  		setupOpts: setHostHeader,
  4411  	})
  4412  	t.RunTraffic(TrafficTestCase{
  4413  		name:             "unmatched token:404",
  4414  		targetMatchers:   matchers,
  4415  		workloadAgnostic: true,
  4416  		viaIngress:       true,
  4417  		config:           configAll,
  4418  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4419  			return map[string]any{
  4420  				"Headers":           []configData{{"@request.auth.claims.sub", "exact", "value-not-matched"}},
  4421  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4422  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4423  			}
  4424  		},
  4425  		opts: echo.CallOptions{
  4426  			Count: 1,
  4427  			Port: echo.Port{
  4428  				Name:     "http",
  4429  				Protocol: protocol.HTTP,
  4430  			},
  4431  			HTTP: echo.HTTP{
  4432  				Headers: headersWithToken,
  4433  			},
  4434  			Check: check.Status(http.StatusNotFound),
  4435  		},
  4436  		setupOpts: setHostHeader,
  4437  	})
  4438  	t.RunTraffic(TrafficTestCase{
  4439  		name:             "unmatched with invalid token:401",
  4440  		targetMatchers:   matchers,
  4441  		workloadAgnostic: true,
  4442  		viaIngress:       true,
  4443  		config:           configAll,
  4444  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4445  			return map[string]any{
  4446  				"Headers":           []configData{{"@request.auth.claims.nested.key1", "exact", "valueA"}},
  4447  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4448  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4449  			}
  4450  		},
  4451  		opts: echo.CallOptions{
  4452  			Count: 1,
  4453  			Port: echo.Port{
  4454  				Name:     "http",
  4455  				Protocol: protocol.HTTP,
  4456  			},
  4457  			HTTP: echo.HTTP{
  4458  				Headers: headersWithInvalidToken,
  4459  			},
  4460  			Check: check.Status(http.StatusUnauthorized),
  4461  		},
  4462  		setupOpts: setHostHeader,
  4463  	})
  4464  	t.RunTraffic(TrafficTestCase{
  4465  		name:             "unmatched with no token:404",
  4466  		targetMatchers:   matchers,
  4467  		workloadAgnostic: true,
  4468  		viaIngress:       true,
  4469  		config:           configAll,
  4470  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4471  			return map[string]any{
  4472  				"Headers":           []configData{{"@request.auth.claims.nested.key1", "exact", "valueA"}},
  4473  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4474  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4475  			}
  4476  		},
  4477  		opts: echo.CallOptions{
  4478  			Count: 1,
  4479  			Port: echo.Port{
  4480  				Name:     "http",
  4481  				Protocol: protocol.HTTP,
  4482  			},
  4483  			HTTP: echo.HTTP{
  4484  				Headers: headersWithNoToken,
  4485  			},
  4486  			Check: check.Status(http.StatusNotFound),
  4487  		},
  4488  		setupOpts: setHostHeader,
  4489  	})
  4490  	t.RunTraffic(TrafficTestCase{
  4491  		name:             "unmatched with no token but same header:404",
  4492  		targetMatchers:   matchers,
  4493  		workloadAgnostic: true,
  4494  		viaIngress:       true,
  4495  		config:           configAll,
  4496  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4497  			return map[string]any{
  4498  				"Headers":           []configData{{"@request.auth.claims.nested.key1", "exact", "valueA"}},
  4499  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4500  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4501  			}
  4502  		},
  4503  		opts: echo.CallOptions{
  4504  			Count: 1,
  4505  			Port: echo.Port{
  4506  				Name:     "http",
  4507  				Protocol: protocol.HTTP,
  4508  			},
  4509  			HTTP: echo.HTTP{
  4510  				// Include a header @request.auth.claims.nested.key1 and value same as the JWT claim, should not be routed.
  4511  				Headers: headersWithNoTokenButSameHeader,
  4512  			},
  4513  			Check: check.Status(http.StatusNotFound),
  4514  		},
  4515  		setupOpts: setHostHeader,
  4516  	})
  4517  	t.RunTraffic(TrafficTestCase{
  4518  		name:             "unmatched with no request authentication:404",
  4519  		targetMatchers:   matchers,
  4520  		workloadAgnostic: true,
  4521  		viaIngress:       true,
  4522  		config:           configRoute,
  4523  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4524  			return map[string]any{
  4525  				"Headers":           []configData{{"@request.auth.claims.nested.key1", "exact", "valueA"}},
  4526  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4527  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4528  			}
  4529  		},
  4530  		opts: echo.CallOptions{
  4531  			Count: 1,
  4532  			Port: echo.Port{
  4533  				Name:     "http",
  4534  				Protocol: protocol.HTTP,
  4535  			},
  4536  			HTTP: echo.HTTP{
  4537  				Headers: headersWithToken,
  4538  			},
  4539  			Check: check.Status(http.StatusNotFound),
  4540  		},
  4541  		setupOpts: setHostHeader,
  4542  	})
  4543  
  4544  	// ---------------------------------------------
  4545  	// Usage 2: using `[]` as a separator test cases
  4546  	// ---------------------------------------------
  4547  
  4548  	t.RunTraffic(TrafficTestCase{
  4549  		name:             "usage2: matched with nested claims:200",
  4550  		targetMatchers:   matchers,
  4551  		workloadAgnostic: true,
  4552  		viaIngress:       true,
  4553  		config:           configAll,
  4554  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4555  			return map[string]any{
  4556  				"Headers":           []configData{{"@request.auth.claims[nested][key1]", "exact", "valueA"}},
  4557  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4558  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4559  			}
  4560  		},
  4561  		opts: echo.CallOptions{
  4562  			Count: 1,
  4563  			Port: echo.Port{
  4564  				Name:     "http",
  4565  				Protocol: protocol.HTTP,
  4566  			},
  4567  			HTTP: echo.HTTP{
  4568  				Headers: headersWithToken,
  4569  			},
  4570  			Check: check.Status(http.StatusOK),
  4571  		},
  4572  		setupOpts: setHostHeader,
  4573  	})
  4574  	t.RunTraffic(TrafficTestCase{
  4575  		name:             "usage2: matched with single claim:200",
  4576  		targetMatchers:   matchers,
  4577  		workloadAgnostic: true,
  4578  		viaIngress:       true,
  4579  		config:           configAll,
  4580  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4581  			return map[string]any{
  4582  				"Headers":           []configData{{"@request.auth.claims[sub]", "prefix", "sub"}},
  4583  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4584  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4585  			}
  4586  		},
  4587  		opts: echo.CallOptions{
  4588  			Count: 1,
  4589  			Port: echo.Port{
  4590  				Name:     "http",
  4591  				Protocol: protocol.HTTP,
  4592  			},
  4593  			HTTP: echo.HTTP{
  4594  				Headers: headersWithToken,
  4595  			},
  4596  			Check: check.Status(http.StatusOK),
  4597  		},
  4598  		setupOpts: setHostHeader,
  4599  	})
  4600  	t.RunTraffic(TrafficTestCase{
  4601  		name:             "usage2: matched multiple claims with regex:200",
  4602  		targetMatchers:   matchers,
  4603  		workloadAgnostic: true,
  4604  		viaIngress:       true,
  4605  		config:           configAll,
  4606  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4607  			return map[string]any{
  4608  				"Headers": []configData{
  4609  					{"@request.auth.claims[sub]", "regex", "(\\W|^)(sub-1|sub-2)(\\W|$)"},
  4610  					{"@request.auth.claims[nested][key1]", "regex", "(\\W|^)value[AB](\\W|$)"},
  4611  				},
  4612  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4613  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4614  			}
  4615  		},
  4616  		opts: echo.CallOptions{
  4617  			Count: 1,
  4618  			Port: echo.Port{
  4619  				Name:     "http",
  4620  				Protocol: protocol.HTTP,
  4621  			},
  4622  			HTTP: echo.HTTP{
  4623  				Headers: headersWithToken,
  4624  			},
  4625  			Check: check.Status(http.StatusOK),
  4626  		},
  4627  		setupOpts: setHostHeader,
  4628  	})
  4629  	t.RunTraffic(TrafficTestCase{
  4630  		name:             "usage2: matched multiple claims:200",
  4631  		targetMatchers:   matchers,
  4632  		workloadAgnostic: true,
  4633  		viaIngress:       true,
  4634  		config:           configAll,
  4635  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4636  			return map[string]any{
  4637  				"Headers": []configData{
  4638  					{"@request.auth.claims[nested][key1]", "exact", "valueA"},
  4639  					{"@request.auth.claims[sub]", "prefix", "sub"},
  4640  				},
  4641  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4642  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4643  			}
  4644  		},
  4645  		opts: echo.CallOptions{
  4646  			Count: 1,
  4647  			Port: echo.Port{
  4648  				Name:     "http",
  4649  				Protocol: protocol.HTTP,
  4650  			},
  4651  			HTTP: echo.HTTP{
  4652  				Headers: headersWithToken,
  4653  			},
  4654  			Check: check.Status(http.StatusOK),
  4655  		},
  4656  		setupOpts: setHostHeader,
  4657  	})
  4658  	t.RunTraffic(TrafficTestCase{
  4659  		name:             "usage2: matched without claim:200",
  4660  		targetMatchers:   matchers,
  4661  		workloadAgnostic: true,
  4662  		viaIngress:       true,
  4663  		config:           configAll,
  4664  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4665  			return map[string]any{
  4666  				"WithoutHeaders":    []configData{{"@request.auth.claims[nested][key1]", "exact", "value-not-matched"}},
  4667  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4668  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4669  			}
  4670  		},
  4671  		opts: echo.CallOptions{
  4672  			Count: 1,
  4673  			Port: echo.Port{
  4674  				Name:     "http",
  4675  				Protocol: protocol.HTTP,
  4676  			},
  4677  			HTTP: echo.HTTP{
  4678  				Headers: headersWithToken,
  4679  			},
  4680  			Check: check.Status(http.StatusOK),
  4681  		},
  4682  		setupOpts: setHostHeader,
  4683  	})
  4684  	t.RunTraffic(TrafficTestCase{
  4685  		name:             "usage2: unmatched without claim:404",
  4686  		targetMatchers:   matchers,
  4687  		workloadAgnostic: true,
  4688  		viaIngress:       true,
  4689  		config:           configAll,
  4690  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4691  			return map[string]any{
  4692  				"WithoutHeaders":    []configData{{"@request.auth.claims[nested][key1]", "exact", "valueA"}},
  4693  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4694  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4695  			}
  4696  		},
  4697  		opts: echo.CallOptions{
  4698  			Count: 1,
  4699  			Port: echo.Port{
  4700  				Name:     "http",
  4701  				Protocol: protocol.HTTP,
  4702  			},
  4703  			HTTP: echo.HTTP{
  4704  				Headers: headersWithToken,
  4705  			},
  4706  			Check: check.Status(http.StatusNotFound),
  4707  		},
  4708  		setupOpts: setHostHeader,
  4709  	})
  4710  	t.RunTraffic(TrafficTestCase{
  4711  		name:             "usage2: matched both with and without claims with regex:200",
  4712  		targetMatchers:   matchers,
  4713  		workloadAgnostic: true,
  4714  		viaIngress:       true,
  4715  		config:           configAll,
  4716  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4717  			return map[string]any{
  4718  				"Headers": []configData{{"@request.auth.claims[sub]", "prefix", "sub"}},
  4719  				"WithoutHeaders": []configData{
  4720  					{"@request.auth.claims[nested][key1]", "exact", "value-not-matched"},
  4721  					{"@request.auth.claims[nested][key1]", "regex", "(\\W|^)value\\s{0,3}not{0,1}\\s{0,3}matched(\\W|$)"},
  4722  				},
  4723  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4724  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4725  			}
  4726  		},
  4727  		opts: echo.CallOptions{
  4728  			Count: 1,
  4729  			Port: echo.Port{
  4730  				Name:     "http",
  4731  				Protocol: protocol.HTTP,
  4732  			},
  4733  			HTTP: echo.HTTP{
  4734  				Headers: headersWithToken,
  4735  			},
  4736  			Check: check.Status(http.StatusOK),
  4737  		},
  4738  		setupOpts: setHostHeader,
  4739  	})
  4740  	t.RunTraffic(TrafficTestCase{
  4741  		name:             "usage2: unmatched multiple claims:404",
  4742  		targetMatchers:   matchers,
  4743  		workloadAgnostic: true,
  4744  		viaIngress:       true,
  4745  		config:           configAll,
  4746  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4747  			return map[string]any{
  4748  				"Headers": []configData{
  4749  					{"@request.auth.claims[nested][key1]", "exact", "valueA"},
  4750  					{"@request.auth.claims[sub]", "prefix", "value-not-matched"},
  4751  				},
  4752  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4753  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4754  			}
  4755  		},
  4756  		opts: echo.CallOptions{
  4757  			Count: 1,
  4758  			Port: echo.Port{
  4759  				Name:     "http",
  4760  				Protocol: protocol.HTTP,
  4761  			},
  4762  			HTTP: echo.HTTP{
  4763  				Headers: headersWithToken,
  4764  			},
  4765  			Check: check.Status(http.StatusNotFound),
  4766  		},
  4767  		setupOpts: setHostHeader,
  4768  	})
  4769  	t.RunTraffic(TrafficTestCase{
  4770  		name:             "usage2: unmatched token:404",
  4771  		targetMatchers:   matchers,
  4772  		workloadAgnostic: true,
  4773  		viaIngress:       true,
  4774  		config:           configAll,
  4775  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4776  			return map[string]any{
  4777  				"Headers":           []configData{{"@request.auth.claims[sub]", "exact", "value-not-matched"}},
  4778  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4779  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4780  			}
  4781  		},
  4782  		opts: echo.CallOptions{
  4783  			Count: 1,
  4784  			Port: echo.Port{
  4785  				Name:     "http",
  4786  				Protocol: protocol.HTTP,
  4787  			},
  4788  			HTTP: echo.HTTP{
  4789  				Headers: headersWithToken,
  4790  			},
  4791  			Check: check.Status(http.StatusNotFound),
  4792  		},
  4793  		setupOpts: setHostHeader,
  4794  	})
  4795  	t.RunTraffic(TrafficTestCase{
  4796  		name:             "usage2: unmatched with invalid token:401",
  4797  		targetMatchers:   matchers,
  4798  		workloadAgnostic: true,
  4799  		viaIngress:       true,
  4800  		config:           configAll,
  4801  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4802  			return map[string]any{
  4803  				"Headers":           []configData{{"@request.auth.claims[nested][key1]", "exact", "valueA"}},
  4804  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4805  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4806  			}
  4807  		},
  4808  		opts: echo.CallOptions{
  4809  			Count: 1,
  4810  			Port: echo.Port{
  4811  				Name:     "http",
  4812  				Protocol: protocol.HTTP,
  4813  			},
  4814  			HTTP: echo.HTTP{
  4815  				Headers: headersWithInvalidToken,
  4816  			},
  4817  			Check: check.Status(http.StatusUnauthorized),
  4818  		},
  4819  		setupOpts: setHostHeader,
  4820  	})
  4821  	t.RunTraffic(TrafficTestCase{
  4822  		name:             "usage2: unmatched with no token:404",
  4823  		targetMatchers:   matchers,
  4824  		workloadAgnostic: true,
  4825  		viaIngress:       true,
  4826  		config:           configAll,
  4827  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4828  			return map[string]any{
  4829  				"Headers":           []configData{{"@request.auth.claims[nested][key1]", "exact", "valueA"}},
  4830  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4831  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4832  			}
  4833  		},
  4834  		opts: echo.CallOptions{
  4835  			Count: 1,
  4836  			Port: echo.Port{
  4837  				Name:     "http",
  4838  				Protocol: protocol.HTTP,
  4839  			},
  4840  			HTTP: echo.HTTP{
  4841  				Headers: headersWithNoToken,
  4842  			},
  4843  			Check: check.Status(http.StatusNotFound),
  4844  		},
  4845  		setupOpts: setHostHeader,
  4846  	})
  4847  	t.RunTraffic(TrafficTestCase{
  4848  		name:             "usage2: unmatched with no token but same header:404",
  4849  		targetMatchers:   matchers,
  4850  		workloadAgnostic: true,
  4851  		viaIngress:       true,
  4852  		config:           configAll,
  4853  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4854  			return map[string]any{
  4855  				"Headers":           []configData{{"@request.auth.claims[nested][key1]", "exact", "valueA"}},
  4856  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4857  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4858  			}
  4859  		},
  4860  		opts: echo.CallOptions{
  4861  			Count: 1,
  4862  			Port: echo.Port{
  4863  				Name:     "http",
  4864  				Protocol: protocol.HTTP,
  4865  			},
  4866  			HTTP: echo.HTTP{
  4867  				// Include a header @request.auth.claims[nested][key1] and value same as the JWT claim, should not be routed.
  4868  				Headers: headersWithNoTokenButSameHeader,
  4869  			},
  4870  			Check: check.Status(http.StatusNotFound),
  4871  		},
  4872  		setupOpts: setHostHeader,
  4873  	})
  4874  	t.RunTraffic(TrafficTestCase{
  4875  		name:             "usage2: unmatched with no request authentication:404",
  4876  		targetMatchers:   matchers,
  4877  		workloadAgnostic: true,
  4878  		viaIngress:       true,
  4879  		config:           configRoute,
  4880  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4881  			return map[string]any{
  4882  				"Headers":           []configData{{"@request.auth.claims[nested][key1]", "exact", "valueA"}},
  4883  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4884  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4885  			}
  4886  		},
  4887  		opts: echo.CallOptions{
  4888  			Count: 1,
  4889  			Port: echo.Port{
  4890  				Name:     "http",
  4891  				Protocol: protocol.HTTP,
  4892  			},
  4893  			HTTP: echo.HTTP{
  4894  				Headers: headersWithToken,
  4895  			},
  4896  			Check: check.Status(http.StatusNotFound),
  4897  		},
  4898  		setupOpts: setHostHeader,
  4899  	})
  4900  
  4901  	t.RunTraffic(TrafficTestCase{
  4902  		name:             "usage2: matched with simple collision-resistant claim name:200",
  4903  		targetMatchers:   matchers,
  4904  		workloadAgnostic: true,
  4905  		viaIngress:       true,
  4906  		config:           configAll,
  4907  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4908  			return map[string]any{
  4909  				"Headers":           []configData{{"@request.auth.claims[test-issuer-1@istio.io/simple]", "exact", "valueC"}},
  4910  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4911  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4912  			}
  4913  		},
  4914  		opts: echo.CallOptions{
  4915  			Count: 1,
  4916  			Port: echo.Port{
  4917  				Name:     "http",
  4918  				Protocol: protocol.HTTP,
  4919  			},
  4920  			HTTP: echo.HTTP{
  4921  				Headers: headersWithToken3,
  4922  			},
  4923  			Check: check.Status(http.StatusOK),
  4924  		},
  4925  		setupOpts: setHostHeader,
  4926  	})
  4927  
  4928  	t.RunTraffic(TrafficTestCase{
  4929  		name:             "usage2: unmatched with simple collision-resistant claim name:404",
  4930  		targetMatchers:   matchers,
  4931  		workloadAgnostic: true,
  4932  		viaIngress:       true,
  4933  		config:           configAll,
  4934  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4935  			return map[string]any{
  4936  				"Headers":           []configData{{"@request.auth.claims[test-issuer-1@istio.io/simple]", "exact", "value-not-matched"}},
  4937  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4938  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4939  			}
  4940  		},
  4941  		opts: echo.CallOptions{
  4942  			Count: 1,
  4943  			Port: echo.Port{
  4944  				Name:     "http",
  4945  				Protocol: protocol.HTTP,
  4946  			},
  4947  			HTTP: echo.HTTP{
  4948  				Headers: headersWithToken3,
  4949  			},
  4950  			Check: check.Status(http.StatusNotFound),
  4951  		},
  4952  		setupOpts: setHostHeader,
  4953  	})
  4954  
  4955  	t.RunTraffic(TrafficTestCase{
  4956  		name:             "usage2: matched with nested collision-resistant claim name:200",
  4957  		targetMatchers:   matchers,
  4958  		workloadAgnostic: true,
  4959  		viaIngress:       true,
  4960  		config:           configAll,
  4961  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4962  			return map[string]any{
  4963  				"Headers":           []configData{{"@request.auth.claims[test-issuer-1@istio.io/nested][key1]", "exact", "valueC"}},
  4964  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4965  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4966  			}
  4967  		},
  4968  		opts: echo.CallOptions{
  4969  			Count: 1,
  4970  			Port: echo.Port{
  4971  				Name:     "http",
  4972  				Protocol: protocol.HTTP,
  4973  			},
  4974  			HTTP: echo.HTTP{
  4975  				Headers: headersWithToken3,
  4976  			},
  4977  			Check: check.Status(http.StatusOK),
  4978  		},
  4979  		setupOpts: setHostHeader,
  4980  	})
  4981  
  4982  	t.RunTraffic(TrafficTestCase{
  4983  		name:             "usage2: unmatched with nested collision-resistant claim name:404",
  4984  		targetMatchers:   matchers,
  4985  		workloadAgnostic: true,
  4986  		viaIngress:       true,
  4987  		config:           configAll,
  4988  		templateVars: func(src echo.Callers, dest echo.Instances) map[string]any {
  4989  			return map[string]any{
  4990  				"Headers":           []configData{{"@request.auth.claims[test-issuer-1@istio.io/nested][key1]", "exact", "value-not-matched"}},
  4991  				"SystemNamespace":   t.Istio.Settings().SystemNamespace,
  4992  				"GatewayIstioLabel": t.Istio.Settings().IngressGatewayIstioLabel,
  4993  			}
  4994  		},
  4995  		opts: echo.CallOptions{
  4996  			Count: 1,
  4997  			Port: echo.Port{
  4998  				Name:     "http",
  4999  				Protocol: protocol.HTTP,
  5000  			},
  5001  			HTTP: echo.HTTP{
  5002  				Headers: headersWithToken3,
  5003  			},
  5004  			Check: check.Status(http.StatusNotFound),
  5005  		},
  5006  		setupOpts: setHostHeader,
  5007  	})
  5008  }
  5009  
  5010  func LocationHeader(expected string) echo.Checker {
  5011  	return check.Each(
  5012  		func(r echoClient.Response) error {
  5013  			originalHostname, err := url.Parse(r.RequestURL)
  5014  			if err != nil {
  5015  				return err
  5016  			}
  5017  			exp := tmpl.MustEvaluate(expected, map[string]string{
  5018  				"Hostname": originalHostname.Hostname(),
  5019  			})
  5020  			return ExpectString(r.ResponseHeaders.Get("Location"),
  5021  				exp,
  5022  				"Location")
  5023  		})
  5024  }