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 }