istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/authz_test.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package security 19 20 import ( 21 "fmt" 22 "net/http" 23 "testing" 24 25 "istio.io/istio/pkg/config/protocol" 26 "istio.io/istio/pkg/http/headers" 27 "istio.io/istio/pkg/test/framework" 28 "istio.io/istio/pkg/test/framework/components/authz" 29 "istio.io/istio/pkg/test/framework/components/echo" 30 "istio.io/istio/pkg/test/framework/components/echo/check" 31 "istio.io/istio/pkg/test/framework/components/echo/common/ports" 32 "istio.io/istio/pkg/test/framework/components/echo/config" 33 "istio.io/istio/pkg/test/framework/components/echo/config/param" 34 "istio.io/istio/pkg/test/framework/components/echo/echotest" 35 "istio.io/istio/pkg/test/framework/components/echo/match" 36 "istio.io/istio/pkg/test/framework/components/istio" 37 "istio.io/istio/pkg/test/framework/components/istio/ingress" 38 "istio.io/istio/pkg/test/framework/components/namespace" 39 "istio.io/istio/pkg/test/framework/label" 40 "istio.io/istio/tests/common/jwt" 41 ) 42 43 func TestAuthz_Principal(t *testing.T) { 44 framework.NewTest(t). 45 Run(func(t framework.TestContext) { 46 allowed := apps.Ns1.A 47 denied := apps.Ns2.A 48 49 from := allowed.Append(denied) 50 fromMatch := match.AnyServiceName(from.NamespacedNames()) 51 toMatch := match.Not(fromMatch) 52 to := toMatch.GetServiceMatches(apps.Ns1.All) 53 fromAndTo := to.Instances().Append(from) 54 55 config.New(t). 56 Source(config.File("testdata/authz/mtls.yaml.tmpl")). 57 Source(config.File("testdata/authz/allow-principal.yaml.tmpl").WithParams( 58 param.Params{ 59 "Allowed": allowed, 60 })). 61 BuildAll(nil, to). 62 Apply() 63 64 newTrafficTest(t, fromAndTo). 65 FromMatch(fromMatch). 66 ToMatch(toMatch). 67 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 68 allow := allowValue(from.NamespacedName() == allowed.NamespacedName()) 69 70 cases := []struct { 71 ports []echo.Port 72 path string 73 allow allowValue 74 }{ 75 { 76 ports: []echo.Port{ports.GRPC, ports.TCP}, 77 allow: allow, 78 }, 79 { 80 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 81 path: "/allow", 82 allow: allow, 83 }, 84 { 85 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 86 path: "/allow?param=value", 87 allow: allow, 88 }, 89 { 90 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 91 path: "/deny", 92 allow: false, 93 }, 94 { 95 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 96 path: "/deny?param=value", 97 allow: false, 98 }, 99 } 100 101 for _, c := range cases { 102 newAuthzTest(). 103 From(from). 104 To(to). 105 Allow(c.allow). 106 Path(c.path). 107 BuildAndRunForPorts(t, c.ports...) 108 } 109 }) 110 }) 111 } 112 113 func TestAuthz_DenyPrincipal(t *testing.T) { 114 framework.NewTest(t). 115 Run(func(t framework.TestContext) { 116 allowed := apps.Ns1.A 117 denied := apps.Ns2.A 118 119 from := allowed.Append(denied) 120 fromMatch := match.AnyServiceName(from.NamespacedNames()) 121 toMatch := match.Not(fromMatch) 122 to := toMatch.GetServiceMatches(apps.Ns1.All) 123 fromAndTo := to.Instances().Append(from) 124 125 config.New(t). 126 Source(config.File("testdata/authz/mtls.yaml.tmpl")). 127 Source(config.File("testdata/authz/deny-global.yaml.tmpl").WithParams(param.Params{ 128 param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t), 129 })). 130 Source(config.File("testdata/authz/deny-principal.yaml.tmpl").WithParams( 131 param.Params{ 132 "Denied": denied, 133 })). 134 BuildAll(nil, to). 135 Apply() 136 137 newTrafficTest(t, fromAndTo). 138 FromMatch(fromMatch). 139 ToMatch(toMatch). 140 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 141 allow := allowValue(from.NamespacedName() != denied.NamespacedName()) 142 143 cases := []struct { 144 ports []echo.Port 145 path string 146 allow allowValue 147 }{ 148 { 149 ports: []echo.Port{ports.GRPC, ports.TCP}, 150 allow: allow, 151 }, 152 { 153 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 154 path: "/deny", 155 allow: allow, 156 }, 157 { 158 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 159 path: "/deny?param=value", 160 allow: allow, 161 }, 162 { 163 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 164 path: "/deny/allow", 165 allow: true, 166 }, 167 { 168 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 169 path: "/deny/allow?param=value", 170 allow: true, 171 }, 172 { 173 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 174 path: "/allow", 175 allow: true, 176 }, 177 { 178 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 179 path: "/allow?param=value", 180 allow: true, 181 }, 182 { 183 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 184 path: "/global-deny", 185 allow: false, 186 }, 187 { 188 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 189 path: "/global-deny?param=value", 190 allow: false, 191 }, 192 { 193 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 194 path: "/global-deny/allow", 195 allow: true, 196 }, 197 { 198 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 199 path: "/global-deny/allow?param=value", 200 allow: true, 201 }, 202 } 203 204 for _, c := range cases { 205 newAuthzTest(). 206 From(from). 207 To(to). 208 Allow(c.allow). 209 Path(c.path). 210 BuildAndRunForPorts(t, c.ports...) 211 } 212 }) 213 }) 214 } 215 216 func TestAuthz_Namespace(t *testing.T) { 217 framework.NewTest(t). 218 Run(func(t framework.TestContext) { 219 // Allow anything from ns1. Any service in ns1 will work as the `from` (just using ns1.A) 220 allowed := apps.Ns1.A 221 denied := apps.Ns2.A 222 223 from := allowed.Append(denied) 224 fromMatch := match.AnyServiceName(from.NamespacedNames()) 225 toMatch := match.Not(fromMatch) 226 to := toMatch.GetServiceMatches(apps.Ns1AndNs2) 227 fromAndTo := to.Instances().Append(from) 228 229 config.New(t). 230 Source(config.File("testdata/authz/mtls.yaml.tmpl")). 231 Source(config.File("testdata/authz/allow-namespace.yaml.tmpl").WithParams( 232 param.Params{ 233 "Allowed": allowed, 234 })). 235 BuildAll(nil, to). 236 Apply() 237 238 newTrafficTest(t, fromAndTo). 239 FromMatch(fromMatch). 240 ToMatch(toMatch). 241 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 242 allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name()) 243 244 cases := []struct { 245 ports []echo.Port 246 path string 247 allow allowValue 248 }{ 249 { 250 ports: []echo.Port{ports.GRPC, ports.TCP}, 251 allow: allow, 252 }, 253 { 254 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 255 path: "/allow", 256 allow: allow, 257 }, 258 { 259 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 260 path: "/allow?param=value", 261 allow: allow, 262 }, 263 { 264 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 265 path: "/deny", 266 allow: false, 267 }, 268 { 269 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 270 path: "/deny?param=value", 271 allow: false, 272 }, 273 } 274 275 for _, c := range cases { 276 newAuthzTest(). 277 From(from). 278 To(to). 279 Allow(c.allow). 280 Path(c.path). 281 BuildAndRunForPorts(t, c.ports...) 282 } 283 }) 284 }) 285 } 286 287 func TestAuthz_DenyNamespace(t *testing.T) { 288 framework.NewTest(t). 289 Run(func(t framework.TestContext) { 290 allowed := apps.Ns1.A 291 denied := apps.Ns2.A 292 293 from := allowed.Append(denied) 294 fromMatch := match.AnyServiceName(from.NamespacedNames()) 295 toMatch := match.Not(fromMatch) 296 to := toMatch.GetServiceMatches(apps.Ns1AndNs2) 297 fromAndTo := to.Instances().Append(from) 298 299 config.New(t). 300 Source(config.File("testdata/authz/mtls.yaml.tmpl")). 301 Source(config.File("testdata/authz/deny-global.yaml.tmpl").WithParams(param.Params{ 302 param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t), 303 })). 304 Source(config.File("testdata/authz/deny-namespace.yaml.tmpl").WithParams( 305 param.Params{ 306 "Denied": denied, 307 })). 308 BuildAll(nil, to). 309 Apply() 310 311 newTrafficTest(t, fromAndTo). 312 FromMatch(fromMatch). 313 ToMatch(toMatch). 314 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 315 allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name()) 316 317 cases := []struct { 318 ports []echo.Port 319 path string 320 allow allowValue 321 }{ 322 { 323 ports: []echo.Port{ports.GRPC, ports.TCP}, 324 allow: allow, 325 }, 326 { 327 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 328 path: "/deny", 329 allow: allow, 330 }, 331 { 332 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 333 path: "/deny?param=value", 334 allow: allow, 335 }, 336 { 337 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 338 path: "/deny/allow", 339 allow: true, 340 }, 341 { 342 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 343 path: "/deny/allow?param=value", 344 allow: true, 345 }, 346 { 347 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 348 path: "/allow", 349 allow: true, 350 }, 351 { 352 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 353 path: "/allow?param=value", 354 allow: true, 355 }, 356 { 357 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 358 path: "/global-deny", 359 allow: false, 360 }, 361 { 362 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 363 path: "/global-deny?param=value", 364 allow: false, 365 }, 366 { 367 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 368 path: "/global-deny/allow", 369 allow: true, 370 }, 371 { 372 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 373 path: "/global-deny/allow?param=value", 374 allow: true, 375 }, 376 } 377 378 for _, c := range cases { 379 newAuthzTest(). 380 From(from). 381 To(to). 382 Allow(c.allow). 383 Path(c.path). 384 BuildAndRunForPorts(t, c.ports...) 385 } 386 }) 387 }) 388 } 389 390 func TestAuthz_NotNamespace(t *testing.T) { 391 framework.NewTest(t). 392 Run(func(t framework.TestContext) { 393 allowed := apps.Ns1.A 394 denied := apps.Ns2.A 395 396 from := allowed.Append(denied) 397 fromMatch := match.AnyServiceName(from.NamespacedNames()) 398 toMatch := match.Not(fromMatch) 399 to := toMatch.GetServiceMatches(apps.Ns1.All) 400 fromAndTo := to.Instances().Append(from) 401 402 config.New(t). 403 Source(config.File("testdata/authz/mtls.yaml.tmpl")). 404 Source(config.File("testdata/authz/not-namespace.yaml.tmpl").WithParams( 405 param.Params{ 406 "Allowed": allowed, 407 })). 408 BuildAll(nil, to). 409 Apply() 410 411 newTrafficTest(t, fromAndTo). 412 FromMatch(fromMatch). 413 ToMatch(toMatch). 414 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 415 allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name()) 416 417 newAuthzTest(). 418 From(from). 419 To(to). 420 Allow(allow). 421 BuildAndRunForPorts(t, ports.GRPC, ports.TCP, ports.HTTP, ports.HTTP2) 422 }) 423 }) 424 } 425 426 func TestAuthz_NotHost(t *testing.T) { 427 framework.NewTest(t). 428 Run(func(t framework.TestContext) { 429 from := apps.Ns1.A 430 fromMatch := match.AnyServiceName(from.NamespacedNames()) 431 toMatch := match.Not(fromMatch) 432 to := toMatch.GetServiceMatches(apps.Ns1.All) 433 fromAndTo := to.Instances().Append(from) 434 435 config.New(t). 436 Source(config.File("testdata/authz/not-host.yaml.tmpl").WithParams(param.Params{ 437 "GatewayIstioLabel": i.Settings().IngressGatewayIstioLabel, 438 })). 439 BuildAll(nil, to). 440 Apply() 441 442 newTrafficTest(t, fromAndTo). 443 FromMatch(fromMatch). 444 ToMatch(toMatch). 445 RunViaIngress(func(t framework.TestContext, from ingress.Instance, to echo.Target) { 446 cases := []struct { 447 host string 448 allow allowValue 449 }{ 450 { 451 host: fmt.Sprintf("allow.%s.com", to.Config().Service), 452 allow: true, 453 }, 454 { 455 host: fmt.Sprintf("deny.%s.com", to.Config().Service), 456 allow: false, 457 }, 458 } 459 460 for _, c := range cases { 461 c := c 462 testName := fmt.Sprintf("%s(%s)/http", c.host, c.allow) 463 t.NewSubTest(testName).Run(func(t framework.TestContext) { 464 wantCode := http.StatusOK 465 if !c.allow { 466 wantCode = http.StatusForbidden 467 } 468 469 opts := echo.CallOptions{ 470 Port: echo.Port{ 471 Protocol: protocol.HTTP, 472 }, 473 HTTP: echo.HTTP{ 474 Headers: headers.New().WithHost(c.host).Build(), 475 }, 476 Check: check.And(check.NoError(), check.Status(wantCode)), 477 } 478 from.CallOrFail(t, opts) 479 }) 480 } 481 }) 482 }) 483 } 484 485 func TestAuthz_NotMethod(t *testing.T) { 486 // NOTE: negative match for mtls is tested by TestAuthz_DenyPlaintext. 487 // Negative match for paths is tested by TestAuthz_DenyPrincipal, TestAuthz_DenyNamespace. 488 framework.NewTest(t). 489 Run(func(t framework.TestContext) { 490 from := apps.Ns1.A 491 fromMatch := match.AnyServiceName(from.NamespacedNames()) 492 toMatch := match.Not(fromMatch) 493 to := toMatch.GetServiceMatches(apps.Ns1AndNs2) 494 fromAndTo := to.Instances().Append(from) 495 496 config.New(t). 497 Source(config.File("testdata/authz/not-method.yaml.tmpl")). 498 BuildAll(nil, to). 499 Apply() 500 501 newTrafficTest(t, fromAndTo). 502 FromMatch(fromMatch). 503 ToMatch(toMatch). 504 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 505 cases := []struct { 506 ports []echo.Port 507 method string 508 allow allowValue 509 }{ 510 { 511 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 512 method: "GET", 513 allow: true, 514 }, 515 { 516 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 517 method: "PUT", 518 allow: false, 519 }, 520 } 521 522 for _, c := range cases { 523 newAuthzTest(). 524 From(from). 525 To(to). 526 Allow(c.allow). 527 Method(c.method). 528 BuildAndRunForPorts(t, c.ports...) 529 } 530 }) 531 }) 532 } 533 534 func TestAuthz_NotPort(t *testing.T) { 535 framework.NewTest(t). 536 Run(func(t framework.TestContext) { 537 from := apps.Ns1.A 538 fromMatch := match.AnyServiceName(from.NamespacedNames()) 539 toMatch := match.Not(fromMatch) 540 to := toMatch.GetServiceMatches(apps.Ns1AndNs2) 541 fromAndTo := to.Instances().Append(from) 542 543 config.New(t). 544 Source(config.File("testdata/authz/not-port.yaml.tmpl")). 545 BuildAll(nil, to). 546 Apply() 547 548 newTrafficTest(t, fromAndTo). 549 FromMatch(fromMatch). 550 ToMatch(toMatch). 551 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 552 cases := []struct { 553 ports []echo.Port 554 allow allowValue 555 }{ 556 { 557 ports: []echo.Port{ports.HTTP}, 558 allow: true, 559 }, 560 { 561 ports: []echo.Port{ports.HTTP2}, 562 allow: false, 563 }, 564 } 565 566 for _, c := range cases { 567 newAuthzTest(). 568 From(from). 569 To(to). 570 Allow(c.allow). 571 BuildAndRunForPorts(t, c.ports...) 572 } 573 }) 574 }) 575 } 576 577 func TestAuthz_DenyPlaintext(t *testing.T) { 578 framework.NewTest(t). 579 Run(func(t framework.TestContext) { 580 allowed := apps.Ns1.A 581 denied := apps.Ns2.A 582 583 newTrafficTest(t, apps.Ns1.All.Instances().Append(denied)). 584 Config(config.File("testdata/authz/plaintext.yaml.tmpl").WithParams(param.Params{ 585 "Denied": denied, 586 // The namespaces for each resource are specified in the file. Use "" as the ns to apply to. 587 param.Namespace.String(): "", 588 })). 589 // Just test from A in each namespace to show the policy works. 590 FromMatch(match.AnyServiceName(allowed.Append(denied).NamespacedNames())). 591 ToMatch(match.Namespace(apps.Ns1.Namespace)). 592 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 593 allow := allowValue(from.Config().Namespace.Name() == allowed.Config().Namespace.Name()) 594 newAuthzTest(). 595 From(from). 596 To(to). 597 Allow(allow). 598 BuildAndRunForPorts(t, ports.GRPC, ports.TCP, ports.HTTP, ports.HTTP2) 599 }) 600 }) 601 } 602 603 func TestAuthz_JWT(t *testing.T) { 604 framework.NewTest(t). 605 Label(label.IPv4). // https://github.com/istio/istio/issues/35835 606 Run(func(t framework.TestContext) { 607 from := apps.Ns1.A 608 fromMatch := match.ServiceName(from.NamespacedName()) 609 toMatch := match.Not(fromMatch) 610 to := toMatch.GetServiceMatches(apps.Ns1.All) 611 fromAndTo := to.Instances().Append(from) 612 613 config.New(t). 614 Source(config.File("testdata/authz/jwt.yaml.tmpl").WithNamespace(apps.Ns1.Namespace)). 615 BuildAll(nil, to). 616 Apply() 617 618 newTrafficTest(t, fromAndTo). 619 FromMatch(fromMatch). 620 ToMatch(toMatch). 621 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 622 cases := []struct { 623 prefix string 624 jwt string 625 path string 626 allow allowValue 627 }{ 628 { 629 prefix: "[No JWT]", 630 jwt: "", 631 path: "/token1", 632 allow: false, 633 }, 634 { 635 prefix: "[No JWT]", 636 jwt: "", 637 path: "/token2", 638 allow: false, 639 }, 640 { 641 prefix: "[Token1]", 642 jwt: jwt.TokenIssuer1, path: "/token1", allow: true, 643 }, 644 { 645 prefix: "[Token1]", 646 jwt: jwt.TokenIssuer1, 647 path: "/token2", 648 allow: false, 649 }, 650 { 651 prefix: "[Token2]", 652 jwt: jwt.TokenIssuer2, 653 path: "/token1", 654 allow: false, 655 }, 656 { 657 prefix: "[Token2]", 658 jwt: jwt.TokenIssuer2, 659 path: "/token2", 660 allow: true, 661 }, 662 { 663 prefix: "[Token3]", 664 jwt: jwt.TokenIssuer1, 665 path: "/token3", 666 allow: false, 667 }, 668 { 669 prefix: "[Token3]", 670 jwt: jwt.TokenIssuer2, 671 path: "/token3", 672 allow: true, 673 }, 674 { 675 prefix: "[Token1]", 676 jwt: jwt.TokenIssuer1, 677 path: "/tokenAny", 678 allow: true, 679 }, 680 { 681 prefix: "[Token2]", 682 jwt: jwt.TokenIssuer2, 683 path: "/tokenAny", 684 allow: true, 685 }, 686 { 687 prefix: "[PermissionToken1]", 688 jwt: jwt.TokenIssuer1, 689 path: "/permission", 690 allow: false, 691 }, 692 { 693 prefix: "[PermissionToken2]", 694 jwt: jwt.TokenIssuer2, 695 path: "/permission", 696 allow: false, 697 }, 698 { 699 prefix: "[PermissionTokenWithSpaceDelimitedScope]", 700 jwt: jwt.TokenIssuer2WithSpaceDelimitedScope, 701 path: "/permission", 702 allow: true, 703 }, 704 { 705 prefix: "[NestedToken1]", 706 jwt: jwt.TokenIssuer1WithNestedClaims1, 707 path: "/nested-key1", 708 allow: true, 709 }, 710 { 711 prefix: "[NestedToken2]", 712 jwt: jwt.TokenIssuer1WithNestedClaims2, 713 path: "/nested-key1", 714 allow: false, 715 }, 716 { 717 prefix: "[NestedToken1]", 718 jwt: jwt.TokenIssuer1WithNestedClaims1, 719 path: "/nested-key2", 720 allow: false, 721 }, 722 { 723 prefix: "[NestedToken2]", 724 jwt: jwt.TokenIssuer1WithNestedClaims2, 725 path: "/nested-key2", 726 allow: true, 727 }, 728 { 729 prefix: "[NestedToken1]", 730 jwt: jwt.TokenIssuer1WithNestedClaims1, 731 path: "/nested-2-key1", 732 allow: true, 733 }, 734 { 735 prefix: "[NestedToken2]", 736 jwt: jwt.TokenIssuer1WithNestedClaims2, 737 path: "/nested-2-key1", 738 allow: false, 739 }, 740 { 741 prefix: "[NestedToken1]", 742 jwt: jwt.TokenIssuer1WithNestedClaims1, 743 path: "/nested-non-exist", 744 allow: false, 745 }, 746 { 747 prefix: "[NestedToken2]", 748 jwt: jwt.TokenIssuer1WithNestedClaims2, 749 path: "/nested-non-exist", 750 allow: false, 751 }, 752 { 753 prefix: "[NoJWT]", 754 jwt: "", 755 path: "/tokenAny", 756 allow: false, 757 }, 758 { 759 prefix: "[JWTWithAud]", 760 jwt: jwt.TokenIssuer1WithAud, 761 path: "/audiences", 762 allow: true, 763 }, 764 { 765 prefix: "[JWTWithAudList]", 766 jwt: jwt.TokenIssuer1WithAudList, 767 path: "/audiences", 768 allow: true, 769 }, 770 } 771 for _, c := range cases { 772 h := headers.New().WithAuthz(c.jwt).Build() 773 newAuthzTest(). 774 From(from). 775 To(to). 776 Allow(c.allow). 777 Prefix(c.prefix). 778 Path(c.path). 779 Headers(h). 780 BuildAndRunForPorts(t, ports.HTTP, ports.HTTP2) 781 } 782 }) 783 }) 784 } 785 786 func TestAuthz_WorkloadSelector(t *testing.T) { 787 framework.NewTest(t). 788 Run(func(t framework.TestContext) { 789 // Verify that the workload-specific path (/policy-<ns>-<svc>) works only on the selected workload. 790 t.NewSubTestf("single workload"). 791 Run(func(t framework.TestContext) { 792 from := apps.Ns1.A 793 fromMatch := match.ServiceName(from.NamespacedName()) 794 toMatch := match.Not(fromMatch) 795 to := toMatch.GetServiceMatches(apps.Ns1.All) 796 fromAndTo := to.Instances().Append(from) 797 798 config.New(t). 799 Source(config.File("testdata/authz/workload.yaml.tmpl")). 800 // Also define a bad workload selector for path /policy-<ns>-<svc>-bad. 801 Source(config.File("testdata/authz/workload-bad.yaml.tmpl")). 802 // Allow /policy-<ns>-all for all workloads. 803 Source(config.File("testdata/authz/workload-ns.yaml.tmpl").WithParams(param.Params{ 804 param.Namespace.String(): apps.Ns1.Namespace, 805 })). 806 Source(config.File("testdata/authz/workload-ns.yaml.tmpl").WithParams(param.Params{ 807 param.Namespace.String(): apps.Ns2.Namespace, 808 })). 809 // Allow /policy-istio-system-<svc> for all services in all namespaces. Just using ns1 to avoid 810 // creating duplicate resources. 811 Source(config.File("testdata/authz/workload-system-ns.yaml.tmpl").WithParams(param.Params{ 812 param.Namespace.String(): istio.ClaimSystemNamespaceOrFail(t, t), 813 })). 814 BuildAll(nil, to). 815 Apply() 816 817 newTrafficTest(t, fromAndTo). 818 FromMatch(fromMatch). 819 ToMatch(toMatch). 820 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 821 type testCase struct { 822 path string 823 allow allowValue 824 updateLabel bool 825 } 826 827 cases := []testCase{ 828 { 829 // Make sure the bad policy did not select this workload. 830 path: fmt.Sprintf("/policy-%s-%s-bad", to.Config().Namespace.Prefix(), to.Config().Service), 831 allow: false, 832 }, 833 { 834 // Make sure the bad policy select this workload. 835 // Skip vm 836 path: fmt.Sprintf("/policy-%s-%s-bad", to.Config().Namespace.Prefix(), to.Config().Service), 837 allow: true, 838 updateLabel: true, 839 }, 840 } 841 842 // Make sure the namespace-wide policy was applied to this workload. 843 for _, ns := range []namespace.Instance{apps.Ns1.Namespace, apps.Ns2.Namespace} { 844 cases = append(cases, 845 testCase{ 846 path: fmt.Sprintf("/policy-%s-all", ns.Prefix()), 847 allow: ns.Name() == to.Config().Namespace.Name(), 848 }) 849 } 850 851 // Make sure the workload-specific paths succeeds. 852 cases = append(cases, 853 testCase{ 854 path: fmt.Sprintf("/policy-%s-%s", to.Config().Namespace.Prefix(), to.Config().Service), 855 allow: true, 856 }, 857 testCase{ 858 path: fmt.Sprintf("/policy-system-%s", to.Config().Service), 859 allow: true, 860 }) 861 862 // The workload-specific paths should fail for another service (just add a single test case). 863 for _, svc := range apps.Ns1.All { 864 if svc.Config().Service != to.Config().Service { 865 cases = append(cases, 866 testCase{ 867 path: fmt.Sprintf("/policy-%s-%s", svc.Config().Namespace.Prefix(), svc.Config().Service), 868 allow: false, 869 }, 870 testCase{ 871 path: fmt.Sprintf("/policy-system-%s", svc.Config().Service), 872 allow: false, 873 }) 874 break 875 } 876 } 877 878 for _, c := range cases { 879 if c.updateLabel { 880 // skip updating pod labels for VM 881 if to.Config().DeployAsVM { 882 continue 883 } 884 for _, instance := range to.Instances() { 885 err := instance.UpdateWorkloadLabel(map[string]string{"foo": "bla"}, nil) 886 if err != nil { 887 t.Fatal(err) 888 } 889 } 890 } 891 newAuthzTest(). 892 From(from). 893 To(to). 894 Allow(c.allow). 895 Path(c.path). 896 BuildForPorts(t, ports.HTTP, ports.HTTP2). 897 RunInSerial(t) 898 899 if c.updateLabel { 900 // skip updating pod labels for VM 901 if to.Config().DeployAsVM { 902 continue 903 } 904 for _, instance := range to.Instances() { 905 err := instance.UpdateWorkloadLabel(nil, []string{"foo"}) 906 if err != nil { 907 t.Fatal(err) 908 } 909 } 910 } 911 } 912 }) 913 }) 914 }) 915 } 916 917 func TestAuthz_PathPrecedence(t *testing.T) { 918 framework.NewTest(t). 919 Run(func(t framework.TestContext) { 920 from := apps.Ns1.A 921 fromMatch := match.ServiceName(from.NamespacedName()) 922 toMatch := match.Not(fromMatch) 923 to := toMatch.GetServiceMatches(apps.Ns1.All) 924 fromAndTo := to.Instances().Append(from) 925 926 config.New(t). 927 Source(config.File("testdata/authz/path-precedence.yaml.tmpl")). 928 BuildAll(nil, to). 929 Apply() 930 931 newTrafficTest(t, fromAndTo). 932 FromMatch(fromMatch). 933 ToMatch(toMatch). 934 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 935 cases := []struct { 936 path string 937 allow allowValue 938 }{ 939 { 940 path: "/allow/admin", 941 allow: false, 942 }, 943 { 944 path: "/allow/admin?param=value", 945 allow: false, 946 }, 947 { 948 path: "/allow", 949 allow: true, 950 }, 951 { 952 path: "/allow?param=value", 953 allow: true, 954 }, 955 } 956 957 for _, c := range cases { 958 newAuthzTest(). 959 From(from). 960 To(to). 961 Allow(c.allow). 962 Path(c.path). 963 BuildAndRunForPorts(t, ports.HTTP, ports.HTTP2) 964 } 965 }) 966 }) 967 } 968 969 func TestAuthz_PathTemplating(t *testing.T) { 970 framework.NewTest(t). 971 Run(func(t framework.TestContext) { 972 from := apps.Ns1.A 973 fromMatch := match.ServiceName(from.NamespacedName()) 974 toMatch := match.Not(fromMatch) 975 to := toMatch.GetServiceMatches(apps.Ns1.All) 976 fromAndTo := to.Instances().Append(from) 977 978 config.New(t). 979 Source(config.File("testdata/authz/path-templating.yaml.tmpl")). 980 BuildAll(nil, to). 981 Apply() 982 983 newTrafficTest(t, fromAndTo). 984 FromMatch(fromMatch). 985 ToMatch(toMatch). 986 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 987 cases := []struct { 988 path string 989 allow allowValue 990 }{ 991 // Test matches for `/allow/admin/{**}` 992 { 993 path: "/allow/admin/", 994 allow: true, 995 }, 996 { 997 // When `**` is the last segment and operator in the template, the path must have a trailing `/` to match 998 path: "/allow/admin", 999 allow: false, 1000 }, 1001 { 1002 path: "/allow/user/", 1003 allow: false, 1004 }, 1005 { 1006 path: "/allow/admin?param=value", 1007 allow: false, 1008 }, 1009 { 1010 path: "/allow", 1011 allow: false, 1012 }, 1013 { 1014 path: "/allow/admin/temp", 1015 allow: true, 1016 }, 1017 { 1018 path: "/allow/admin/temp.txt", 1019 allow: true, 1020 }, 1021 { 1022 path: "/allow/admin/foo/temp.txt", 1023 allow: true, 1024 }, 1025 // Test matches for `/foo/{*}/bar/{**}` 1026 { 1027 path: "/foo/buzz/bar/bat.txt", 1028 allow: true, 1029 }, 1030 { 1031 path: "/foo/buzz/bar/bat.temp.txt", 1032 allow: true, 1033 }, 1034 { 1035 path: "/foo/buzz/bar/bat/fuzz.txt", 1036 allow: true, 1037 }, 1038 { 1039 path: "/foo/bar/bat.txt", 1040 allow: false, 1041 }, 1042 { 1043 path: "/foo//bar/bat.txt", 1044 allow: false, 1045 }, 1046 { 1047 path: "/foo/buzz/bar/bat", 1048 allow: true, 1049 }, 1050 // Test matches for `/store/{**}/cart` 1051 { 1052 path: "/store//cart", 1053 allow: true, 1054 }, 1055 { 1056 path: "/store/cart", 1057 allow: false, 1058 }, 1059 } 1060 1061 for _, c := range cases { 1062 c := c 1063 testName := fmt.Sprintf("%s(%s)/http", c.path, c.allow) 1064 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1065 newAuthzTest(). 1066 From(from). 1067 To(to). 1068 Allow(c.allow). 1069 Path(c.path). 1070 BuildAndRunForPorts(t, ports.HTTP, ports.HTTP2) 1071 }) 1072 } 1073 }) 1074 }) 1075 } 1076 1077 func TestAuthz_IngressGateway(t *testing.T) { 1078 framework.NewTest(t). 1079 Run(func(t framework.TestContext) { 1080 to := apps.Ns1.All 1081 config.New(t). 1082 Source(config.File("testdata/authz/ingress-gateway.yaml.tmpl").WithParams(param.Params{ 1083 // The namespaces for each resource are specified in the file. Use "" as the ns to apply to. 1084 param.Namespace.String(): "", 1085 "GatewayIstioLabel": i.Settings().IngressGatewayIstioLabel, 1086 })). 1087 BuildAll(nil, to). 1088 Apply() 1089 newTrafficTest(t, to.Instances()). 1090 RunViaIngress(func(t framework.TestContext, from ingress.Instance, to echo.Target) { 1091 host := func(fmtStr string) string { 1092 return fmt.Sprintf(fmtStr, to.Config().Service) 1093 } 1094 cases := []struct { 1095 host string 1096 path string 1097 ip string 1098 allow allowValue 1099 }{ 1100 { 1101 host: host("deny.%s.com"), 1102 allow: false, 1103 }, 1104 { 1105 host: host("DENY.%s.COM"), 1106 allow: false, 1107 }, 1108 { 1109 host: host("Deny.%s.Com"), 1110 allow: false, 1111 }, 1112 { 1113 host: host("deny.suffix.%s.com"), 1114 allow: false, 1115 }, 1116 { 1117 host: host("DENY.SUFFIX.%s.COM"), 1118 allow: false, 1119 }, 1120 { 1121 host: host("Deny.Suffix.%s.Com"), 1122 allow: false, 1123 }, 1124 { 1125 host: host("prefix.%s.com"), 1126 allow: false, 1127 }, 1128 { 1129 host: host("PREFIX.%s.COM"), 1130 allow: false, 1131 }, 1132 { 1133 host: host("Prefix.%s.Com"), 1134 allow: false, 1135 }, 1136 { 1137 host: host("www.%s.com"), 1138 path: "/", 1139 ip: "172.16.0.1", 1140 allow: true, 1141 }, 1142 { 1143 host: host("www.%s.com"), 1144 path: "/private", 1145 ip: "172.16.0.1", 1146 allow: false, 1147 }, 1148 { 1149 host: host("www.%s.com"), 1150 path: "/public", 1151 ip: "172.16.0.1", 1152 allow: true, 1153 }, 1154 { 1155 host: host("internal.%s.com"), 1156 path: "/", 1157 ip: "172.16.0.1", 1158 allow: false, 1159 }, 1160 { 1161 host: host("internal.%s.com"), 1162 path: "/private", 1163 ip: "172.16.0.1", 1164 allow: false, 1165 }, 1166 { 1167 host: host("remoteipblocks.%s.com"), 1168 path: "/", 1169 ip: "172.17.72.46", 1170 allow: false, 1171 }, 1172 { 1173 host: host("remoteipblocks.%s.com"), 1174 path: "/", 1175 ip: "192.168.5.233", 1176 allow: false, 1177 }, 1178 { 1179 host: host("remoteipblocks.%s.com"), 1180 path: "/", 1181 ip: "10.4.5.6", 1182 allow: true, 1183 }, 1184 { 1185 host: host("notremoteipblocks.%s.com"), 1186 path: "/", 1187 ip: "10.2.3.4", 1188 allow: false, 1189 }, 1190 { 1191 host: host("notremoteipblocks.%s.com"), 1192 path: "/", 1193 ip: "172.23.242.188", 1194 allow: true, 1195 }, 1196 { 1197 host: host("remoteipattr.%s.com"), 1198 path: "/", 1199 ip: "10.242.5.7", 1200 allow: false, 1201 }, 1202 { 1203 host: host("remoteipattr.%s.com"), 1204 path: "/", 1205 ip: "10.124.99.10", 1206 allow: false, 1207 }, 1208 { 1209 host: host("remoteipattr.%s.com"), 1210 path: "/", 1211 ip: "10.4.5.6", 1212 allow: true, 1213 }, 1214 } 1215 1216 for _, c := range cases { 1217 c := c 1218 testName := fmt.Sprintf("%s%s(%s)/http", c.host, c.path, c.allow) 1219 if len(c.ip) > 0 { 1220 testName = c.ip + "->" + testName 1221 } 1222 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1223 wantCode := http.StatusOK 1224 if !c.allow { 1225 wantCode = http.StatusForbidden 1226 } 1227 1228 opts := echo.CallOptions{ 1229 Port: echo.Port{ 1230 Protocol: protocol.HTTP, 1231 }, 1232 HTTP: echo.HTTP{ 1233 Path: c.path, 1234 Headers: headers.New().WithHost(c.host).WithXForwardedFor(c.ip).Build(), 1235 }, 1236 Check: check.And(check.NoError(), check.Status(wantCode)), 1237 } 1238 from.CallOrFail(t, opts) 1239 }) 1240 } 1241 }) 1242 }) 1243 } 1244 1245 func TestAuthz_EgressGateway(t *testing.T) { 1246 framework.NewTest(t). 1247 Label(label.IPv4). // https://github.com/istio/istio/issues/35835 1248 Run(func(t framework.TestContext) { 1249 allowed := apps.Ns1.A 1250 denied := apps.Ns2.A 1251 1252 from := allowed.Append(denied) 1253 fromMatch := match.AnyServiceName(from.NamespacedNames()) 1254 toMatch := match.Not(fromMatch) 1255 to := toMatch.GetServiceMatches(apps.Ns1.All) 1256 fromAndTo := to.Instances().Append(from) 1257 1258 newTrafficTest(t, fromAndTo). 1259 FromMatch(fromMatch). 1260 ToMatch(toMatch). 1261 Config(config.File("testdata/authz/egress-gateway.yaml.tmpl").WithParams(param.Params{ 1262 // The namespaces for each resource are specified in the file. Use "" as the ns to apply to. 1263 param.Namespace.String(): "", 1264 "EgressGatewayIstioLabel": i.Settings().EgressGatewayIstioLabel, 1265 "EgressGatewayServiceName": i.Settings().EgressGatewayServiceName, 1266 "EgressGatewayServiceNamespace": i.Settings().EgressGatewayServiceNamespace, 1267 "Allowed": allowed, 1268 })). 1269 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 1270 allow := allowValue(from.NamespacedName() == allowed.Config().NamespacedName()) 1271 1272 cases := []struct { 1273 host string 1274 path string 1275 token string 1276 allow allowValue 1277 }{ 1278 { 1279 host: "www.company.com", 1280 path: "/allow", 1281 allow: true, 1282 }, 1283 { 1284 host: "www.company.com", 1285 path: "/deny", 1286 allow: false, 1287 }, 1288 { 1289 host: fmt.Sprintf("%s-%s-only.com", allowed.Config().Service, allowed.Config().Namespace.Name()), 1290 path: "/", 1291 allow: allow, 1292 }, 1293 { 1294 host: "jwt-only.com", 1295 path: "/", 1296 token: jwt.TokenIssuer1, 1297 allow: true, 1298 }, 1299 { 1300 host: "jwt-only.com", 1301 path: "/", 1302 token: jwt.TokenIssuer2, 1303 allow: false, 1304 }, 1305 { 1306 host: fmt.Sprintf("jwt-and-%s-%s-only.com", allowed.Config().Service, allowed.Config().Namespace.Name()), 1307 path: "/", 1308 token: jwt.TokenIssuer1, 1309 allow: allow, 1310 }, 1311 { 1312 path: "/", 1313 host: fmt.Sprintf("jwt-and-%s-%s-only.com", allowed.Config().Service, allowed.Config().Namespace.Name()), 1314 token: jwt.TokenIssuer2, 1315 allow: false, 1316 }, 1317 } 1318 1319 for _, c := range cases { 1320 c := c 1321 testName := fmt.Sprintf("%s%s(%s)/http", c.host, c.path, c.allow) 1322 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1323 wantCode := http.StatusOK 1324 body := "handled-by-egress-gateway" 1325 if !c.allow { 1326 wantCode = http.StatusForbidden 1327 body = "RBAC: access denied" 1328 } 1329 1330 opts := echo.CallOptions{ 1331 // Use a fake IP address to bypass DNS lookup (which will fail). The host 1332 // header will be used for routing decisions. 1333 Address: "10.4.4.4", 1334 Port: echo.Port{ 1335 Name: ports.HTTP.Name, 1336 Protocol: protocol.HTTP, 1337 ServicePort: 80, 1338 }, 1339 HTTP: echo.HTTP{ 1340 Path: c.path, 1341 Headers: headers.New().WithHost(c.host).WithAuthz(c.token).Build(), 1342 }, 1343 Check: check.And(check.NoErrorAndStatus(wantCode), check.BodyContains(body)), 1344 } 1345 1346 from.CallOrFail(t, opts) 1347 }) 1348 } 1349 }) 1350 }) 1351 } 1352 1353 func TestAuthz_Conditions(t *testing.T) { 1354 framework.NewTest(t). 1355 Run(func(t framework.TestContext) { 1356 allowed := apps.Ns1.A 1357 denied := apps.Ns2.A 1358 1359 from := allowed.Append(denied) 1360 fromMatch := match.AnyServiceName(from.NamespacedNames()) 1361 toMatch := match.Not(fromMatch) 1362 to := toMatch.GetServiceMatches(apps.Ns1.All) 1363 fromAndTo := to.Instances().Append(from) 1364 1365 config.New(t). 1366 Source(config.File("testdata/authz/mtls.yaml.tmpl")). 1367 Source(config.File("testdata/authz/conditions.yaml.tmpl").WithParams(param.Params{ 1368 "Allowed": allowed, 1369 "Denied": denied, 1370 })). 1371 BuildAll(nil, to). 1372 Apply() 1373 1374 newTrafficTest(t, fromAndTo). 1375 FromMatch(fromMatch). 1376 ToMatch(toMatch). 1377 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 1378 allow := allowValue(from.NamespacedName() == allowed.Config().NamespacedName()) 1379 1380 skipSourceIPTestsForMulticluster := func(t framework.TestContext) { 1381 t.Helper() 1382 if t.Clusters().IsMulticluster() { 1383 // TODO(nmittler): Needs to be documented as a limitation for multi-network. 1384 t.Skip("https://github.com/istio/istio/issues/37307: " + 1385 "Source IP-based authz tests are not supported in multi-network configurations " + 1386 "due to the fact that the origin source IP will be lost when traversing the " + 1387 "east-west gateway.") 1388 } 1389 } 1390 1391 cases := []struct { 1392 path string 1393 headers http.Header 1394 allow allowValue 1395 skipFn func(t framework.TestContext) 1396 }{ 1397 // Test headers. 1398 { 1399 path: "/request-headers", 1400 headers: headers.New().With("x-foo", "foo").Build(), 1401 allow: true, 1402 }, 1403 { 1404 path: "/request-headers", 1405 headers: headers.New().With("x-foo", "bar").Build(), 1406 allow: false, 1407 }, 1408 { 1409 path: "/request-headers", 1410 allow: false, 1411 }, 1412 { 1413 path: "/request-headers-notValues", 1414 headers: headers.New().With("x-foo", "foo").Build(), 1415 allow: true, 1416 }, 1417 { 1418 path: "/request-headers-notValues", 1419 headers: headers.New().With("x-foo", "bar").Build(), 1420 allow: false, 1421 }, 1422 1423 // Test source IP 1424 { 1425 path: "/source-ip", 1426 allow: allow, 1427 skipFn: skipSourceIPTestsForMulticluster, 1428 }, 1429 { 1430 path: "/source-ip-notValues", 1431 allow: allow, 1432 skipFn: skipSourceIPTestsForMulticluster, 1433 }, 1434 1435 // Test source namespace 1436 { 1437 path: "/source-namespace", 1438 allow: allow, 1439 }, 1440 { 1441 path: "/source-namespace-notValues", 1442 allow: allow, 1443 }, 1444 1445 // Test source principal 1446 { 1447 path: "/source-principal", 1448 allow: allow, 1449 }, 1450 { 1451 path: "/source-principal-notValues", 1452 allow: allow, 1453 }, 1454 1455 // Test destination IP 1456 { 1457 path: "/destination-ip-good", 1458 allow: true, 1459 }, 1460 { 1461 path: "/destination-ip-bad", 1462 allow: false, 1463 }, 1464 { 1465 path: "/destination-ip-notValues", 1466 allow: false, 1467 }, 1468 1469 // Test destination port 1470 { 1471 path: "/destination-port-good", 1472 allow: true, 1473 }, 1474 { 1475 path: "/destination-port-bad", 1476 allow: false, 1477 }, 1478 { 1479 path: "/destination-port-notValues", 1480 allow: false, 1481 }, 1482 1483 // Test SNI 1484 { 1485 path: "/connection-sni-good", 1486 allow: true, 1487 }, 1488 { 1489 path: "/connection-sni-bad", 1490 allow: false, 1491 }, 1492 { 1493 path: "/connection-sni-notValues", 1494 allow: false, 1495 }, 1496 1497 { 1498 path: "/other", 1499 allow: false, 1500 }, 1501 } 1502 1503 for _, c := range cases { 1504 c := c 1505 xfooHeader := "" 1506 if c.headers != nil { 1507 xfooHeader = "?x-foo=" + c.headers.Get("x-foo") 1508 } 1509 testName := fmt.Sprintf("%s%s(%s)/http", c.path, xfooHeader, c.allow) 1510 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1511 if c.skipFn != nil { 1512 c.skipFn(t) 1513 } 1514 1515 newAuthzTest(). 1516 From(from). 1517 To(to). 1518 PortName(ports.HTTP.Name). 1519 Path(c.path). 1520 Allow(c.allow). 1521 Headers(c.headers). 1522 BuildAndRun(t) 1523 }) 1524 } 1525 }) 1526 }) 1527 } 1528 1529 func TestAuthz_PathNormalization(t *testing.T) { 1530 framework.NewTest(t). 1531 Run(func(t framework.TestContext) { 1532 from := apps.Ns1.A 1533 fromMatch := match.ServiceName(from.NamespacedName()) 1534 toMatch := match.Not(fromMatch) 1535 to := toMatch.GetServiceMatches(apps.Ns1.All) 1536 fromAndTo := to.Instances().Append(from) 1537 1538 config.New(t). 1539 Source(config.File("testdata/authz/path-normalization.yaml.tmpl")). 1540 BuildAll(nil, to). 1541 Apply() 1542 1543 newTrafficTest(t, fromAndTo). 1544 FromMatch(fromMatch). 1545 ToMatch(toMatch). 1546 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 1547 cases := []struct { 1548 path string 1549 allow allowValue 1550 }{ 1551 { 1552 path: "/public", 1553 allow: true, 1554 }, 1555 { 1556 path: "/public/../public", 1557 allow: true, 1558 }, 1559 { 1560 path: "/private", 1561 allow: false, 1562 }, 1563 { 1564 path: "/public/../private", 1565 allow: false, 1566 }, 1567 { 1568 path: "/public/./../private", 1569 allow: false, 1570 }, 1571 { 1572 path: "/public/.././private", 1573 allow: false, 1574 }, 1575 { 1576 path: "/public/%2E%2E/private", 1577 allow: false, 1578 }, 1579 { 1580 path: "/public/%2e%2e/private", 1581 allow: false, 1582 }, 1583 { 1584 path: "/public/%2E/%2E%2E/private", 1585 allow: false, 1586 }, 1587 { 1588 path: "/public/%2e/%2e%2e/private", 1589 allow: false, 1590 }, 1591 { 1592 path: "/public/%2E%2E/%2E/private", 1593 allow: false, 1594 }, 1595 { 1596 path: "/public/%2e%2e/%2e/private", 1597 allow: false, 1598 }, 1599 } 1600 1601 for _, c := range cases { 1602 c := c 1603 testName := fmt.Sprintf("%s(%s)/http", c.path, c.allow) 1604 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1605 newAuthzTest(). 1606 From(from). 1607 To(to). 1608 PortName(ports.HTTP.Name). 1609 Path(c.path). 1610 Allow(c.allow). 1611 BuildAndRun(t) 1612 }) 1613 } 1614 }) 1615 }) 1616 } 1617 1618 func TestAuthz_CustomServer(t *testing.T) { 1619 framework.NewTest(t). 1620 Run(func(t framework.TestContext) { 1621 extAuthzHeaders := func(value string) http.Header { 1622 return headers.New(). 1623 With(authz.XExtAuthz, value). 1624 With(authz.XExtAuthzAdditionalHeaderOverride, "should-be-override"). 1625 Build() 1626 } 1627 allowHeaders := func() http.Header { 1628 return extAuthzHeaders(authz.XExtAuthzAllow) 1629 } 1630 denyHeaders := func() http.Header { 1631 return extAuthzHeaders("deny") 1632 } 1633 1634 allProviders := append(authzServer.Providers(), localAuthzServer.Providers()...) 1635 for _, provider := range allProviders { 1636 t.NewSubTest(provider.Name()).Run(func(t framework.TestContext) { 1637 // The ext-authz server is hard-coded to allow requests from any service account ending in 1638 // "/sa/a". Since the namespace is ignored, we use ns2.B for our denied app (rather than ns2.A). 1639 // We'll only need the service account for TCP, since we send headers in all other protocols 1640 // to control the server's behavior. 1641 allowed := apps.Ns1.A 1642 var denied echo.Instances 1643 if provider.IsProtocolSupported(protocol.TCP) { 1644 denied = apps.Ns2.B 1645 } 1646 1647 from := allowed.Append(denied) 1648 fromMatch := match.AnyServiceName(from.NamespacedNames()) 1649 toMatch := match.And(match.Not(fromMatch), provider.MatchSupportedTargets()) 1650 to := toMatch.GetServiceMatches(apps.Ns1.All) 1651 fromAndTo := to.Instances().Append(from) 1652 1653 config.New(t). 1654 Source(config.File("testdata/authz/custom-provider.yaml.tmpl").WithParams(param.Params{ 1655 "Provider": provider, 1656 })). 1657 BuildAll(nil, to). 1658 Apply() 1659 1660 newTrafficTest(t, fromAndTo). 1661 FromMatch(fromMatch). 1662 ToMatch(toMatch). 1663 Run(func(t framework.TestContext, from echo.Instance, to echo.Target) { 1664 fromAllowed := from.NamespacedName() == allowed.NamespacedName() 1665 1666 authzPath := "/custom" 1667 cases := []struct { 1668 ports []echo.Port 1669 path string 1670 headers http.Header 1671 allow allowValue 1672 skip bool 1673 }{ 1674 { 1675 ports: []echo.Port{ports.TCP}, 1676 // For TCP, we rely on the hard-coded allowed service account. 1677 allow: allowValue(fromAllowed), 1678 }, 1679 { 1680 ports: []echo.Port{ports.HTTP, ports.HTTP2, ports.GRPC}, 1681 path: authzPath, 1682 headers: allowHeaders(), 1683 allow: true, 1684 skip: !fromAllowed, 1685 }, 1686 { 1687 ports: []echo.Port{ports.HTTP, ports.HTTP2, ports.GRPC}, 1688 path: authzPath, 1689 headers: denyHeaders(), 1690 allow: false, 1691 skip: !fromAllowed, 1692 }, 1693 { 1694 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 1695 path: "/health", 1696 headers: extAuthzHeaders(authz.XExtAuthzAllow), 1697 allow: true, 1698 skip: !fromAllowed, 1699 }, 1700 { 1701 ports: []echo.Port{ports.HTTP, ports.HTTP2}, 1702 path: "/health", 1703 headers: extAuthzHeaders("deny"), 1704 allow: true, 1705 skip: !fromAllowed, 1706 }, 1707 } 1708 1709 for _, c := range cases { 1710 c := c 1711 if c.skip { 1712 continue 1713 } 1714 tsts := newAuthzTest(). 1715 From(from). 1716 To(to). 1717 Path(c.path). 1718 Headers(c.headers). 1719 Allow(c.allow). 1720 BuildForPorts(t, c.ports...). 1721 Filter(func(tst authzTest) bool { 1722 return provider.IsProtocolSupported(tst.opts.Port.Protocol) 1723 }) 1724 for _, tst := range tsts { 1725 tst := tst 1726 params := "" 1727 if c.headers != nil { 1728 params = fmt.Sprintf("?%s=%s", authz.XExtAuthz, c.headers.Get(authz.XExtAuthz)) 1729 } 1730 testName := fmt.Sprintf("%s%s(%s)/%s", c.path, params, c.allow, tst.opts.Port.Name) 1731 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1732 if c.path == authzPath { 1733 tst.opts.Check = check.And(tst.opts.Check, provider.Check(tst.opts, c.allow.Bool())) 1734 } 1735 1736 tst.Run(t) 1737 }) 1738 } 1739 } 1740 }) 1741 }) 1742 } 1743 }) 1744 } 1745 1746 func newTrafficTest(t framework.TestContext, echos ...echo.Instances) *echotest.T { 1747 var all []echo.Instance 1748 for _, e := range echos { 1749 all = append(all, e...) 1750 } 1751 1752 return echotest.New(t, all). 1753 WithDefaultFilters(1, 1). 1754 FromMatch(match.And( 1755 match.NotNaked, 1756 match.NotProxylessGRPC)). 1757 ToMatch(match.And( 1758 match.NotNaked, 1759 match.NotProxylessGRPC)). 1760 ConditionallyTo(echotest.NoSelfCalls) 1761 } 1762 1763 type allowValue bool 1764 1765 func (v allowValue) Bool() bool { 1766 return bool(v) 1767 } 1768 1769 func (v allowValue) String() string { 1770 if v { 1771 return "allow" 1772 } 1773 return "deny" 1774 } 1775 1776 type authzTest struct { 1777 from echo.Instance 1778 opts echo.CallOptions 1779 allow allowValue 1780 prefix string 1781 } 1782 1783 func newAuthzTest() *authzTest { 1784 return &authzTest{} 1785 } 1786 1787 func (b *authzTest) Prefix(prefix string) *authzTest { 1788 b.prefix = prefix 1789 return b 1790 } 1791 1792 func (b *authzTest) From(from echo.Instance) *authzTest { 1793 b.from = from 1794 return b 1795 } 1796 1797 func (b *authzTest) To(to echo.Target) *authzTest { 1798 b.opts.To = to 1799 return b 1800 } 1801 1802 func (b *authzTest) PortName(portName string) *authzTest { 1803 b.opts.Port.Name = portName 1804 return b 1805 } 1806 1807 func (b *authzTest) Method(method string) *authzTest { 1808 b.opts.HTTP.Method = method 1809 return b 1810 } 1811 1812 func (b *authzTest) Path(path string) *authzTest { 1813 b.opts.HTTP.Path = path 1814 return b 1815 } 1816 1817 func (b *authzTest) Headers(headers http.Header) *authzTest { 1818 b.opts.HTTP.Headers = headers 1819 return b 1820 } 1821 1822 func (b *authzTest) Allow(allow allowValue) *authzTest { 1823 b.allow = allow 1824 return b 1825 } 1826 1827 func (b *authzTest) Build(t framework.TestContext) *authzTest { 1828 t.Helper() 1829 // Set check now, as FillDefaults requires it 1830 b.opts.Check = check.OK() 1831 // Fill in the defaults; we need this to get the dest protocol 1832 b.opts.FillDefaultsOrFail(t) 1833 if b.allow { 1834 b.opts.Check = check.And(check.OK(), check.ReachedTargetClusters(t)) 1835 } else { 1836 b.opts.Check = check.Forbidden(b.opts.Port.Protocol) 1837 } 1838 return b 1839 } 1840 1841 func (b *authzTest) BuildForPorts(t framework.TestContext, ports ...echo.Port) authzTests { 1842 out := make(authzTests, 0, len(ports)) 1843 for _, p := range ports { 1844 opts := b.opts.DeepCopy() 1845 opts.Port.Name = p.Name 1846 1847 tst := (&authzTest{ 1848 prefix: b.prefix, 1849 from: b.from, 1850 opts: opts, 1851 allow: b.allow, 1852 }).Build(t) 1853 out = append(out, *tst) 1854 } 1855 return out 1856 } 1857 1858 func (b *authzTest) BuildAndRunForPorts(t framework.TestContext, ports ...echo.Port) { 1859 tsts := b.BuildForPorts(t, ports...) 1860 tsts.RunAll(t) 1861 } 1862 1863 func (b *authzTest) Run(t framework.TestContext) { 1864 t.Helper() 1865 b.from.CallOrFail(t, b.opts) 1866 } 1867 1868 func (b *authzTest) BuildAndRun(t framework.TestContext) { 1869 t.Helper() 1870 b.Build(t).Run(t) 1871 } 1872 1873 type authzTests []authzTest 1874 1875 func (tsts authzTests) checkValid() { 1876 path := tsts[0].opts.HTTP.Path 1877 allow := tsts[0].allow 1878 prefix := tsts[0].prefix 1879 for _, tst := range tsts { 1880 if tst.opts.HTTP.Path != path { 1881 panic("authz tests have different paths") 1882 } 1883 if tst.allow != allow { 1884 panic("authz tests have different allow") 1885 } 1886 if tst.prefix != prefix { 1887 panic("authz tests have different prefixes") 1888 } 1889 } 1890 } 1891 1892 func (tsts authzTests) Filter(keep func(authzTest) bool) authzTests { 1893 out := make(authzTests, 0, len(tsts)) 1894 for _, tst := range tsts { 1895 if keep(tst) { 1896 out = append(out, tst) 1897 } 1898 } 1899 return out 1900 } 1901 1902 func (tsts authzTests) RunAll(t framework.TestContext) { 1903 t.Helper() 1904 1905 firstTest := tsts[0] 1906 if len(tsts) == 1 { 1907 // Testing a single port. Just run a single test. 1908 testName := fmt.Sprintf("%s%s(%s)/%s", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow, firstTest.opts.Port.Name) 1909 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1910 firstTest.BuildAndRun(t) 1911 }) 1912 return 1913 } 1914 1915 tsts.checkValid() 1916 1917 // Testing multiple ports... 1918 // Name outer test with constant info. Name inner test with port. 1919 outerTestName := fmt.Sprintf("%s%s(%s)", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow) 1920 t.NewSubTest(outerTestName).Run(func(t framework.TestContext) { 1921 for _, tst := range tsts { 1922 tst := tst 1923 t.NewSubTest(tst.opts.Port.Name).Run(func(t framework.TestContext) { 1924 tst.BuildAndRun(t) 1925 }) 1926 } 1927 }) 1928 } 1929 1930 func (tsts authzTests) RunInSerial(t framework.TestContext) { 1931 t.Helper() 1932 1933 firstTest := tsts[0] 1934 if len(tsts) == 1 { 1935 // Testing a single port. Just run a single test. 1936 testName := fmt.Sprintf("%s%s(%s)/%s", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow, firstTest.opts.Port.Name) 1937 t.NewSubTest(testName).Run(func(t framework.TestContext) { 1938 firstTest.BuildAndRun(t) 1939 }) 1940 return 1941 } 1942 1943 tsts.checkValid() 1944 1945 // Testing multiple ports... 1946 // Name outer test with constant info. Name inner test with port. 1947 outerTestName := fmt.Sprintf("%s%s(%s)", firstTest.prefix, firstTest.opts.HTTP.Path, firstTest.allow) 1948 t.NewSubTest(outerTestName).Run(func(t framework.TestContext) { 1949 for _, tst := range tsts { 1950 tst := tst 1951 t.NewSubTest(tst.opts.Port.Name).Run(func(t framework.TestContext) { 1952 tst.BuildAndRun(t) 1953 }) 1954 } 1955 }) 1956 }