istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/ambient/baseline_test.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 ambient
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"net/http"
    24  	"path/filepath"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	authenticationv1 "k8s.io/api/authentication/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  
    33  	"istio.io/api/networking/v1alpha3"
    34  	"istio.io/istio/pkg/config/constants"
    35  	"istio.io/istio/pkg/http/headers"
    36  	"istio.io/istio/pkg/kube/inject"
    37  	"istio.io/istio/pkg/ptr"
    38  	echot "istio.io/istio/pkg/test/echo"
    39  	"istio.io/istio/pkg/test/echo/common/scheme"
    40  	"istio.io/istio/pkg/test/env"
    41  	"istio.io/istio/pkg/test/framework"
    42  	"istio.io/istio/pkg/test/framework/components/ambient"
    43  	"istio.io/istio/pkg/test/framework/components/echo"
    44  	"istio.io/istio/pkg/test/framework/components/echo/check"
    45  	"istio.io/istio/pkg/test/framework/components/echo/common"
    46  	"istio.io/istio/pkg/test/framework/components/echo/common/ports"
    47  	"istio.io/istio/pkg/test/framework/components/echo/config"
    48  	"istio.io/istio/pkg/test/framework/components/echo/config/param"
    49  	"istio.io/istio/pkg/test/framework/components/echo/echotest"
    50  	"istio.io/istio/pkg/test/framework/components/echo/match"
    51  	"istio.io/istio/pkg/test/framework/components/istio"
    52  	"istio.io/istio/pkg/test/framework/components/istio/ingress"
    53  	"istio.io/istio/pkg/test/framework/components/istioctl"
    54  	"istio.io/istio/pkg/test/framework/components/namespace"
    55  	"istio.io/istio/pkg/test/framework/components/prometheus"
    56  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    57  	"istio.io/istio/pkg/test/framework/resource/config/cleanup"
    58  	kubetest "istio.io/istio/pkg/test/kube"
    59  	"istio.io/istio/pkg/test/util/assert"
    60  	"istio.io/istio/pkg/test/util/file"
    61  	"istio.io/istio/pkg/test/util/retry"
    62  	"istio.io/istio/pkg/util/sets"
    63  	"istio.io/istio/tests/common/jwt"
    64  	"istio.io/istio/tests/integration/security/util/reachability"
    65  	util "istio.io/istio/tests/integration/telemetry"
    66  )
    67  
    68  const (
    69  	templateFile = "manifests/charts/istio-control/istio-discovery/files/waypoint.yaml"
    70  )
    71  
    72  func IsL7() echo.Checker {
    73  	return check.Each(func(r echot.Response) error {
    74  		// TODO: response headers?
    75  		_, f := r.RequestHeaders[http.CanonicalHeaderKey("X-Request-Id")]
    76  		if !f {
    77  			return fmt.Errorf("X-Request-Id not set, is L7 processing enabled?")
    78  		}
    79  		return nil
    80  	})
    81  }
    82  
    83  func IsL4() echo.Checker {
    84  	return check.Each(func(r echot.Response) error {
    85  		// TODO: response headers?
    86  		_, f := r.RequestHeaders[http.CanonicalHeaderKey("X-Request-Id")]
    87  		if f {
    88  			return fmt.Errorf("X-Request-Id set, is L7 processing enabled unexpectedly?")
    89  		}
    90  		return nil
    91  	})
    92  }
    93  
    94  var (
    95  	httpValidator = check.And(check.OK(), IsL7())
    96  	tcpValidator  = check.And(check.OK(), IsL4())
    97  	callOptions   = []echo.CallOptions{
    98  		{
    99  			Port:   echo.Port{Name: "http"},
   100  			Scheme: scheme.HTTP,
   101  			Count:  10, // TODO use more
   102  		},
   103  		//{
   104  		//	Port: echo.Port{Name: "http"},
   105  		//	Scheme:   scheme.WebSocket,
   106  		//	Count:    4,
   107  		//	Timeout:  time.Second * 2,
   108  		//},
   109  		{
   110  			Port:   echo.Port{Name: "tcp"},
   111  			Scheme: scheme.TCP,
   112  			Count:  1,
   113  		},
   114  		//{
   115  		//	Port: echo.Port{Name: "grpc"},
   116  		//	Scheme:   scheme.GRPC,
   117  		//	Count:    4,
   118  		//	Timeout:  time.Second * 2,
   119  		//},
   120  		//{
   121  		//	Port: echo.Port{Name: "https"},
   122  		//	Scheme:   scheme.HTTPS,
   123  		//	Count:    4,
   124  		//	Timeout:  time.Second * 2,
   125  		//},
   126  	}
   127  )
   128  
   129  func OriginalSourceCheck(t framework.TestContext, src echo.Instance) echo.Checker {
   130  	// Check that each response saw one of the workload IPs for the src echo instance
   131  	addresses := sets.New(src.WorkloadsOrFail(t).Addresses()...)
   132  	return check.Each(func(response echot.Response) error {
   133  		if !addresses.Contains(response.IP) {
   134  			return fmt.Errorf("expected original source (%v) to be propogated, but got %v", addresses.UnsortedList(), response.IP)
   135  		}
   136  		return nil
   137  	})
   138  }
   139  
   140  func supportsL7(opt echo.CallOptions, src, dst echo.Instance) bool {
   141  	s := src.Config().HasSidecar()
   142  	d := dst.Config().HasSidecar() || dst.Config().HasAnyWaypointProxy()
   143  	isL7Scheme := opt.Scheme == scheme.HTTP || opt.Scheme == scheme.GRPC || opt.Scheme == scheme.WebSocket
   144  	return (s || d) && isL7Scheme
   145  }
   146  
   147  // Assumption is ambient test suite sidecars will support HBONE
   148  // If the assumption is incorrect hboneClient may return invalid result
   149  func hboneClient(instance echo.Instance) bool {
   150  	return instance.Config().ZTunnelCaptured()
   151  }
   152  
   153  func TestServices(t *testing.T) {
   154  	runTest(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   155  		if supportsL7(opt, src, dst) {
   156  			opt.Check = httpValidator
   157  		} else {
   158  			opt.Check = tcpValidator
   159  		}
   160  
   161  		if !dst.Config().HasServiceAddressedWaypointProxy() &&
   162  			!src.Config().HasServiceAddressedWaypointProxy() &&
   163  			(src.Config().Service != dst.Config().Service) &&
   164  			!dst.Config().HasSidecar() {
   165  			// Check original source, unless there is a waypoint in the path. For waypoint, we don't (yet?) propagate original src.
   166  			// Self call is also (temporarily) broken
   167  			// Sidecars lose the original src
   168  			opt.Check = check.And(opt.Check, OriginalSourceCheck(t, src))
   169  		}
   170  
   171  		// Non-HBONE clients will attempt to bypass the waypoint
   172  		if !src.Config().WaypointClient() && dst.Config().HasAnyWaypointProxy() && !src.Config().HasSidecar() {
   173  			// TODO currently leads to no L7 processing, in the future it might be denied
   174  			// opt.Check = check.Error()
   175  			opt.Check = tcpValidator
   176  		}
   177  
   178  		// Any client will attempt to bypass a workload waypoint (not both service and workload waypoint)
   179  		// because this test always addresses by service.
   180  		if dst.Config().HasWorkloadAddressedWaypointProxy() && !dst.Config().HasServiceAddressedWaypointProxy() {
   181  			// TODO currently leads to no L7 processing, in the future it might be denied
   182  			// opt.Check = check.Error()
   183  			opt.Check = tcpValidator
   184  		}
   185  
   186  		if src.Config().HasSidecar() && dst.Config().HasWorkloadAddressedWaypointProxy() {
   187  			// We are testing to svc traffic but presently sidecar has not been updated to know that to svc traffic should not
   188  			// go to a workload-attached waypoint
   189  			t.Skip("https://github.com/istio/istio/pull/50182")
   190  		}
   191  
   192  		// TODO test from all source workloads as well
   193  		src.CallOrFail(t, opt)
   194  	})
   195  }
   196  
   197  func TestPodIP(t *testing.T) {
   198  	framework.NewTest(t).Run(func(t framework.TestContext) {
   199  		for _, src := range apps.All {
   200  			for _, srcWl := range src.WorkloadsOrFail(t) {
   201  				srcWl := srcWl
   202  				t.NewSubTestf("from %v %v", src.Config().Service, srcWl.Address()).Run(func(t framework.TestContext) {
   203  					for _, dst := range apps.All {
   204  						for _, dstWl := range dst.WorkloadsOrFail(t) {
   205  							t.NewSubTestf("to %v %v", dst.Config().Service, dstWl.Address()).Run(func(t framework.TestContext) {
   206  								src, dst, srcWl, dstWl := src, dst, srcWl, dstWl
   207  								if src.Config().HasSidecar() {
   208  									t.Skip("not supported yet")
   209  								}
   210  								for _, opt := range callOptions {
   211  									opt := opt.DeepCopy()
   212  									selfSend := dstWl.Address() == srcWl.Address()
   213  									if supportsL7(opt, src, dst) {
   214  										opt.Check = httpValidator
   215  									} else {
   216  										opt.Check = tcpValidator
   217  									}
   218  
   219  									opt.Address = dstWl.Address()
   220  									opt.Check = check.And(opt.Check, check.Hostname(dstWl.PodName()))
   221  
   222  									opt.Port = echo.Port{ServicePort: ports.All().MustForName(opt.Port.Name).WorkloadPort}
   223  									opt.ToWorkload = dst.WithWorkloads(dstWl)
   224  
   225  									// Uncaptured means we won't traverse the waypoint
   226  									// We cannot bypass the waypoint, so this fails.
   227  									if !src.Config().WaypointClient() && dst.Config().HasAnyWaypointProxy() {
   228  										// TODO currently leads to no L7 processing, in the future it might be denied
   229  										// opt.Check = check.NotOK()
   230  										opt.Check = tcpValidator
   231  									}
   232  
   233  									// Only marked to use service waypoint. We'll deny since it's not traversed.
   234  									// Not traversed, since traffic is to-workload IP.
   235  									if dst.Config().HasServiceAddressedWaypointProxy() && !dst.Config().HasWorkloadAddressedWaypointProxy() {
   236  										// TODO currently leads to no L7 processing, in the future it might be denied
   237  										// opt.Check = check.NotOK()
   238  										opt.Check = tcpValidator
   239  									}
   240  
   241  									if selfSend {
   242  										// Calls to ourself (by pod IP) are not captured
   243  										opt.Check = tcpValidator
   244  									}
   245  
   246  									t.NewSubTestf("%v", opt.Scheme).RunParallel(func(t framework.TestContext) {
   247  										src.WithWorkloads(srcWl).CallOrFail(t, opt)
   248  									})
   249  								}
   250  							})
   251  						}
   252  					}
   253  				})
   254  			}
   255  		}
   256  	})
   257  }
   258  
   259  func TestServerSideLB(t *testing.T) {
   260  	// TODO: test that naked client reusing connections will load balance
   261  	runTest(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   262  		if src.Config().ZTunnelCaptured() && dst.Config().HasWorkloadAddressedWaypointProxy() && !dst.Config().HasServiceAddressedWaypointProxy() {
   263  			// This is to-service traffic without a service waypoint but with a workload waypoint
   264  			// Ztunnel is going to specifically skip the workload waypoint because this is service addressed but
   265  			// there is a later check for having a waypoint but not coming from a waypoint which drops the traffic.
   266  			// That's a bug in ztunnel to sort out
   267  			t.Skip("TODO: ztunnel bug will cause this to fail")
   268  		}
   269  
   270  		// Need HTTP
   271  		if opt.Scheme != scheme.HTTP {
   272  			return
   273  		}
   274  		if src.Config().IsUncaptured() {
   275  			// For this case, it is broken if the src and dst are on the same node.
   276  			// TODO: fix this and remove this skip
   277  			t.Skip("broken")
   278  		}
   279  		var singleHost echo.Checker = func(result echo.CallResult, _ error) error {
   280  			hostnames := make([]string, len(result.Responses))
   281  			for i, r := range result.Responses {
   282  				hostnames[i] = r.Hostname
   283  			}
   284  			unique := sets.SortedList(sets.New(hostnames...))
   285  			if len(unique) != 1 {
   286  				return fmt.Errorf("excepted only one destination, got: %v", unique)
   287  			}
   288  			return nil
   289  		}
   290  		var multipleHost echo.Checker = func(result echo.CallResult, _ error) error {
   291  			hostnames := make([]string, len(result.Responses))
   292  			for i, r := range result.Responses {
   293  				hostnames[i] = r.Hostname
   294  			}
   295  			unique := sets.SortedList(sets.New(hostnames...))
   296  			want := dst.WorkloadsOrFail(t)
   297  			wn := []string{}
   298  			for _, w := range want {
   299  				wn = append(wn, w.PodName())
   300  			}
   301  			if len(unique) != len(wn) {
   302  				return fmt.Errorf("excepted all destinations (%v), got: %v", wn, unique)
   303  			}
   304  			return nil
   305  		}
   306  
   307  		shouldBalance := dst.Config().HasServiceAddressedWaypointProxy()
   308  		// Istio client will not reuse connections for HTTP/1.1
   309  		opt.HTTP.HTTP2 = true
   310  		// Make sure we make multiple calls
   311  		opt.Count = 10
   312  		c := singleHost
   313  		if shouldBalance {
   314  			c = multipleHost
   315  		}
   316  		opt.Check = check.And(check.OK(), c)
   317  		opt.NewConnectionPerRequest = false
   318  		src.CallOrFail(t, opt)
   319  	})
   320  }
   321  
   322  func TestWaypointChanges(t *testing.T) {
   323  	framework.NewTest(t).Run(func(t framework.TestContext) {
   324  		getGracePeriod := func(want int64) bool {
   325  			pods, err := kubetest.NewPodFetch(t.AllClusters()[0], apps.Namespace.Name(), constants.GatewayNameLabel+"=waypoint")()
   326  			assert.NoError(t, err)
   327  			for _, p := range pods {
   328  				grace := p.Spec.TerminationGracePeriodSeconds
   329  				if grace != nil && *grace == want {
   330  					return true
   331  				}
   332  			}
   333  			return false
   334  		}
   335  		// check that waypoint deployment is unmodified
   336  		retry.UntilOrFail(t, func() bool {
   337  			return getGracePeriod(2)
   338  		})
   339  		// change the waypoint template
   340  		istio.GetOrFail(t, t).UpdateInjectionConfig(t, func(cfg *inject.Config) error {
   341  			mainTemplate := file.MustAsString(filepath.Join(env.IstioSrc, templateFile))
   342  			cfg.RawTemplates["waypoint"] = strings.ReplaceAll(mainTemplate, "terminationGracePeriodSeconds: 2", "terminationGracePeriodSeconds: 3")
   343  			return nil
   344  		}, cleanup.Always)
   345  
   346  		retry.UntilOrFail(t, func() bool {
   347  			return getGracePeriod(3)
   348  		})
   349  	})
   350  }
   351  
   352  func TestOtherRevisionIgnored(t *testing.T) {
   353  	framework.NewTest(t).Run(func(t framework.TestContext) {
   354  		// This is a negative test, ensuring gateways with tags other
   355  		// than my tags do not get controlled by me.
   356  		nsConfig, err := namespace.New(t, namespace.Config{
   357  			Prefix: "badgateway",
   358  			Inject: false,
   359  			Labels: map[string]string{
   360  				constants.DataplaneModeLabel: "ambient",
   361  			},
   362  		})
   363  		if err != nil {
   364  			t.Fatal(err)
   365  		}
   366  		istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
   367  			"x",
   368  			"waypoint",
   369  			"apply",
   370  			"--namespace",
   371  			nsConfig.Name(),
   372  			"--revision",
   373  			"foo",
   374  		})
   375  		waypointError := retry.UntilSuccess(func() error {
   376  			fetch := kubetest.NewPodFetch(t.AllClusters()[0], nsConfig.Name(), constants.GatewayNameLabel+"="+"sa")
   377  			if _, err := kubetest.CheckPodsAreReady(fetch); err != nil {
   378  				return fmt.Errorf("gateway is not ready: %v", err)
   379  			}
   380  			return nil
   381  		}, retry.Timeout(15*time.Second), retry.BackoffDelay(time.Millisecond*100))
   382  		if waypointError == nil {
   383  			t.Fatal("Waypoint for non-existent tag foo created deployment!")
   384  		}
   385  	})
   386  }
   387  
   388  func TestRemoveAddWaypoint(t *testing.T) {
   389  	framework.NewTest(t).Run(func(t framework.TestContext) {
   390  		istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
   391  			"x",
   392  			"waypoint",
   393  			"apply",
   394  			"--namespace",
   395  			apps.Namespace.Name(),
   396  			"--name", "captured-waypoint",
   397  			"--wait",
   398  		})
   399  		t.Cleanup(func() {
   400  			istioctl.NewOrFail(t, t, istioctl.Config{}).InvokeOrFail(t, []string{
   401  				"x",
   402  				"waypoint",
   403  				"delete",
   404  				"--namespace",
   405  				apps.Namespace.Name(),
   406  				"captured-waypoint",
   407  			})
   408  		})
   409  
   410  		t.NewSubTest("before").Run(func(t framework.TestContext) {
   411  			dst := apps.Captured
   412  			for _, src := range apps.All {
   413  				if src.Config().IsUncaptured() {
   414  					continue
   415  				}
   416  				t.NewSubTestf("from %v", src.Config().Service).Run(func(t framework.TestContext) {
   417  					c := IsL4()
   418  					if src.Config().HasSidecar() {
   419  						c = IsL7()
   420  					}
   421  					opt := echo.CallOptions{
   422  						To:     dst,
   423  						Port:   echo.Port{Name: "http"},
   424  						Scheme: scheme.HTTP,
   425  						Count:  10,
   426  						Check:  check.And(check.OK(), c),
   427  					}
   428  					src.CallOrFail(t, opt)
   429  				})
   430  			}
   431  		})
   432  
   433  		SetWaypoint(t, Captured, "captured-waypoint")
   434  
   435  		// Now should always be L7
   436  		t.NewSubTest("after").Run(func(t framework.TestContext) {
   437  			dst := apps.Captured
   438  			for _, src := range apps.All {
   439  				if src.Config().IsUncaptured() {
   440  					continue
   441  				}
   442  				t.NewSubTestf("from %v", src.Config().Service).Run(func(t framework.TestContext) {
   443  					opt := echo.CallOptions{
   444  						To:     dst,
   445  						Port:   echo.Port{Name: "http"},
   446  						Scheme: scheme.HTTP,
   447  						Count:  10,
   448  						Check:  check.And(check.OK(), IsL7()),
   449  					}
   450  					src.CallOrFail(t, opt)
   451  				})
   452  			}
   453  		})
   454  	})
   455  }
   456  
   457  func TestBogusUseWaypoint(t *testing.T) {
   458  	framework.NewTest(t).Run(func(t framework.TestContext) {
   459  		check := func(t framework.TestContext) {
   460  			dst := apps.Captured
   461  			for _, src := range apps.All {
   462  				if src.Config().IsUncaptured() {
   463  					continue
   464  				}
   465  				t.NewSubTestf("from %v", src.Config().Service).Run(func(t framework.TestContext) {
   466  					c := IsL4()
   467  					if src.Config().HasSidecar() {
   468  						c = IsL7()
   469  					}
   470  					opt := echo.CallOptions{
   471  						To:     dst,
   472  						Port:   echo.Port{Name: "http"},
   473  						Scheme: scheme.HTTP,
   474  						Count:  10,
   475  						Check:  check.And(check.OK(), c),
   476  					}
   477  					src.CallOrFail(t, opt)
   478  				})
   479  			}
   480  		}
   481  		t.NewSubTest("before").Run(check)
   482  
   483  		SetWaypoint(t, Captured, "bogus-waypoint")
   484  		t.NewSubTest("with waypoint").Run(check)
   485  
   486  		SetWaypoint(t, Captured, "")
   487  		t.NewSubTest("waypoint removed").Run(check)
   488  	})
   489  }
   490  
   491  func TestServerRouting(t *testing.T) {
   492  	runTest(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   493  		// Need waypoint proxy and HTTP
   494  		if opt.Scheme != scheme.HTTP {
   495  			return
   496  		}
   497  		if !dst.Config().HasServiceAddressedWaypointProxy() {
   498  			return
   499  		}
   500  		if src.Config().IsUncaptured() {
   501  			// TODO: fix this and remove this skip
   502  			t.Skip("https://github.com/istio/istio/issues/43238")
   503  		}
   504  		t.NewSubTest("set header").Run(func(t framework.TestContext) {
   505  			t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   506  				"Destination": dst.Config().Service,
   507  			}, `apiVersion: networking.istio.io/v1alpha3
   508  kind: VirtualService
   509  metadata:
   510    name: route
   511  spec:
   512    hosts:
   513    - "{{.Destination}}"
   514    http:
   515    - headers:
   516        request:
   517          add:
   518            istio-custom-header: user-defined-value
   519      route:
   520      - destination:
   521          host: "{{.Destination}}"
   522  `).ApplyOrFail(t)
   523  			opt.Check = check.And(
   524  				check.OK(),
   525  				check.RequestHeader("Istio-Custom-Header", "user-defined-value"))
   526  			src.CallOrFail(t, opt)
   527  		})
   528  		t.NewSubTest("subset").Run(func(t framework.TestContext) {
   529  			t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   530  				"Destination": dst.Config().Service,
   531  			}, `apiVersion: networking.istio.io/v1alpha3
   532  kind: VirtualService
   533  metadata:
   534    name: route
   535  spec:
   536    hosts:
   537    - "{{.Destination}}"
   538    http:
   539    - route:
   540      - destination:
   541          host: "{{.Destination}}"
   542          subset: v1
   543  ---
   544  apiVersion: networking.istio.io/v1beta1
   545  kind: DestinationRule
   546  metadata:
   547    name: route
   548    namespace:
   549  spec:
   550    host: "{{.Destination}}"
   551    subsets:
   552    - labels:
   553        version: v1
   554      name: v1
   555    - labels:
   556        version: v2
   557      name: v2
   558  `).ApplyOrFail(t)
   559  			var exp string
   560  			for _, w := range dst.WorkloadsOrFail(t) {
   561  				if strings.Contains(w.PodName(), "-v1") {
   562  					exp = w.PodName()
   563  				}
   564  			}
   565  			opt.Count = 10
   566  			opt.Check = check.And(
   567  				check.OK(),
   568  				check.Hostname(exp))
   569  			src.CallOrFail(t, opt)
   570  		})
   571  	})
   572  }
   573  
   574  func TestWaypointEnvoyFilter(t *testing.T) {
   575  	runTest(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   576  		// Need at least one waypoint proxy and HTTP
   577  		if opt.Scheme != scheme.HTTP {
   578  			return
   579  		}
   580  		if !dst.Config().HasServiceAddressedWaypointProxy() {
   581  			return
   582  		}
   583  		if src.Config().IsUncaptured() {
   584  			// TODO: fix this and remove this skip
   585  			t.Skip("https://github.com/istio/istio/issues/43238")
   586  		}
   587  		t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   588  			"Destination": "waypoint",
   589  		}, `apiVersion: networking.istio.io/v1alpha3
   590  kind: EnvoyFilter
   591  metadata:
   592    name: inbound
   593  spec:
   594    workloadSelector:
   595      labels:
   596        gateway.networking.k8s.io/gateway-name: "{{.Destination}}"
   597    configPatches:
   598    - applyTo: HTTP_FILTER
   599      match:
   600        context: SIDECAR_INBOUND
   601        listener:
   602          filterChain:
   603            filter:
   604              name: "envoy.filters.network.http_connection_manager"
   605              subFilter:
   606                name: "envoy.filters.http.router"
   607      patch:
   608        operation: INSERT_BEFORE
   609        value:
   610          name: envoy.lua
   611          typed_config:
   612            "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
   613            inlineCode: |
   614              function envoy_on_request(request_handle)
   615                request_handle:headers():add("x-lua-inbound", "hello world")
   616              end
   617    - applyTo: VIRTUAL_HOST
   618      match:
   619        context: SIDECAR_INBOUND
   620      patch:
   621        operation: MERGE
   622        value:
   623          request_headers_to_add:
   624          - header:
   625              key: x-vhost-inbound
   626              value: "hello world"
   627    - applyTo: CLUSTER
   628      match:
   629        context: SIDECAR_INBOUND
   630        cluster: {}
   631      patch:
   632        operation: MERGE
   633        value:
   634          http2_protocol_options: {}
   635  `).ApplyOrFail(t)
   636  		opt.Count = 5
   637  		opt.Timeout = time.Second * 10
   638  		opt.Check = check.And(
   639  			check.OK(),
   640  			check.RequestHeaders(map[string]string{
   641  				"X-Lua-Inbound":   "hello world",
   642  				"X-Vhost-Inbound": "hello world",
   643  			}))
   644  		src.CallOrFail(t, opt)
   645  	})
   646  }
   647  
   648  func TestTrafficSplit(t *testing.T) {
   649  	runTest(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   650  		// Need at least one waypoint proxy and HTTP
   651  		if opt.Scheme != scheme.HTTP {
   652  			return
   653  		}
   654  		if !dst.Config().HasServiceAddressedWaypointProxy() {
   655  			return
   656  		}
   657  		if src.Config().IsUncaptured() {
   658  			// TODO: fix this and remove this skip
   659  			t.Skip("https://github.com/istio/istio/issues/43238")
   660  		}
   661  		t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   662  			"Destination": dst.Config().Service,
   663  		}, `apiVersion: networking.istio.io/v1alpha3
   664  kind: VirtualService
   665  metadata:
   666    name: route
   667  spec:
   668    hosts:
   669    - "{{.Destination}}"
   670    http:
   671    - match:
   672      - headers:
   673          user:
   674            exact: istio-custom-user
   675      route:
   676      - destination:
   677          host: "{{.Destination}}"
   678          subset: v2
   679    - route:
   680      - destination:
   681          host: "{{.Destination}}"
   682          subset: v1
   683  `).ApplyOrFail(t)
   684  		t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   685  			"Destination": dst.Config().Service,
   686  		}, `apiVersion: networking.istio.io/v1alpha3
   687  kind: DestinationRule
   688  metadata:
   689    name: dr
   690  spec:
   691    host: "{{.Destination}}"
   692    subsets:
   693    - name: v1
   694      labels:
   695        version: v1
   696    - name: v2
   697      labels:
   698        version: v2
   699  `).ApplyOrFail(t)
   700  		t.NewSubTest("v1").Run(func(t framework.TestContext) {
   701  			opt = opt.DeepCopy()
   702  			opt.Count = 5
   703  			opt.Timeout = time.Second * 10
   704  			opt.Check = check.And(
   705  				check.OK(),
   706  				func(result echo.CallResult, _ error) error {
   707  					for _, r := range result.Responses {
   708  						if r.Version != "v1" {
   709  							return fmt.Errorf("expected service version %q, got %q", "v1", r.Version)
   710  						}
   711  					}
   712  					return nil
   713  				})
   714  			src.CallOrFail(t, opt)
   715  		})
   716  
   717  		t.NewSubTest("v2").Run(func(t framework.TestContext) {
   718  			opt = opt.DeepCopy()
   719  			opt.Count = 5
   720  			opt.Timeout = time.Second * 10
   721  			if opt.HTTP.Headers == nil {
   722  				opt.HTTP.Headers = map[string][]string{}
   723  			}
   724  			opt.HTTP.Headers.Set("user", "istio-custom-user")
   725  			opt.Check = check.And(
   726  				check.OK(),
   727  				func(result echo.CallResult, _ error) error {
   728  					for _, r := range result.Responses {
   729  						if r.Version != "v2" {
   730  							return fmt.Errorf("expected service version %q, got %q", "v2", r.Version)
   731  						}
   732  					}
   733  					return nil
   734  				})
   735  			src.CallOrFail(t, opt)
   736  		})
   737  	})
   738  }
   739  
   740  func TestPeerAuthentication(t *testing.T) {
   741  	framework.NewTest(t).Run(func(t framework.TestContext) {
   742  		// Workaround https://github.com/istio/istio/issues/43239
   743  		t.ConfigIstio().YAML(apps.Namespace.Name(), `apiVersion: networking.istio.io/v1alpha3
   744  kind: DestinationRule
   745  metadata:
   746    name: single-request
   747  spec:
   748    host: '*.svc.cluster.local'
   749    trafficPolicy:
   750      connectionPool:
   751        http:
   752          maxRequestsPerConnection: 1`).ApplyOrFail(t)
   753  		runTestContext(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   754  			if opt.Scheme != scheme.TCP {
   755  				return
   756  			}
   757  			// Ensure we don't get stuck on old connections with old RBAC rules. This causes 45s test times
   758  			// due to draining.
   759  			opt.NewConnectionPerRequest = true
   760  			if src.Config().IsUncaptured() {
   761  				// For this case, it is broken if the src and dst are on the same node.
   762  				// TODO: fix this and remove this skip
   763  				t.Skip("https://github.com/istio/istio/issues/43238")
   764  			}
   765  
   766  			if src.Config().ZTunnelCaptured() && dst.Config().HasWorkloadAddressedWaypointProxy() {
   767  				// this case should bypass waypoints because traffic is svc addressed but
   768  				// presently a ztunnel bug will drop this traffic because it doesn't differentiate
   769  				// between svc and wl addressed traffic when determining if the connection
   770  				// should have gone through a waypoint.
   771  				t.Skip("TODO: open an issue to address this ztunnel issue")
   772  			}
   773  
   774  			t.NewSubTest("permissive").Run(func(t framework.TestContext) {
   775  				t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   776  					"Destination": dst.Config().Service,
   777  					"Source":      src.Config().Service,
   778  					"Namespace":   apps.Namespace.Name(),
   779  				}, `
   780  apiVersion: security.istio.io/v1beta1
   781  kind: PeerAuthentication
   782  metadata:
   783    name: global-permissive
   784  spec:
   785    mtls:
   786      mode: PERMISSIVE
   787  `).ApplyOrFail(t)
   788  				opt = opt.DeepCopy()
   789  				src.CallOrFail(t, opt)
   790  			})
   791  			t.NewSubTest("strict").Run(func(t framework.TestContext) {
   792  				t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   793  					"Destination": dst.Config().Service,
   794  					"Source":      src.Config().Service,
   795  					"Namespace":   apps.Namespace.Name(),
   796  				}, `
   797  apiVersion: security.istio.io/v1beta1
   798  kind: PeerAuthentication
   799  metadata:
   800    name: global-strict
   801  spec:
   802    mtls:
   803      mode: STRICT
   804  				`).ApplyOrFail(t)
   805  				opt = opt.DeepCopy()
   806  				if inMesh.All([]echo.Instance{src, dst}) { // If both src and dst are in the mesh, the request should succeed
   807  					opt.Check = check.OK()
   808  				} else { // If not, the request should fail
   809  					opt.Check = CheckDeny
   810  				}
   811  				src.CallOrFail(t, opt)
   812  			})
   813  			// globally peerauth == STRICT, but we have a port-specific allowlist that is PERMISSIVE,
   814  			// so anything hitting that port should not be rejected
   815  			t.NewSubTest("strict-permissive-ports").Run(func(t framework.TestContext) {
   816  				t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   817  					"Destination": dst.Config().Service,
   818  					"Source":      src.Config().Service,
   819  					"Namespace":   apps.Namespace.Name(),
   820  				}, `
   821  apiVersion: security.istio.io/v1beta1
   822  kind: PeerAuthentication
   823  metadata:
   824    name: global-strict
   825  spec:
   826    selector:
   827      matchLabels:
   828        app: "{{ .Destination }}"
   829    mtls:
   830      mode: STRICT
   831    portLevelMtls:
   832      8080:
   833        mode: PERMISSIVE
   834  				`).ApplyOrFail(t)
   835  				opt = opt.DeepCopy()
   836  				// Should pass for all workloads, in or out of mesh, targeting this port
   837  				src.CallOrFail(t, opt)
   838  			})
   839  		})
   840  	})
   841  }
   842  
   843  func TestAuthorizationL4(t *testing.T) {
   844  	framework.NewTest(t).Run(func(t framework.TestContext) {
   845  		applyDrainingWorkaround(t)
   846  		// pairs x allow/deny
   847  		runTestContext(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
   848  			if opt.Scheme != scheme.TCP {
   849  				return
   850  			}
   851  			// Ensure we don't get stuck on old connections with old RBAC rules. This causes 45s test times
   852  			// due to draining.
   853  			opt.NewConnectionPerRequest = true
   854  			if src.Config().IsUncaptured() {
   855  				// For this case, it is broken if the src and dst are on the same node.
   856  				// TODO: fix this and remove this skip
   857  				t.Skip("https://github.com/istio/istio/issues/43238")
   858  			}
   859  
   860  			overrideCheck := func(src echo.Instance, dst echo.Instance, opt *echo.CallOptions) {
   861  				switch {
   862  				case src.Config().IsUncaptured() && dst.Config().HasAnyWaypointProxy():
   863  					// For this case, it is broken if the src and dst are on the same node.
   864  					// Because client request is not captured to perform the hairpin
   865  					// TODO: fix this and remove this skip
   866  					opt.Check = check.OK()
   867  				case dst.Config().IsUncaptured() && !dst.Config().HasSidecar():
   868  					// No destination means no RBAC to apply. Make sure we do not accidentally reject
   869  					opt.Check = check.OK()
   870  				}
   871  			}
   872  
   873  			authzCases := []struct {
   874  				name  string
   875  				spec  string
   876  				check echo.Checker
   877  			}{
   878  				{
   879  					name:  "allow",
   880  					check: check.OK(),
   881  					spec: `
   882    rules:
   883    - from:
   884      - source:
   885          principals: ["cluster.local/ns/{{.Namespace}}/sa/{{.Source}}", "cluster.local/ns/{{.Namespace}}/sa/{{.WaypointName}}"]
   886  `,
   887  				},
   888  				{
   889  					name:  "not allow",
   890  					check: CheckDeny,
   891  					spec: `
   892    rules:
   893    - from:
   894      - source:
   895          principals: ["cluster.local/ns/something/sa/else"]
   896            `,
   897  				},
   898  			}
   899  
   900  			for _, tc := range authzCases {
   901  				t.NewSubTest(tc.name).Run(func(t framework.TestContext) {
   902  					t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   903  						"Destination":  dst.Config().Service,
   904  						"Source":       src.Config().Service,
   905  						"Namespace":    apps.Namespace.Name(),
   906  						"WaypointName": dst.Config().ServiceWaypointProxy,
   907  					}, `
   908  apiVersion: security.istio.io/v1beta1
   909  kind: AuthorizationPolicy
   910  metadata:
   911    name: policy-waypoint
   912  spec:
   913    targetRefs:
   914    # affects Waypoints
   915    - kind: Service
   916      group: core
   917      name: "{{ .Destination }}"
   918  `+tc.spec+`
   919  ---
   920  apiVersion: security.istio.io/v1beta1
   921  kind: AuthorizationPolicy
   922  metadata:
   923    name: policy
   924  spec:
   925    # affects zTunnels and Sidecars
   926    selector:
   927      matchLabels:
   928        app: "{{ .Destination }}"
   929  `+tc.spec).ApplyOrFail(t)
   930  					perCaseOpt := opt.DeepCopy()
   931  					perCaseOpt.Check = tc.check
   932  					overrideCheck(src, dst, &perCaseOpt)
   933  					src.CallOrFail(t, perCaseOpt)
   934  				})
   935  			}
   936  		})
   937  	})
   938  }
   939  
   940  func TestAuthorizationServiceAttached(t *testing.T) {
   941  	framework.NewTest(t).Run(func(t framework.TestContext) {
   942  		applyDrainingWorkaround(t)
   943  		src := apps.Captured
   944  		authzDst := apps.ServiceAddressedWaypoint
   945  		otherDst := apps.WorkloadAddressedWaypoint
   946  
   947  		// make another target use our waypoint, but don't expect authz there
   948  		ambient.SetWaypointForService(t, apps.Namespace, otherDst.ServiceName(), authzDst.Config().ServiceWaypointProxy)
   949  
   950  		t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
   951  			"Destination": authzDst.Config().Service,
   952  		}, `
   953  apiVersion: security.istio.io/v1beta1
   954  kind: AuthorizationPolicy
   955  metadata:
   956    name: policy-waypoint
   957  spec:
   958    targetRefs:
   959    - kind: Service
   960      group: core
   961      name: "{{ .Destination }}"
   962    rules:
   963    - from:
   964      - source:
   965          principals: ["cluster.local/ns/something/sa/else"]
   966    `).ApplyOrFail(t)
   967  
   968  		for _, src := range src.Instances() {
   969  			t.NewSubTest(src.Config().Cluster.StableName()).Run(func(t framework.TestContext) {
   970  				t.NewSubTest("authz target deny").RunParallel(func(t framework.TestContext) {
   971  					opts := echo.CallOptions{
   972  						To:     authzDst,
   973  						Check:  CheckDeny,
   974  						Port:   echo.Port{Name: "http"},
   975  						Scheme: scheme.HTTP,
   976  						Count:  10,
   977  					}
   978  					src.CallOrFail(t, opts)
   979  				})
   980  				t.NewSubTest("non-authz target allow").RunParallel(func(t framework.TestContext) {
   981  					opts := echo.CallOptions{
   982  						To:     otherDst,
   983  						Check:  check.OK(),
   984  						Port:   echo.Port{Name: "http"},
   985  						Scheme: scheme.HTTP,
   986  						Count:  10,
   987  					}
   988  					src.CallOrFail(t, opts)
   989  				})
   990  			})
   991  		}
   992  	})
   993  }
   994  
   995  func TestAuthorizationGateway(t *testing.T) {
   996  	runTest := func(t framework.TestContext, f func(t framework.TestContext, src echo.Caller, dst echo.Instance, opt echo.CallOptions)) {
   997  		svcs := apps.All
   998  		for _, dst := range svcs {
   999  			t.NewSubTestf("to %v", dst.Config().Service).Run(func(t framework.TestContext) {
  1000  				dst := dst
  1001  				opt := echo.CallOptions{
  1002  					Port:    echo.Port{Name: "http"},
  1003  					Scheme:  scheme.HTTP,
  1004  					Count:   5,
  1005  					Timeout: time.Second * 2,
  1006  					Check:   check.OK(),
  1007  					To:      dst,
  1008  				}
  1009  				f(t, istio.DefaultIngressOrFail(t, t), dst, opt)
  1010  			})
  1011  		}
  1012  	}
  1013  	framework.NewTest(t).Run(func(t framework.TestContext) {
  1014  		applyDrainingWorkaround(t)
  1015  		runTest(t, func(t framework.TestContext, src echo.Caller, dst echo.Instance, opt echo.CallOptions) {
  1016  			if opt.Scheme != scheme.HTTP {
  1017  				return
  1018  			}
  1019  
  1020  			// Ensure we don't get stuck on old connections with old RBAC rules. This causes 45s test times
  1021  			// due to draining.
  1022  			opt.NewConnectionPerRequest = true
  1023  
  1024  			policySpec := `
  1025    rules:
  1026    - from:
  1027      - source:
  1028          principals: ["cluster.local/ns/istio-system/sa/{{.Source}}"]
  1029      to:
  1030      - operation:
  1031          ports: ["{{.PortAllowWorkload}}"]
  1032    - from:
  1033      - source:
  1034          principals: ["cluster.local/ns/{{.Namespace}}/sa/someone-else"]
  1035      to:
  1036      - operation:
  1037          ports: ["{{.PortDenyWorkload}}"]
  1038  `
  1039  			t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
  1040  				"Destination":       dst.Config().Service,
  1041  				"Source":            "istio-ingressgateway-service-account",
  1042  				"Namespace":         apps.Namespace.Name(),
  1043  				"PortAllow":         strconv.Itoa(ports.HTTP.ServicePort),
  1044  				"PortAllowWorkload": strconv.Itoa(ports.HTTP.WorkloadPort),
  1045  				"PortDeny":          strconv.Itoa(ports.HTTP2.ServicePort),
  1046  				"PortDenyWorkload":  strconv.Itoa(ports.HTTP2.WorkloadPort),
  1047  			}, `
  1048  apiVersion: security.istio.io/v1beta1
  1049  kind: AuthorizationPolicy
  1050  metadata:
  1051    name: policy
  1052  spec:
  1053    selector:
  1054      matchLabels:
  1055        app: "{{ .Destination }}"
  1056  `+policySpec+`
  1057  ---
  1058  apiVersion: networking.istio.io/v1alpha3
  1059  kind: Gateway
  1060  metadata:
  1061    name: gateway
  1062  spec:
  1063    selector:
  1064      istio: ingressgateway
  1065    servers:
  1066    - port:
  1067        number: 80
  1068        name: http
  1069        protocol: HTTP
  1070      hosts: ["*"]
  1071  ---
  1072  apiVersion: networking.istio.io/v1alpha3
  1073  kind: VirtualService
  1074  metadata:
  1075    name: route
  1076  spec:
  1077    gateways:
  1078    - gateway
  1079    hosts:
  1080    - "*"
  1081    http:
  1082    - match:
  1083      - uri:
  1084          exact: /allowed
  1085      route:
  1086      - destination:
  1087          host: "{{.Destination}}"
  1088          port:
  1089            number: {{.PortAllow}}
  1090    - match:
  1091      - uri:
  1092          exact: /deny
  1093      route:
  1094      - destination:
  1095          host: "{{.Destination}}"
  1096          port:
  1097            number: {{.PortDeny}}
  1098  `).ApplyOrFail(t)
  1099  			overrideCheck := func(opt *echo.CallOptions) {
  1100  				switch {
  1101  				case !dst.Config().HasProxyCapabilities():
  1102  					// No destination proxy means no RBAC to apply. Make sure we do not accidentally reject
  1103  					opt.Check = check.OK()
  1104  				}
  1105  			}
  1106  			t.NewSubTest("simple deny").Run(func(t framework.TestContext) {
  1107  				opt = opt.DeepCopy()
  1108  				opt.HTTP.Path = "/deny"
  1109  				opt.Check = CheckDeny
  1110  				overrideCheck(&opt)
  1111  				src.CallOrFail(t, opt)
  1112  			})
  1113  			t.NewSubTest("simple allow").Run(func(t framework.TestContext) {
  1114  				opt = opt.DeepCopy()
  1115  				opt.HTTP.Path = "/allowed"
  1116  				opt.Check = check.OK()
  1117  				overrideCheck(&opt)
  1118  				src.CallOrFail(t, opt)
  1119  			})
  1120  		})
  1121  	})
  1122  }
  1123  
  1124  func TestAuthorizationL7(t *testing.T) {
  1125  	framework.NewTest(t).Run(func(t framework.TestContext) {
  1126  		applyDrainingWorkaround(t)
  1127  		runTestContext(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
  1128  			if opt.Scheme != scheme.HTTP {
  1129  				return
  1130  			}
  1131  			// Ensure we don't get stuck on old connections with old RBAC rules. This causes 45s test times
  1132  			// due to draining.
  1133  			opt.NewConnectionPerRequest = true
  1134  			if src.Config().IsUncaptured() {
  1135  				// TODO: fix this and remove this skip
  1136  				t.Skip("https://github.com/istio/istio/issues/43238")
  1137  			}
  1138  
  1139  			policySpec := `
  1140    rules:
  1141    - to:
  1142      - operation:
  1143          paths: ["/allowed"]
  1144          methods: ["GET"]
  1145    - from:
  1146      - source:
  1147          principals: ["cluster.local/ns/{{.Namespace}}/sa/{{.Source}}"]
  1148      to:
  1149      - operation:
  1150          paths: ["/allowed-identity"]
  1151          methods: ["GET"]
  1152    - from:
  1153      - source:
  1154          principals: ["cluster.local/ns/{{.Namespace}}/sa/someone-else"]
  1155      to:
  1156      - operation:
  1157          paths: ["/denied-identity"]
  1158          methods: ["GET"]
  1159    - to:
  1160      - operation:
  1161          methods: ["GET"]
  1162          paths: ["/allowed-wildcard*"]
  1163    - to:
  1164      - operation:
  1165          methods: ["GET"]
  1166          paths: ["/headers"]
  1167      when:
  1168      - key: request.headers[x-test-header]
  1169        values: ["match"]
  1170        notValues: ["do-not-match"]
  1171    - to:
  1172      - operation:
  1173          methods: ["POST"]
  1174  `
  1175  			denySpec := `
  1176    action: DENY
  1177    rules:
  1178    - to:
  1179      - operation:
  1180          paths: ["/explicit-deny"]
  1181  `
  1182  			// for most cases just use the normal policy spec
  1183  			policySpecWL := policySpec
  1184  			if dst.Config().HasAnyWaypointProxy() {
  1185  				// for svc addressed traffic we want the WL policy to allow Waypoint -> Workload
  1186  				policySpecWL = `
  1187    rules:
  1188    - from:
  1189      - source:
  1190          principals: ["cluster.local/ns/{{.Namespace}}/sa/{{.WaypointName}}"]
  1191  `
  1192  			}
  1193  			waypointName := "none"
  1194  			switch {
  1195  			case dst.Config().HasServiceAddressedWaypointProxy():
  1196  				waypointName = dst.Config().ServiceWaypointProxy
  1197  			case dst.Config().HasWorkloadAddressedWaypointProxy():
  1198  				waypointName = dst.Config().WorkloadWaypointProxy
  1199  			}
  1200  			t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
  1201  				"Destination":  dst.Config().Service,
  1202  				"Source":       src.Config().Service,
  1203  				"Namespace":    apps.Namespace.Name(),
  1204  				"WaypointName": waypointName,
  1205  			}, `
  1206  apiVersion: security.istio.io/v1beta1
  1207  kind: AuthorizationPolicy
  1208  metadata:
  1209    name: policy
  1210  spec:
  1211    selector:
  1212      matchLabels:
  1213        app: "{{ .Destination }}"
  1214  `+policySpecWL+`
  1215  ---
  1216  apiVersion: security.istio.io/v1beta1
  1217  kind: AuthorizationPolicy
  1218  metadata:
  1219    name: policy-waypoint
  1220  spec:
  1221    targetRefs:
  1222    - kind: Gateway
  1223      group: gateway.networking.k8s.io
  1224      name: waypoint
  1225  `+policySpec+`
  1226  ---
  1227  apiVersion: security.istio.io/v1beta1
  1228  kind: AuthorizationPolicy
  1229  metadata:
  1230    name: deny-policy
  1231  spec:
  1232    selector:
  1233      matchLabels:
  1234        app: "{{ .Destination }}"
  1235  `+denySpec+`
  1236  ---
  1237  apiVersion: security.istio.io/v1beta1
  1238  kind: AuthorizationPolicy
  1239  metadata:
  1240    name: deny-policy-waypoint
  1241  spec:
  1242    targetRefs:
  1243    - kind: Gateway
  1244      group: gateway.networking.k8s.io
  1245      name: waypoint
  1246  `+denySpec).ApplyOrFail(t)
  1247  			overrideCheck := func(opt *echo.CallOptions) {
  1248  				switch {
  1249  				case dst.Config().IsUncaptured() && !dst.Config().HasSidecar():
  1250  					// No destination means no RBAC to apply. Make sure we do not accidentally reject
  1251  					opt.Check = check.OK()
  1252  				case !dst.Config().HasAnyWaypointProxy() && !dst.Config().HasSidecar():
  1253  					// Only waypoint proxy can handle L7 policies
  1254  					opt.Check = CheckDeny
  1255  				case dst.Config().HasWorkloadAddressedWaypointProxy() && !dst.Config().HasServiceAddressedWaypointProxy():
  1256  					// send traffic to the workload instead of the service so it will redirect to the WL waypoint
  1257  					opt.Address = dst.MustWorkloads().Addresses()[0]
  1258  					opt.Port = echo.Port{ServicePort: ports.All().MustForName(opt.Port.Name).WorkloadPort}
  1259  				}
  1260  			}
  1261  			if src == dst {
  1262  				t.Skip("self call is not captured, L7 features will not work")
  1263  			}
  1264  			t.NewSubTest("simple deny").Run(func(t framework.TestContext) {
  1265  				opt := opt.DeepCopy()
  1266  				opt.HTTP.Path = "/deny"
  1267  				opt.Check = CheckDeny
  1268  				overrideCheck(&opt)
  1269  				src.CallOrFail(t, opt)
  1270  			})
  1271  			t.NewSubTest("simple allow").Run(func(t framework.TestContext) {
  1272  				opt := opt.DeepCopy()
  1273  				opt.HTTP.Path = "/allowed"
  1274  				opt.Check = check.OK()
  1275  				overrideCheck(&opt)
  1276  				src.CallOrFail(t, opt)
  1277  			})
  1278  			t.NewSubTest("identity deny").Run(func(t framework.TestContext) {
  1279  				opt := opt.DeepCopy()
  1280  				opt.HTTP.Path = "/denied-identity"
  1281  				opt.Check = CheckDeny
  1282  				overrideCheck(&opt)
  1283  				src.CallOrFail(t, opt)
  1284  			})
  1285  			t.NewSubTest("identity allow").Run(func(t framework.TestContext) {
  1286  				opt := opt.DeepCopy()
  1287  				opt.HTTP.Path = "/allowed-identity"
  1288  				opt.Check = check.OK()
  1289  				if !src.Config().HasProxyCapabilities() && !dst.Config().HasServiceAddressedWaypointProxy() {
  1290  					// TODO: remove waypoint check (https://github.com/istio/istio/issues/42640)
  1291  					// No identity from uncaptured
  1292  					opt.Check = CheckDeny
  1293  				}
  1294  				overrideCheck(&opt)
  1295  				src.CallOrFail(t, opt)
  1296  			})
  1297  			t.NewSubTest("explicit deny").Run(func(t framework.TestContext) {
  1298  				opt := opt.DeepCopy()
  1299  				opt.HTTP.Path = "/explicit-deny"
  1300  				opt.HTTP.Method = http.MethodPost
  1301  				opt.Check = CheckDeny
  1302  				overrideCheck(&opt)
  1303  				src.CallOrFail(t, opt)
  1304  			})
  1305  			t.NewSubTest("wildcard allow").Run(func(t framework.TestContext) {
  1306  				opt := opt.DeepCopy()
  1307  				opt.HTTP.Path = "/allowed-wildcardtest"
  1308  				opt.Check = check.OK()
  1309  				overrideCheck(&opt)
  1310  				src.CallOrFail(t, opt)
  1311  			})
  1312  			t.NewSubTest("headers allow").Run(func(t framework.TestContext) {
  1313  				opt := opt.DeepCopy()
  1314  				opt.HTTP.Path = "/headers"
  1315  				if opt.HTTP.Headers == nil {
  1316  					opt.HTTP.Headers = map[string][]string{}
  1317  				}
  1318  				opt.HTTP.Headers.Set("x-test-header", "match")
  1319  				opt.Check = check.OK()
  1320  				overrideCheck(&opt)
  1321  				src.CallOrFail(t, opt)
  1322  			})
  1323  			t.NewSubTest("headers deny").Run(func(t framework.TestContext) {
  1324  				opt := opt.DeepCopy()
  1325  				opt.HTTP.Path = "/headers"
  1326  				if opt.HTTP.Headers == nil {
  1327  					opt.HTTP.Headers = map[string][]string{}
  1328  				}
  1329  				opt.HTTP.Headers.Set("x-test-header", "do-not-match")
  1330  				opt.Check = CheckDeny
  1331  				overrideCheck(&opt)
  1332  				src.CallOrFail(t, opt)
  1333  			})
  1334  		})
  1335  	})
  1336  }
  1337  
  1338  func TestL7JWT(t *testing.T) {
  1339  	// Workaround https://github.com/istio/istio/issues/43239
  1340  	framework.NewTest(t).Run(func(t framework.TestContext) {
  1341  		applyDrainingWorkaround(t)
  1342  		runTestContext(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
  1343  			if opt.Scheme != scheme.HTTP {
  1344  				return
  1345  			}
  1346  			// Ensure we don't get stuck on old connections with old RBAC rules. This causes 45s test times
  1347  			// due to draining.
  1348  			opt.NewConnectionPerRequest = true
  1349  			if src.Config().IsUncaptured() {
  1350  				// TODO: fix this and remove this skip
  1351  				t.Skip("https://github.com/istio/istio/issues/43238")
  1352  			}
  1353  
  1354  			if !dst.Config().HasAnyWaypointProxy() {
  1355  				t.Skip("L7 JWT is only for waypoints")
  1356  			}
  1357  
  1358  			switch {
  1359  			case dst.Config().HasWorkloadAddressedWaypointProxy() && !dst.Config().HasServiceAddressedWaypointProxy():
  1360  				// send traffic to the workload instead of the service so it will redirect to the WL waypoint
  1361  				opt.Address = dst.MustWorkloads().Addresses()[0]
  1362  				opt.Port = echo.Port{ServicePort: ports.All().MustForName(opt.Port.Name).WorkloadPort}
  1363  				if src == dst {
  1364  					t.Skip("self call is not captured, L7 features will not work")
  1365  				}
  1366  			}
  1367  
  1368  			t.ConfigIstio().New().EvalFile(apps.Namespace.Name(), map[string]any{
  1369  				param.Namespace.String(): apps.Namespace.Name(),
  1370  				"Services":               apps.ServiceAddressedWaypoint,
  1371  				"To":                     dst,
  1372  			}, "testdata/requestauthn/waypoint-jwt.yaml.tmpl").ApplyOrFail(t)
  1373  
  1374  			t.NewSubTest("deny without token").Run(func(t framework.TestContext) {
  1375  				opt := opt.DeepCopy()
  1376  				opt.HTTP.Path = "/"
  1377  				opt.Check = check.Status(http.StatusForbidden)
  1378  				src.CallOrFail(t, opt)
  1379  			})
  1380  
  1381  			t.NewSubTest("allow with sub-1 token").Run(func(t framework.TestContext) {
  1382  				opt := opt.DeepCopy()
  1383  				opt.HTTP.Path = "/"
  1384  				opt.HTTP.Headers = headers.New().
  1385  					WithAuthz(jwt.TokenIssuer1).
  1386  					Build()
  1387  				opt.Check = check.OK()
  1388  			})
  1389  
  1390  			t.NewSubTest("deny with sub-3 token due to ignored RequestAuthentication").Run(func(t framework.TestContext) {
  1391  				opt := opt.DeepCopy()
  1392  				opt.HTTP.Path = "/"
  1393  				opt.HTTP.Headers = headers.New().
  1394  					WithAuthz(jwt.TokenIssuer3).
  1395  					Build()
  1396  				opt.Check = check.Status(http.StatusUnauthorized)
  1397  				src.CallOrFail(t, opt)
  1398  			})
  1399  
  1400  			t.NewSubTest("deny with sub-2 token").Run(func(t framework.TestContext) {
  1401  				opt := opt.DeepCopy()
  1402  				opt.HTTP.Path = "/"
  1403  				opt.HTTP.Headers = headers.New().
  1404  					WithAuthz(jwt.TokenIssuer2).
  1405  					Build()
  1406  				opt.Check = check.Status(http.StatusForbidden)
  1407  				src.CallOrFail(t, opt)
  1408  			})
  1409  
  1410  			t.NewSubTest("deny with expired token").Run(func(t framework.TestContext) {
  1411  				opt := opt.DeepCopy()
  1412  				opt.HTTP.Path = "/"
  1413  				opt.HTTP.Headers = headers.New().
  1414  					WithAuthz(jwt.TokenExpired).
  1415  					Build()
  1416  				opt.Check = check.Status(http.StatusUnauthorized)
  1417  				src.CallOrFail(t, opt)
  1418  			})
  1419  
  1420  			t.NewSubTest("allow healthz").Run(func(t framework.TestContext) {
  1421  				opt := opt.DeepCopy()
  1422  				opt.HTTP.Path = "/healthz"
  1423  				opt.Check = check.OK()
  1424  				src.CallOrFail(t, opt)
  1425  			})
  1426  		})
  1427  	})
  1428  }
  1429  
  1430  func applyDrainingWorkaround(t framework.TestContext) {
  1431  	// Workaround https://github.com/istio/istio/issues/43239
  1432  	t.ConfigIstio().YAML(apps.Namespace.Name(), `apiVersion: networking.istio.io/v1alpha3
  1433  kind: DestinationRule
  1434  metadata:
  1435    name: single-request
  1436  spec:
  1437    host: '*.svc.cluster.local'
  1438    trafficPolicy:
  1439      connectionPool:
  1440        http:
  1441          maxRequestsPerConnection: 1`).ApplyOrFail(t)
  1442  }
  1443  
  1444  // Relies on the suite running in a cluster with a CNI which enforces K8s netpol but presently has no check
  1445  func TestK8sNetPol(t *testing.T) {
  1446  	framework.NewTest(t).
  1447  		Run(func(t framework.TestContext) {
  1448  			t.Skip("https://github.com/istio/istio/issues/49301")
  1449  			systemNM := istio.ClaimSystemNamespaceOrFail(t, t)
  1450  
  1451  			// configure a NetPol which will only allow HBONE traffic in the test app namespace
  1452  			// we should figure out what our recommendation for NetPol will be and have this reflect it
  1453  			t.ConfigIstio().File(apps.Namespace.Name(), "testdata/only-hbone.yaml").ApplyOrFail(t)
  1454  
  1455  			Always := func(echo.Instance, echo.CallOptions) bool {
  1456  				return true
  1457  			}
  1458  			Never := func(echo.Instance, echo.CallOptions) bool {
  1459  				return false
  1460  			}
  1461  			SameNetwork := func(from echo.Instance, to echo.Target) echo.Instances {
  1462  				return match.Network(from.Config().Cluster.NetworkName()).GetMatches(to.Instances())
  1463  			}
  1464  			SupportsHBone := func(from echo.Instance, opts echo.CallOptions) bool {
  1465  				if !from.Config().IsUncaptured() && !opts.To.Config().IsUncaptured() {
  1466  					return true
  1467  				}
  1468  				if !from.Config().IsUncaptured() && opts.To.Config().HasSidecar() {
  1469  					return true
  1470  				}
  1471  				if from.Config().HasSidecar() && !opts.To.Config().IsUncaptured() {
  1472  					return true
  1473  				}
  1474  				if from.Config().HasSidecar() && opts.To.Config().HasSidecar() {
  1475  					return true
  1476  				}
  1477  				return false
  1478  			}
  1479  			_ = Never
  1480  			_ = SameNetwork
  1481  			testCases := []reachability.TestCase{
  1482  				{
  1483  					ConfigFile:    "beta-mtls-on.yaml",
  1484  					Namespace:     systemNM,
  1485  					Include:       Always,
  1486  					ExpectSuccess: SupportsHBone,
  1487  					// we do not expect HBONE traffic to have mutated user traffic
  1488  					// presently ExpectMTLS is checking that headers were added to user traffic
  1489  					ExpectMTLS: Never,
  1490  				},
  1491  				{
  1492  					ConfigFile:    "beta-mtls-permissive.yaml",
  1493  					Namespace:     systemNM,
  1494  					Include:       Always,
  1495  					ExpectSuccess: SupportsHBone,
  1496  					// we do not expect HBONE traffic to have mutated user traffic
  1497  					// presently ExpectMTLS is checking that headers were added to user traffic
  1498  					ExpectMTLS: Never,
  1499  				},
  1500  				{
  1501  					ConfigFile:    "beta-mtls-off.yaml",
  1502  					Namespace:     systemNM,
  1503  					Include:       Always,
  1504  					ExpectSuccess: SupportsHBone,
  1505  					// we do not expect HBONE traffic to have mutated user traffic
  1506  					// presently ExpectMTLS is checking that headers were added to user traffic
  1507  					ExpectMTLS: Never,
  1508  				},
  1509  			}
  1510  			RunReachability(testCases, t)
  1511  		})
  1512  }
  1513  
  1514  func TestMTLS(t *testing.T) {
  1515  	framework.NewTest(t).
  1516  		Run(func(t framework.TestContext) {
  1517  			t.Skip("https://github.com/istio/istio/issues/42696")
  1518  			systemNM := istio.ClaimSystemNamespaceOrFail(t, t)
  1519  			// mtlsOnExpect defines our expectations for when mTLS is expected when its enabled
  1520  			mtlsOnExpect := func(from echo.Instance, opts echo.CallOptions) bool {
  1521  				if from.Config().IsNaked() || opts.To.Config().IsNaked() {
  1522  					// If one of the two endpoints is naked, we don't send mTLS
  1523  					return false
  1524  				}
  1525  				if opts.To.Config().IsHeadless() && opts.To.Instances().Contains(from) {
  1526  					// pod calling its own pod IP will not be intercepted
  1527  					return false
  1528  				}
  1529  				return true
  1530  			}
  1531  			Always := func(echo.Instance, echo.CallOptions) bool {
  1532  				return true
  1533  			}
  1534  			Never := func(echo.Instance, echo.CallOptions) bool {
  1535  				return false
  1536  			}
  1537  			SameNetwork := func(from echo.Instance, to echo.Target) echo.Instances {
  1538  				return match.Network(from.Config().Cluster.NetworkName()).GetMatches(to.Instances())
  1539  			}
  1540  			_ = Never
  1541  			_ = SameNetwork
  1542  			testCases := []reachability.TestCase{
  1543  				{
  1544  					ConfigFile: "beta-mtls-on.yaml",
  1545  					Namespace:  systemNM,
  1546  					Include:    Always,
  1547  					ExpectSuccess: func(from echo.Instance, opts echo.CallOptions) bool {
  1548  						if from.Config().HasProxyCapabilities() != opts.To.Config().HasProxyCapabilities() {
  1549  							if from.Config().HasProxyCapabilities() && !from.Config().HasAnyWaypointProxy() {
  1550  								if from.Config().HasSidecar() && !opts.To.Config().HasProxyCapabilities() {
  1551  									// Sidecar respects it ISTIO_MUTUAL, will only send mTLS
  1552  									return false
  1553  								}
  1554  								return true
  1555  							}
  1556  							if !from.Config().HasProxyCapabilities() && opts.To.Config().HasAnyWaypointProxy() {
  1557  								// TODO: support hairpin
  1558  								return true
  1559  							}
  1560  							if !from.Config().HasProxyCapabilities() && !opts.To.Config().HasSidecar() {
  1561  								// TODO: https://github.com/istio/istio/issues/42696
  1562  								return true
  1563  							}
  1564  							return false
  1565  						}
  1566  						if !from.Config().HasProxyCapabilities() && opts.To.Config().HasSidecar() {
  1567  							return false
  1568  						}
  1569  						return true
  1570  					},
  1571  					ExpectMTLS: mtlsOnExpect,
  1572  				},
  1573  				{
  1574  					ConfigFile: "beta-mtls-permissive.yaml",
  1575  					Namespace:  systemNM,
  1576  					Include: func(_ echo.Instance, opts echo.CallOptions) bool {
  1577  						// Exclude calls to naked since we are applying ISTIO_MUTUAL
  1578  						return !opts.To.Config().IsNaked()
  1579  					},
  1580  					ExpectSuccess: func(from echo.Instance, opts echo.CallOptions) bool {
  1581  						if (from.Config().HasAnyWaypointProxy() || from.Config().HasSidecar()) && !opts.To.Config().HasProxyCapabilities() {
  1582  							return false
  1583  						}
  1584  						return true
  1585  					},
  1586  					ExpectMTLS: mtlsOnExpect,
  1587  				},
  1588  				{
  1589  					ConfigFile:    "beta-mtls-off.yaml",
  1590  					Namespace:     systemNM,
  1591  					Include:       Always,
  1592  					ExpectSuccess: Always,
  1593  					ExpectMTLS:    Never,
  1594  					// Without TLS we can't perform SNI routing required for multi-network
  1595  					ExpectDestinations: SameNetwork,
  1596  				},
  1597  				{
  1598  					ConfigFile:    "plaintext-to-permissive.yaml",
  1599  					Namespace:     systemNM,
  1600  					Include:       Always,
  1601  					ExpectSuccess: Always,
  1602  					ExpectMTLS:    Never,
  1603  					// Since we are only sending plaintext and Without TLS
  1604  					// we can't perform SNI routing required for multi-network
  1605  					ExpectDestinations: SameNetwork,
  1606  				},
  1607  				{
  1608  					ConfigFile: "beta-mtls-automtls.yaml",
  1609  					Namespace:  apps.Namespace,
  1610  					Include:    Always,
  1611  					ExpectSuccess: func(from echo.Instance, opts echo.CallOptions) bool {
  1612  						if !from.Config().HasProxyCapabilities() && !opts.To.Config().HasSidecar() {
  1613  							// TODO: https://github.com/istio/istio/issues/42696
  1614  							return true
  1615  						}
  1616  						// autoMtls doesn't work for client that doesn't have proxy, unless target doesn't
  1617  						// have proxy neither.
  1618  						if !from.Config().HasProxyCapabilities() {
  1619  							return !opts.To.Config().HasProxyCapabilities()
  1620  						}
  1621  						return true
  1622  					},
  1623  					ExpectMTLS: mtlsOnExpect,
  1624  				},
  1625  				{
  1626  					ConfigFile: "no-peer-authn.yaml",
  1627  					Namespace:  systemNM,
  1628  					Include: func(_ echo.Instance, opts echo.CallOptions) bool {
  1629  						// Exclude calls to naked since we are applying ISTIO_MUTUAL
  1630  						return !opts.To.Config().IsNaked()
  1631  					},
  1632  					ExpectSuccess: func(from echo.Instance, opts echo.CallOptions) bool {
  1633  						if from.Config().HasSidecar() && !opts.To.Config().HasProxyCapabilities() {
  1634  							// Sidecar respects it
  1635  							return false
  1636  						}
  1637  						if from.Config().HasAnyWaypointProxy() && !opts.To.Config().HasProxyCapabilities() {
  1638  							// Waypoint respects it
  1639  							return false
  1640  						}
  1641  						return true
  1642  					},
  1643  					ExpectMTLS: mtlsOnExpect,
  1644  				},
  1645  				{
  1646  					ConfigFile: "global-plaintext.yaml",
  1647  					Namespace:  systemNM,
  1648  					ExpectDestinations: func(from echo.Instance, to echo.Target) echo.Instances {
  1649  						// Without TLS we can't perform SNI routing required for multi-network
  1650  						return match.Network(from.Config().Cluster.NetworkName()).GetMatches(to.Instances())
  1651  					},
  1652  					ExpectSuccess: Always,
  1653  					ExpectMTLS:    Never,
  1654  				},
  1655  				{
  1656  					ConfigFile: "automtls-passthrough.yaml",
  1657  					Namespace:  systemNM,
  1658  					Include: func(_ echo.Instance, opts echo.CallOptions) bool {
  1659  						// VM passthrough doesn't work. We will send traffic to the ClusterIP of
  1660  						// the VM service, which will have 0 Endpoints. If we generated
  1661  						// EndpointSlice's for VMs this might work.
  1662  						return !opts.To.Config().IsVM()
  1663  					},
  1664  					ExpectSuccess: func(from echo.Instance, opts echo.CallOptions) bool {
  1665  						// nolint: gosimple
  1666  						if from.Config().HasAnyWaypointProxy() {
  1667  							if opts.To.Config().HasServiceAddressedWaypointProxy() {
  1668  								return true
  1669  							}
  1670  							// TODO: https://github.com/istio/istio/issues/43242
  1671  							return false
  1672  						}
  1673  						return true
  1674  					},
  1675  					ExpectMTLS: func(from echo.Instance, opts echo.CallOptions) bool {
  1676  						return mtlsOnExpect(from, opts)
  1677  					},
  1678  
  1679  					ExpectDestinations: func(from echo.Instance, to echo.Target) echo.Instances {
  1680  						// Since we are doing passthrough, only single cluster is relevant here, as we
  1681  						// are bypassing any Istio cluster load balancing
  1682  						return match.Cluster(from.Config().Cluster).GetMatches(to.Instances())
  1683  					},
  1684  				},
  1685  			}
  1686  			RunReachability(testCases, t)
  1687  		})
  1688  }
  1689  
  1690  func TestOutboundPolicyAllowAny(t *testing.T) {
  1691  	framework.NewTest(t).
  1692  		Run(func(t framework.TestContext) {
  1693  			skipOnNativeZtunnel(t, "TODO? not sure why this is broken")
  1694  			svcs := apps.All
  1695  			for _, svc := range svcs {
  1696  				if svc.Config().IsUncaptured() || svc.Config().HasSidecar() {
  1697  					continue
  1698  				}
  1699  				t.NewSubTestf("ALLOW_ANY %v to external service", svc.Config().Service).Run(func(t framework.TestContext) {
  1700  					// TODO use Sidecar to simulate external service (see tests/integration/pilot/mirror_test.go)
  1701  					svc.CallOrFail(t, echo.CallOptions{
  1702  						Address: "httpbin.org",
  1703  						Port:    echo.Port{Name: "http", ServicePort: 80},
  1704  						Scheme:  scheme.HTTP,
  1705  						HTTP: echo.HTTP{
  1706  							Path: "/headers",
  1707  						},
  1708  						Check: check.OK(),
  1709  					})
  1710  				})
  1711  			}
  1712  		})
  1713  }
  1714  
  1715  func TestServiceEntryDNS(t *testing.T) {
  1716  	framework.NewTest(t).
  1717  		Run(func(t framework.TestContext) {
  1718  			skipOnNativeZtunnel(t, "ServiceEntry not supported")
  1719  			svcs := apps.All
  1720  			for _, svc := range svcs {
  1721  				if svc.Config().IsUncaptured() || svc.Config().HasSidecar() {
  1722  					continue
  1723  				}
  1724  				if err := t.ConfigIstio().YAML(svc.NamespaceName(), `apiVersion: networking.istio.io/v1beta1
  1725  kind: ServiceEntry
  1726  metadata:
  1727    name: externalservice-httpbin
  1728  spec:
  1729    exportTo:
  1730    - .
  1731    hosts:
  1732    - httpbin.org
  1733    ports:
  1734    - name: http
  1735      number: 80
  1736      protocol: HTTP
  1737    resolution: DNS`).Apply(apply.NoCleanup); err != nil {
  1738  					t.Fatal(err)
  1739  				}
  1740  				t.NewSubTestf("%v to ServiceEntry", svc.Config().Service).Run(func(t framework.TestContext) {
  1741  					// TODO use Sidecar to simulate external service (see tests/integration/pilot/mirror_test.go)
  1742  					svc.CallOrFail(t, echo.CallOptions{
  1743  						Address: "httpbin.org",
  1744  						Port:    echo.Port{Name: "http", ServicePort: 80},
  1745  						Scheme:  scheme.HTTP,
  1746  						HTTP: echo.HTTP{
  1747  							Path: "/headers",
  1748  						},
  1749  						Check: check.OK(),
  1750  					})
  1751  				})
  1752  			}
  1753  		})
  1754  }
  1755  
  1756  func TestServiceEntryInlinedWorkloadEntry(t *testing.T) {
  1757  	framework.NewTest(t).
  1758  		Run(func(t framework.TestContext) {
  1759  			testCases := []struct {
  1760  				location   v1alpha3.ServiceEntry_Location
  1761  				resolution v1alpha3.ServiceEntry_Resolution
  1762  				to         echo.Instances
  1763  			}{
  1764  				{
  1765  					location:   v1alpha3.ServiceEntry_MESH_INTERNAL,
  1766  					resolution: v1alpha3.ServiceEntry_STATIC,
  1767  					to:         apps.Mesh,
  1768  				},
  1769  				{
  1770  					location:   v1alpha3.ServiceEntry_MESH_EXTERNAL,
  1771  					resolution: v1alpha3.ServiceEntry_STATIC,
  1772  					to:         apps.MeshExternal,
  1773  				},
  1774  				// TODO dns cases
  1775  			}
  1776  
  1777  			// Configure a gateway with one app as the destination to be accessible through the ingress
  1778  			t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
  1779  				"Destination": apps.Captured[0].Config().Service,
  1780  			}, `apiVersion: networking.istio.io/v1alpha3
  1781  kind: Gateway
  1782  metadata:
  1783    name: gateway
  1784  spec:
  1785    selector:
  1786      istio: ingressgateway
  1787    servers:
  1788    - port:
  1789        number: 80
  1790        name: http
  1791        protocol: HTTP
  1792      hosts: ["*"]
  1793  ---
  1794  apiVersion: networking.istio.io/v1alpha3
  1795  kind: VirtualService
  1796  metadata:
  1797    name: route
  1798  spec:
  1799    gateways:
  1800    - gateway
  1801    hosts:
  1802    - "*"
  1803    http:
  1804    - route:
  1805      - destination:
  1806          host: "{{.Destination}}"
  1807  `).ApplyOrFail(t)
  1808  
  1809  			cfg := config.YAML(`
  1810  {{ $to := .To }}
  1811  apiVersion: networking.istio.io/v1beta1
  1812  kind: ServiceEntry
  1813  metadata:
  1814    name: test-se
  1815  spec:
  1816    hosts:
  1817    - serviceentry.istio.io # not used
  1818    addresses:
  1819    - 111.111.222.222
  1820    ports:
  1821    - number: 80
  1822      name: http
  1823      protocol: HTTP
  1824    resolution: {{.Resolution}}
  1825    location: {{.Location}}
  1826    endpoints:
  1827    # we send directly to a Pod IP here. This is essentially headless
  1828    - address: {{.IngressIp}} # TODO won't work with DNS resolution tests
  1829      ports:
  1830        http: {{.IngressHttpPort}}`).
  1831  				WithParams(param.Params{}.SetWellKnown(param.Namespace, apps.Namespace))
  1832  
  1833  			ips, ports := istio.DefaultIngressOrFail(t, t).HTTPAddresses()
  1834  			for _, tc := range testCases {
  1835  				tc := tc
  1836  				for i, ip := range ips {
  1837  					t.NewSubTestf("%s %s %s", tc.location, tc.resolution, ip).Run(func(t framework.TestContext) {
  1838  						echotest.
  1839  							New(t, apps.All).
  1840  							// TODO eventually we can do this for uncaptured -> l7
  1841  							FromMatch(match.Not(match.ServiceName(echo.NamespacedName{
  1842  								Name:      "uncaptured",
  1843  								Namespace: apps.Namespace,
  1844  							}))).
  1845  							Config(cfg.WithParams(param.Params{
  1846  								"Resolution":      tc.resolution.String(),
  1847  								"Location":        tc.location.String(),
  1848  								"IngressIp":       ip,
  1849  								"IngressHttpPort": ports[i],
  1850  							})).
  1851  							Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  1852  								// TODO validate L7 processing/some headers indicating we reach the svc we wanted
  1853  								from.CallOrFail(t, echo.CallOptions{
  1854  									Address: "111.111.222.222",
  1855  									Port:    to.PortForName("http"),
  1856  								})
  1857  							})
  1858  					})
  1859  				}
  1860  			}
  1861  		})
  1862  }
  1863  
  1864  func TestServiceEntrySelectsWorkloadEntry(t *testing.T) {
  1865  	framework.NewTest(t).
  1866  		Run(func(t framework.TestContext) {
  1867  			testCases := []struct {
  1868  				location   v1alpha3.ServiceEntry_Location
  1869  				resolution v1alpha3.ServiceEntry_Resolution
  1870  				to         echo.Instances
  1871  			}{
  1872  				{
  1873  					location:   v1alpha3.ServiceEntry_MESH_INTERNAL,
  1874  					resolution: v1alpha3.ServiceEntry_STATIC,
  1875  					to:         apps.Mesh,
  1876  				},
  1877  				{
  1878  					location:   v1alpha3.ServiceEntry_MESH_EXTERNAL,
  1879  					resolution: v1alpha3.ServiceEntry_STATIC,
  1880  					to:         apps.MeshExternal,
  1881  				},
  1882  				// TODO dns cases
  1883  			}
  1884  
  1885  			// Configure a gateway with one app as the destination to be accessible through the ingress
  1886  			t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
  1887  				"Destination": apps.Captured[0].Config().Service,
  1888  			}, `apiVersion: networking.istio.io/v1alpha3
  1889  kind: Gateway
  1890  metadata:
  1891    name: gateway
  1892  spec:
  1893    selector:
  1894      istio: ingressgateway
  1895    servers:
  1896    - port:
  1897        number: 80
  1898        name: http
  1899        protocol: HTTP
  1900      hosts: ["*"]
  1901  ---
  1902  apiVersion: networking.istio.io/v1alpha3
  1903  kind: VirtualService
  1904  metadata:
  1905    name: route
  1906  spec:
  1907    gateways:
  1908    - gateway
  1909    hosts:
  1910    - "*"
  1911    http:
  1912    - route:
  1913      - destination:
  1914          host: "{{.Destination}}"
  1915  `).ApplyOrFail(t)
  1916  
  1917  			cfg := config.YAML(`
  1918  {{ $to := .To }}
  1919  apiVersion: networking.istio.io/v1beta1
  1920  kind: WorkloadEntry
  1921  metadata:
  1922    name: test-we
  1923  spec:
  1924    address: {{.IngressIp}}
  1925    ports:
  1926      http: {{.IngressHttpPort}}
  1927    labels:
  1928      app: selected
  1929  ---
  1930  apiVersion: networking.istio.io/v1beta1
  1931  kind: ServiceEntry
  1932  metadata:
  1933    name: test-se
  1934  spec:
  1935    hosts:
  1936    - serviceentry.istio.io # not used
  1937    addresses:
  1938    - 111.111.222.222
  1939    ports:
  1940    - number: 80
  1941      name: http
  1942      protocol: HTTP
  1943    resolution: {{.Resolution}}
  1944    location: {{.Location}}
  1945    workloadSelector:
  1946      labels:
  1947        app: selected`).
  1948  				WithParams(param.Params{}.SetWellKnown(param.Namespace, apps.Namespace))
  1949  
  1950  			ips, ports := istio.DefaultIngressOrFail(t, t).HTTPAddresses()
  1951  			for _, tc := range testCases {
  1952  				tc := tc
  1953  				for i, ip := range ips {
  1954  					t.NewSubTestf("%s %s %s", tc.location, tc.resolution, ip).Run(func(t framework.TestContext) {
  1955  						echotest.
  1956  							New(t, apps.All).
  1957  							// TODO eventually we can do this for uncaptured -> l7
  1958  							FromMatch(match.Not(match.ServiceName(echo.NamespacedName{
  1959  								Name:      "uncaptured",
  1960  								Namespace: apps.Namespace,
  1961  							}))).
  1962  							Config(cfg.WithParams(param.Params{
  1963  								"Resolution":      tc.resolution.String(),
  1964  								"Location":        tc.location.String(),
  1965  								"IngressIp":       ip,
  1966  								"IngressHttpPort": ports[i],
  1967  							})).
  1968  							Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  1969  								// TODO validate L7 processing/some headers indicating we reach the svc we wanted
  1970  								from.CallOrFail(t, echo.CallOptions{
  1971  									Address: "111.111.222.222",
  1972  									Port:    to.PortForName("http"),
  1973  								})
  1974  							})
  1975  					})
  1976  				}
  1977  
  1978  			}
  1979  		})
  1980  }
  1981  
  1982  func TestServiceEntrySelectsUncapturedPod(t *testing.T) {
  1983  	framework.NewTest(t).
  1984  		Run(func(t framework.TestContext) {
  1985  			testCases := []struct {
  1986  				location   v1alpha3.ServiceEntry_Location
  1987  				resolution v1alpha3.ServiceEntry_Resolution
  1988  				to         echo.Instances
  1989  			}{
  1990  				{
  1991  					location:   v1alpha3.ServiceEntry_MESH_INTERNAL,
  1992  					resolution: v1alpha3.ServiceEntry_STATIC,
  1993  					to:         apps.Mesh,
  1994  				},
  1995  				{
  1996  					location:   v1alpha3.ServiceEntry_MESH_EXTERNAL,
  1997  					resolution: v1alpha3.ServiceEntry_STATIC,
  1998  					to:         apps.MeshExternal,
  1999  				},
  2000  				// TODO dns cases
  2001  			}
  2002  
  2003  			cfg := config.YAML(`
  2004  {{ $to := .To }}
  2005  apiVersion: networking.istio.io/v1beta1
  2006  kind: ServiceEntry
  2007  metadata:
  2008    name: test-se
  2009  spec:
  2010    hosts:
  2011    - serviceentry.istio.io
  2012    addresses:
  2013    - 111.111.222.222
  2014    ports:
  2015    - number: 80
  2016      name: http
  2017      protocol: HTTP
  2018      targetPort: 8080
  2019    resolution: {{.Resolution}}
  2020    location: {{.Location}}
  2021    workloadSelector:
  2022      labels:
  2023        app: uncaptured`). // cannot select pods captured in ambient mesh; IPs are unique per network
  2024  				WithParams(param.Params{}.SetWellKnown(param.Namespace, apps.Namespace))
  2025  
  2026  			for _, tc := range testCases {
  2027  				tc := tc
  2028  				t.NewSubTestf("%s %s", tc.location, tc.resolution).Run(func(t framework.TestContext) {
  2029  					echotest.
  2030  						New(t, apps.All).
  2031  						// TODO eventually we can do this for uncaptured -> l7
  2032  						FromMatch(match.Not(match.ServiceName(echo.NamespacedName{
  2033  							Name:      "uncaptured",
  2034  							Namespace: apps.Namespace,
  2035  						}))).
  2036  						ToMatch(match.ServiceName(echo.NamespacedName{
  2037  							Name:      "uncaptured",
  2038  							Namespace: apps.Namespace,
  2039  						})).
  2040  						Config(cfg.WithParams(param.Params{
  2041  							"Resolution": tc.resolution.String(),
  2042  							"Location":   tc.location.String(),
  2043  						})).
  2044  						Run(func(t framework.TestContext, from echo.Instance, to echo.Target) {
  2045  							from.CallOrFail(t, echo.CallOptions{
  2046  								Address: "serviceentry.istio.io", // host here is important to test ztunnel DNS resolution
  2047  								Port:    to.PortForName("http"),
  2048  								// sample response:
  2049  								//
  2050  								// ServiceVersion=v1
  2051  								// ServicePort=8080
  2052  								// Host=serviceentry.istio.io
  2053  								// URL=/any/path
  2054  								// Cluster=cluster-0
  2055  								// IstioVersion=
  2056  								// Method=GET
  2057  								// Proto=HTTP/1.1
  2058  								// IP=10.244.2.20
  2059  								// Alpn=
  2060  								// RequestHeader=Accept:*/*
  2061  								// RequestHeader=User-Agent:curl/7.81.0
  2062  								// Hostname=uncaptured-v1-868c9b59b5-rxvfq
  2063  								Check: check.BodyContains(`Hostname=uncaptured-v`), // can hit v1 or v2
  2064  							})
  2065  						})
  2066  				})
  2067  			}
  2068  		})
  2069  }
  2070  
  2071  // Ambient ServiceEntry support for auto assigned vips is lacking for now, but planned.
  2072  // for more, see https://github.com/istio/istio/pull/45621#discussion_r1254970579
  2073  func TestServiceEntryDNSWithAutoAssign(t *testing.T) {
  2074  	framework.NewTest(t).
  2075  		Run(func(t framework.TestContext) {
  2076  			t.Skip("this will work once we resolve https://github.com/istio/ztunnel/issues/582")
  2077  			yaml := `apiVersion: networking.istio.io/v1beta1
  2078  kind: ServiceEntry
  2079  metadata:
  2080    name: test-service-entry
  2081  spec:
  2082    hosts:
  2083    - serviceentry.istio.io
  2084    ports:
  2085    - name: http
  2086      number: 80
  2087      protocol: HTTP
  2088      targetPort: 8080
  2089    location: MESH_EXTERNAL
  2090    resolution: STATIC # not honored for now; everything is static
  2091    workloadSelector:
  2092      labels:
  2093        app: uncaptured` // cannot select pods captured in ambient mesh; IPs are unique per network
  2094  			svcs := apps.All
  2095  			for _, svc := range svcs {
  2096  				if svc.Config().IsUncaptured() || svc.Config().HasSidecar() {
  2097  					// TODO(kdorosh) skip if waypoint? waypoints should not need to resolve service entry hostnames
  2098  					continue
  2099  				}
  2100  				if err := t.ConfigIstio().YAML(svc.NamespaceName(), yaml).Apply(apply.NoCleanup); err != nil {
  2101  					t.Fatal(err)
  2102  				}
  2103  				t.NewSubTestf("%v to uncaptured-v1 via ServiceEntry", svc.Config().Service).Run(func(t framework.TestContext) {
  2104  					svc.CallOrFail(t, echo.CallOptions{
  2105  						Address: "serviceentry.istio.io",
  2106  						Port:    echo.Port{Name: "http", ServicePort: 80},
  2107  						Scheme:  scheme.HTTP,
  2108  						HTTP: echo.HTTP{
  2109  							Path: "/any/path",
  2110  						},
  2111  						// sample response:
  2112  						//
  2113  						// ServiceVersion=v1
  2114  						// ServicePort=8080
  2115  						// Host=serviceentry.istio.io
  2116  						// URL=/any/path
  2117  						// Cluster=cluster-0
  2118  						// IstioVersion=
  2119  						// Method=GET
  2120  						// Proto=HTTP/1.1
  2121  						// IP=10.244.2.20
  2122  						// Alpn=
  2123  						// RequestHeader=Accept:*/*
  2124  						// RequestHeader=User-Agent:curl/7.81.0
  2125  						// Hostname=uncaptured-v1-868c9b59b5-rxvfq
  2126  						Check: check.BodyContains(`Hostname=uncaptured-v`), // can hit v1 or v2
  2127  					})
  2128  				})
  2129  
  2130  				if err := t.ConfigIstio().YAML(svc.NamespaceName(), yaml).Delete(); err != nil {
  2131  					t.Fatal(err)
  2132  				}
  2133  
  2134  				t.NewSubTestf("%v to uncaptured via ServiceEntry -- cleanup", svc.Config().Service).Run(func(t framework.TestContext) {
  2135  					svc.CallOrFail(t, echo.CallOptions{
  2136  						Address: "serviceentry.istio.io",
  2137  						Port:    echo.Port{Name: "http", ServicePort: 80},
  2138  						Scheme:  scheme.HTTP,
  2139  						HTTP: echo.HTTP{
  2140  							Path: "/any/path",
  2141  						},
  2142  						Check: check.NotOK(),
  2143  					})
  2144  				})
  2145  			}
  2146  		})
  2147  }
  2148  
  2149  // Run runs the given reachability test cases with the context.
  2150  func RunReachability(testCases []reachability.TestCase, t framework.TestContext) {
  2151  	runTest := func(t framework.TestContext, f func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions)) {
  2152  		svcs := apps.All
  2153  		for _, src := range svcs {
  2154  			src := src
  2155  			t.NewSubTestf("from %v", src.Config().Service).RunParallel(func(t framework.TestContext) {
  2156  				for _, dst := range svcs {
  2157  					dst := dst
  2158  					t.NewSubTestf("to %v", dst.Config().Service).RunParallel(func(t framework.TestContext) {
  2159  						for _, opt := range callOptions {
  2160  							opt := opt
  2161  							t.NewSubTestf("%v", opt.Scheme).RunParallel(func(t framework.TestContext) {
  2162  								opt = opt.DeepCopy()
  2163  								opt.To = dst
  2164  								opt.Check = check.OK()
  2165  								f(t, src, dst, opt)
  2166  							})
  2167  						}
  2168  					})
  2169  				}
  2170  			})
  2171  		}
  2172  	}
  2173  	for _, c := range testCases {
  2174  		// Create a copy to avoid races, as tests are run in parallel
  2175  		c := c
  2176  		testName := strings.TrimSuffix(c.ConfigFile, filepath.Ext(c.ConfigFile))
  2177  		t.NewSubTest(testName).Run(func(t framework.TestContext) {
  2178  			// Apply the policy.
  2179  			cfg := t.ConfigIstio().File(c.Namespace.Name(), filepath.Join("testdata", c.ConfigFile))
  2180  			retry.UntilSuccessOrFail(t, func() error {
  2181  				t.Logf("[%s] [%v] Apply config %s", testName, time.Now(), c.ConfigFile)
  2182  				// TODO(https://github.com/istio/istio/issues/20460) We shouldn't need a retry loop
  2183  				return cfg.Apply(apply.Wait)
  2184  			})
  2185  			runTest(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) {
  2186  				expectSuccess := c.ExpectSuccess(src, opt)
  2187  				expectMTLS := c.ExpectMTLS(src, opt)
  2188  
  2189  				var tpe string
  2190  				if expectSuccess {
  2191  					tpe = "positive"
  2192  					opt.Check = check.And(
  2193  						check.OK(),
  2194  						check.ReachedTargetClusters(t))
  2195  					if expectMTLS {
  2196  						opt.Check = check.And(opt.Check, check.MTLSForHTTP())
  2197  					}
  2198  				} else {
  2199  					tpe = "negative"
  2200  					opt.Check = check.NotOK()
  2201  				}
  2202  				t.Logf("expected result: %v", tpe)
  2203  
  2204  				include := c.Include
  2205  				if include == nil {
  2206  					include = func(_ echo.Instance, _ echo.CallOptions) bool { return true }
  2207  				}
  2208  				if !include(src, opt) {
  2209  					t.Skip("excluded")
  2210  				}
  2211  				src.CallOrFail(t, opt)
  2212  			})
  2213  		})
  2214  	}
  2215  }
  2216  
  2217  func TestIngress(t *testing.T) {
  2218  	runIngressTest(t, func(t framework.TestContext, src ingress.Instance, dst echo.Instance, opt echo.CallOptions) {
  2219  		if opt.Scheme != scheme.HTTP {
  2220  			return
  2221  		}
  2222  
  2223  		// TODO implement waypoint enforcement mechanism
  2224  		// Ingress currently never sends to Waypoints
  2225  		// We cannot bypass the waypoint, so this fails.
  2226  		// if dst.Config().HasAnyWaypointProxy() {
  2227  		// 	opt.Check = check.Error()
  2228  		// }
  2229  
  2230  		t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{
  2231  			"Destination": dst.Config().Service,
  2232  		}, `apiVersion: networking.istio.io/v1alpha3
  2233  kind: Gateway
  2234  metadata:
  2235    name: gateway
  2236  spec:
  2237    selector:
  2238      istio: ingressgateway
  2239    servers:
  2240    - port:
  2241        number: 80
  2242        name: http
  2243        protocol: HTTP
  2244      hosts: ["*"]
  2245  ---
  2246  apiVersion: networking.istio.io/v1alpha3
  2247  kind: VirtualService
  2248  metadata:
  2249    name: route
  2250  spec:
  2251    gateways:
  2252    - gateway
  2253    hosts:
  2254    - "*"
  2255    http:
  2256    - route:
  2257      - destination:
  2258          host: "{{.Destination}}"
  2259  `).ApplyOrFail(t)
  2260  		src.CallOrFail(t, opt)
  2261  	})
  2262  }
  2263  
  2264  var CheckDeny = check.Or(
  2265  	check.ErrorContains("rpc error: code = PermissionDenied"), // gRPC
  2266  	check.ErrorContains("EOF"),                                // TCP envoy
  2267  	check.ErrorContains("read: connection reset by peer"),     // TCP ztunnel
  2268  	check.NoErrorAndStatus(http.StatusForbidden),              // HTTP
  2269  	check.NoErrorAndStatus(http.StatusServiceUnavailable),     // HTTP client, TCP server
  2270  )
  2271  
  2272  func runTest(t *testing.T, f func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions)) {
  2273  	framework.NewTest(t).Run(func(t framework.TestContext) {
  2274  		runTestContext(t, f)
  2275  	})
  2276  }
  2277  
  2278  func runTestContext(t framework.TestContext, f func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions)) {
  2279  	svcs := apps.All
  2280  	for _, src := range svcs {
  2281  		t.NewSubTestf("from %v", src.Config().Service).Run(func(t framework.TestContext) {
  2282  			for _, dst := range svcs {
  2283  				t.NewSubTestf("to %v", dst.Config().Service).Run(func(t framework.TestContext) {
  2284  					for _, opt := range callOptions {
  2285  						src, dst, opt := src, dst, opt
  2286  						t.NewSubTestf("%v", opt.Scheme).Run(func(t framework.TestContext) {
  2287  							opt = opt.DeepCopy()
  2288  							opt.To = dst
  2289  							opt.Check = check.OK()
  2290  							f(t, src, dst, opt)
  2291  						})
  2292  					}
  2293  				})
  2294  			}
  2295  		})
  2296  	}
  2297  }
  2298  
  2299  func runIngressTest(t *testing.T, f func(t framework.TestContext, src ingress.Instance, dst echo.Instance, opt echo.CallOptions)) {
  2300  	framework.NewTest(t).Run(func(t framework.TestContext) {
  2301  		svcs := apps.All
  2302  		for _, dst := range svcs {
  2303  			t.NewSubTestf("to %v", dst.Config().Service).Run(func(t framework.TestContext) {
  2304  				dst := dst
  2305  				opt := echo.CallOptions{
  2306  					Port:    echo.Port{Name: "http"},
  2307  					Scheme:  scheme.HTTP,
  2308  					Count:   5,
  2309  					Timeout: time.Second * 2,
  2310  					Check:   check.OK(),
  2311  					To:      dst,
  2312  				}
  2313  				f(t, istio.DefaultIngressOrFail(t, t), dst, opt)
  2314  			})
  2315  		}
  2316  	})
  2317  }
  2318  
  2319  // skipOnNativeZtunnel used to skip only when on rust based ztunnel; now this is the only option so it always skips
  2320  // TODO: fix all these cases and remove
  2321  func skipOnNativeZtunnel(tc framework.TestContext, reason string) {
  2322  	tc.Skipf("Not currently supported: %v", reason)
  2323  }
  2324  
  2325  func TestL7Telemetry(t *testing.T) {
  2326  	framework.NewTest(t).
  2327  		Run(func(tc framework.TestContext) {
  2328  			// ensure that some traffic from each captured workload is
  2329  			// sent to each waypoint proxy. This will likely have happened in
  2330  			// the other tests (without the teardown), but we want to make
  2331  			// sure that some traffic is seen. This test will not validate
  2332  			// exact traffic counts, but rather focus on validating that
  2333  			// the telemetry is being created and collected properly.
  2334  			for _, src := range apps.Captured {
  2335  				for _, dst := range apps.ServiceAddressedWaypoint {
  2336  					tc.NewSubTestf("from %q to %q", src.Config().Service, dst.Config().Service).Run(func(stc framework.TestContext) {
  2337  						localDst := dst
  2338  						localSrc := src
  2339  						opt := echo.CallOptions{
  2340  							Port:    echo.Port{Name: "http"},
  2341  							Scheme:  scheme.HTTP,
  2342  							Count:   5,
  2343  							Timeout: time.Second,
  2344  							Check:   check.OK(),
  2345  							To:      localDst,
  2346  						}
  2347  						// allow for delay between prometheus pulls from target pod
  2348  						// pulls should happen every 15s, so timeout if not found within 30s
  2349  
  2350  						query := buildQuery(localSrc, localDst)
  2351  						stc.Logf("prometheus query: %#v", query)
  2352  						err := retry.Until(func() bool {
  2353  							stc.Logf("sending call from %q to %q", deployName(localSrc), localDst.Config().Service)
  2354  							localSrc.CallOrFail(stc, opt)
  2355  							reqs, err := prom.QuerySum(localSrc.Config().Cluster, query)
  2356  							if err != nil {
  2357  								stc.Logf("could not query for traffic from %q to %q: %v", deployName(localSrc), localDst.Config().Service, err)
  2358  								return false
  2359  							}
  2360  							if reqs == 0.0 {
  2361  								stc.Logf("found zero-valued sum for traffic from %q to %q: %v", deployName(localSrc), localDst.Config().Service, err)
  2362  								return false
  2363  							}
  2364  							return true
  2365  						}, retry.Timeout(30*time.Second), retry.BackoffDelay(1*time.Second))
  2366  						if err != nil {
  2367  							util.PromDiff(t, prom, localSrc.Config().Cluster, query)
  2368  							stc.Errorf("could not validate L7 telemetry for %q to %q: %v", deployName(localSrc), localDst.Config().Service, err)
  2369  						}
  2370  					})
  2371  				}
  2372  			}
  2373  		})
  2374  }
  2375  
  2376  func TestL4Telemetry(t *testing.T) {
  2377  	framework.NewTest(t).
  2378  		Run(func(tc framework.TestContext) {
  2379  			// ensure that some traffic from each captured workload is
  2380  			// sent to each waypoint proxy. This will likely have happened in
  2381  			// the other tests (without the teardown), but we want to make
  2382  			// sure that some traffic is seen. This test will not validate
  2383  			// exact traffic counts, but rather focus on validating that
  2384  			// the telemetry is being created and collected properly.
  2385  			for _, src := range apps.Captured {
  2386  				for _, dst := range apps.Captured {
  2387  					tc.NewSubTestf("from %q to %q", src.Config().Service, dst.Config().Service).Run(func(stc framework.TestContext) {
  2388  						localDst := dst
  2389  						localSrc := src
  2390  						opt := echo.CallOptions{
  2391  							Port:    echo.Port{Name: "tcp"},
  2392  							Scheme:  scheme.TCP,
  2393  							Count:   5,
  2394  							Timeout: time.Second,
  2395  							Check:   check.OK(),
  2396  							To:      localDst,
  2397  						}
  2398  						// allow for delay between prometheus pulls from target pod
  2399  						// pulls should happen every 15s, so timeout if not found within 30s
  2400  
  2401  						query := buildL4Query(localSrc, localDst)
  2402  						stc.Logf("prometheus query: %#v", query)
  2403  						err := retry.Until(func() bool {
  2404  							stc.Logf("sending call from %q to %q", deployName(localSrc), localDst.Config().Service)
  2405  							localSrc.CallOrFail(stc, opt)
  2406  							reqs, err := prom.QuerySum(localSrc.Config().Cluster, query)
  2407  							if err != nil {
  2408  								stc.Logf("could not query for traffic from %q to %q: %v", deployName(localSrc), localDst.Config().Service, err)
  2409  								return false
  2410  							}
  2411  							if reqs == 0.0 {
  2412  								stc.Logf("found zero-valued sum for traffic from %q to %q: %v", deployName(localSrc), localDst.Config().Service, err)
  2413  								return false
  2414  							}
  2415  							return true
  2416  						}, retry.Timeout(15*time.Second), retry.BackoffDelay(1*time.Second))
  2417  						if err != nil {
  2418  							util.PromDiff(t, prom, localSrc.Config().Cluster, query)
  2419  							stc.Errorf("could not validate L4 telemetry for %q to %q: %v", deployName(localSrc), localDst.Config().Service, err)
  2420  						}
  2421  					})
  2422  				}
  2423  			}
  2424  		})
  2425  }
  2426  
  2427  func buildQuery(src, dst echo.Instance) prometheus.Query {
  2428  	query := prometheus.Query{}
  2429  
  2430  	srcns := src.NamespaceName()
  2431  	destns := dst.NamespaceName()
  2432  
  2433  	labels := map[string]string{
  2434  		"reporter":                       "waypoint",
  2435  		"request_protocol":               "http",
  2436  		"response_code":                  "200",
  2437  		"response_flags":                 "-",
  2438  		"connection_security_policy":     "mutual_tls",
  2439  		"destination_canonical_service":  dst.ServiceName(),
  2440  		"destination_canonical_revision": dst.Config().Version,
  2441  		"destination_service":            fmt.Sprintf("%s.%s.svc.cluster.local", dst.Config().Service, destns),
  2442  		"destination_principal":          fmt.Sprintf("spiffe://cluster.local/ns/%v/sa/%s", destns, dst.Config().AccountName()),
  2443  		"destination_service_name":       dst.Config().Service,
  2444  		"destination_workload":           deployName(dst),
  2445  		"destination_workload_namespace": destns,
  2446  		"destination_service_namespace":  destns,
  2447  		"source_canonical_service":       src.ServiceName(),
  2448  		"source_canonical_revision":      src.Config().Version,
  2449  		"source_principal":               "spiffe://" + src.Config().ServiceAccountName(),
  2450  		"source_workload":                deployName(src),
  2451  		"source_workload_namespace":      srcns,
  2452  	}
  2453  
  2454  	query.Metric = "istio_requests_total"
  2455  	query.Labels = labels
  2456  
  2457  	return query
  2458  }
  2459  
  2460  func buildL4Query(src, dst echo.Instance) prometheus.Query {
  2461  	query := prometheus.Query{}
  2462  
  2463  	srcns := src.NamespaceName()
  2464  	destns := dst.NamespaceName()
  2465  
  2466  	labels := map[string]string{
  2467  		"reporter":                       "destination",
  2468  		"connection_security_policy":     "mutual_tls",
  2469  		"destination_canonical_service":  dst.ServiceName(),
  2470  		"destination_canonical_revision": dst.Config().Version,
  2471  		"destination_service":            fmt.Sprintf("%s.%s.svc.cluster.local", dst.Config().Service, destns),
  2472  		"destination_service_name":       dst.Config().Service,
  2473  		"destination_service_namespace":  destns,
  2474  		"destination_principal":          "spiffe://" + dst.Config().ServiceAccountName(),
  2475  		"destination_version":            dst.Config().Version,
  2476  		"destination_workload":           deployName(dst),
  2477  		"destination_workload_namespace": destns,
  2478  		"source_canonical_service":       src.ServiceName(),
  2479  		"source_canonical_revision":      src.Config().Version,
  2480  		"source_principal":               "spiffe://" + src.Config().ServiceAccountName(),
  2481  		"source_version":                 src.Config().Version,
  2482  		"source_workload":                deployName(src),
  2483  		"source_workload_namespace":      srcns,
  2484  	}
  2485  
  2486  	query.Metric = "istio_tcp_connections_opened_total"
  2487  	query.Labels = labels
  2488  
  2489  	return query
  2490  }
  2491  
  2492  func deployName(inst echo.Instance) string {
  2493  	return inst.ServiceName() + "-" + inst.Config().Version
  2494  }
  2495  
  2496  func TestMetadataServer(t *testing.T) {
  2497  	framework.NewTest(t).Run(func(t framework.TestContext) {
  2498  		ver, _ := t.Clusters().Default().GetKubernetesVersion()
  2499  		if !strings.Contains(ver.GitVersion, "-gke") {
  2500  			t.Skip("requires GKE cluster")
  2501  		}
  2502  		svcs := apps.All
  2503  		for _, src := range svcs {
  2504  			src := src
  2505  			t.NewSubTestf("from %v", src.Config().Service).Run(func(t framework.TestContext) {
  2506  				// curl -H "Metadata-Flavor: Google" 169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity
  2507  				opts := echo.CallOptions{
  2508  					Address: "169.254.169.254",
  2509  					Port:    echo.Port{ServicePort: 80},
  2510  					Scheme:  scheme.HTTP,
  2511  					HTTP: echo.HTTP{
  2512  						// TODO: detect which platform?
  2513  						Headers: headers.New().With("Metadata-Flavor", "Google").Build(),
  2514  						Path:    "/computeMetadata/v1/instance/service-accounts/default/identity",
  2515  					},
  2516  					// Test that we see our own identity -- not the ztunnel (istio-system/ztunnel).
  2517  					// TODO: if the test SA actually had workload identity enabled the result is probably different
  2518  					Check: check.BodyContains(fmt.Sprintf(`Your Kubernetes service account (%s/%s)`, src.NamespaceName(), src.Config().AccountName())),
  2519  				}
  2520  				src.CallOrFail(t, opts)
  2521  			})
  2522  		}
  2523  	})
  2524  }
  2525  
  2526  func TestAPIServer(t *testing.T) {
  2527  	framework.NewTest(t).Run(func(t framework.TestContext) {
  2528  		svcs := apps.All
  2529  		token, err := t.Clusters().Default().Kube().CoreV1().ServiceAccounts(apps.Namespace.Name()).CreateToken(context.Background(), "default",
  2530  			&authenticationv1.TokenRequest{
  2531  				Spec: authenticationv1.TokenRequestSpec{
  2532  					Audiences:         []string{"kubernetes.default.svc"},
  2533  					ExpirationSeconds: ptr.Of(int64(600)),
  2534  				},
  2535  			}, metav1.CreateOptions{})
  2536  		assert.NoError(t, err)
  2537  
  2538  		for _, src := range svcs {
  2539  			src := src
  2540  			t.NewSubTestf("from %v", src.Config().Service).Run(func(t framework.TestContext) {
  2541  				opts := echo.CallOptions{
  2542  					Address: "kubernetes.default.svc",
  2543  					Port:    echo.Port{ServicePort: 443},
  2544  					Scheme:  scheme.HTTPS,
  2545  					HTTP: echo.HTTP{
  2546  						Headers: headers.New().With("Authorization", "Bearer "+token.Status.Token).Build(),
  2547  						Path:    "/",
  2548  					},
  2549  					// Test that we see our own identity -- not the ztunnel (istio-system/ztunnel).
  2550  					Check: check.BodyContains(fmt.Sprintf(`system:serviceaccount:%v:default`, apps.Namespace.Name())),
  2551  				}
  2552  				src.CallOrFail(t, opts)
  2553  			})
  2554  		}
  2555  	})
  2556  }
  2557  
  2558  func TestDirect(t *testing.T) {
  2559  	framework.NewTest(t).Run(func(t framework.TestContext) {
  2560  		t.NewSubTest("waypoint").Run(func(t framework.TestContext) {
  2561  			c := common.NewCaller()
  2562  			cert, err := istio.CreateCertificate(t, i, apps.Captured.ServiceName(), apps.Namespace.Name())
  2563  			if err != nil {
  2564  				t.Fatal(err)
  2565  			}
  2566  			// this is real odd but we're going to assume for now that we've just got the one waypoint I guess?
  2567  			hbwl := echo.HBONE{
  2568  				Address:            apps.WaypointProxies[apps.WorkloadAddressedWaypoint.Config().WorkloadWaypointProxy].Inbound(),
  2569  				Headers:            nil,
  2570  				Cert:               string(cert.ClientCert),
  2571  				Key:                string(cert.Key),
  2572  				CaCert:             string(cert.RootCert),
  2573  				InsecureSkipVerify: true,
  2574  			}
  2575  			hbsvc := echo.HBONE{
  2576  				Address:            apps.WaypointProxies[apps.ServiceAddressedWaypoint.Config().ServiceWaypointProxy].Inbound(),
  2577  				Headers:            nil,
  2578  				Cert:               string(cert.ClientCert),
  2579  				Key:                string(cert.Key),
  2580  				CaCert:             string(cert.RootCert),
  2581  				InsecureSkipVerify: true,
  2582  			}
  2583  			run := func(name string, options echo.CallOptions) {
  2584  				t.NewSubTest(name).Run(func(t framework.TestContext) {
  2585  					_, err := c.CallEcho(nil, options)
  2586  					if err != nil {
  2587  						t.Fatal(err)
  2588  					}
  2589  				})
  2590  			}
  2591  			run("named destination", echo.CallOptions{
  2592  				To:    apps.WorkloadAddressedWaypoint, // TODO: not sure how this is actually addressed?
  2593  				Count: 1,
  2594  				Port:  echo.Port{Name: ports.HTTP.Name},
  2595  				HBONE: hbwl,
  2596  				// This is not supported now, discussion in https://github.com/istio/istio/issues/43241
  2597  				Check: check.Error(),
  2598  			})
  2599  			run("VIP destination", echo.CallOptions{
  2600  				To:      apps.ServiceAddressedWaypoint,
  2601  				Count:   1,
  2602  				Address: apps.ServiceAddressedWaypoint[0].Address(),
  2603  				Port:    echo.Port{Name: ports.HTTP.Name},
  2604  				HBONE:   hbsvc,
  2605  				Check:   check.OK(),
  2606  			})
  2607  			run("VIP destination, unknown port", echo.CallOptions{
  2608  				To:      apps.ServiceAddressedWaypoint,
  2609  				Count:   1,
  2610  				Address: apps.ServiceAddressedWaypoint[0].Address(),
  2611  				Port:    echo.Port{ServicePort: 12345},
  2612  				Scheme:  scheme.HTTP,
  2613  				HBONE:   hbsvc,
  2614  				// TODO: VIP:* should error sooner for undeclared ports
  2615  				Check: check.Error(),
  2616  			})
  2617  			run("Pod IP destination", echo.CallOptions{
  2618  				To:      apps.WorkloadAddressedWaypoint,
  2619  				Count:   1,
  2620  				Address: apps.WorkloadAddressedWaypoint[0].WorkloadsOrFail(t)[0].Address(),
  2621  				Port:    echo.Port{ServicePort: ports.HTTP.WorkloadPort},
  2622  				Scheme:  scheme.HTTP,
  2623  				HBONE:   hbwl,
  2624  				Check:   check.OK(),
  2625  			})
  2626  			run("Unserved VIP destination", echo.CallOptions{
  2627  				To:      apps.Captured,
  2628  				Count:   1,
  2629  				Address: apps.Captured[0].Address(),
  2630  				Port:    echo.Port{ServicePort: ports.HTTP.ServicePort},
  2631  				Scheme:  scheme.HTTP,
  2632  				HBONE:   hbsvc,
  2633  				Check:   check.Error(),
  2634  			})
  2635  			run("Unserved pod destination", echo.CallOptions{
  2636  				To:      apps.Captured,
  2637  				Count:   1,
  2638  				Address: apps.Captured[0].WorkloadsOrFail(t)[0].Address(),
  2639  				Port:    echo.Port{ServicePort: ports.HTTP.ServicePort},
  2640  				Scheme:  scheme.HTTP,
  2641  				HBONE:   hbwl,
  2642  				Check:   check.Error(),
  2643  			})
  2644  			run("Waypoint destination", echo.CallOptions{
  2645  				To:      apps.ServiceAddressedWaypoint,
  2646  				Count:   1,
  2647  				Address: apps.WaypointProxies[apps.ServiceAddressedWaypoint.Config().ServiceWaypointProxy].PodIP(),
  2648  				Port:    echo.Port{ServicePort: 15000},
  2649  				Scheme:  scheme.HTTP,
  2650  				HBONE:   hbsvc,
  2651  				Check:   check.Error(),
  2652  			})
  2653  		})
  2654  	})
  2655  }