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