istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/security/authn/policy_applier_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package authn 16 17 import ( 18 "reflect" 19 "testing" 20 "time" 21 22 "github.com/davecgh/go-spew/spew" 23 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 24 route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 25 envoy_jwt "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3" 26 hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 27 tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 28 "github.com/google/go-cmp/cmp" 29 "google.golang.org/protobuf/proto" 30 "google.golang.org/protobuf/testing/protocmp" 31 "google.golang.org/protobuf/types/known/durationpb" 32 "google.golang.org/protobuf/types/known/emptypb" 33 34 authn_alpha "istio.io/api/authentication/v1alpha1" 35 authn_filter "istio.io/api/envoy/config/filter/http/authn/v2alpha1" 36 "istio.io/api/security/v1beta1" 37 type_beta "istio.io/api/type/v1beta1" 38 "istio.io/istio/pilot/pkg/features" 39 "istio.io/istio/pilot/pkg/model" 40 "istio.io/istio/pilot/pkg/model/test" 41 "istio.io/istio/pilot/pkg/util/protoconv" 42 "istio.io/istio/pkg/config" 43 "istio.io/istio/pkg/config/host" 44 "istio.io/istio/pkg/jwt" 45 protovalue "istio.io/istio/pkg/proto" 46 istiotest "istio.io/istio/pkg/test" 47 "istio.io/istio/pkg/test/util/assert" 48 ) 49 50 func TestJwtFilter(t *testing.T) { 51 ms, err := test.StartNewServer() 52 if err != nil { 53 t.Fatal("failed to start a mock server") 54 } 55 56 jwksURI := ms.URL + "/oauth2/v3/certs" 57 58 cases := []struct { 59 name string 60 in []*config.Config 61 jwksFetchMode jwt.JwksFetchMode 62 expected *hcm.HttpFilter 63 }{ 64 { 65 name: "No policy", 66 in: []*config.Config{}, 67 expected: nil, 68 }, 69 { 70 name: "Empty policy", 71 in: []*config.Config{ 72 { 73 Spec: &v1beta1.RequestAuthentication{}, 74 }, 75 }, 76 expected: nil, 77 }, 78 { 79 name: "Single JWT policy", 80 in: []*config.Config{ 81 { 82 Spec: &v1beta1.RequestAuthentication{ 83 JwtRules: []*v1beta1.JWTRule{ 84 { 85 Issuer: "https://secret.foo.com", 86 JwksUri: jwksURI, 87 }, 88 }, 89 }, 90 }, 91 }, 92 expected: &hcm.HttpFilter{ 93 Name: "envoy.filters.http.jwt_authn", 94 ConfigType: &hcm.HttpFilter_TypedConfig{ 95 TypedConfig: protoconv.MessageToAny( 96 &envoy_jwt.JwtAuthentication{ 97 Rules: []*envoy_jwt.RequirementRule{ 98 { 99 Match: &route.RouteMatch{ 100 PathSpecifier: &route.RouteMatch_Prefix{ 101 Prefix: "/", 102 }, 103 }, 104 RequirementType: &envoy_jwt.RequirementRule_Requires{ 105 Requires: &envoy_jwt.JwtRequirement{ 106 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 107 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 108 Requirements: []*envoy_jwt.JwtRequirement{ 109 { 110 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 111 ProviderName: "origins-0", 112 }, 113 }, 114 { 115 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 116 AllowMissing: &emptypb.Empty{}, 117 }, 118 }, 119 }, 120 }, 121 }, 122 }, 123 }, 124 }, 125 }, 126 Providers: map[string]*envoy_jwt.JwtProvider{ 127 "origins-0": { 128 Issuer: "https://secret.foo.com", 129 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 130 LocalJwks: &core.DataSource{ 131 Specifier: &core.DataSource_InlineString{ 132 InlineString: test.JwtPubKey1, 133 }, 134 }, 135 }, 136 Forward: false, 137 PayloadInMetadata: "https://secret.foo.com", 138 }, 139 }, 140 BypassCorsPreflight: true, 141 }), 142 }, 143 }, 144 }, 145 { 146 name: "JWT policy with Mesh cluster as issuer and remote jwks mode Hybrid", 147 in: []*config.Config{ 148 { 149 Spec: &v1beta1.RequestAuthentication{ 150 JwtRules: []*v1beta1.JWTRule{ 151 { 152 Issuer: "mesh cluster", 153 JwksUri: "http://jwt-token-issuer.mesh:7443/jwks", 154 }, 155 }, 156 }, 157 }, 158 }, 159 jwksFetchMode: jwt.Hybrid, 160 expected: &hcm.HttpFilter{ 161 Name: "envoy.filters.http.jwt_authn", 162 ConfigType: &hcm.HttpFilter_TypedConfig{ 163 TypedConfig: protoconv.MessageToAny( 164 &envoy_jwt.JwtAuthentication{ 165 Rules: []*envoy_jwt.RequirementRule{ 166 { 167 Match: &route.RouteMatch{ 168 PathSpecifier: &route.RouteMatch_Prefix{ 169 Prefix: "/", 170 }, 171 }, 172 RequirementType: &envoy_jwt.RequirementRule_Requires{ 173 Requires: &envoy_jwt.JwtRequirement{ 174 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 175 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 176 Requirements: []*envoy_jwt.JwtRequirement{ 177 { 178 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 179 ProviderName: "origins-0", 180 }, 181 }, 182 { 183 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 184 AllowMissing: &emptypb.Empty{}, 185 }, 186 }, 187 }, 188 }, 189 }, 190 }, 191 }, 192 }, 193 }, 194 Providers: map[string]*envoy_jwt.JwtProvider{ 195 "origins-0": { 196 Issuer: "mesh cluster", 197 JwksSourceSpecifier: &envoy_jwt.JwtProvider_RemoteJwks{ 198 RemoteJwks: &envoy_jwt.RemoteJwks{ 199 HttpUri: &core.HttpUri{ 200 Uri: "http://jwt-token-issuer.mesh:7443/jwks", 201 HttpUpstreamType: &core.HttpUri_Cluster{ 202 Cluster: "outbound|7443||jwt-token-issuer.mesh.svc.cluster.local", 203 }, 204 Timeout: &durationpb.Duration{Seconds: 5}, 205 }, 206 CacheDuration: &durationpb.Duration{Seconds: 5 * 60}, 207 }, 208 }, 209 Forward: false, 210 PayloadInMetadata: "mesh cluster", 211 }, 212 }, 213 BypassCorsPreflight: true, 214 }), 215 }, 216 }, 217 }, 218 { 219 name: "JWT policy with Mesh cluster as issuer and remote jwks mode Envoy", 220 in: []*config.Config{ 221 { 222 Spec: &v1beta1.RequestAuthentication{ 223 JwtRules: []*v1beta1.JWTRule{ 224 { 225 Issuer: "mesh cluster", 226 JwksUri: "http://jwt-token-issuer.mesh:7443/jwks", 227 }, 228 }, 229 }, 230 }, 231 }, 232 jwksFetchMode: jwt.Envoy, 233 expected: &hcm.HttpFilter{ 234 Name: "envoy.filters.http.jwt_authn", 235 ConfigType: &hcm.HttpFilter_TypedConfig{ 236 TypedConfig: protoconv.MessageToAny( 237 &envoy_jwt.JwtAuthentication{ 238 Rules: []*envoy_jwt.RequirementRule{ 239 { 240 Match: &route.RouteMatch{ 241 PathSpecifier: &route.RouteMatch_Prefix{ 242 Prefix: "/", 243 }, 244 }, 245 RequirementType: &envoy_jwt.RequirementRule_Requires{ 246 Requires: &envoy_jwt.JwtRequirement{ 247 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 248 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 249 Requirements: []*envoy_jwt.JwtRequirement{ 250 { 251 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 252 ProviderName: "origins-0", 253 }, 254 }, 255 { 256 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 257 AllowMissing: &emptypb.Empty{}, 258 }, 259 }, 260 }, 261 }, 262 }, 263 }, 264 }, 265 }, 266 }, 267 Providers: map[string]*envoy_jwt.JwtProvider{ 268 "origins-0": { 269 Issuer: "mesh cluster", 270 JwksSourceSpecifier: &envoy_jwt.JwtProvider_RemoteJwks{ 271 RemoteJwks: &envoy_jwt.RemoteJwks{ 272 HttpUri: &core.HttpUri{ 273 Uri: "http://jwt-token-issuer.mesh:7443/jwks", 274 HttpUpstreamType: &core.HttpUri_Cluster{ 275 Cluster: "outbound|7443||jwt-token-issuer.mesh.svc.cluster.local", 276 }, 277 Timeout: &durationpb.Duration{Seconds: 5}, 278 }, 279 CacheDuration: &durationpb.Duration{Seconds: 5 * 60}, 280 }, 281 }, 282 Forward: false, 283 PayloadInMetadata: "mesh cluster", 284 }, 285 }, 286 BypassCorsPreflight: true, 287 }), 288 }, 289 }, 290 }, 291 { 292 name: "JWT policy with non Mesh cluster as issuer and remote jwks mode Hybrid", 293 in: []*config.Config{ 294 { 295 Spec: &v1beta1.RequestAuthentication{ 296 JwtRules: []*v1beta1.JWTRule{ 297 { 298 Issuer: "invalid|7443|", 299 JwksUri: jwksURI, 300 }, 301 }, 302 }, 303 }, 304 }, 305 jwksFetchMode: jwt.Hybrid, 306 expected: &hcm.HttpFilter{ 307 Name: "envoy.filters.http.jwt_authn", 308 ConfigType: &hcm.HttpFilter_TypedConfig{ 309 TypedConfig: protoconv.MessageToAny( 310 &envoy_jwt.JwtAuthentication{ 311 Rules: []*envoy_jwt.RequirementRule{ 312 { 313 Match: &route.RouteMatch{ 314 PathSpecifier: &route.RouteMatch_Prefix{ 315 Prefix: "/", 316 }, 317 }, 318 RequirementType: &envoy_jwt.RequirementRule_Requires{ 319 Requires: &envoy_jwt.JwtRequirement{ 320 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 321 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 322 Requirements: []*envoy_jwt.JwtRequirement{ 323 { 324 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 325 ProviderName: "origins-0", 326 }, 327 }, 328 { 329 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 330 AllowMissing: &emptypb.Empty{}, 331 }, 332 }, 333 }, 334 }, 335 }, 336 }, 337 }, 338 }, 339 }, 340 Providers: map[string]*envoy_jwt.JwtProvider{ 341 "origins-0": { 342 Issuer: "invalid|7443|", 343 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 344 LocalJwks: &core.DataSource{ 345 Specifier: &core.DataSource_InlineString{ 346 InlineString: test.JwtPubKey2, 347 }, 348 }, 349 }, 350 Forward: false, 351 PayloadInMetadata: "invalid|7443|", 352 }, 353 }, 354 BypassCorsPreflight: true, 355 }), 356 }, 357 }, 358 }, 359 { 360 name: "JWT policy with non Mesh cluster as issuer and remote jwks mode Envoy", 361 in: []*config.Config{ 362 { 363 Spec: &v1beta1.RequestAuthentication{ 364 JwtRules: []*v1beta1.JWTRule{ 365 { 366 Issuer: "invalid|7443|", 367 JwksUri: "http://invalid-issuer.com:7443/jwks", 368 }, 369 }, 370 }, 371 }, 372 }, 373 jwksFetchMode: jwt.Envoy, 374 expected: &hcm.HttpFilter{ 375 Name: "envoy.filters.http.jwt_authn", 376 ConfigType: &hcm.HttpFilter_TypedConfig{ 377 TypedConfig: protoconv.MessageToAny( 378 &envoy_jwt.JwtAuthentication{ 379 Rules: []*envoy_jwt.RequirementRule{ 380 { 381 Match: &route.RouteMatch{ 382 PathSpecifier: &route.RouteMatch_Prefix{ 383 Prefix: "/", 384 }, 385 }, 386 RequirementType: &envoy_jwt.RequirementRule_Requires{ 387 Requires: &envoy_jwt.JwtRequirement{ 388 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 389 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 390 Requirements: []*envoy_jwt.JwtRequirement{ 391 { 392 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 393 ProviderName: "origins-0", 394 }, 395 }, 396 { 397 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 398 AllowMissing: &emptypb.Empty{}, 399 }, 400 }, 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 Providers: map[string]*envoy_jwt.JwtProvider{ 409 "origins-0": { 410 Issuer: "invalid|7443|", 411 JwksSourceSpecifier: &envoy_jwt.JwtProvider_RemoteJwks{ 412 RemoteJwks: &envoy_jwt.RemoteJwks{ 413 HttpUri: &core.HttpUri{ 414 Uri: "http://invalid-issuer.com:7443/jwks", 415 HttpUpstreamType: &core.HttpUri_Cluster{ 416 Cluster: "outbound|7443||invalid-issuer.com", 417 }, 418 Timeout: &durationpb.Duration{Seconds: 5}, 419 }, 420 CacheDuration: &durationpb.Duration{Seconds: 5 * 60}, 421 }, 422 }, 423 Forward: false, 424 PayloadInMetadata: "invalid|7443|", 425 }, 426 }, 427 BypassCorsPreflight: true, 428 }), 429 }, 430 }, 431 }, 432 { 433 name: "Multi JWTs policy", 434 in: []*config.Config{ 435 { 436 Spec: &v1beta1.RequestAuthentication{ 437 JwtRules: []*v1beta1.JWTRule{ 438 { 439 Issuer: "https://secret.foo.com", 440 JwksUri: jwksURI, 441 }, 442 }, 443 }, 444 }, 445 { 446 Spec: &v1beta1.RequestAuthentication{}, 447 }, 448 { 449 Spec: &v1beta1.RequestAuthentication{ 450 JwtRules: []*v1beta1.JWTRule{ 451 { 452 Issuer: "https://secret.bar.com", 453 Jwks: "jwks-inline-data", 454 }, 455 }, 456 }, 457 }, 458 }, 459 expected: &hcm.HttpFilter{ 460 Name: "envoy.filters.http.jwt_authn", 461 ConfigType: &hcm.HttpFilter_TypedConfig{ 462 TypedConfig: protoconv.MessageToAny( 463 &envoy_jwt.JwtAuthentication{ 464 Rules: []*envoy_jwt.RequirementRule{ 465 { 466 Match: &route.RouteMatch{ 467 PathSpecifier: &route.RouteMatch_Prefix{ 468 Prefix: "/", 469 }, 470 }, 471 RequirementType: &envoy_jwt.RequirementRule_Requires{ 472 Requires: &envoy_jwt.JwtRequirement{ 473 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 474 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 475 Requirements: []*envoy_jwt.JwtRequirement{ 476 { 477 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 478 ProviderName: "origins-0", 479 }, 480 }, 481 { 482 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 483 ProviderName: "origins-1", 484 }, 485 }, 486 { 487 RequiresType: &envoy_jwt.JwtRequirement_RequiresAll{ 488 RequiresAll: &envoy_jwt.JwtRequirementAndList{ 489 Requirements: []*envoy_jwt.JwtRequirement{ 490 { 491 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 492 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 493 Requirements: []*envoy_jwt.JwtRequirement{ 494 { 495 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 496 ProviderName: "origins-0", 497 }, 498 }, 499 { 500 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 501 AllowMissing: &emptypb.Empty{}, 502 }, 503 }, 504 }, 505 }, 506 }, 507 }, 508 { 509 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 510 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 511 Requirements: []*envoy_jwt.JwtRequirement{ 512 { 513 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 514 ProviderName: "origins-1", 515 }, 516 }, 517 { 518 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 519 AllowMissing: &emptypb.Empty{}, 520 }, 521 }, 522 }, 523 }, 524 }, 525 }, 526 }, 527 }, 528 }, 529 }, 530 }, 531 }, 532 }, 533 }, 534 }, 535 }, 536 }, 537 Providers: map[string]*envoy_jwt.JwtProvider{ 538 "origins-0": { 539 Issuer: "https://secret.bar.com", 540 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 541 LocalJwks: &core.DataSource{ 542 Specifier: &core.DataSource_InlineString{ 543 InlineString: "jwks-inline-data", 544 }, 545 }, 546 }, 547 Forward: false, 548 PayloadInMetadata: "https://secret.bar.com", 549 }, 550 "origins-1": { 551 Issuer: "https://secret.foo.com", 552 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 553 LocalJwks: &core.DataSource{ 554 Specifier: &core.DataSource_InlineString{ 555 InlineString: test.JwtPubKey1, 556 }, 557 }, 558 }, 559 Forward: false, 560 PayloadInMetadata: "https://secret.foo.com", 561 }, 562 }, 563 BypassCorsPreflight: true, 564 }), 565 }, 566 }, 567 }, 568 { 569 name: "JWT policy with inline Jwks", 570 in: []*config.Config{ 571 { 572 Spec: &v1beta1.RequestAuthentication{ 573 JwtRules: []*v1beta1.JWTRule{ 574 { 575 Issuer: "https://secret.foo.com", 576 Jwks: "inline-jwks-data", 577 }, 578 }, 579 }, 580 }, 581 }, 582 expected: &hcm.HttpFilter{ 583 Name: "envoy.filters.http.jwt_authn", 584 ConfigType: &hcm.HttpFilter_TypedConfig{ 585 TypedConfig: protoconv.MessageToAny( 586 &envoy_jwt.JwtAuthentication{ 587 Rules: []*envoy_jwt.RequirementRule{ 588 { 589 Match: &route.RouteMatch{ 590 PathSpecifier: &route.RouteMatch_Prefix{ 591 Prefix: "/", 592 }, 593 }, 594 RequirementType: &envoy_jwt.RequirementRule_Requires{ 595 Requires: &envoy_jwt.JwtRequirement{ 596 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 597 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 598 Requirements: []*envoy_jwt.JwtRequirement{ 599 { 600 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 601 ProviderName: "origins-0", 602 }, 603 }, 604 { 605 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 606 AllowMissing: &emptypb.Empty{}, 607 }, 608 }, 609 }, 610 }, 611 }, 612 }, 613 }, 614 }, 615 }, 616 Providers: map[string]*envoy_jwt.JwtProvider{ 617 "origins-0": { 618 Issuer: "https://secret.foo.com", 619 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 620 LocalJwks: &core.DataSource{ 621 Specifier: &core.DataSource_InlineString{ 622 InlineString: "inline-jwks-data", 623 }, 624 }, 625 }, 626 Forward: false, 627 PayloadInMetadata: "https://secret.foo.com", 628 }, 629 }, 630 BypassCorsPreflight: true, 631 }), 632 }, 633 }, 634 }, 635 { 636 name: "JWT policy with bad Jwks URI", 637 in: []*config.Config{ 638 { 639 Spec: &v1beta1.RequestAuthentication{ 640 JwtRules: []*v1beta1.JWTRule{ 641 { 642 Issuer: "https://secret.foo.com", 643 JwksUri: "http://site.not.exist", 644 }, 645 }, 646 }, 647 }, 648 }, 649 expected: &hcm.HttpFilter{ 650 Name: "envoy.filters.http.jwt_authn", 651 ConfigType: &hcm.HttpFilter_TypedConfig{ 652 TypedConfig: protoconv.MessageToAny( 653 &envoy_jwt.JwtAuthentication{ 654 Rules: []*envoy_jwt.RequirementRule{ 655 { 656 Match: &route.RouteMatch{ 657 PathSpecifier: &route.RouteMatch_Prefix{ 658 Prefix: "/", 659 }, 660 }, 661 RequirementType: &envoy_jwt.RequirementRule_Requires{ 662 Requires: &envoy_jwt.JwtRequirement{ 663 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 664 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 665 Requirements: []*envoy_jwt.JwtRequirement{ 666 { 667 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 668 ProviderName: "origins-0", 669 }, 670 }, 671 { 672 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 673 AllowMissing: &emptypb.Empty{}, 674 }, 675 }, 676 }, 677 }, 678 }, 679 }, 680 }, 681 }, 682 }, 683 Providers: map[string]*envoy_jwt.JwtProvider{ 684 "origins-0": { 685 Issuer: "https://secret.foo.com", 686 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 687 LocalJwks: &core.DataSource{ 688 Specifier: &core.DataSource_InlineString{ 689 InlineString: model.FakeJwks, 690 }, 691 }, 692 }, 693 Forward: false, 694 PayloadInMetadata: "https://secret.foo.com", 695 }, 696 }, 697 BypassCorsPreflight: true, 698 }), 699 }, 700 }, 701 }, 702 { 703 name: "Forward original token", 704 in: []*config.Config{ 705 { 706 Spec: &v1beta1.RequestAuthentication{ 707 JwtRules: []*v1beta1.JWTRule{ 708 { 709 Issuer: "https://secret.foo.com", 710 JwksUri: jwksURI, 711 ForwardOriginalToken: true, 712 }, 713 }, 714 }, 715 }, 716 }, 717 expected: &hcm.HttpFilter{ 718 Name: "envoy.filters.http.jwt_authn", 719 ConfigType: &hcm.HttpFilter_TypedConfig{ 720 TypedConfig: protoconv.MessageToAny( 721 &envoy_jwt.JwtAuthentication{ 722 Rules: []*envoy_jwt.RequirementRule{ 723 { 724 Match: &route.RouteMatch{ 725 PathSpecifier: &route.RouteMatch_Prefix{ 726 Prefix: "/", 727 }, 728 }, 729 RequirementType: &envoy_jwt.RequirementRule_Requires{ 730 Requires: &envoy_jwt.JwtRequirement{ 731 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 732 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 733 Requirements: []*envoy_jwt.JwtRequirement{ 734 { 735 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 736 ProviderName: "origins-0", 737 }, 738 }, 739 { 740 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 741 AllowMissing: &emptypb.Empty{}, 742 }, 743 }, 744 }, 745 }, 746 }, 747 }, 748 }, 749 }, 750 }, 751 Providers: map[string]*envoy_jwt.JwtProvider{ 752 "origins-0": { 753 Issuer: "https://secret.foo.com", 754 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 755 LocalJwks: &core.DataSource{ 756 Specifier: &core.DataSource_InlineString{ 757 InlineString: test.JwtPubKey1, 758 }, 759 }, 760 }, 761 Forward: true, 762 PayloadInMetadata: "https://secret.foo.com", 763 }, 764 }, 765 BypassCorsPreflight: true, 766 }), 767 }, 768 }, 769 }, 770 { 771 name: "Output payload", 772 in: []*config.Config{ 773 { 774 Spec: &v1beta1.RequestAuthentication{ 775 JwtRules: []*v1beta1.JWTRule{ 776 { 777 Issuer: "https://secret.foo.com", 778 JwksUri: jwksURI, 779 ForwardOriginalToken: true, 780 OutputPayloadToHeader: "x-foo", 781 }, 782 }, 783 }, 784 }, 785 }, 786 expected: &hcm.HttpFilter{ 787 Name: "envoy.filters.http.jwt_authn", 788 ConfigType: &hcm.HttpFilter_TypedConfig{ 789 TypedConfig: protoconv.MessageToAny( 790 &envoy_jwt.JwtAuthentication{ 791 Rules: []*envoy_jwt.RequirementRule{ 792 { 793 Match: &route.RouteMatch{ 794 PathSpecifier: &route.RouteMatch_Prefix{ 795 Prefix: "/", 796 }, 797 }, 798 RequirementType: &envoy_jwt.RequirementRule_Requires{ 799 Requires: &envoy_jwt.JwtRequirement{ 800 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 801 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 802 Requirements: []*envoy_jwt.JwtRequirement{ 803 { 804 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 805 ProviderName: "origins-0", 806 }, 807 }, 808 { 809 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 810 AllowMissing: &emptypb.Empty{}, 811 }, 812 }, 813 }, 814 }, 815 }, 816 }, 817 }, 818 }, 819 }, 820 Providers: map[string]*envoy_jwt.JwtProvider{ 821 "origins-0": { 822 Issuer: "https://secret.foo.com", 823 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 824 LocalJwks: &core.DataSource{ 825 Specifier: &core.DataSource_InlineString{ 826 InlineString: test.JwtPubKey1, 827 }, 828 }, 829 }, 830 Forward: true, 831 ForwardPayloadHeader: "x-foo", 832 PayloadInMetadata: "https://secret.foo.com", 833 }, 834 }, 835 BypassCorsPreflight: true, 836 }), 837 }, 838 }, 839 }, 840 { 841 name: "Output claim to header", 842 in: []*config.Config{ 843 { 844 Spec: &v1beta1.RequestAuthentication{ 845 JwtRules: []*v1beta1.JWTRule{ 846 { 847 Issuer: "https://secret.foo.com", 848 JwksUri: jwksURI, 849 ForwardOriginalToken: true, 850 OutputClaimToHeaders: []*v1beta1.ClaimToHeader{ 851 {Header: "x-jwt-key1", Claim: "value1"}, 852 {Header: "x-jwt-key2", Claim: "value2"}, 853 }, 854 }, 855 }, 856 }, 857 }, 858 }, 859 expected: &hcm.HttpFilter{ 860 Name: "envoy.filters.http.jwt_authn", 861 ConfigType: &hcm.HttpFilter_TypedConfig{ 862 TypedConfig: protoconv.MessageToAny( 863 &envoy_jwt.JwtAuthentication{ 864 Rules: []*envoy_jwt.RequirementRule{ 865 { 866 Match: &route.RouteMatch{ 867 PathSpecifier: &route.RouteMatch_Prefix{ 868 Prefix: "/", 869 }, 870 }, 871 RequirementType: &envoy_jwt.RequirementRule_Requires{ 872 Requires: &envoy_jwt.JwtRequirement{ 873 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 874 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 875 Requirements: []*envoy_jwt.JwtRequirement{ 876 { 877 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 878 ProviderName: "origins-0", 879 }, 880 }, 881 { 882 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 883 AllowMissing: &emptypb.Empty{}, 884 }, 885 }, 886 }, 887 }, 888 }, 889 }, 890 }, 891 }, 892 }, 893 Providers: map[string]*envoy_jwt.JwtProvider{ 894 "origins-0": { 895 Issuer: "https://secret.foo.com", 896 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 897 LocalJwks: &core.DataSource{ 898 Specifier: &core.DataSource_InlineString{ 899 InlineString: test.JwtPubKey1, 900 }, 901 }, 902 }, 903 Forward: true, 904 ClaimToHeaders: []*envoy_jwt.JwtClaimToHeader{ 905 {HeaderName: "x-jwt-key1", ClaimName: "value1"}, 906 {HeaderName: "x-jwt-key2", ClaimName: "value2"}, 907 }, 908 PayloadInMetadata: "https://secret.foo.com", 909 }, 910 }, 911 BypassCorsPreflight: true, 912 }), 913 }, 914 }, 915 }, 916 } 917 push := model.NewPushContext() 918 push.JwtKeyResolver = model.NewJwksResolver( 919 model.JwtPubKeyEvictionDuration, model.JwtPubKeyRefreshInterval, 920 model.JwtPubKeyRefreshIntervalOnFailure, 10*time.Millisecond) 921 922 defer push.JwtKeyResolver.Close() 923 924 push.ServiceIndex.HostnameAndNamespace[host.Name("jwt-token-issuer.mesh")] = map[string]*model.Service{} 925 push.ServiceIndex.HostnameAndNamespace[host.Name("jwt-token-issuer.mesh")]["mesh"] = &model.Service{ 926 Hostname: "jwt-token-issuer.mesh.svc.cluster.local", 927 } 928 for _, c := range cases { 929 t.Run(c.name, func(t *testing.T) { 930 istiotest.SetForTest(t, &features.JwksFetchMode, c.jwksFetchMode) 931 if got := newPolicyApplier("root-namespace", c.in, nil, push).JwtFilter(false, false); !reflect.DeepEqual(c.expected, got) { 932 t.Errorf("got:\n%s\nwanted:\n%s", spew.Sdump(got), spew.Sdump(c.expected)) 933 } 934 }) 935 } 936 } 937 938 func TestConvertToEnvoyJwtConfig(t *testing.T) { 939 ms, err := test.StartNewServer() 940 if err != nil { 941 t.Fatal("failed to start a mock server") 942 } 943 944 jwksURI := ms.URL + "/oauth2/v3/certs" 945 946 cases := []struct { 947 name string 948 in []*v1beta1.JWTRule 949 expected *envoy_jwt.JwtAuthentication 950 }{ 951 { 952 name: "No rule", 953 in: []*v1beta1.JWTRule{}, 954 expected: nil, 955 }, 956 { 957 name: "Single JWT rule", 958 in: []*v1beta1.JWTRule{ 959 { 960 Issuer: "https://secret.foo.com", 961 JwksUri: jwksURI, 962 }, 963 }, 964 expected: &envoy_jwt.JwtAuthentication{ 965 Rules: []*envoy_jwt.RequirementRule{ 966 { 967 Match: &route.RouteMatch{ 968 PathSpecifier: &route.RouteMatch_Prefix{ 969 Prefix: "/", 970 }, 971 }, 972 RequirementType: &envoy_jwt.RequirementRule_Requires{ 973 Requires: &envoy_jwt.JwtRequirement{ 974 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 975 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 976 Requirements: []*envoy_jwt.JwtRequirement{ 977 { 978 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 979 ProviderName: "origins-0", 980 }, 981 }, 982 { 983 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 984 AllowMissing: &emptypb.Empty{}, 985 }, 986 }, 987 }, 988 }, 989 }, 990 }, 991 }, 992 }, 993 }, 994 Providers: map[string]*envoy_jwt.JwtProvider{ 995 "origins-0": { 996 Issuer: "https://secret.foo.com", 997 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 998 LocalJwks: &core.DataSource{ 999 Specifier: &core.DataSource_InlineString{ 1000 InlineString: test.JwtPubKey1, 1001 }, 1002 }, 1003 }, 1004 Forward: false, 1005 PayloadInMetadata: "https://secret.foo.com", 1006 }, 1007 }, 1008 BypassCorsPreflight: true, 1009 }, 1010 }, 1011 { 1012 name: "Multiple JWT rule", 1013 in: []*v1beta1.JWTRule{ 1014 { 1015 Issuer: "https://secret.foo.com", 1016 JwksUri: jwksURI, 1017 }, 1018 { 1019 Issuer: "https://secret.bar.com", 1020 Jwks: "jwks-inline-data", 1021 }, 1022 }, 1023 expected: &envoy_jwt.JwtAuthentication{ 1024 Rules: []*envoy_jwt.RequirementRule{ 1025 { 1026 Match: &route.RouteMatch{ 1027 PathSpecifier: &route.RouteMatch_Prefix{ 1028 Prefix: "/", 1029 }, 1030 }, 1031 RequirementType: &envoy_jwt.RequirementRule_Requires{ 1032 Requires: &envoy_jwt.JwtRequirement{ 1033 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 1034 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 1035 Requirements: []*envoy_jwt.JwtRequirement{ 1036 { 1037 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 1038 ProviderName: "origins-0", 1039 }, 1040 }, 1041 { 1042 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 1043 ProviderName: "origins-1", 1044 }, 1045 }, 1046 { 1047 RequiresType: &envoy_jwt.JwtRequirement_RequiresAll{ 1048 RequiresAll: &envoy_jwt.JwtRequirementAndList{ 1049 Requirements: []*envoy_jwt.JwtRequirement{ 1050 { 1051 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 1052 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 1053 Requirements: []*envoy_jwt.JwtRequirement{ 1054 { 1055 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 1056 ProviderName: "origins-0", 1057 }, 1058 }, 1059 { 1060 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 1061 AllowMissing: &emptypb.Empty{}, 1062 }, 1063 }, 1064 }, 1065 }, 1066 }, 1067 }, 1068 { 1069 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 1070 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 1071 Requirements: []*envoy_jwt.JwtRequirement{ 1072 { 1073 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 1074 ProviderName: "origins-1", 1075 }, 1076 }, 1077 { 1078 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 1079 AllowMissing: &emptypb.Empty{}, 1080 }, 1081 }, 1082 }, 1083 }, 1084 }, 1085 }, 1086 }, 1087 }, 1088 }, 1089 }, 1090 }, 1091 }, 1092 }, 1093 }, 1094 }, 1095 }, 1096 }, 1097 Providers: map[string]*envoy_jwt.JwtProvider{ 1098 "origins-0": { 1099 Issuer: "https://secret.foo.com", 1100 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 1101 LocalJwks: &core.DataSource{ 1102 Specifier: &core.DataSource_InlineString{ 1103 InlineString: test.JwtPubKey1, 1104 }, 1105 }, 1106 }, 1107 Forward: false, 1108 PayloadInMetadata: "https://secret.foo.com", 1109 }, 1110 "origins-1": { 1111 Issuer: "https://secret.bar.com", 1112 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 1113 LocalJwks: &core.DataSource{ 1114 Specifier: &core.DataSource_InlineString{ 1115 InlineString: "jwks-inline-data", 1116 }, 1117 }, 1118 }, 1119 Forward: false, 1120 PayloadInMetadata: "https://secret.bar.com", 1121 }, 1122 }, 1123 BypassCorsPreflight: true, 1124 }, 1125 }, 1126 { 1127 name: "Empty Jwks URI", 1128 in: []*v1beta1.JWTRule{ 1129 { 1130 Issuer: "https://secret.foo.com", 1131 }, 1132 }, 1133 expected: &envoy_jwt.JwtAuthentication{ 1134 Rules: []*envoy_jwt.RequirementRule{ 1135 { 1136 Match: &route.RouteMatch{ 1137 PathSpecifier: &route.RouteMatch_Prefix{ 1138 Prefix: "/", 1139 }, 1140 }, 1141 RequirementType: &envoy_jwt.RequirementRule_Requires{ 1142 Requires: &envoy_jwt.JwtRequirement{ 1143 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 1144 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 1145 Requirements: []*envoy_jwt.JwtRequirement{ 1146 { 1147 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 1148 ProviderName: "origins-0", 1149 }, 1150 }, 1151 { 1152 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 1153 AllowMissing: &emptypb.Empty{}, 1154 }, 1155 }, 1156 }, 1157 }, 1158 }, 1159 }, 1160 }, 1161 }, 1162 }, 1163 Providers: map[string]*envoy_jwt.JwtProvider{ 1164 "origins-0": { 1165 Issuer: "https://secret.foo.com", 1166 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 1167 LocalJwks: &core.DataSource{ 1168 Specifier: &core.DataSource_InlineString{ 1169 InlineString: model.FakeJwks, 1170 }, 1171 }, 1172 }, 1173 Forward: false, 1174 PayloadInMetadata: "https://secret.foo.com", 1175 }, 1176 }, 1177 BypassCorsPreflight: true, 1178 }, 1179 }, 1180 { 1181 name: "Unreachable Jwks URI", 1182 in: []*v1beta1.JWTRule{ 1183 { 1184 Issuer: "https://secret.foo.com", 1185 JwksUri: "http://site.not.exist", 1186 }, 1187 }, 1188 expected: &envoy_jwt.JwtAuthentication{ 1189 Rules: []*envoy_jwt.RequirementRule{ 1190 { 1191 Match: &route.RouteMatch{ 1192 PathSpecifier: &route.RouteMatch_Prefix{ 1193 Prefix: "/", 1194 }, 1195 }, 1196 RequirementType: &envoy_jwt.RequirementRule_Requires{ 1197 Requires: &envoy_jwt.JwtRequirement{ 1198 RequiresType: &envoy_jwt.JwtRequirement_RequiresAny{ 1199 RequiresAny: &envoy_jwt.JwtRequirementOrList{ 1200 Requirements: []*envoy_jwt.JwtRequirement{ 1201 { 1202 RequiresType: &envoy_jwt.JwtRequirement_ProviderName{ 1203 ProviderName: "origins-0", 1204 }, 1205 }, 1206 { 1207 RequiresType: &envoy_jwt.JwtRequirement_AllowMissing{ 1208 AllowMissing: &emptypb.Empty{}, 1209 }, 1210 }, 1211 }, 1212 }, 1213 }, 1214 }, 1215 }, 1216 }, 1217 }, 1218 Providers: map[string]*envoy_jwt.JwtProvider{ 1219 "origins-0": { 1220 Issuer: "https://secret.foo.com", 1221 JwksSourceSpecifier: &envoy_jwt.JwtProvider_LocalJwks{ 1222 LocalJwks: &core.DataSource{ 1223 Specifier: &core.DataSource_InlineString{ 1224 InlineString: model.FakeJwks, 1225 }, 1226 }, 1227 }, 1228 Forward: false, 1229 PayloadInMetadata: "https://secret.foo.com", 1230 }, 1231 }, 1232 BypassCorsPreflight: true, 1233 }, 1234 }, 1235 } 1236 1237 push := &model.PushContext{} 1238 push.JwtKeyResolver = model.NewJwksResolver( 1239 model.JwtPubKeyEvictionDuration, model.JwtPubKeyRefreshInterval, 1240 model.JwtPubKeyRefreshIntervalOnFailure, 10*time.Millisecond) 1241 defer push.JwtKeyResolver.Close() 1242 1243 for _, c := range cases { 1244 t.Run(c.name, func(t *testing.T) { 1245 if got := convertToEnvoyJwtConfig(c.in, push, false, false); !reflect.DeepEqual(c.expected, got) { 1246 t.Errorf("got:\n%s\nwanted:\n%s\n", spew.Sdump(got), spew.Sdump(c.expected)) 1247 } 1248 }) 1249 } 1250 } 1251 1252 func humanReadableAuthnFilterDump(filter *hcm.HttpFilter) string { 1253 if filter == nil { 1254 return "<nil>" 1255 } 1256 config := &authn_filter.FilterConfig{} 1257 filter.GetTypedConfig().UnmarshalTo(config) 1258 return spew.Sdump(config) 1259 } 1260 1261 func TestAuthnFilterConfig(t *testing.T) { 1262 ms, err := test.StartNewServer() 1263 if err != nil { 1264 t.Fatal("failed to start a mock server") 1265 } 1266 jwksURI := ms.URL + "/oauth2/v3/certs" 1267 1268 cases := []struct { 1269 name string 1270 forSidecar bool 1271 jwtIn []*config.Config 1272 peerIn []*config.Config 1273 expected *hcm.HttpFilter 1274 }{ 1275 { 1276 name: "no-policy", 1277 expected: nil, 1278 }, 1279 { 1280 name: "beta-jwt", 1281 jwtIn: []*config.Config{ 1282 { 1283 Spec: &v1beta1.RequestAuthentication{ 1284 JwtRules: []*v1beta1.JWTRule{ 1285 { 1286 Issuer: "https://secret.foo.com", 1287 JwksUri: jwksURI, 1288 }, 1289 }, 1290 }, 1291 }, 1292 }, 1293 expected: &hcm.HttpFilter{ 1294 Name: "istio_authn", 1295 ConfigType: &hcm.HttpFilter_TypedConfig{ 1296 TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{ 1297 SkipValidateTrustDomain: true, 1298 Policy: &authn_alpha.Policy{ 1299 Origins: []*authn_alpha.OriginAuthenticationMethod{ 1300 { 1301 Jwt: &authn_alpha.Jwt{ 1302 Issuer: "https://secret.foo.com", 1303 }, 1304 }, 1305 }, 1306 OriginIsOptional: true, 1307 PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN, 1308 }, 1309 }), 1310 }, 1311 }, 1312 }, 1313 { 1314 name: "beta-jwt-for-sidecar", 1315 forSidecar: true, 1316 jwtIn: []*config.Config{ 1317 { 1318 Spec: &v1beta1.RequestAuthentication{ 1319 JwtRules: []*v1beta1.JWTRule{ 1320 { 1321 Issuer: "https://secret.foo.com", 1322 JwksUri: jwksURI, 1323 }, 1324 }, 1325 }, 1326 }, 1327 }, 1328 expected: &hcm.HttpFilter{ 1329 Name: "istio_authn", 1330 ConfigType: &hcm.HttpFilter_TypedConfig{ 1331 TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{ 1332 SkipValidateTrustDomain: true, 1333 DisableClearRouteCache: true, 1334 Policy: &authn_alpha.Policy{ 1335 Origins: []*authn_alpha.OriginAuthenticationMethod{ 1336 { 1337 Jwt: &authn_alpha.Jwt{ 1338 Issuer: "https://secret.foo.com", 1339 }, 1340 }, 1341 }, 1342 OriginIsOptional: true, 1343 PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN, 1344 }, 1345 }), 1346 }, 1347 }, 1348 }, 1349 { 1350 name: "multi-beta-jwt", 1351 jwtIn: []*config.Config{ 1352 { 1353 Spec: &v1beta1.RequestAuthentication{ 1354 JwtRules: []*v1beta1.JWTRule{ 1355 { 1356 Issuer: "https://secret.bar.com", 1357 JwksUri: jwksURI, 1358 }, 1359 }, 1360 }, 1361 }, 1362 { 1363 Spec: &v1beta1.RequestAuthentication{}, 1364 }, 1365 { 1366 Spec: &v1beta1.RequestAuthentication{ 1367 JwtRules: []*v1beta1.JWTRule{ 1368 { 1369 Issuer: "https://secret.foo.com", 1370 Jwks: "jwks-inline-data", 1371 }, 1372 }, 1373 }, 1374 }, 1375 }, 1376 expected: &hcm.HttpFilter{ 1377 Name: "istio_authn", 1378 ConfigType: &hcm.HttpFilter_TypedConfig{ 1379 TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{ 1380 SkipValidateTrustDomain: true, 1381 Policy: &authn_alpha.Policy{ 1382 Origins: []*authn_alpha.OriginAuthenticationMethod{ 1383 { 1384 Jwt: &authn_alpha.Jwt{ 1385 Issuer: "https://secret.bar.com", 1386 }, 1387 }, 1388 { 1389 Jwt: &authn_alpha.Jwt{ 1390 Issuer: "https://secret.foo.com", 1391 }, 1392 }, 1393 }, 1394 OriginIsOptional: true, 1395 PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN, 1396 }, 1397 }), 1398 }, 1399 }, 1400 }, 1401 { 1402 name: "multi-beta-jwt-sort-by-issuer-again", 1403 jwtIn: []*config.Config{ 1404 { 1405 Spec: &v1beta1.RequestAuthentication{ 1406 JwtRules: []*v1beta1.JWTRule{ 1407 { 1408 Issuer: "https://secret.foo.com", 1409 JwksUri: jwksURI, 1410 }, 1411 }, 1412 }, 1413 }, 1414 { 1415 Spec: &v1beta1.RequestAuthentication{}, 1416 }, 1417 { 1418 Spec: &v1beta1.RequestAuthentication{ 1419 JwtRules: []*v1beta1.JWTRule{ 1420 { 1421 Issuer: "https://secret.bar.com", 1422 Jwks: "jwks-inline-data", 1423 }, 1424 }, 1425 }, 1426 }, 1427 }, 1428 expected: &hcm.HttpFilter{ 1429 Name: "istio_authn", 1430 ConfigType: &hcm.HttpFilter_TypedConfig{ 1431 TypedConfig: protoconv.MessageToAny(&authn_filter.FilterConfig{ 1432 SkipValidateTrustDomain: true, 1433 Policy: &authn_alpha.Policy{ 1434 Origins: []*authn_alpha.OriginAuthenticationMethod{ 1435 { 1436 Jwt: &authn_alpha.Jwt{ 1437 Issuer: "https://secret.bar.com", 1438 }, 1439 }, 1440 { 1441 Jwt: &authn_alpha.Jwt{ 1442 Issuer: "https://secret.foo.com", 1443 }, 1444 }, 1445 }, 1446 OriginIsOptional: true, 1447 PrincipalBinding: authn_alpha.PrincipalBinding_USE_ORIGIN, 1448 }, 1449 }), 1450 }, 1451 }, 1452 }, 1453 { 1454 name: "beta-mtls", 1455 peerIn: []*config.Config{ 1456 { 1457 Spec: &v1beta1.PeerAuthentication{ 1458 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1459 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1460 }, 1461 }, 1462 }, 1463 }, 1464 expected: nil, 1465 }, 1466 { 1467 name: "beta-mtls-disable", 1468 peerIn: []*config.Config{ 1469 { 1470 Spec: &v1beta1.PeerAuthentication{ 1471 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1472 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1473 }, 1474 }, 1475 }, 1476 }, 1477 expected: nil, 1478 }, 1479 { 1480 name: "beta-mtls-skip-trust-domain", 1481 peerIn: []*config.Config{ 1482 { 1483 Spec: &v1beta1.PeerAuthentication{ 1484 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1485 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1486 }, 1487 }, 1488 }, 1489 }, 1490 expected: nil, 1491 }, 1492 } 1493 for _, c := range cases { 1494 t.Run(c.name, func(t *testing.T) { 1495 got := newPolicyApplier("root-namespace", c.jwtIn, c.peerIn, &model.PushContext{}).AuthNFilter(c.forSidecar) 1496 if !reflect.DeepEqual(c.expected, got) { 1497 t.Errorf("got:\n%v\nwanted:\n%v\n", humanReadableAuthnFilterDump(got), humanReadableAuthnFilterDump(c.expected)) 1498 } 1499 }) 1500 } 1501 } 1502 1503 func TestInboundMTLSSettings(t *testing.T) { 1504 now := time.Now() 1505 tlsContext := &tls.DownstreamTlsContext{ 1506 CommonTlsContext: &tls.CommonTlsContext{ 1507 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 1508 { 1509 Name: "default", 1510 SdsConfig: &core.ConfigSource{ 1511 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 1512 ApiConfigSource: &core.ApiConfigSource{ 1513 ApiType: core.ApiConfigSource_GRPC, 1514 SetNodeOnFirstMessageOnly: true, 1515 TransportApiVersion: core.ApiVersion_V3, 1516 GrpcServices: []*core.GrpcService{ 1517 { 1518 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 1519 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 1520 }, 1521 }, 1522 }, 1523 }, 1524 }, 1525 InitialFetchTimeout: durationpb.New(time.Second * 0), 1526 ResourceApiVersion: core.ApiVersion_V3, 1527 }, 1528 }, 1529 }, 1530 ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{ 1531 CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{ 1532 DefaultValidationContext: &tls.CertificateValidationContext{}, 1533 ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{ 1534 Name: "ROOTCA", 1535 SdsConfig: &core.ConfigSource{ 1536 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 1537 ApiConfigSource: &core.ApiConfigSource{ 1538 ApiType: core.ApiConfigSource_GRPC, 1539 SetNodeOnFirstMessageOnly: true, 1540 TransportApiVersion: core.ApiVersion_V3, 1541 GrpcServices: []*core.GrpcService{ 1542 { 1543 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 1544 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 1545 }, 1546 }, 1547 }, 1548 }, 1549 }, 1550 InitialFetchTimeout: durationpb.New(time.Second * 0), 1551 ResourceApiVersion: core.ApiVersion_V3, 1552 }, 1553 }, 1554 }, 1555 }, 1556 AlpnProtocols: []string{"istio-peer-exchange", "h2", "http/1.1"}, 1557 TlsParams: &tls.TlsParameters{ 1558 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 1559 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 1560 CipherSuites: []string{ 1561 "ECDHE-ECDSA-AES256-GCM-SHA384", 1562 "ECDHE-RSA-AES256-GCM-SHA384", 1563 "ECDHE-ECDSA-AES128-GCM-SHA256", 1564 "ECDHE-RSA-AES128-GCM-SHA256", 1565 "AES256-GCM-SHA384", 1566 "AES128-GCM-SHA256", 1567 }, 1568 }, 1569 }, 1570 RequireClientCertificate: protovalue.BoolTrue, 1571 } 1572 tlsContextHTTP := proto.Clone(tlsContext).(*tls.DownstreamTlsContext) 1573 tlsContextHTTP.CommonTlsContext.AlpnProtocols = []string{"h2", "http/1.1"} 1574 1575 expectedStrict := MTLSSettings{ 1576 Port: 8080, 1577 Mode: model.MTLSStrict, 1578 TCP: tlsContext, 1579 HTTP: tlsContextHTTP, 1580 } 1581 expectedPermissive := MTLSSettings{ 1582 Port: 8080, 1583 Mode: model.MTLSPermissive, 1584 TCP: tlsContext, 1585 HTTP: tlsContextHTTP, 1586 } 1587 1588 cases := []struct { 1589 name string 1590 peerPolicies []*config.Config 1591 expected MTLSSettings 1592 }{ 1593 { 1594 name: "No policy - behave as permissive", 1595 expected: expectedPermissive, 1596 }, 1597 { 1598 name: "Single policy - disable mode", 1599 peerPolicies: []*config.Config{ 1600 { 1601 Spec: &v1beta1.PeerAuthentication{ 1602 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1603 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1604 }, 1605 }, 1606 }, 1607 }, 1608 expected: MTLSSettings{Port: 8080, Mode: model.MTLSDisable}, 1609 }, 1610 { 1611 name: "Single policy - permissive mode", 1612 peerPolicies: []*config.Config{ 1613 { 1614 Spec: &v1beta1.PeerAuthentication{ 1615 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1616 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 1617 }, 1618 }, 1619 }, 1620 }, 1621 expected: expectedPermissive, 1622 }, 1623 { 1624 name: "Single policy - strict mode", 1625 peerPolicies: []*config.Config{ 1626 { 1627 Spec: &v1beta1.PeerAuthentication{ 1628 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1629 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1630 }, 1631 }, 1632 }, 1633 }, 1634 expected: expectedStrict, 1635 }, 1636 { 1637 name: "Multiple policies resolved to STRICT", 1638 peerPolicies: []*config.Config{ 1639 { 1640 Meta: config.Meta{ 1641 Name: "now", 1642 Namespace: "my-ns", 1643 CreationTimestamp: now, 1644 }, 1645 Spec: &v1beta1.PeerAuthentication{ 1646 Selector: &type_beta.WorkloadSelector{ 1647 MatchLabels: map[string]string{ 1648 "app": "foo", 1649 }, 1650 }, 1651 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1652 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1653 }, 1654 }, 1655 }, 1656 { 1657 Meta: config.Meta{ 1658 Name: "later", 1659 Namespace: "my-ns", 1660 CreationTimestamp: now.Add(time.Second), 1661 }, 1662 Spec: &v1beta1.PeerAuthentication{ 1663 Selector: &type_beta.WorkloadSelector{ 1664 MatchLabels: map[string]string{ 1665 "app": "foo", 1666 }, 1667 }, 1668 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1669 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1670 }, 1671 }, 1672 }, 1673 }, 1674 expected: expectedStrict, 1675 }, 1676 { 1677 name: "Multiple policies resolved to PERMISSIVE", 1678 peerPolicies: []*config.Config{ 1679 { 1680 Meta: config.Meta{ 1681 Name: "now", 1682 Namespace: "my-ns", 1683 CreationTimestamp: now, 1684 }, 1685 Spec: &v1beta1.PeerAuthentication{ 1686 Selector: &type_beta.WorkloadSelector{ 1687 MatchLabels: map[string]string{ 1688 "app": "foo", 1689 }, 1690 }, 1691 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1692 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1693 }, 1694 }, 1695 }, 1696 { 1697 Meta: config.Meta{ 1698 Name: "earlier", 1699 Namespace: "my-ns", 1700 CreationTimestamp: now.Add(time.Second * -1), 1701 }, 1702 Spec: &v1beta1.PeerAuthentication{ 1703 Selector: &type_beta.WorkloadSelector{ 1704 MatchLabels: map[string]string{ 1705 "app": "foo", 1706 }, 1707 }, 1708 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1709 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 1710 }, 1711 }, 1712 }, 1713 }, 1714 expected: expectedPermissive, 1715 }, 1716 { 1717 name: "Port level hit", 1718 peerPolicies: []*config.Config{ 1719 { 1720 Spec: &v1beta1.PeerAuthentication{ 1721 Selector: &type_beta.WorkloadSelector{ 1722 MatchLabels: map[string]string{ 1723 "app": "foo", 1724 }, 1725 }, 1726 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1727 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1728 }, 1729 PortLevelMtls: map[uint32]*v1beta1.PeerAuthentication_MutualTLS{ 1730 8080: { 1731 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1732 }, 1733 }, 1734 }, 1735 }, 1736 }, 1737 expected: expectedStrict, 1738 }, 1739 { 1740 name: "Port level miss", 1741 peerPolicies: []*config.Config{ 1742 { 1743 Spec: &v1beta1.PeerAuthentication{ 1744 Selector: &type_beta.WorkloadSelector{ 1745 MatchLabels: map[string]string{ 1746 "app": "foo", 1747 }, 1748 }, 1749 PortLevelMtls: map[uint32]*v1beta1.PeerAuthentication_MutualTLS{ 1750 7070: { 1751 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1752 }, 1753 }, 1754 }, 1755 }, 1756 }, 1757 expected: expectedPermissive, 1758 }, 1759 } 1760 1761 testNode := &model.Proxy{ 1762 Labels: map[string]string{ 1763 "app": "foo", 1764 }, 1765 Metadata: &model.NodeMetadata{}, 1766 } 1767 for _, tc := range cases { 1768 t.Run(tc.name, func(t *testing.T) { 1769 got := newPolicyApplier("root-namespace", nil, tc.peerPolicies, &model.PushContext{}).InboundMTLSSettings( 1770 8080, 1771 testNode, 1772 []string{}, 1773 NoOverride, 1774 ) 1775 if diff := cmp.Diff(tc.expected, got, protocmp.Transform()); diff != "" { 1776 t.Errorf("unexpected filter chains: %v", diff) 1777 } 1778 }) 1779 } 1780 } 1781 1782 func TestComposePeerAuthentication(t *testing.T) { 1783 now := time.Now() 1784 tests := []struct { 1785 name string 1786 configs []*config.Config 1787 want MergedPeerAuthentication 1788 }{ 1789 { 1790 name: "no config", 1791 configs: []*config.Config{}, 1792 want: MergedPeerAuthentication{ 1793 Mode: model.MTLSPermissive, 1794 }, 1795 }, 1796 { 1797 name: "mesh only", 1798 configs: []*config.Config{ 1799 { 1800 Meta: config.Meta{ 1801 Name: "default", 1802 Namespace: "root-namespace", 1803 }, 1804 Spec: &v1beta1.PeerAuthentication{ 1805 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1806 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1807 }, 1808 }, 1809 }, 1810 }, 1811 want: MergedPeerAuthentication{ 1812 Mode: model.MTLSStrict, 1813 }, 1814 }, 1815 { 1816 name: "mesh vs namespace", 1817 configs: []*config.Config{ 1818 { 1819 Meta: config.Meta{ 1820 Name: "default", 1821 Namespace: "root-namespace", 1822 }, 1823 Spec: &v1beta1.PeerAuthentication{ 1824 Selector: &type_beta.WorkloadSelector{ 1825 MatchLabels: map[string]string{}, 1826 }, 1827 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1828 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1829 }, 1830 }, 1831 }, 1832 { 1833 Meta: config.Meta{ 1834 Name: "default", 1835 Namespace: "my-ns", 1836 }, 1837 Spec: &v1beta1.PeerAuthentication{ 1838 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1839 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 1840 }, 1841 }, 1842 }, 1843 }, 1844 want: MergedPeerAuthentication{ 1845 Mode: model.MTLSPermissive, 1846 }, 1847 }, 1848 { 1849 name: "ignore non-emptypb selector in root namespace", 1850 configs: []*config.Config{ 1851 { 1852 Meta: config.Meta{ 1853 Name: "default", 1854 Namespace: "root-namespace", 1855 }, 1856 Spec: &v1beta1.PeerAuthentication{ 1857 Selector: &type_beta.WorkloadSelector{ 1858 MatchLabels: map[string]string{ 1859 "app": "foo", 1860 }, 1861 }, 1862 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1863 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1864 }, 1865 }, 1866 }, 1867 }, 1868 want: MergedPeerAuthentication{ 1869 Mode: model.MTLSPermissive, 1870 }, 1871 }, 1872 { 1873 name: "workload vs namespace config", 1874 configs: []*config.Config{ 1875 { 1876 Meta: config.Meta{ 1877 Name: "default", 1878 Namespace: "my-ns", 1879 }, 1880 Spec: &v1beta1.PeerAuthentication{}, 1881 }, 1882 { 1883 Meta: config.Meta{ 1884 Name: "foo", 1885 Namespace: "my-ns", 1886 }, 1887 Spec: &v1beta1.PeerAuthentication{ 1888 Selector: &type_beta.WorkloadSelector{ 1889 MatchLabels: map[string]string{ 1890 "app": "foo", 1891 }, 1892 }, 1893 }, 1894 }, 1895 }, 1896 want: MergedPeerAuthentication{ 1897 Mode: model.MTLSPermissive, 1898 }, 1899 }, 1900 { 1901 name: "workload vs mesh config", 1902 configs: []*config.Config{ 1903 { 1904 Meta: config.Meta{ 1905 Name: "default", 1906 Namespace: "my-ns", 1907 }, 1908 Spec: &v1beta1.PeerAuthentication{ 1909 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1910 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 1911 }, 1912 }, 1913 }, 1914 { 1915 Meta: config.Meta{ 1916 Name: "default", 1917 Namespace: "root-namespace", 1918 }, 1919 Spec: &v1beta1.PeerAuthentication{ 1920 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1921 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1922 }, 1923 }, 1924 }, 1925 }, 1926 want: MergedPeerAuthentication{ 1927 Mode: model.MTLSPermissive, 1928 }, 1929 }, 1930 { 1931 name: "multiple mesh policy", 1932 configs: []*config.Config{ 1933 { 1934 Meta: config.Meta{ 1935 Name: "now", 1936 Namespace: "root-namespace", 1937 CreationTimestamp: now, 1938 }, 1939 Spec: &v1beta1.PeerAuthentication{ 1940 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1941 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1942 }, 1943 }, 1944 }, 1945 { 1946 Meta: config.Meta{ 1947 Name: "second ago", 1948 Namespace: "root-namespace", 1949 CreationTimestamp: now.Add(time.Second * -1), 1950 }, 1951 Spec: &v1beta1.PeerAuthentication{ 1952 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1953 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 1954 }, 1955 }, 1956 }, 1957 { 1958 Meta: config.Meta{ 1959 Name: "second later", 1960 Namespace: "root-namespace", 1961 CreationTimestamp: now.Add(time.Second * -1), 1962 }, 1963 Spec: &v1beta1.PeerAuthentication{ 1964 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1965 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 1966 }, 1967 }, 1968 }, 1969 }, 1970 want: MergedPeerAuthentication{ 1971 Mode: model.MTLSPermissive, 1972 }, 1973 }, 1974 { 1975 name: "multiple namespace policy", 1976 configs: []*config.Config{ 1977 { 1978 Meta: config.Meta{ 1979 Name: "now", 1980 Namespace: "my-ns", 1981 CreationTimestamp: now, 1982 }, 1983 Spec: &v1beta1.PeerAuthentication{ 1984 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1985 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 1986 }, 1987 }, 1988 }, 1989 { 1990 Meta: config.Meta{ 1991 Name: "second ago", 1992 Namespace: "my-ns", 1993 CreationTimestamp: now.Add(time.Second * -1), 1994 }, 1995 Spec: &v1beta1.PeerAuthentication{ 1996 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 1997 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 1998 }, 1999 }, 2000 }, 2001 { 2002 Meta: config.Meta{ 2003 Name: "second later", 2004 Namespace: "my-ns", 2005 CreationTimestamp: now.Add(time.Second * -1), 2006 }, 2007 Spec: &v1beta1.PeerAuthentication{ 2008 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2009 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 2010 }, 2011 }, 2012 }, 2013 }, 2014 want: MergedPeerAuthentication{ 2015 Mode: model.MTLSPermissive, 2016 }, 2017 }, 2018 { 2019 name: "multiple workload policy", 2020 configs: []*config.Config{ 2021 { 2022 Meta: config.Meta{ 2023 Name: "now", 2024 Namespace: "my-ns", 2025 CreationTimestamp: now, 2026 }, 2027 Spec: &v1beta1.PeerAuthentication{ 2028 Selector: &type_beta.WorkloadSelector{ 2029 MatchLabels: map[string]string{ 2030 "app": "foo", 2031 }, 2032 }, 2033 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2034 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 2035 }, 2036 }, 2037 }, 2038 { 2039 Meta: config.Meta{ 2040 Name: "second ago", 2041 Namespace: "my-ns", 2042 CreationTimestamp: now.Add(time.Second * -1), 2043 }, 2044 Spec: &v1beta1.PeerAuthentication{ 2045 Selector: &type_beta.WorkloadSelector{ 2046 MatchLabels: map[string]string{ 2047 "app": "foo", 2048 }, 2049 }, 2050 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2051 Mode: v1beta1.PeerAuthentication_MutualTLS_PERMISSIVE, 2052 }, 2053 }, 2054 }, 2055 { 2056 Meta: config.Meta{ 2057 Name: "second later", 2058 Namespace: "my-ns", 2059 CreationTimestamp: now.Add(time.Second * -1), 2060 }, 2061 Spec: &v1beta1.PeerAuthentication{ 2062 Selector: &type_beta.WorkloadSelector{ 2063 MatchLabels: map[string]string{ 2064 "stage": "prod", 2065 }, 2066 }, 2067 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2068 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 2069 }, 2070 }, 2071 }, 2072 }, 2073 want: MergedPeerAuthentication{ 2074 Mode: model.MTLSPermissive, 2075 }, 2076 }, 2077 { 2078 name: "inheritance: default mesh", 2079 configs: []*config.Config{ 2080 { 2081 Meta: config.Meta{ 2082 Name: "default", 2083 Namespace: "root-namespace", 2084 }, 2085 Spec: &v1beta1.PeerAuthentication{ 2086 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2087 Mode: v1beta1.PeerAuthentication_MutualTLS_UNSET, 2088 }, 2089 }, 2090 }, 2091 }, 2092 want: MergedPeerAuthentication{ 2093 Mode: model.MTLSPermissive, 2094 }, 2095 }, 2096 { 2097 name: "inheritance: mesh to workload", 2098 configs: []*config.Config{ 2099 { 2100 Meta: config.Meta{ 2101 Name: "default", 2102 Namespace: "root-namespace", 2103 }, 2104 Spec: &v1beta1.PeerAuthentication{ 2105 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2106 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 2107 }, 2108 }, 2109 }, 2110 { 2111 Meta: config.Meta{ 2112 Name: "foo", 2113 Namespace: "my-ns", 2114 }, 2115 Spec: &v1beta1.PeerAuthentication{ 2116 Selector: &type_beta.WorkloadSelector{ 2117 MatchLabels: map[string]string{ 2118 "app": "foo", 2119 }, 2120 }, 2121 }, 2122 }, 2123 }, 2124 want: MergedPeerAuthentication{ 2125 Mode: model.MTLSStrict, 2126 }, 2127 }, 2128 { 2129 name: "inheritance: namespace to workload", 2130 configs: []*config.Config{ 2131 { 2132 Meta: config.Meta{ 2133 Name: "default", 2134 Namespace: "my-ns", 2135 }, 2136 Spec: &v1beta1.PeerAuthentication{ 2137 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2138 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 2139 }, 2140 }, 2141 }, 2142 { 2143 Meta: config.Meta{ 2144 Name: "foo", 2145 Namespace: "my-ns", 2146 }, 2147 Spec: &v1beta1.PeerAuthentication{ 2148 Selector: &type_beta.WorkloadSelector{ 2149 MatchLabels: map[string]string{ 2150 "app": "foo", 2151 }, 2152 }, 2153 }, 2154 }, 2155 }, 2156 want: MergedPeerAuthentication{ 2157 Mode: model.MTLSStrict, 2158 }, 2159 }, 2160 { 2161 name: "inheritance: mesh to namespace to workload", 2162 configs: []*config.Config{ 2163 { 2164 Meta: config.Meta{ 2165 Name: "default", 2166 Namespace: "root-namespace", 2167 }, 2168 Spec: &v1beta1.PeerAuthentication{ 2169 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2170 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 2171 }, 2172 }, 2173 }, 2174 { 2175 Meta: config.Meta{ 2176 Name: "default", 2177 Namespace: "my-ns", 2178 }, 2179 Spec: &v1beta1.PeerAuthentication{ 2180 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2181 Mode: v1beta1.PeerAuthentication_MutualTLS_UNSET, 2182 }, 2183 }, 2184 }, 2185 { 2186 Meta: config.Meta{ 2187 Name: "foo", 2188 Namespace: "my-ns", 2189 }, 2190 Spec: &v1beta1.PeerAuthentication{ 2191 Selector: &type_beta.WorkloadSelector{ 2192 MatchLabels: map[string]string{ 2193 "app": "foo", 2194 }, 2195 }, 2196 }, 2197 }, 2198 }, 2199 want: MergedPeerAuthentication{ 2200 Mode: model.MTLSStrict, 2201 }, 2202 }, 2203 { 2204 name: "port level", 2205 configs: []*config.Config{ 2206 { 2207 Meta: config.Meta{ 2208 Name: "default", 2209 Namespace: "root-namespace", 2210 }, 2211 Spec: &v1beta1.PeerAuthentication{ 2212 Mtls: &v1beta1.PeerAuthentication_MutualTLS{ 2213 Mode: v1beta1.PeerAuthentication_MutualTLS_STRICT, 2214 }, 2215 }, 2216 }, 2217 { 2218 Meta: config.Meta{ 2219 Name: "foo", 2220 Namespace: "my-ns", 2221 }, 2222 Spec: &v1beta1.PeerAuthentication{ 2223 Selector: &type_beta.WorkloadSelector{ 2224 MatchLabels: map[string]string{ 2225 "app": "foo", 2226 }, 2227 }, 2228 PortLevelMtls: map[uint32]*v1beta1.PeerAuthentication_MutualTLS{ 2229 80: { 2230 Mode: v1beta1.PeerAuthentication_MutualTLS_DISABLE, 2231 }, 2232 90: { 2233 Mode: v1beta1.PeerAuthentication_MutualTLS_UNSET, 2234 }, 2235 100: {}, 2236 }, 2237 }, 2238 }, 2239 }, 2240 want: MergedPeerAuthentication{ 2241 Mode: model.MTLSStrict, 2242 PerPort: map[uint32]model.MutualTLSMode{ 2243 80: model.MTLSDisable, 2244 90: model.MTLSStrict, 2245 100: model.MTLSStrict, 2246 }, 2247 }, 2248 }, 2249 } 2250 for _, tt := range tests { 2251 t.Run(tt.name, func(t *testing.T) { 2252 got := ComposePeerAuthentication("root-namespace", tt.configs) 2253 assert.Equal(t, got, tt.want) 2254 }) 2255 } 2256 }