istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/route/route_internal_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 route 16 17 import ( 18 "reflect" 19 "testing" 20 21 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 22 route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 23 xdsfault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/fault/v3" 24 cors "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3" 25 xdshttpfault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3" 26 matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 27 xdstype "github.com/envoyproxy/go-control-plane/envoy/type/v3" 28 "google.golang.org/protobuf/types/known/durationpb" 29 "google.golang.org/protobuf/types/known/wrapperspb" 30 31 networking "istio.io/api/networking/v1alpha3" 32 "istio.io/istio/pilot/pkg/model" 33 authzmatcher "istio.io/istio/pilot/pkg/security/authz/matcher" 34 authz "istio.io/istio/pilot/pkg/security/authz/model" 35 "istio.io/istio/pkg/config/labels" 36 "istio.io/istio/pkg/util/sets" 37 ) 38 39 func TestIsCatchAllRoute(t *testing.T) { 40 cases := []struct { 41 name string 42 route *route.Route 43 want bool 44 }{ 45 { 46 name: "catch all prefix", 47 route: &route.Route{ 48 Name: "catch-all", 49 Match: &route.RouteMatch{ 50 PathSpecifier: &route.RouteMatch_Prefix{ 51 Prefix: "/", 52 }, 53 }, 54 }, 55 want: true, 56 }, 57 { 58 name: "catch all prefix >= 1.14", 59 route: &route.Route{ 60 Name: "catch-all", 61 Match: &route.RouteMatch{ 62 PathSpecifier: &route.RouteMatch_PathSeparatedPrefix{ 63 PathSeparatedPrefix: "/", 64 }, 65 }, 66 }, 67 want: true, 68 }, 69 { 70 name: "catch all regex", 71 route: &route.Route{ 72 Name: "catch-all", 73 Match: &route.RouteMatch{ 74 PathSpecifier: &route.RouteMatch_SafeRegex{ 75 SafeRegex: &matcher.RegexMatcher{ 76 Regex: ".*", 77 }, 78 }, 79 }, 80 }, 81 want: true, 82 }, 83 { 84 name: "catch all prefix with headers", 85 route: &route.Route{ 86 Name: "catch-all", 87 Match: &route.RouteMatch{ 88 PathSpecifier: &route.RouteMatch_Prefix{ 89 Prefix: "/", 90 }, 91 Headers: []*route.HeaderMatcher{ 92 { 93 Name: "Authentication", 94 HeaderMatchSpecifier: &route.HeaderMatcher_ExactMatch{ 95 ExactMatch: "test", 96 }, 97 }, 98 }, 99 }, 100 }, 101 want: false, 102 }, 103 { 104 name: "uri regex with headers", 105 route: &route.Route{ 106 Name: "non-catch-all", 107 Match: &route.RouteMatch{ 108 PathSpecifier: &route.RouteMatch_SafeRegex{ 109 SafeRegex: &matcher.RegexMatcher{ 110 // nolint: staticcheck 111 Regex: ".*", 112 }, 113 }, 114 Headers: []*route.HeaderMatcher{ 115 { 116 Name: "Authentication", 117 HeaderMatchSpecifier: &route.HeaderMatcher_StringMatch{ 118 StringMatch: &matcher.StringMatcher{ 119 MatchPattern: &matcher.StringMatcher_SafeRegex{ 120 SafeRegex: &matcher.RegexMatcher{ 121 Regex: ".*", 122 }, 123 }, 124 }, 125 }, 126 }, 127 }, 128 }, 129 }, 130 want: false, 131 }, 132 { 133 name: "uri regex with query params", 134 route: &route.Route{ 135 Name: "non-catch-all", 136 Match: &route.RouteMatch{ 137 PathSpecifier: &route.RouteMatch_SafeRegex{ 138 SafeRegex: &matcher.RegexMatcher{ 139 // nolint: staticcheck 140 Regex: ".*", 141 }, 142 }, 143 QueryParameters: []*route.QueryParameterMatcher{ 144 { 145 Name: "Authentication", 146 QueryParameterMatchSpecifier: &route.QueryParameterMatcher_PresentMatch{ 147 PresentMatch: true, 148 }, 149 }, 150 }, 151 }, 152 }, 153 want: false, 154 }, 155 } 156 157 for _, tt := range cases { 158 t.Run(tt.name, func(t *testing.T) { 159 catchall := IsCatchAllRoute(tt.route) 160 if catchall != tt.want { 161 t.Errorf("Unexpected catchAllMatch want %v, got %v", tt.want, catchall) 162 } 163 }) 164 } 165 } 166 167 func TestTranslateCORSPolicyForwardNotMatchingPreflights(t *testing.T) { 168 node := &model.Proxy{ 169 IstioVersion: &model.IstioVersion{ 170 Major: 1, 171 Minor: 23, 172 Patch: 0, 173 }, 174 } 175 corsPolicy := &networking.CorsPolicy{ 176 AllowOrigins: []*networking.StringMatch{ 177 {MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 178 {MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}}, 179 {MatchType: &networking.StringMatch_Regex{Regex: "regex"}}, 180 }, 181 UnmatchedPreflights: networking.CorsPolicy_IGNORE, 182 } 183 expectedCorsPolicy := &cors.CorsPolicy{ 184 ForwardNotMatchingPreflights: wrapperspb.Bool(false), 185 AllowOriginStringMatch: []*matcher.StringMatcher{ 186 {MatchPattern: &matcher.StringMatcher_Exact{Exact: "exact"}}, 187 {MatchPattern: &matcher.StringMatcher_Prefix{Prefix: "prefix"}}, 188 { 189 MatchPattern: &matcher.StringMatcher_SafeRegex{ 190 SafeRegex: &matcher.RegexMatcher{ 191 Regex: "regex", 192 }, 193 }, 194 }, 195 }, 196 FilterEnabled: &core.RuntimeFractionalPercent{ 197 DefaultValue: &xdstype.FractionalPercent{ 198 Numerator: 100, 199 Denominator: xdstype.FractionalPercent_HUNDRED, 200 }, 201 }, 202 } 203 if got := TranslateCORSPolicy(node, corsPolicy); !reflect.DeepEqual(got, expectedCorsPolicy) { 204 t.Errorf("TranslateCORSPolicy() = \n%v, want \n%v", got, expectedCorsPolicy) 205 } 206 } 207 208 func TestTranslateCORSPolicy(t *testing.T) { 209 node := &model.Proxy{ 210 IstioVersion: &model.IstioVersion{ 211 Major: 1, 212 Minor: 21, 213 Patch: 0, 214 }, 215 } 216 corsPolicy := &networking.CorsPolicy{ 217 AllowOrigins: []*networking.StringMatch{ 218 {MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 219 {MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}}, 220 {MatchType: &networking.StringMatch_Regex{Regex: "regex"}}, 221 }, 222 } 223 expectedCorsPolicy := &cors.CorsPolicy{ 224 AllowOriginStringMatch: []*matcher.StringMatcher{ 225 {MatchPattern: &matcher.StringMatcher_Exact{Exact: "exact"}}, 226 {MatchPattern: &matcher.StringMatcher_Prefix{Prefix: "prefix"}}, 227 { 228 MatchPattern: &matcher.StringMatcher_SafeRegex{ 229 SafeRegex: &matcher.RegexMatcher{ 230 Regex: "regex", 231 }, 232 }, 233 }, 234 }, 235 FilterEnabled: &core.RuntimeFractionalPercent{ 236 DefaultValue: &xdstype.FractionalPercent{ 237 Numerator: 100, 238 Denominator: xdstype.FractionalPercent_HUNDRED, 239 }, 240 }, 241 } 242 if got := TranslateCORSPolicy(node, corsPolicy); !reflect.DeepEqual(got, expectedCorsPolicy) { 243 t.Errorf("TranslateCORSPolicy() = \n%v, want \n%v", got, expectedCorsPolicy) 244 } 245 } 246 247 func TestMirrorPercent(t *testing.T) { 248 cases := []struct { 249 name string 250 route *networking.HTTPRoute 251 want *core.RuntimeFractionalPercent 252 }{ 253 { 254 name: "zero mirror percent", 255 route: &networking.HTTPRoute{ 256 Mirror: &networking.Destination{}, 257 MirrorPercent: &wrapperspb.UInt32Value{Value: 0.0}, 258 }, 259 want: nil, 260 }, 261 { 262 name: "mirror with no value given", 263 route: &networking.HTTPRoute{ 264 Mirror: &networking.Destination{}, 265 }, 266 want: &core.RuntimeFractionalPercent{ 267 DefaultValue: &xdstype.FractionalPercent{ 268 Numerator: 100, 269 Denominator: xdstype.FractionalPercent_HUNDRED, 270 }, 271 }, 272 }, 273 { 274 name: "mirror with actual percent", 275 route: &networking.HTTPRoute{ 276 Mirror: &networking.Destination{}, 277 MirrorPercent: &wrapperspb.UInt32Value{Value: 50}, 278 }, 279 want: &core.RuntimeFractionalPercent{ 280 DefaultValue: &xdstype.FractionalPercent{ 281 Numerator: 50, 282 Denominator: xdstype.FractionalPercent_HUNDRED, 283 }, 284 }, 285 }, 286 { 287 name: "zero mirror percentage", 288 route: &networking.HTTPRoute{ 289 Mirror: &networking.Destination{}, 290 MirrorPercentage: &networking.Percent{Value: 0.0}, 291 }, 292 want: nil, 293 }, 294 { 295 name: "mirrorpercentage with actual percent", 296 route: &networking.HTTPRoute{ 297 Mirror: &networking.Destination{}, 298 MirrorPercentage: &networking.Percent{Value: 50.0}, 299 }, 300 want: &core.RuntimeFractionalPercent{ 301 DefaultValue: &xdstype.FractionalPercent{ 302 Numerator: 500000, 303 Denominator: xdstype.FractionalPercent_MILLION, 304 }, 305 }, 306 }, 307 { 308 name: "mirrorpercentage takes precedence when both are given", 309 route: &networking.HTTPRoute{ 310 Mirror: &networking.Destination{}, 311 MirrorPercent: &wrapperspb.UInt32Value{Value: 40}, 312 MirrorPercentage: &networking.Percent{Value: 50.0}, 313 }, 314 want: &core.RuntimeFractionalPercent{ 315 DefaultValue: &xdstype.FractionalPercent{ 316 Numerator: 500000, 317 Denominator: xdstype.FractionalPercent_MILLION, 318 }, 319 }, 320 }, 321 } 322 323 for _, tt := range cases { 324 t.Run(tt.name, func(t *testing.T) { 325 mp := MirrorPercent(tt.route) 326 if !reflect.DeepEqual(mp, tt.want) { 327 t.Errorf("Unexpected mirror percent want %v, got %v", tt.want, mp) 328 } 329 }) 330 } 331 } 332 333 func TestMirrorPercentByPolicy(t *testing.T) { 334 cases := []struct { 335 name string 336 policy *networking.HTTPMirrorPolicy 337 want *core.RuntimeFractionalPercent 338 }{ 339 { 340 name: "mirror with no value given", 341 policy: &networking.HTTPMirrorPolicy{ 342 Destination: &networking.Destination{}, 343 }, 344 want: &core.RuntimeFractionalPercent{ 345 DefaultValue: &xdstype.FractionalPercent{ 346 Numerator: 100, 347 Denominator: xdstype.FractionalPercent_HUNDRED, 348 }, 349 }, 350 }, 351 { 352 name: "zero mirror percentage", 353 policy: &networking.HTTPMirrorPolicy{ 354 Destination: &networking.Destination{}, 355 Percentage: &networking.Percent{Value: 0.0}, 356 }, 357 want: nil, 358 }, 359 { 360 name: "mirrorpercentage with actual percent", 361 policy: &networking.HTTPMirrorPolicy{ 362 Destination: &networking.Destination{}, 363 Percentage: &networking.Percent{Value: 50.0}, 364 }, 365 want: &core.RuntimeFractionalPercent{ 366 DefaultValue: &xdstype.FractionalPercent{ 367 Numerator: 500000, 368 Denominator: xdstype.FractionalPercent_MILLION, 369 }, 370 }, 371 }, 372 } 373 374 for _, tt := range cases { 375 t.Run(tt.name, func(t *testing.T) { 376 mp := MirrorPercentByPolicy(tt.policy) 377 if !reflect.DeepEqual(mp, tt.want) { 378 t.Errorf("Unexpected mirror percent want %v, got %v", tt.want, mp) 379 } 380 }) 381 } 382 } 383 384 func TestSourceMatchHTTP(t *testing.T) { 385 type args struct { 386 match *networking.HTTPMatchRequest 387 proxyLabels labels.Instance 388 gatewayNames sets.String 389 proxyNamespace string 390 } 391 tests := []struct { 392 name string 393 args args 394 want bool 395 }{ 396 { 397 "source namespace match", 398 args{ 399 match: &networking.HTTPMatchRequest{ 400 SourceNamespace: "foo", 401 }, 402 proxyNamespace: "foo", 403 }, 404 true, 405 }, 406 { 407 "source namespace not match", 408 args{ 409 match: &networking.HTTPMatchRequest{ 410 SourceNamespace: "foo", 411 }, 412 proxyNamespace: "bar", 413 }, 414 false, 415 }, 416 { 417 "source namespace not match when empty", 418 args{ 419 match: &networking.HTTPMatchRequest{ 420 SourceNamespace: "foo", 421 }, 422 proxyNamespace: "", 423 }, 424 false, 425 }, 426 { 427 "source namespace any", 428 args{ 429 match: &networking.HTTPMatchRequest{}, 430 proxyNamespace: "bar", 431 }, 432 true, 433 }, 434 } 435 for _, tt := range tests { 436 t.Run(tt.name, func(t *testing.T) { 437 if got := sourceMatchHTTP(tt.args.match, tt.args.proxyLabels, tt.args.gatewayNames, tt.args.proxyNamespace); got != tt.want { 438 t.Errorf("sourceMatchHTTP() = %v, want %v", got, tt.want) 439 } 440 }) 441 } 442 } 443 444 func TestTranslateMetadataMatch(t *testing.T) { 445 cases := []struct { 446 name string 447 in *networking.StringMatch 448 want *matcher.MetadataMatcher 449 450 useExtended bool 451 }{ 452 { 453 name: "@request.auth.claims", 454 }, 455 { 456 name: "@request.auth.claims-", 457 }, 458 { 459 name: "request.auth.claims.", 460 }, 461 { 462 name: "@request.auth.claims.", 463 }, 464 { 465 name: "@request.auth.claims-abc", 466 }, 467 { 468 name: "x-some-other-header", 469 }, 470 { 471 name: "@request.auth.claims.key1", 472 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 473 want: authz.MetadataMatcherForJWTClaims([]string{"key1"}, authzmatcher.StringMatcher("exact"), false), 474 }, 475 { 476 name: "@request.auth.claims.key1.KEY2", 477 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 478 want: authz.MetadataMatcherForJWTClaims([]string{"key1", "KEY2"}, authzmatcher.StringMatcher("exact"), false), 479 }, 480 { 481 name: "@request.auth.claims.key1-key2", 482 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 483 want: authz.MetadataMatcherForJWTClaims([]string{"key1-key2"}, authzmatcher.StringMatcher("exact"), false), 484 }, 485 { 486 name: "@request.auth.claims.prefix", 487 in: &networking.StringMatch{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}}, 488 want: authz.MetadataMatcherForJWTClaims([]string{"prefix"}, authzmatcher.StringMatcher("prefix*"), false), 489 }, 490 { 491 name: "@request.auth.claims.regex", 492 in: &networking.StringMatch{MatchType: &networking.StringMatch_Regex{Regex: ".+?\\..+?\\..+?"}}, 493 want: authz.MetadataMatcherForJWTClaims([]string{"regex"}, authzmatcher.StringMatcherRegex(".+?\\..+?\\..+?"), false), 494 }, 495 { 496 name: "@request.auth.claims[key1", 497 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 498 }, 499 { 500 name: "@request.auth.claims]key1", 501 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 502 }, 503 { 504 // have `@request.auth.claims` prefix, but no separator 505 name: "@request.auth.claimskey1", 506 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 507 }, 508 { 509 // if `.` exists, use `.` as separator 510 name: "@request.auth.claims.[key1]", 511 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 512 want: authz.MetadataMatcherForJWTClaims([]string{"[key1]"}, authzmatcher.StringMatcher("exact"), false), 513 }, 514 { 515 name: "@request.auth.claims[key1]", 516 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 517 want: authz.MetadataMatcherForJWTClaims([]string{"key1"}, authzmatcher.StringMatcher("exact"), false), 518 }, 519 { 520 name: "@request.auth.claims[key1][key2]", 521 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 522 want: authz.MetadataMatcherForJWTClaims([]string{"key1", "key2"}, authzmatcher.StringMatcher("exact"), false), 523 }, 524 { 525 name: "@request.auth.claims[test-issuer-2@istio.io]", 526 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 527 want: authz.MetadataMatcherForJWTClaims([]string{"test-issuer-2@istio.io"}, authzmatcher.StringMatcher("exact"), false), 528 }, 529 { 530 name: "@request.auth.claims[test-issuer-2@istio.io][key1]", 531 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 532 want: authz.MetadataMatcherForJWTClaims([]string{"test-issuer-2@istio.io", "key1"}, authzmatcher.StringMatcher("exact"), false), 533 }, 534 { 535 name: "@request.auth.claims[test-issuer-2@istio.io][key1]", 536 in: &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 537 want: authz.MetadataMatcherForJWTClaims([]string{"test-issuer-2@istio.io", "key1"}, authzmatcher.StringMatcher("exact"), true), 538 539 useExtended: true, 540 }, 541 } 542 for _, tc := range cases { 543 t.Run(tc.name, func(t *testing.T) { 544 got := translateMetadataMatch(tc.name, tc.in, tc.useExtended) 545 if !reflect.DeepEqual(got, tc.want) { 546 t.Errorf("Unexpected metadata matcher want %v, got %v", tc.want, got) 547 } 548 }) 549 } 550 } 551 552 func TestTranslateFault(t *testing.T) { 553 cases := []struct { 554 name string 555 fault *networking.HTTPFaultInjection 556 want *xdshttpfault.HTTPFault 557 }{ 558 { 559 name: "http delay", 560 fault: &networking.HTTPFaultInjection{ 561 Delay: &networking.HTTPFaultInjection_Delay{ 562 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 563 FixedDelay: &durationpb.Duration{ 564 Seconds: int64(3), 565 }, 566 }, 567 Percentage: &networking.Percent{ 568 Value: float64(50), 569 }, 570 }, 571 }, 572 want: &xdshttpfault.HTTPFault{ 573 Delay: &xdsfault.FaultDelay{ 574 Percentage: &xdstype.FractionalPercent{ 575 Numerator: uint32(50 * 10000), 576 Denominator: xdstype.FractionalPercent_MILLION, 577 }, 578 FaultDelaySecifier: &xdsfault.FaultDelay_FixedDelay{ 579 FixedDelay: &durationpb.Duration{ 580 Seconds: int64(3), 581 }, 582 }, 583 }, 584 }, 585 }, 586 { 587 name: "grpc abort", 588 fault: &networking.HTTPFaultInjection{ 589 Abort: &networking.HTTPFaultInjection_Abort{ 590 ErrorType: &networking.HTTPFaultInjection_Abort_GrpcStatus{ 591 GrpcStatus: "DEADLINE_EXCEEDED", 592 }, 593 Percentage: &networking.Percent{ 594 Value: float64(50), 595 }, 596 }, 597 }, 598 want: &xdshttpfault.HTTPFault{ 599 Abort: &xdshttpfault.FaultAbort{ 600 Percentage: &xdstype.FractionalPercent{ 601 Numerator: uint32(50 * 10000), 602 Denominator: xdstype.FractionalPercent_MILLION, 603 }, 604 ErrorType: &xdshttpfault.FaultAbort_GrpcStatus{ 605 GrpcStatus: uint32(4), 606 }, 607 }, 608 }, 609 }, 610 { 611 name: "both delay and abort", 612 fault: &networking.HTTPFaultInjection{ 613 Delay: &networking.HTTPFaultInjection_Delay{ 614 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 615 FixedDelay: &durationpb.Duration{ 616 Seconds: int64(3), 617 }, 618 }, 619 Percentage: &networking.Percent{ 620 Value: float64(50), 621 }, 622 }, 623 Abort: &networking.HTTPFaultInjection_Abort{ 624 ErrorType: &networking.HTTPFaultInjection_Abort_GrpcStatus{ 625 GrpcStatus: "DEADLINE_EXCEEDED", 626 }, 627 Percentage: &networking.Percent{ 628 Value: float64(50), 629 }, 630 }, 631 }, 632 want: &xdshttpfault.HTTPFault{ 633 Delay: &xdsfault.FaultDelay{ 634 Percentage: &xdstype.FractionalPercent{ 635 Numerator: uint32(50 * 10000), 636 Denominator: xdstype.FractionalPercent_MILLION, 637 }, 638 FaultDelaySecifier: &xdsfault.FaultDelay_FixedDelay{ 639 FixedDelay: &durationpb.Duration{ 640 Seconds: int64(3), 641 }, 642 }, 643 }, 644 Abort: &xdshttpfault.FaultAbort{ 645 Percentage: &xdstype.FractionalPercent{ 646 Numerator: uint32(50 * 10000), 647 Denominator: xdstype.FractionalPercent_MILLION, 648 }, 649 ErrorType: &xdshttpfault.FaultAbort_GrpcStatus{ 650 GrpcStatus: uint32(4), 651 }, 652 }, 653 }, 654 }, 655 } 656 657 for _, tt := range cases { 658 t.Run(tt.name, func(t *testing.T) { 659 tf := TranslateFault(tt.fault) 660 if !reflect.DeepEqual(tf, tt.want) { 661 t.Errorf("Unexpected translate fault want %v, got %v", tt.want, tf) 662 } 663 }) 664 } 665 }