1// Copyright 2017 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 15package validation 16 17import ( 18 "fmt" 19 "strings" 20 "testing" 21 "time" 22 23 "github.com/gogo/protobuf/proto" 24 "github.com/gogo/protobuf/types" 25 "github.com/hashicorp/go-multierror" 26 27 authn "istio.io/api/authentication/v1alpha1" 28 meshconfig "istio.io/api/mesh/v1alpha1" 29 mpb "istio.io/api/mixer/v1" 30 mccpb "istio.io/api/mixer/v1/config/client" 31 networking "istio.io/api/networking/v1alpha3" 32 rbac "istio.io/api/rbac/v1alpha1" 33 security_beta "istio.io/api/security/v1beta1" 34 api "istio.io/api/type/v1beta1" 35 36 "istio.io/istio/pkg/config/constants" 37) 38 39const ( 40 // Config name for testing 41 someName = "foo" 42 // Config namespace for testing. 43 someNamespace = "bar" 44) 45 46func TestValidateFQDN(t *testing.T) { 47 tests := []struct { 48 fqdn string 49 valid bool 50 name string 51 }{ 52 { 53 fqdn: strings.Repeat("x", 256), 54 valid: false, 55 name: "long FQDN", 56 }, 57 { 58 fqdn: "", 59 valid: false, 60 name: "empty FQDN", 61 }, 62 { 63 fqdn: "istio.io", 64 valid: true, 65 name: "standard FQDN", 66 }, 67 { 68 fqdn: "istio.io.", 69 valid: true, 70 name: "unambiguous FQDN", 71 }, 72 { 73 fqdn: "istio-pilot.istio-system.svc.cluster.local", 74 valid: true, 75 name: "standard kubernetes FQDN", 76 }, 77 { 78 fqdn: "istio-pilot.istio-system.svc.cluster.local.", 79 valid: true, 80 name: "unambiguous kubernetes FQDN", 81 }, 82 } 83 for _, tt := range tests { 84 tt := tt 85 t.Run(tt.name, func(t *testing.T) { 86 err := ValidateFQDN(tt.fqdn) 87 valid := err == nil 88 if valid != tt.valid { 89 t.Errorf("Expected valid=%v, got valid=%v for %v", tt.valid, valid, tt.fqdn) 90 } 91 }) 92 93 } 94} 95 96func TestValidateWildcardDomain(t *testing.T) { 97 tests := []struct { 98 name string 99 in string 100 out string 101 }{ 102 {"empty", "", "empty"}, 103 {"too long", strings.Repeat("x", 256), "too long"}, 104 {"happy", strings.Repeat("x", 63), ""}, 105 {"wildcard", "*", ""}, 106 {"wildcard multi-segment", "*.bar.com", ""}, 107 {"wildcard single segment", "*foo", ""}, 108 {"wildcard prefix", "*foo.bar.com", ""}, 109 {"wildcard prefix dash", "*-foo.bar.com", ""}, 110 {"bad wildcard", "foo.*.com", "invalid"}, 111 {"bad wildcard", "foo*.bar.com", "invalid"}, 112 {"IP address", "1.1.1.1", "invalid"}, 113 } 114 for _, tt := range tests { 115 t.Run(tt.name, func(t *testing.T) { 116 err := ValidateWildcardDomain(tt.in) 117 if err == nil && tt.out != "" { 118 t.Fatalf("ValidateWildcardDomain(%v) = nil, wanted %q", tt.in, tt.out) 119 } else if err != nil && tt.out == "" { 120 t.Fatalf("ValidateWildcardDomain(%v) = %v, wanted nil", tt.in, err) 121 } else if err != nil && !strings.Contains(err.Error(), tt.out) { 122 t.Fatalf("ValidateWildcardDomain(%v) = %v, wanted %q", tt.in, err, tt.out) 123 } 124 }) 125 } 126} 127 128func TestValidatePort(t *testing.T) { 129 ports := map[int]bool{ 130 0: false, 131 65536: false, 132 -1: false, 133 100: true, 134 1000: true, 135 65535: true, 136 } 137 for port, valid := range ports { 138 if got := ValidatePort(port); (got == nil) != valid { 139 t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %d", got == nil, valid, got, port) 140 } 141 } 142} 143 144func TestValidateProxyAddress(t *testing.T) { 145 addresses := map[string]bool{ 146 "istio-pilot:80": true, 147 "istio-pilot": false, 148 "isti..:80": false, 149 "10.0.0.100:9090": true, 150 "10.0.0.100": false, 151 "istio-pilot:port": false, 152 "istio-pilot:100000": false, 153 "[2001:db8::100]:80": true, 154 "[2001:db8::10::20]:80": false, 155 "[2001:db8::100]": false, 156 "[2001:db8::100]:port": false, 157 "2001:db8::100:80": false, 158 } 159 for addr, valid := range addresses { 160 if got := ValidateProxyAddress(addr); (got == nil) != valid { 161 t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %s", got == nil, valid, got, addr) 162 } 163 } 164} 165 166func TestValidateDuration(t *testing.T) { 167 type durationCheck struct { 168 duration *types.Duration 169 isValid bool 170 } 171 172 checks := []durationCheck{ 173 { 174 duration: &types.Duration{Seconds: 1}, 175 isValid: true, 176 }, 177 { 178 duration: &types.Duration{Seconds: 1, Nanos: -1}, 179 isValid: false, 180 }, 181 { 182 duration: &types.Duration{Seconds: -11, Nanos: -1}, 183 isValid: false, 184 }, 185 { 186 duration: &types.Duration{Nanos: 1}, 187 isValid: false, 188 }, 189 { 190 duration: &types.Duration{Seconds: 1, Nanos: 1}, 191 isValid: false, 192 }, 193 } 194 195 for _, check := range checks { 196 if got := ValidateDuration(check.duration); (got == nil) != check.isValid { 197 t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %v", got == nil, check.isValid, got, check.duration) 198 } 199 } 200} 201 202func TestValidateParentAndDrain(t *testing.T) { 203 type ParentDrainTime struct { 204 Parent types.Duration 205 Drain types.Duration 206 Valid bool 207 } 208 209 combinations := []ParentDrainTime{ 210 { 211 Parent: types.Duration{Seconds: 2}, 212 Drain: types.Duration{Seconds: 1}, 213 Valid: true, 214 }, 215 { 216 Parent: types.Duration{Seconds: 1}, 217 Drain: types.Duration{Seconds: 1}, 218 Valid: false, 219 }, 220 { 221 Parent: types.Duration{Seconds: 1}, 222 Drain: types.Duration{Seconds: 2}, 223 Valid: false, 224 }, 225 { 226 Parent: types.Duration{Seconds: 2}, 227 Drain: types.Duration{Seconds: 1, Nanos: 1000000}, 228 Valid: false, 229 }, 230 { 231 Parent: types.Duration{Seconds: 2, Nanos: 1000000}, 232 Drain: types.Duration{Seconds: 1}, 233 Valid: false, 234 }, 235 { 236 Parent: types.Duration{Seconds: -2}, 237 Drain: types.Duration{Seconds: 1}, 238 Valid: false, 239 }, 240 { 241 Parent: types.Duration{Seconds: 2}, 242 Drain: types.Duration{Seconds: -1}, 243 Valid: false, 244 }, 245 { 246 Parent: types.Duration{Seconds: 1 + int64(time.Hour/time.Second)}, 247 Drain: types.Duration{Seconds: 10}, 248 Valid: false, 249 }, 250 { 251 Parent: types.Duration{Seconds: 10}, 252 Drain: types.Duration{Seconds: 1 + int64(time.Hour/time.Second)}, 253 Valid: false, 254 }, 255 } 256 for _, combo := range combinations { 257 if got := ValidateParentAndDrain(&combo.Drain, &combo.Parent); (got == nil) != combo.Valid { 258 t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for Parent:%v Drain:%v", 259 got == nil, combo.Valid, got, combo.Parent, combo.Drain) 260 } 261 } 262} 263 264func TestValidateConnectTimeout(t *testing.T) { 265 type durationCheck struct { 266 duration *types.Duration 267 isValid bool 268 } 269 270 checks := []durationCheck{ 271 { 272 duration: &types.Duration{Seconds: 1}, 273 isValid: true, 274 }, 275 { 276 duration: &types.Duration{Seconds: 31}, 277 isValid: false, 278 }, 279 { 280 duration: &types.Duration{Nanos: 99999}, 281 isValid: false, 282 }, 283 } 284 285 for _, check := range checks { 286 if got := ValidateConnectTimeout(check.duration); (got == nil) != check.isValid { 287 t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %v", got == nil, check.isValid, got, check.duration) 288 } 289 } 290} 291 292func TestValidateProtocolDetectionTimeout(t *testing.T) { 293 type durationCheck struct { 294 duration *types.Duration 295 isValid bool 296 } 297 298 checks := []durationCheck{ 299 { 300 duration: &types.Duration{Seconds: 1}, 301 isValid: true, 302 }, 303 { 304 duration: &types.Duration{Nanos: 99999}, 305 isValid: false, 306 }, 307 { 308 duration: &types.Duration{Nanos: 0}, 309 isValid: true, 310 }, 311 } 312 313 for _, check := range checks { 314 if got := ValidateProtocolDetectionTimeout(check.duration); (got == nil) != check.isValid { 315 t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %v", got == nil, check.isValid, got, check.duration) 316 } 317 } 318} 319 320func TestValidateMeshConfig(t *testing.T) { 321 if ValidateMeshConfig(&meshconfig.MeshConfig{}) == nil { 322 t.Error("expected an error on an empty mesh config") 323 } 324 325 invalid := meshconfig.MeshConfig{ 326 MixerCheckServer: "10.0.0.100", 327 MixerReportServer: "10.0.0.100", 328 ProxyListenPort: 0, 329 ConnectTimeout: types.DurationProto(-1 * time.Second), 330 DefaultConfig: &meshconfig.ProxyConfig{}, 331 } 332 333 err := ValidateMeshConfig(&invalid) 334 if err == nil { 335 t.Errorf("expected an error on invalid proxy mesh config: %v", invalid) 336 } else { 337 switch err := err.(type) { 338 case *multierror.Error: 339 // each field must cause an error in the field 340 if len(err.Errors) < 6 { 341 t.Errorf("expected an error for each field %v", err) 342 } 343 default: 344 t.Errorf("expected a multi error as output") 345 } 346 } 347} 348 349func TestValidateProxyConfig(t *testing.T) { 350 valid := &meshconfig.ProxyConfig{ 351 ConfigPath: "/etc/istio/proxy", 352 BinaryPath: "/usr/local/bin/envoy", 353 DiscoveryAddress: "istio-pilot.istio-system:15010", 354 ProxyAdminPort: 15000, 355 DrainDuration: types.DurationProto(45 * time.Second), 356 ParentShutdownDuration: types.DurationProto(60 * time.Second), 357 ServiceCluster: "istio-proxy", 358 StatsdUdpAddress: "istio-statsd-prom-bridge.istio-system:9125", 359 EnvoyMetricsService: &meshconfig.RemoteService{Address: "metrics-service.istio-system:15000"}, 360 EnvoyAccessLogService: &meshconfig.RemoteService{Address: "accesslog-service.istio-system:15000"}, 361 ControlPlaneAuthPolicy: 1, 362 Tracing: nil, 363 } 364 365 modify := func(config *meshconfig.ProxyConfig, fieldSetter func(*meshconfig.ProxyConfig)) *meshconfig.ProxyConfig { 366 clone := proto.Clone(config).(*meshconfig.ProxyConfig) 367 fieldSetter(clone) 368 return clone 369 } 370 371 cases := []struct { 372 name string 373 in *meshconfig.ProxyConfig 374 isValid bool 375 }{ 376 { 377 name: "empty proxy config", 378 in: &meshconfig.ProxyConfig{}, 379 isValid: false, 380 }, 381 { 382 name: "valid proxy config", 383 in: valid, 384 isValid: true, 385 }, 386 { 387 name: "config path invalid", 388 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.ConfigPath = "" }), 389 isValid: false, 390 }, 391 { 392 name: "binary path invalid", 393 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.BinaryPath = "" }), 394 isValid: false, 395 }, 396 { 397 name: "discovery address invalid", 398 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.DiscoveryAddress = "10.0.0.100" }), 399 isValid: false, 400 }, 401 { 402 name: "proxy admin port invalid", 403 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.ProxyAdminPort = 0 }), 404 isValid: false, 405 }, 406 { 407 name: "proxy admin port invalid", 408 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.ProxyAdminPort = 0 }), 409 isValid: false, 410 }, 411 { 412 name: "drain duration invalid", 413 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.DrainDuration = types.DurationProto(-1 * time.Second) }), 414 isValid: false, 415 }, 416 { 417 name: "parent shutdown duration invalid", 418 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.ParentShutdownDuration = types.DurationProto(-1 * time.Second) }), 419 isValid: false, 420 }, 421 { 422 name: "service cluster invalid", 423 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.ServiceCluster = "" }), 424 isValid: false, 425 }, 426 { 427 name: "statsd udp address invalid", 428 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.StatsdUdpAddress = "10.0.0.100" }), 429 isValid: false, 430 }, 431 { 432 name: "envoy metrics service address invalid", 433 in: modify(valid, func(c *meshconfig.ProxyConfig) { 434 c.EnvoyMetricsService = &meshconfig.RemoteService{Address: "metrics-service.istio-system"} 435 }), 436 isValid: false, 437 }, 438 { 439 name: "envoy access log service address invalid", 440 in: modify(valid, func(c *meshconfig.ProxyConfig) { 441 c.EnvoyAccessLogService = &meshconfig.RemoteService{Address: "accesslog-service.istio-system"} 442 }), 443 isValid: false, 444 }, 445 { 446 name: "control plane auth policy invalid", 447 in: modify(valid, func(c *meshconfig.ProxyConfig) { c.ControlPlaneAuthPolicy = -1 }), 448 isValid: false, 449 }, 450 { 451 name: "zipkin address is valid", 452 in: modify(valid, 453 func(c *meshconfig.ProxyConfig) { 454 c.Tracing = &meshconfig.Tracing{ 455 Tracer: &meshconfig.Tracing_Zipkin_{ 456 Zipkin: &meshconfig.Tracing_Zipkin{ 457 Address: "zipkin.istio-system:9411", 458 }, 459 }, 460 } 461 }, 462 ), 463 isValid: true, 464 }, 465 { 466 name: "zipkin config invalid", 467 in: modify(valid, 468 func(c *meshconfig.ProxyConfig) { 469 c.Tracing = &meshconfig.Tracing{ 470 Tracer: &meshconfig.Tracing_Zipkin_{ 471 Zipkin: &meshconfig.Tracing_Zipkin{ 472 Address: "10.0.0.100", 473 }, 474 }, 475 } 476 }, 477 ), 478 isValid: false, 479 }, 480 { 481 name: "lightstep config is valid", 482 in: modify(valid, 483 func(c *meshconfig.ProxyConfig) { 484 c.Tracing = &meshconfig.Tracing{ 485 Tracer: &meshconfig.Tracing_Lightstep_{ 486 Lightstep: &meshconfig.Tracing_Lightstep{ 487 Address: "collector.lightstep:8080", 488 AccessToken: "abcdefg1234567", 489 }, 490 }, 491 } 492 }, 493 ), 494 isValid: true, 495 }, 496 { 497 name: "lightstep address invalid", 498 in: modify(valid, 499 func(c *meshconfig.ProxyConfig) { 500 c.Tracing = &meshconfig.Tracing{ 501 Tracer: &meshconfig.Tracing_Lightstep_{ 502 Lightstep: &meshconfig.Tracing_Lightstep{ 503 Address: "10.0.0.100", 504 AccessToken: "abcdefg1234567", 505 }, 506 }, 507 } 508 }, 509 ), 510 isValid: false, 511 }, 512 { 513 name: "lightstep address empty but lightstep access token is not", 514 in: modify(valid, 515 func(c *meshconfig.ProxyConfig) { 516 c.Tracing = &meshconfig.Tracing{ 517 Tracer: &meshconfig.Tracing_Lightstep_{ 518 Lightstep: &meshconfig.Tracing_Lightstep{ 519 Address: "", 520 AccessToken: "abcdefg1234567", 521 }, 522 }, 523 } 524 }, 525 ), 526 isValid: false, 527 }, 528 { 529 name: "lightstep address is valid but access token is empty", 530 in: modify(valid, 531 func(c *meshconfig.ProxyConfig) { 532 c.Tracing = &meshconfig.Tracing{ 533 Tracer: &meshconfig.Tracing_Lightstep_{ 534 Lightstep: &meshconfig.Tracing_Lightstep{ 535 Address: "collector.lightstep:8080", 536 AccessToken: "", 537 }, 538 }, 539 } 540 }, 541 ), 542 isValid: false, 543 }, 544 { 545 name: "lightstep access token empty but lightstep address is not", 546 in: modify(valid, 547 func(c *meshconfig.ProxyConfig) { 548 c.Tracing = &meshconfig.Tracing{ 549 Tracer: &meshconfig.Tracing_Lightstep_{ 550 Lightstep: &meshconfig.Tracing_Lightstep{ 551 Address: "10.0.0.100", 552 AccessToken: "", 553 }, 554 }, 555 } 556 }, 557 ), 558 isValid: false, 559 }, 560 { 561 name: "lightstep address and lightstep token both empty", 562 in: modify(valid, 563 func(c *meshconfig.ProxyConfig) { 564 c.Tracing = &meshconfig.Tracing{ 565 Tracer: &meshconfig.Tracing_Lightstep_{ 566 Lightstep: &meshconfig.Tracing_Lightstep{ 567 Address: "", 568 AccessToken: "", 569 }, 570 }, 571 } 572 }, 573 ), 574 isValid: false, 575 }, 576 { 577 name: "datadog without address", 578 in: modify(valid, 579 func(c *meshconfig.ProxyConfig) { 580 c.Tracing = &meshconfig.Tracing{ 581 Tracer: &meshconfig.Tracing_Datadog_{ 582 Datadog: &meshconfig.Tracing_Datadog{}, 583 }, 584 } 585 }, 586 ), 587 isValid: false, 588 }, 589 { 590 name: "datadog with correct address", 591 in: modify(valid, 592 func(c *meshconfig.ProxyConfig) { 593 c.Tracing = &meshconfig.Tracing{ 594 Tracer: &meshconfig.Tracing_Datadog_{ 595 Datadog: &meshconfig.Tracing_Datadog{ 596 Address: "datadog-agent:8126", 597 }, 598 }, 599 } 600 }, 601 ), 602 isValid: true, 603 }, 604 { 605 name: "datadog with invalid address", 606 in: modify(valid, 607 func(c *meshconfig.ProxyConfig) { 608 c.Tracing = &meshconfig.Tracing{ 609 Tracer: &meshconfig.Tracing_Datadog_{ 610 Datadog: &meshconfig.Tracing_Datadog{ 611 Address: "address-missing-port-number", 612 }, 613 }, 614 } 615 }, 616 ), 617 isValid: false, 618 }, 619 } 620 for _, c := range cases { 621 t.Run(c.name, func(t *testing.T) { 622 if got := ValidateProxyConfig(c.in); (got == nil) != c.isValid { 623 if c.isValid { 624 t.Errorf("got error %v, wanted none", got) 625 } else { 626 t.Error("got no error, wanted one") 627 } 628 } 629 }) 630 } 631 632 invalid := meshconfig.ProxyConfig{ 633 ConfigPath: "", 634 BinaryPath: "", 635 DiscoveryAddress: "10.0.0.100", 636 ProxyAdminPort: 0, 637 DrainDuration: types.DurationProto(-1 * time.Second), 638 ParentShutdownDuration: types.DurationProto(-1 * time.Second), 639 ServiceCluster: "", 640 StatsdUdpAddress: "10.0.0.100", 641 EnvoyMetricsService: &meshconfig.RemoteService{Address: "metrics-service"}, 642 EnvoyAccessLogService: &meshconfig.RemoteService{Address: "accesslog-service"}, 643 ControlPlaneAuthPolicy: -1, 644 Tracing: &meshconfig.Tracing{ 645 Tracer: &meshconfig.Tracing_Zipkin_{ 646 Zipkin: &meshconfig.Tracing_Zipkin{ 647 Address: "10.0.0.100", 648 }, 649 }, 650 }, 651 } 652 653 err := ValidateProxyConfig(&invalid) 654 if err == nil { 655 t.Errorf("expected an error on invalid proxy mesh config: %v", invalid) 656 } else { 657 switch err := err.(type) { 658 case *multierror.Error: 659 // each field must cause an error in the field 660 if len(err.Errors) != 12 { 661 t.Errorf("expected an error for each field %v", err) 662 } 663 default: 664 t.Errorf("expected a multi error as output") 665 } 666 } 667} 668 669var ( 670 validService = &mccpb.IstioService{Service: "*cnn.com"} 671 validAttributes = &mpb.Attributes{ 672 Attributes: map[string]*mpb.Attributes_AttributeValue{ 673 "api.service": {Value: &mpb.Attributes_AttributeValue_StringValue{"my-service"}}, 674 }, 675 } 676 invalidAttributes = &mpb.Attributes{ 677 Attributes: map[string]*mpb.Attributes_AttributeValue{ 678 "api.service": {Value: &mpb.Attributes_AttributeValue_StringValue{""}}, 679 }, 680 } 681) 682 683func TestValidateMixerAttributes(t *testing.T) { 684 cases := []struct { 685 name string 686 in *mpb.Attributes_AttributeValue 687 valid bool 688 }{ 689 {"happy string", 690 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringValue{"my-service"}}, 691 true}, 692 {"invalid string", 693 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringValue{""}}, 694 false}, 695 {"happy duration", 696 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_DurationValue{&types.Duration{Seconds: 1}}}, 697 true}, 698 {"invalid duration", 699 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_DurationValue{&types.Duration{Nanos: -1e9}}}, 700 false}, 701 {"happy bytes", 702 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_BytesValue{[]byte{1, 2, 3}}}, 703 true}, 704 {"invalid bytes", 705 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_BytesValue{[]byte{}}}, 706 false}, 707 {"happy timestamp", 708 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_TimestampValue{&types.Timestamp{}}}, 709 true}, 710 {"invalid timestamp", 711 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_TimestampValue{&types.Timestamp{Nanos: -1}}}, 712 false}, 713 {"nil timestamp", 714 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_TimestampValue{nil}}, 715 false}, 716 {"happy stringmap", 717 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringMapValue{ 718 &mpb.Attributes_StringMap{Entries: map[string]string{"foo": "bar"}}}}, 719 true}, 720 {"invalid stringmap", 721 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringMapValue{ 722 &mpb.Attributes_StringMap{Entries: nil}}}, 723 false}, 724 {"nil stringmap", 725 &mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringMapValue{nil}}, 726 false}, 727 } 728 for _, c := range cases { 729 t.Run(c.name, func(t *testing.T) { 730 attrs := &mpb.Attributes{ 731 Attributes: map[string]*mpb.Attributes_AttributeValue{"key": c.in}, 732 } 733 if got := ValidateMixerAttributes(attrs); (got == nil) != c.valid { 734 if c.valid { 735 t.Fatal("got error, wanted none") 736 } else { 737 t.Fatal("got no error, wanted one") 738 } 739 } 740 }) 741 } 742} 743 744func TestValidateHTTPAPISpec(t *testing.T) { 745 var ( 746 validPattern = &mccpb.HTTPAPISpecPattern{ 747 Attributes: validAttributes, 748 HttpMethod: "POST", 749 Pattern: &mccpb.HTTPAPISpecPattern_UriTemplate{ 750 UriTemplate: "/pet/{id}", 751 }, 752 } 753 invalidPatternHTTPMethod = &mccpb.HTTPAPISpecPattern{ 754 Attributes: validAttributes, 755 Pattern: &mccpb.HTTPAPISpecPattern_UriTemplate{ 756 UriTemplate: "/pet/{id}", 757 }, 758 } 759 invalidPatternURITemplate = &mccpb.HTTPAPISpecPattern{ 760 Attributes: validAttributes, 761 HttpMethod: "POST", 762 Pattern: &mccpb.HTTPAPISpecPattern_UriTemplate{}, 763 } 764 invalidPatternRegex = &mccpb.HTTPAPISpecPattern{ 765 Attributes: validAttributes, 766 HttpMethod: "POST", 767 Pattern: &mccpb.HTTPAPISpecPattern_Regex{}, 768 } 769 validAPIKey = &mccpb.APIKey{Key: &mccpb.APIKey_Query{"api_key"}} 770 invalidAPIKeyQuery = &mccpb.APIKey{Key: &mccpb.APIKey_Query{}} 771 invalidAPIKeyHeader = &mccpb.APIKey{Key: &mccpb.APIKey_Header{}} 772 invalidAPIKeyCookie = &mccpb.APIKey{Key: &mccpb.APIKey_Cookie{}} 773 ) 774 775 cases := []struct { 776 name string 777 in proto.Message 778 valid bool 779 }{ 780 { 781 name: "missing pattern", 782 in: &mccpb.HTTPAPISpec{ 783 Attributes: validAttributes, 784 ApiKeys: []*mccpb.APIKey{validAPIKey}, 785 }, 786 }, 787 { 788 name: "invalid pattern (bad attributes)", 789 in: &mccpb.HTTPAPISpec{ 790 Attributes: invalidAttributes, 791 Patterns: []*mccpb.HTTPAPISpecPattern{validPattern}, 792 ApiKeys: []*mccpb.APIKey{validAPIKey}, 793 }, 794 }, 795 { 796 name: "invalid pattern (bad http_method)", 797 in: &mccpb.HTTPAPISpec{ 798 Attributes: validAttributes, 799 Patterns: []*mccpb.HTTPAPISpecPattern{invalidPatternHTTPMethod}, 800 ApiKeys: []*mccpb.APIKey{validAPIKey}, 801 }, 802 }, 803 { 804 name: "invalid pattern (missing uri_template)", 805 in: &mccpb.HTTPAPISpec{ 806 Attributes: validAttributes, 807 Patterns: []*mccpb.HTTPAPISpecPattern{invalidPatternURITemplate}, 808 ApiKeys: []*mccpb.APIKey{validAPIKey}, 809 }, 810 }, 811 { 812 name: "invalid pattern (missing regex)", 813 in: &mccpb.HTTPAPISpec{ 814 Attributes: validAttributes, 815 Patterns: []*mccpb.HTTPAPISpecPattern{invalidPatternRegex}, 816 ApiKeys: []*mccpb.APIKey{validAPIKey}, 817 }, 818 }, 819 { 820 name: "invalid api-key (missing query)", 821 in: &mccpb.HTTPAPISpec{ 822 Attributes: validAttributes, 823 Patterns: []*mccpb.HTTPAPISpecPattern{validPattern}, 824 ApiKeys: []*mccpb.APIKey{invalidAPIKeyQuery}, 825 }, 826 }, 827 { 828 name: "invalid api-key (missing header)", 829 in: &mccpb.HTTPAPISpec{ 830 Attributes: validAttributes, 831 Patterns: []*mccpb.HTTPAPISpecPattern{validPattern}, 832 ApiKeys: []*mccpb.APIKey{invalidAPIKeyHeader}, 833 }, 834 }, 835 { 836 name: "invalid api-key (missing cookie)", 837 in: &mccpb.HTTPAPISpec{ 838 Attributes: validAttributes, 839 Patterns: []*mccpb.HTTPAPISpecPattern{validPattern}, 840 ApiKeys: []*mccpb.APIKey{invalidAPIKeyCookie}, 841 }, 842 }, 843 { 844 name: "valid", 845 in: &mccpb.HTTPAPISpec{ 846 Attributes: validAttributes, 847 Patterns: []*mccpb.HTTPAPISpecPattern{validPattern}, 848 ApiKeys: []*mccpb.APIKey{validAPIKey}, 849 }, 850 valid: true, 851 }, 852 { 853 name: "invalid attribute (nil)", 854 in: &mccpb.HTTPAPISpec{ 855 Attributes: &mpb.Attributes{ 856 Attributes: map[string]*mpb.Attributes_AttributeValue{"": nil}, 857 }, 858 }, 859 valid: false, 860 }, 861 } 862 for _, c := range cases { 863 if got := ValidateHTTPAPISpec(someName, someNamespace, c.in); (got == nil) != c.valid { 864 t.Errorf("ValidateHTTPAPISpec(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got) 865 } 866 } 867} 868 869func TestValidateHTTPAPISpecBinding(t *testing.T) { 870 var ( 871 validHTTPAPISpecRef = &mccpb.HTTPAPISpecReference{Name: "foo", Namespace: "bar"} 872 invalidHTTPAPISpecRef = &mccpb.HTTPAPISpecReference{Name: "foo", Namespace: "--bar"} 873 ) 874 cases := []struct { 875 name string 876 in proto.Message 877 valid bool 878 }{ 879 { 880 name: "no service", 881 in: &mccpb.HTTPAPISpecBinding{ 882 Services: []*mccpb.IstioService{}, 883 ApiSpecs: []*mccpb.HTTPAPISpecReference{validHTTPAPISpecRef}, 884 }, 885 }, 886 { 887 name: "no spec", 888 in: &mccpb.HTTPAPISpecBinding{ 889 Services: []*mccpb.IstioService{validService}, 890 ApiSpecs: []*mccpb.HTTPAPISpecReference{}, 891 }, 892 }, 893 { 894 name: "invalid spec", 895 in: &mccpb.HTTPAPISpecBinding{ 896 Services: []*mccpb.IstioService{validService}, 897 ApiSpecs: []*mccpb.HTTPAPISpecReference{invalidHTTPAPISpecRef}, 898 }, 899 }, 900 { 901 name: "valid", 902 in: &mccpb.HTTPAPISpecBinding{ 903 Services: []*mccpb.IstioService{validService}, 904 ApiSpecs: []*mccpb.HTTPAPISpecReference{validHTTPAPISpecRef}, 905 }, 906 valid: true, 907 }, 908 } 909 for _, c := range cases { 910 if got := ValidateHTTPAPISpecBinding(someName, someNamespace, c.in); (got == nil) != c.valid { 911 t.Errorf("ValidateHTTPAPISpecBinding(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got) 912 } 913 } 914} 915 916func TestValidateQuotaSpec(t *testing.T) { 917 var ( 918 validMatch = &mccpb.AttributeMatch{ 919 Clause: map[string]*mccpb.StringMatch{ 920 "api.operation": { 921 MatchType: &mccpb.StringMatch_Exact{ 922 Exact: "getPet", 923 }, 924 }, 925 }, 926 } 927 invalidMatchExact = &mccpb.AttributeMatch{ 928 Clause: map[string]*mccpb.StringMatch{ 929 "api.operation": { 930 MatchType: &mccpb.StringMatch_Exact{Exact: ""}, 931 }, 932 }, 933 } 934 invalidMatchPrefix = &mccpb.AttributeMatch{ 935 Clause: map[string]*mccpb.StringMatch{ 936 "api.operation": { 937 MatchType: &mccpb.StringMatch_Prefix{Prefix: ""}, 938 }, 939 }, 940 } 941 invalidMatchRegex = &mccpb.AttributeMatch{ 942 Clause: map[string]*mccpb.StringMatch{ 943 "api.operation": { 944 MatchType: &mccpb.StringMatch_Regex{Regex: ""}, 945 }, 946 }, 947 } 948 invalidQuota = &mccpb.Quota{ 949 Quota: "", 950 Charge: 0, 951 } 952 validQuota = &mccpb.Quota{ 953 Quota: "myQuota", 954 Charge: 2, 955 } 956 ) 957 cases := []struct { 958 name string 959 in proto.Message 960 valid bool 961 }{ 962 { 963 name: "no rules", 964 in: &mccpb.QuotaSpec{ 965 Rules: []*mccpb.QuotaRule{{}}, 966 }, 967 }, 968 { 969 name: "invalid match (exact)", 970 in: &mccpb.QuotaSpec{ 971 Rules: []*mccpb.QuotaRule{{ 972 Match: []*mccpb.AttributeMatch{invalidMatchExact}, 973 Quotas: []*mccpb.Quota{validQuota}, 974 }}, 975 }, 976 }, 977 { 978 name: "invalid match (prefix)", 979 in: &mccpb.QuotaSpec{ 980 Rules: []*mccpb.QuotaRule{{ 981 Match: []*mccpb.AttributeMatch{invalidMatchPrefix}, 982 Quotas: []*mccpb.Quota{validQuota}, 983 }}, 984 }, 985 }, 986 { 987 name: "invalid match (regex)", 988 in: &mccpb.QuotaSpec{ 989 Rules: []*mccpb.QuotaRule{{ 990 Match: []*mccpb.AttributeMatch{invalidMatchRegex}, 991 Quotas: []*mccpb.Quota{validQuota}, 992 }}, 993 }, 994 }, 995 { 996 name: "no quota", 997 in: &mccpb.QuotaSpec{ 998 Rules: []*mccpb.QuotaRule{{ 999 Match: []*mccpb.AttributeMatch{validMatch}, 1000 Quotas: []*mccpb.Quota{}, 1001 }}, 1002 }, 1003 }, 1004 { 1005 name: "invalid quota/charge", 1006 in: &mccpb.QuotaSpec{ 1007 Rules: []*mccpb.QuotaRule{{ 1008 Match: []*mccpb.AttributeMatch{validMatch}, 1009 Quotas: []*mccpb.Quota{invalidQuota}, 1010 }}, 1011 }, 1012 }, 1013 { 1014 name: "valid", 1015 in: &mccpb.QuotaSpec{ 1016 Rules: []*mccpb.QuotaRule{{ 1017 Match: []*mccpb.AttributeMatch{validMatch}, 1018 Quotas: []*mccpb.Quota{validQuota}, 1019 }}, 1020 }, 1021 valid: true, 1022 }, 1023 { 1024 name: "regression test - nil clause", 1025 in: &mccpb.QuotaSpec{ 1026 Rules: []*mccpb.QuotaRule{{ 1027 Match: []*mccpb.AttributeMatch{{ 1028 Clause: map[string]*mccpb.StringMatch{ 1029 "": nil, 1030 }, 1031 }}, 1032 }}, 1033 }, 1034 valid: false, 1035 }, 1036 } 1037 for _, c := range cases { 1038 if got := ValidateQuotaSpec(someName, someNamespace, c.in); (got == nil) != c.valid { 1039 t.Errorf("ValidateQuotaSpec(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got) 1040 } 1041 } 1042} 1043 1044func TestValidateQuotaSpecBinding(t *testing.T) { 1045 var ( 1046 validQuotaSpecRef = &mccpb.QuotaSpecBinding_QuotaSpecReference{Name: "foo", Namespace: "bar"} 1047 invalidQuotaSpecRef = &mccpb.QuotaSpecBinding_QuotaSpecReference{Name: "foo", Namespace: "--bar"} 1048 ) 1049 cases := []struct { 1050 name string 1051 in proto.Message 1052 valid bool 1053 }{ 1054 { 1055 name: "no service", 1056 in: &mccpb.QuotaSpecBinding{ 1057 Services: []*mccpb.IstioService{}, 1058 QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{validQuotaSpecRef}, 1059 }, 1060 }, 1061 { 1062 name: "no spec", 1063 in: &mccpb.QuotaSpecBinding{ 1064 Services: []*mccpb.IstioService{validService}, 1065 QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{}, 1066 }, 1067 }, 1068 { 1069 name: "invalid spec", 1070 in: &mccpb.QuotaSpecBinding{ 1071 Services: []*mccpb.IstioService{validService}, 1072 QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{invalidQuotaSpecRef}, 1073 }, 1074 }, 1075 { 1076 name: "valid", 1077 in: &mccpb.QuotaSpecBinding{ 1078 Services: []*mccpb.IstioService{validService}, 1079 QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{validQuotaSpecRef}, 1080 }, 1081 valid: true, 1082 }, 1083 } 1084 for _, c := range cases { 1085 if got := ValidateQuotaSpecBinding(someName, someNamespace, c.in); (got == nil) != c.valid { 1086 t.Errorf("ValidateQuotaSpecBinding(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got) 1087 } 1088 } 1089} 1090 1091func TestValidateGateway(t *testing.T) { 1092 tests := []struct { 1093 name string 1094 in proto.Message 1095 out string 1096 }{ 1097 {"empty", &networking.Gateway{}, "server"}, 1098 {"invalid message", &networking.Server{}, "cannot cast"}, 1099 {"happy domain", 1100 &networking.Gateway{ 1101 Servers: []*networking.Server{{ 1102 Hosts: []string{"foo.bar.com"}, 1103 Port: &networking.Port{Name: "name1", Number: 7, Protocol: "http"}, 1104 }}, 1105 }, 1106 ""}, 1107 {"happy multiple servers", 1108 &networking.Gateway{ 1109 Servers: []*networking.Server{ 1110 { 1111 Hosts: []string{"foo.bar.com"}, 1112 Port: &networking.Port{Name: "name1", Number: 7, Protocol: "http"}, 1113 }}, 1114 }, 1115 ""}, 1116 {"invalid port", 1117 &networking.Gateway{ 1118 Servers: []*networking.Server{ 1119 { 1120 Hosts: []string{"foo.bar.com"}, 1121 Port: &networking.Port{Name: "name1", Number: 66000, Protocol: "http"}, 1122 }}, 1123 }, 1124 "port"}, 1125 {"duplicate port names", 1126 &networking.Gateway{ 1127 Servers: []*networking.Server{ 1128 { 1129 Hosts: []string{"foo.bar.com"}, 1130 Port: &networking.Port{Name: "foo", Number: 80, Protocol: "http"}, 1131 }, 1132 { 1133 Hosts: []string{"scooby.doo.com"}, 1134 Port: &networking.Port{Name: "foo", Number: 8080, Protocol: "http"}, 1135 }}, 1136 }, 1137 "port names"}, 1138 {"invalid domain", 1139 &networking.Gateway{ 1140 Servers: []*networking.Server{ 1141 { 1142 Hosts: []string{"foo.*.bar.com"}, 1143 Port: &networking.Port{Number: 7, Protocol: "http"}, 1144 }}, 1145 }, 1146 "domain"}, 1147 } 1148 for _, tt := range tests { 1149 t.Run(tt.name, func(t *testing.T) { 1150 err := ValidateGateway(someName, someNamespace, tt.in) 1151 if err == nil && tt.out != "" { 1152 t.Fatalf("ValidateGateway(%v) = nil, wanted %q", tt.in, tt.out) 1153 } else if err != nil && tt.out == "" { 1154 t.Fatalf("ValidateGateway(%v) = %v, wanted nil", tt.in, err) 1155 } else if err != nil && !strings.Contains(err.Error(), tt.out) { 1156 t.Fatalf("ValidateGateway(%v) = %v, wanted %q", tt.in, err, tt.out) 1157 } 1158 }) 1159 } 1160} 1161 1162func TestValidateServer(t *testing.T) { 1163 tests := []struct { 1164 name string 1165 in *networking.Server 1166 out string 1167 }{ 1168 {"empty", &networking.Server{}, "host"}, 1169 {"empty", &networking.Server{}, "port"}, 1170 {"happy", 1171 &networking.Server{ 1172 Hosts: []string{"foo.bar.com"}, 1173 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1174 }, 1175 ""}, 1176 {"happy ip", 1177 &networking.Server{ 1178 Hosts: []string{"1.1.1.1"}, 1179 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1180 }, 1181 ""}, 1182 {"happy ns/name", 1183 &networking.Server{ 1184 Hosts: []string{"ns1/foo.bar.com"}, 1185 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1186 }, 1187 ""}, 1188 {"happy */name", 1189 &networking.Server{ 1190 Hosts: []string{"*/foo.bar.com"}, 1191 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1192 }, 1193 ""}, 1194 {"happy ./name", 1195 &networking.Server{ 1196 Hosts: []string{"./foo.bar.com"}, 1197 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1198 }, 1199 ""}, 1200 {"invalid domain ns/name format", 1201 &networking.Server{ 1202 Hosts: []string{"ns1/foo.*.bar.com"}, 1203 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1204 }, 1205 "domain"}, 1206 {"invalid domain", 1207 &networking.Server{ 1208 Hosts: []string{"foo.*.bar.com"}, 1209 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1210 }, 1211 "domain"}, 1212 {"invalid short name host", 1213 &networking.Server{ 1214 Hosts: []string{"foo"}, 1215 Port: &networking.Port{Number: 7, Name: "http", Protocol: "http"}, 1216 }, 1217 "short names"}, 1218 {"invalid port", 1219 &networking.Server{ 1220 Hosts: []string{"foo.bar.com"}, 1221 Port: &networking.Port{Number: 66000, Name: "http", Protocol: "http"}, 1222 }, 1223 "port"}, 1224 {"invalid tls options", 1225 &networking.Server{ 1226 Hosts: []string{"foo.bar.com"}, 1227 Port: &networking.Port{Number: 1, Name: "http", Protocol: "http"}, 1228 Tls: &networking.ServerTLSSettings{Mode: networking.ServerTLSSettings_SIMPLE}, 1229 }, 1230 "TLS"}, 1231 {"no tls on HTTPS", 1232 &networking.Server{ 1233 Hosts: []string{"foo.bar.com"}, 1234 Port: &networking.Port{Number: 10000, Name: "https", Protocol: "https"}, 1235 }, 1236 "must have TLS"}, 1237 {"tls on HTTP", 1238 &networking.Server{ 1239 Hosts: []string{"foo.bar.com"}, 1240 Port: &networking.Port{Number: 10000, Name: "http", Protocol: "http"}, 1241 Tls: &networking.ServerTLSSettings{Mode: networking.ServerTLSSettings_SIMPLE}, 1242 }, 1243 "cannot have TLS"}, 1244 {"tls redirect on HTTP", 1245 &networking.Server{ 1246 Hosts: []string{"foo.bar.com"}, 1247 Port: &networking.Port{Number: 10000, Name: "http", Protocol: "http"}, 1248 Tls: &networking.ServerTLSSettings{ 1249 HttpsRedirect: true, 1250 }, 1251 }, 1252 ""}, 1253 } 1254 for _, tt := range tests { 1255 t.Run(tt.name, func(t *testing.T) { 1256 err := validateServer(tt.in) 1257 if err == nil && tt.out != "" { 1258 t.Fatalf("validateServer(%v) = nil, wanted %q", tt.in, tt.out) 1259 } else if err != nil && tt.out == "" { 1260 t.Fatalf("validateServer(%v) = %v, wanted nil", tt.in, err) 1261 } else if err != nil && !strings.Contains(err.Error(), tt.out) { 1262 t.Fatalf("validateServer(%v) = %v, wanted %q", tt.in, err, tt.out) 1263 } 1264 }) 1265 } 1266} 1267 1268func TestValidateServerPort(t *testing.T) { 1269 tests := []struct { 1270 name string 1271 in *networking.Port 1272 out string 1273 }{ 1274 {"empty", &networking.Port{}, "invalid protocol"}, 1275 {"empty", &networking.Port{}, "port name"}, 1276 {"happy", 1277 &networking.Port{ 1278 Protocol: "http", 1279 Number: 1, 1280 Name: "Henry", 1281 }, 1282 ""}, 1283 {"invalid protocol", 1284 &networking.Port{ 1285 Protocol: "kafka", 1286 Number: 1, 1287 Name: "Henry", 1288 }, 1289 "invalid protocol"}, 1290 {"invalid number", 1291 &networking.Port{ 1292 Protocol: "http", 1293 Number: uint32(1 << 30), 1294 Name: "http", 1295 }, 1296 "port number"}, 1297 {"name, no number", 1298 &networking.Port{ 1299 Protocol: "http", 1300 Number: 0, 1301 Name: "Henry", 1302 }, 1303 ""}, 1304 } 1305 for _, tt := range tests { 1306 t.Run(tt.name, func(t *testing.T) { 1307 err := validateServerPort(tt.in) 1308 if err == nil && tt.out != "" { 1309 t.Fatalf("validateServerPort(%v) = nil, wanted %q", tt.in, tt.out) 1310 } else if err != nil && tt.out == "" { 1311 t.Fatalf("validateServerPort(%v) = %v, wanted nil", tt.in, err) 1312 } else if err != nil && !strings.Contains(err.Error(), tt.out) { 1313 t.Fatalf("validateServerPort(%v) = %v, wanted %q", tt.in, err, tt.out) 1314 } 1315 }) 1316 } 1317} 1318 1319func TestValidateTlsOptions(t *testing.T) { 1320 tests := []struct { 1321 name string 1322 in *networking.ServerTLSSettings 1323 out string 1324 }{ 1325 {"empty", &networking.ServerTLSSettings{}, ""}, 1326 {"simple", 1327 &networking.ServerTLSSettings{ 1328 Mode: networking.ServerTLSSettings_SIMPLE, 1329 ServerCertificate: "Captain Jean-Luc Picard", 1330 PrivateKey: "Khan Noonien Singh"}, 1331 ""}, 1332 {"simple with client bundle", 1333 &networking.ServerTLSSettings{ 1334 Mode: networking.ServerTLSSettings_SIMPLE, 1335 ServerCertificate: "Captain Jean-Luc Picard", 1336 PrivateKey: "Khan Noonien Singh", 1337 CaCertificates: "Commander William T. Riker"}, 1338 ""}, 1339 {"simple sds with client bundle", 1340 &networking.ServerTLSSettings{ 1341 Mode: networking.ServerTLSSettings_SIMPLE, 1342 ServerCertificate: "Captain Jean-Luc Picard", 1343 PrivateKey: "Khan Noonien Singh", 1344 CaCertificates: "Commander William T. Riker", 1345 CredentialName: "sds-name"}, 1346 ""}, 1347 {"simple no server cert", 1348 &networking.ServerTLSSettings{ 1349 Mode: networking.ServerTLSSettings_SIMPLE, 1350 ServerCertificate: "", 1351 PrivateKey: "Khan Noonien Singh", 1352 }, 1353 "server certificate"}, 1354 {"simple no private key", 1355 &networking.ServerTLSSettings{ 1356 Mode: networking.ServerTLSSettings_SIMPLE, 1357 ServerCertificate: "Captain Jean-Luc Picard", 1358 PrivateKey: ""}, 1359 "private key"}, 1360 {"simple sds no server cert", 1361 &networking.ServerTLSSettings{ 1362 Mode: networking.ServerTLSSettings_SIMPLE, 1363 ServerCertificate: "", 1364 PrivateKey: "Khan Noonien Singh", 1365 CredentialName: "sds-name", 1366 }, 1367 ""}, 1368 {"simple sds no private key", 1369 &networking.ServerTLSSettings{ 1370 Mode: networking.ServerTLSSettings_SIMPLE, 1371 ServerCertificate: "Captain Jean-Luc Picard", 1372 PrivateKey: "", 1373 CredentialName: "sds-name", 1374 }, 1375 ""}, 1376 {"mutual", 1377 &networking.ServerTLSSettings{ 1378 Mode: networking.ServerTLSSettings_MUTUAL, 1379 ServerCertificate: "Captain Jean-Luc Picard", 1380 PrivateKey: "Khan Noonien Singh", 1381 CaCertificates: "Commander William T. Riker"}, 1382 ""}, 1383 {"mutual sds", 1384 &networking.ServerTLSSettings{ 1385 Mode: networking.ServerTLSSettings_MUTUAL, 1386 ServerCertificate: "Captain Jean-Luc Picard", 1387 PrivateKey: "Khan Noonien Singh", 1388 CaCertificates: "Commander William T. Riker", 1389 CredentialName: "sds-name", 1390 }, 1391 ""}, 1392 {"mutual no server cert", 1393 &networking.ServerTLSSettings{ 1394 Mode: networking.ServerTLSSettings_MUTUAL, 1395 ServerCertificate: "", 1396 PrivateKey: "Khan Noonien Singh", 1397 CaCertificates: "Commander William T. Riker"}, 1398 "server certificate"}, 1399 {"mutual sds no server cert", 1400 &networking.ServerTLSSettings{ 1401 Mode: networking.ServerTLSSettings_MUTUAL, 1402 ServerCertificate: "", 1403 PrivateKey: "Khan Noonien Singh", 1404 CaCertificates: "Commander William T. Riker", 1405 CredentialName: "sds-name"}, 1406 ""}, 1407 {"mutual no client CA bundle", 1408 &networking.ServerTLSSettings{ 1409 Mode: networking.ServerTLSSettings_MUTUAL, 1410 ServerCertificate: "Captain Jean-Luc Picard", 1411 PrivateKey: "Khan Noonien Singh", 1412 CaCertificates: ""}, 1413 "client CA bundle"}, 1414 // this pair asserts we get errors about both client and server certs missing when in mutual mode 1415 // and both are absent, but requires less rewriting of the testing harness than merging the cases 1416 {"mutual no certs", 1417 &networking.ServerTLSSettings{ 1418 Mode: networking.ServerTLSSettings_MUTUAL, 1419 ServerCertificate: "", 1420 PrivateKey: "", 1421 CaCertificates: ""}, 1422 "server certificate"}, 1423 {"mutual no certs", 1424 &networking.ServerTLSSettings{ 1425 Mode: networking.ServerTLSSettings_MUTUAL, 1426 ServerCertificate: "", 1427 PrivateKey: "", 1428 CaCertificates: ""}, 1429 "private key"}, 1430 {"mutual no certs", 1431 &networking.ServerTLSSettings{ 1432 Mode: networking.ServerTLSSettings_MUTUAL, 1433 ServerCertificate: "", 1434 PrivateKey: "", 1435 CaCertificates: ""}, 1436 "client CA bundle"}, 1437 {"pass through sds no certs", 1438 &networking.ServerTLSSettings{ 1439 Mode: networking.ServerTLSSettings_PASSTHROUGH, 1440 ServerCertificate: "", 1441 CaCertificates: "", 1442 CredentialName: "sds-name"}, 1443 ""}, 1444 {"istio_mutual no certs", 1445 &networking.ServerTLSSettings{ 1446 Mode: networking.ServerTLSSettings_ISTIO_MUTUAL, 1447 ServerCertificate: "", 1448 PrivateKey: "", 1449 CaCertificates: ""}, 1450 ""}, 1451 {"istio_mutual with server cert", 1452 &networking.ServerTLSSettings{ 1453 Mode: networking.ServerTLSSettings_ISTIO_MUTUAL, 1454 ServerCertificate: "Captain Jean-Luc Picard"}, 1455 "cannot have associated server cert"}, 1456 {"istio_mutual with client bundle", 1457 &networking.ServerTLSSettings{ 1458 Mode: networking.ServerTLSSettings_ISTIO_MUTUAL, 1459 ServerCertificate: "Captain Jean-Luc Picard", 1460 PrivateKey: "Khan Noonien Singh", 1461 CaCertificates: "Commander William T. Riker"}, 1462 "cannot have associated"}, 1463 {"istio_mutual with private key", 1464 &networking.ServerTLSSettings{ 1465 Mode: networking.ServerTLSSettings_ISTIO_MUTUAL, 1466 PrivateKey: "Khan Noonien Singh"}, 1467 "cannot have associated private key"}, 1468 } 1469 1470 for _, tt := range tests { 1471 t.Run(tt.name, func(t *testing.T) { 1472 err := validateTLSOptions(tt.in) 1473 if err == nil && tt.out != "" { 1474 t.Fatalf("validateTlsOptions(%v) = nil, wanted %q", tt.in, tt.out) 1475 } else if err != nil && tt.out == "" { 1476 t.Fatalf("validateTlsOptions(%v) = %v, wanted nil", tt.in, err) 1477 } else if err != nil && !strings.Contains(err.Error(), tt.out) { 1478 t.Fatalf("validateTlsOptions(%v) = %v, wanted %q", tt.in, err, tt.out) 1479 } 1480 }) 1481 } 1482} 1483 1484func TestValidateHTTPHeaderName(t *testing.T) { 1485 testCases := []struct { 1486 name string 1487 valid bool 1488 }{ 1489 {name: "header1", valid: true}, 1490 {name: "X-Requested-With", valid: true}, 1491 {name: "", valid: false}, 1492 } 1493 1494 for _, tc := range testCases { 1495 if got := ValidateHTTPHeaderName(tc.name); (got == nil) != tc.valid { 1496 t.Errorf("ValidateHTTPHeaderName(%q) => got valid=%v, want valid=%v", 1497 tc.name, got == nil, tc.valid) 1498 } 1499 } 1500} 1501 1502func TestValidateCORSPolicy(t *testing.T) { 1503 testCases := []struct { 1504 name string 1505 in *networking.CorsPolicy 1506 valid bool 1507 }{ 1508 {name: "valid", in: &networking.CorsPolicy{ 1509 AllowMethods: []string{"GET", "POST"}, 1510 AllowHeaders: []string{"header1", "header2"}, 1511 ExposeHeaders: []string{"header3"}, 1512 MaxAge: &types.Duration{Seconds: 2}, 1513 }, valid: true}, 1514 {name: "bad method", in: &networking.CorsPolicy{ 1515 AllowMethods: []string{"GET", "PUTT"}, 1516 AllowHeaders: []string{"header1", "header2"}, 1517 ExposeHeaders: []string{"header3"}, 1518 MaxAge: &types.Duration{Seconds: 2}, 1519 }, valid: false}, 1520 {name: "bad header", in: &networking.CorsPolicy{ 1521 AllowMethods: []string{"GET", "POST"}, 1522 AllowHeaders: []string{"header1", "header2"}, 1523 ExposeHeaders: []string{""}, 1524 MaxAge: &types.Duration{Seconds: 2}, 1525 }, valid: false}, 1526 {name: "bad max age", in: &networking.CorsPolicy{ 1527 AllowMethods: []string{"GET", "POST"}, 1528 AllowHeaders: []string{"header1", "header2"}, 1529 ExposeHeaders: []string{"header3"}, 1530 MaxAge: &types.Duration{Seconds: 2, Nanos: 42}, 1531 }, valid: false}, 1532 {name: "empty matchType AllowOrigins", in: &networking.CorsPolicy{ 1533 AllowOrigins: []*networking.StringMatch{ 1534 {MatchType: &networking.StringMatch_Exact{Exact: ""}}, 1535 {MatchType: &networking.StringMatch_Prefix{Prefix: ""}}, 1536 {MatchType: &networking.StringMatch_Regex{Regex: ""}}, 1537 }, 1538 AllowMethods: []string{"GET", "POST"}, 1539 AllowHeaders: []string{"header1", "header2"}, 1540 ExposeHeaders: []string{"header3"}, 1541 MaxAge: &types.Duration{Seconds: 2}, 1542 }, valid: false}, 1543 {name: "non empty matchType AllowOrigins", in: &networking.CorsPolicy{ 1544 AllowOrigins: []*networking.StringMatch{ 1545 {MatchType: &networking.StringMatch_Exact{Exact: "exact"}}, 1546 {MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}}, 1547 {MatchType: &networking.StringMatch_Regex{Regex: "regex"}}, 1548 }, 1549 AllowMethods: []string{"GET", "POST"}, 1550 AllowHeaders: []string{"header1", "header2"}, 1551 ExposeHeaders: []string{"header3"}, 1552 MaxAge: &types.Duration{Seconds: 2}, 1553 }, valid: true}, 1554 } 1555 1556 for _, tc := range testCases { 1557 t.Run(tc.name, func(t *testing.T) { 1558 if got := validateCORSPolicy(tc.in); (got == nil) != tc.valid { 1559 t.Errorf("got valid=%v, want valid=%v: %v", 1560 got == nil, tc.valid, got) 1561 } 1562 }) 1563 } 1564} 1565 1566func TestValidateHTTPStatus(t *testing.T) { 1567 testCases := []struct { 1568 in int32 1569 valid bool 1570 }{ 1571 {-100, false}, 1572 {0, false}, 1573 {200, true}, 1574 {600, true}, 1575 {601, false}, 1576 } 1577 1578 for _, tc := range testCases { 1579 if got := validateHTTPStatus(tc.in); (got == nil) != tc.valid { 1580 t.Errorf("validateHTTPStatus(%d) => got valid=%v, want valid=%v", 1581 tc.in, got, tc.valid) 1582 } 1583 } 1584} 1585 1586func TestValidateHTTPFaultInjectionAbort(t *testing.T) { 1587 testCases := []struct { 1588 name string 1589 in *networking.HTTPFaultInjection_Abort 1590 valid bool 1591 }{ 1592 {name: "nil", in: nil, valid: true}, 1593 {name: "valid", in: &networking.HTTPFaultInjection_Abort{ 1594 Percentage: &networking.Percent{ 1595 Value: 20, 1596 }, 1597 ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{ 1598 HttpStatus: 200, 1599 }, 1600 }, valid: true}, 1601 {name: "valid default", in: &networking.HTTPFaultInjection_Abort{ 1602 ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{ 1603 HttpStatus: 200, 1604 }, 1605 }, valid: true}, 1606 {name: "invalid http status", in: &networking.HTTPFaultInjection_Abort{ 1607 Percentage: &networking.Percent{ 1608 Value: 20, 1609 }, 1610 ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{ 1611 HttpStatus: 9000, 1612 }, 1613 }, valid: false}, 1614 {name: "invalid low http status", in: &networking.HTTPFaultInjection_Abort{ 1615 Percentage: &networking.Percent{ 1616 Value: 20, 1617 }, 1618 ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{ 1619 HttpStatus: 100, 1620 }, 1621 }, valid: false}, 1622 {name: "valid percentage", in: &networking.HTTPFaultInjection_Abort{ 1623 Percentage: &networking.Percent{ 1624 Value: 0.001, 1625 }, 1626 ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{ 1627 HttpStatus: 200, 1628 }, 1629 }, valid: true}, 1630 {name: "invalid fractional percent", in: &networking.HTTPFaultInjection_Abort{ 1631 Percentage: &networking.Percent{ 1632 Value: -10.0, 1633 }, 1634 ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{ 1635 HttpStatus: 200, 1636 }, 1637 }, valid: false}, 1638 } 1639 1640 for _, tc := range testCases { 1641 t.Run(tc.name, func(t *testing.T) { 1642 if got := validateHTTPFaultInjectionAbort(tc.in); (got == nil) != tc.valid { 1643 t.Errorf("got valid=%v, want valid=%v: %v", 1644 got == nil, tc.valid, got) 1645 } 1646 }) 1647 } 1648} 1649 1650func TestValidateHTTPFaultInjectionDelay(t *testing.T) { 1651 testCases := []struct { 1652 name string 1653 in *networking.HTTPFaultInjection_Delay 1654 valid bool 1655 }{ 1656 {name: "nil", in: nil, valid: true}, 1657 {name: "valid fixed", in: &networking.HTTPFaultInjection_Delay{ 1658 Percentage: &networking.Percent{ 1659 Value: 20, 1660 }, 1661 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 1662 FixedDelay: &types.Duration{Seconds: 3}, 1663 }, 1664 }, valid: true}, 1665 {name: "valid default", in: &networking.HTTPFaultInjection_Delay{ 1666 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 1667 FixedDelay: &types.Duration{Seconds: 3}, 1668 }, 1669 }, valid: true}, 1670 {name: "invalid percent", in: &networking.HTTPFaultInjection_Delay{ 1671 Percentage: &networking.Percent{ 1672 Value: 101, 1673 }, 1674 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 1675 FixedDelay: &types.Duration{Seconds: 3}, 1676 }, 1677 }, valid: false}, 1678 {name: "invalid delay", in: &networking.HTTPFaultInjection_Delay{ 1679 Percentage: &networking.Percent{ 1680 Value: 20, 1681 }, 1682 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 1683 FixedDelay: &types.Duration{Seconds: 3, Nanos: 42}, 1684 }, 1685 }, valid: false}, 1686 {name: "valid fractional percentage", in: &networking.HTTPFaultInjection_Delay{ 1687 Percentage: &networking.Percent{ 1688 Value: 0.001, 1689 }, 1690 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 1691 FixedDelay: &types.Duration{Seconds: 3}, 1692 }, 1693 }, valid: true}, 1694 {name: "invalid fractional percentage", in: &networking.HTTPFaultInjection_Delay{ 1695 Percentage: &networking.Percent{ 1696 Value: -10.0, 1697 }, 1698 HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{ 1699 FixedDelay: &types.Duration{Seconds: 3}, 1700 }, 1701 }, valid: false}, 1702 } 1703 1704 for _, tc := range testCases { 1705 t.Run(tc.name, func(t *testing.T) { 1706 if got := validateHTTPFaultInjectionDelay(tc.in); (got == nil) != tc.valid { 1707 t.Errorf("got valid=%v, want valid=%v: %v", 1708 got == nil, tc.valid, got) 1709 } 1710 }) 1711 } 1712} 1713 1714func TestValidateHTTPRetry(t *testing.T) { 1715 testCases := []struct { 1716 name string 1717 in *networking.HTTPRetry 1718 valid bool 1719 }{ 1720 {name: "valid", in: &networking.HTTPRetry{ 1721 Attempts: 10, 1722 PerTryTimeout: &types.Duration{Seconds: 2}, 1723 RetryOn: "5xx,gateway-error", 1724 }, valid: true}, 1725 {name: "disable retries", in: &networking.HTTPRetry{ 1726 Attempts: 0, 1727 }, valid: true}, 1728 {name: "invalid, retry policy configured but attempts set to zero", in: &networking.HTTPRetry{ 1729 Attempts: 0, 1730 PerTryTimeout: &types.Duration{Seconds: 2}, 1731 RetryOn: "5xx,gateway-error", 1732 }, valid: false}, 1733 {name: "valid default", in: &networking.HTTPRetry{ 1734 Attempts: 10, 1735 }, valid: true}, 1736 {name: "valid http status retryOn", in: &networking.HTTPRetry{ 1737 Attempts: 10, 1738 PerTryTimeout: &types.Duration{Seconds: 2}, 1739 RetryOn: "503,connect-failure", 1740 }, valid: true}, 1741 {name: "invalid attempts", in: &networking.HTTPRetry{ 1742 Attempts: -1, 1743 PerTryTimeout: &types.Duration{Seconds: 2}, 1744 }, valid: false}, 1745 {name: "invalid timeout", in: &networking.HTTPRetry{ 1746 Attempts: 10, 1747 PerTryTimeout: &types.Duration{Seconds: 2, Nanos: 1}, 1748 }, valid: false}, 1749 {name: "timeout too small", in: &networking.HTTPRetry{ 1750 Attempts: 10, 1751 PerTryTimeout: &types.Duration{Nanos: 999}, 1752 }, valid: false}, 1753 {name: "invalid policy retryOn", in: &networking.HTTPRetry{ 1754 Attempts: 10, 1755 PerTryTimeout: &types.Duration{Seconds: 2}, 1756 RetryOn: "5xx,invalid policy", 1757 }, valid: false}, 1758 {name: "invalid http status retryOn", in: &networking.HTTPRetry{ 1759 Attempts: 10, 1760 PerTryTimeout: &types.Duration{Seconds: 2}, 1761 RetryOn: "600,connect-failure", 1762 }, valid: false}, 1763 {name: "invalid, retryRemoteLocalities configured but attempts set to zero", in: &networking.HTTPRetry{ 1764 Attempts: 0, 1765 RetryRemoteLocalities: &types.BoolValue{Value: false}, 1766 }, valid: false}, 1767 } 1768 1769 for _, tc := range testCases { 1770 t.Run(tc.name, func(t *testing.T) { 1771 if got := validateHTTPRetry(tc.in); (got == nil) != tc.valid { 1772 t.Errorf("got valid=%v, want valid=%v: %v", 1773 got == nil, tc.valid, got) 1774 } 1775 }) 1776 } 1777} 1778 1779func TestValidateHTTPRewrite(t *testing.T) { 1780 testCases := []struct { 1781 name string 1782 in *networking.HTTPRewrite 1783 valid bool 1784 }{ 1785 { 1786 name: "nil in", 1787 in: nil, 1788 valid: true, 1789 }, 1790 { 1791 name: "uri and authority", 1792 in: &networking.HTTPRewrite{ 1793 Uri: "/path/to/resource", 1794 Authority: "foobar.org", 1795 }, 1796 valid: true, 1797 }, 1798 { 1799 name: "uri", 1800 in: &networking.HTTPRewrite{ 1801 Uri: "/path/to/resource", 1802 }, 1803 valid: true, 1804 }, 1805 { 1806 name: "authority", 1807 in: &networking.HTTPRewrite{ 1808 Authority: "foobar.org", 1809 }, 1810 valid: true, 1811 }, 1812 { 1813 name: "no uri or authority", 1814 in: &networking.HTTPRewrite{}, 1815 valid: false, 1816 }, 1817 } 1818 1819 for _, tc := range testCases { 1820 t.Run(tc.name, func(t *testing.T) { 1821 if got := validateHTTPRewrite(tc.in); (got == nil) != tc.valid { 1822 t.Errorf("got valid=%v, want valid=%v: %v", 1823 got == nil, tc.valid, got) 1824 } 1825 }) 1826 } 1827} 1828 1829func TestValidatePortName(t *testing.T) { 1830 testCases := []struct { 1831 name string 1832 valid bool 1833 }{ 1834 { 1835 name: "", 1836 valid: false, 1837 }, 1838 { 1839 name: "simple", 1840 valid: true, 1841 }, 1842 { 1843 name: "full", 1844 valid: true, 1845 }, 1846 { 1847 name: "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong", 1848 valid: false, 1849 }, 1850 } 1851 1852 for _, tc := range testCases { 1853 t.Run(tc.name, func(t *testing.T) { 1854 if err := ValidatePortName(tc.name); (err == nil) != tc.valid { 1855 t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err) 1856 } 1857 }) 1858 } 1859} 1860 1861func TestValidateHTTPRedirect(t *testing.T) { 1862 testCases := []struct { 1863 name string 1864 redirect *networking.HTTPRedirect 1865 valid bool 1866 }{ 1867 { 1868 name: "nil redirect", 1869 redirect: nil, 1870 valid: true, 1871 }, 1872 { 1873 name: "empty uri and authority", 1874 redirect: &networking.HTTPRedirect{ 1875 Uri: "", 1876 Authority: "", 1877 }, 1878 valid: false, 1879 }, 1880 { 1881 name: "too small redirect code", 1882 redirect: &networking.HTTPRedirect{ 1883 Uri: "t", 1884 Authority: "", 1885 RedirectCode: 299, 1886 }, 1887 valid: false, 1888 }, 1889 { 1890 name: "too large redirect code", 1891 redirect: &networking.HTTPRedirect{ 1892 Uri: "t", 1893 Authority: "", 1894 RedirectCode: 400, 1895 }, 1896 valid: false, 1897 }, 1898 { 1899 name: "empty authority", 1900 redirect: &networking.HTTPRedirect{ 1901 Uri: "t", 1902 Authority: "", 1903 }, 1904 valid: true, 1905 }, 1906 { 1907 name: "empty uri", 1908 redirect: &networking.HTTPRedirect{ 1909 Uri: "", 1910 Authority: "t", 1911 }, 1912 valid: true, 1913 }, 1914 { 1915 name: "empty redirect code", 1916 redirect: &networking.HTTPRedirect{ 1917 Uri: "t", 1918 Authority: "t", 1919 RedirectCode: 0, 1920 }, 1921 valid: true, 1922 }, 1923 { 1924 name: "normal redirect", 1925 redirect: &networking.HTTPRedirect{ 1926 Uri: "t", 1927 Authority: "t", 1928 RedirectCode: 308, 1929 }, 1930 valid: true, 1931 }, 1932 } 1933 1934 for _, tc := range testCases { 1935 t.Run(tc.name, func(t *testing.T) { 1936 if err := validateHTTPRedirect(tc.redirect); (err == nil) != tc.valid { 1937 t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err) 1938 } 1939 }) 1940 } 1941} 1942 1943func TestValidateDestination(t *testing.T) { 1944 testCases := []struct { 1945 name string 1946 destination *networking.Destination 1947 valid bool 1948 }{ 1949 { 1950 name: "empty", 1951 destination: &networking.Destination{}, // nothing 1952 valid: false, 1953 }, 1954 { 1955 name: "simple", 1956 destination: &networking.Destination{ 1957 Host: "foo.bar", 1958 }, 1959 valid: true, 1960 }, 1961 {name: "full", 1962 destination: &networking.Destination{ 1963 Host: "foo.bar", 1964 Subset: "shiny", 1965 Port: &networking.PortSelector{ 1966 Number: 5000, 1967 }, 1968 }, 1969 valid: true, 1970 }, 1971 {name: "unnumbered-selector", 1972 destination: &networking.Destination{ 1973 Host: "foo.bar", 1974 Subset: "shiny", 1975 Port: &networking.PortSelector{}, 1976 }, 1977 valid: false, 1978 }, 1979 } 1980 1981 for _, tc := range testCases { 1982 t.Run(tc.name, func(t *testing.T) { 1983 if err := validateDestination(tc.destination); (err == nil) != tc.valid { 1984 t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err) 1985 } 1986 }) 1987 } 1988} 1989 1990func TestValidateHTTPRoute(t *testing.T) { 1991 testCases := []struct { 1992 name string 1993 route *networking.HTTPRoute 1994 valid bool 1995 }{ 1996 {name: "empty", route: &networking.HTTPRoute{ // nothing 1997 }, valid: false}, 1998 {name: "simple", route: &networking.HTTPRoute{ 1999 Route: []*networking.HTTPRouteDestination{{ 2000 Destination: &networking.Destination{Host: "foo.baz"}, 2001 }}, 2002 }, valid: true}, 2003 {name: "no destination", route: &networking.HTTPRoute{ 2004 Route: []*networking.HTTPRouteDestination{{ 2005 Destination: nil, 2006 }}, 2007 }, valid: false}, 2008 {name: "weighted", route: &networking.HTTPRoute{ 2009 Route: []*networking.HTTPRouteDestination{{ 2010 Destination: &networking.Destination{Host: "foo.baz.south"}, 2011 Weight: 25, 2012 }, { 2013 Destination: &networking.Destination{Host: "foo.baz.east"}, 2014 Weight: 75, 2015 }}, 2016 }, valid: true}, 2017 {name: "total weight > 100", route: &networking.HTTPRoute{ 2018 Route: []*networking.HTTPRouteDestination{{ 2019 Destination: &networking.Destination{Host: "foo.baz.south"}, 2020 Weight: 55, 2021 }, { 2022 Destination: &networking.Destination{Host: "foo.baz.east"}, 2023 Weight: 50, 2024 }}, 2025 }, valid: false}, 2026 {name: "total weight < 100", route: &networking.HTTPRoute{ 2027 Route: []*networking.HTTPRouteDestination{{ 2028 Destination: &networking.Destination{Host: "foo.baz.south"}, 2029 Weight: 49, 2030 }, { 2031 Destination: &networking.Destination{Host: "foo.baz.east"}, 2032 Weight: 50, 2033 }}, 2034 }, valid: false}, 2035 {name: "simple redirect", route: &networking.HTTPRoute{ 2036 Redirect: &networking.HTTPRedirect{ 2037 Uri: "/lerp", 2038 Authority: "foo.biz", 2039 }, 2040 }, valid: true}, 2041 {name: "conflicting redirect and route", route: &networking.HTTPRoute{ 2042 Route: []*networking.HTTPRouteDestination{{ 2043 Destination: &networking.Destination{Host: "foo.baz"}, 2044 }}, 2045 Redirect: &networking.HTTPRedirect{ 2046 Uri: "/lerp", 2047 Authority: "foo.biz", 2048 }, 2049 }, valid: false}, 2050 {name: "request response headers", route: &networking.HTTPRoute{ 2051 Route: []*networking.HTTPRouteDestination{{ 2052 Destination: &networking.Destination{Host: "foo.baz"}, 2053 }}, 2054 }, valid: true}, 2055 {name: "valid headers", route: &networking.HTTPRoute{ 2056 Route: []*networking.HTTPRouteDestination{{ 2057 Destination: &networking.Destination{Host: "foo.baz"}, 2058 Headers: &networking.Headers{ 2059 Request: &networking.Headers_HeaderOperations{ 2060 Add: map[string]string{ 2061 "name": "", 2062 }, 2063 Set: map[string]string{ 2064 "name": "", 2065 }, 2066 Remove: []string{ 2067 "name", 2068 }, 2069 }, 2070 Response: &networking.Headers_HeaderOperations{ 2071 Add: map[string]string{ 2072 "name": "", 2073 }, 2074 Set: map[string]string{ 2075 "name": "", 2076 }, 2077 Remove: []string{ 2078 "name", 2079 }, 2080 }, 2081 }, 2082 }}, 2083 }, valid: true}, 2084 {name: "empty header name - request add", route: &networking.HTTPRoute{ 2085 Route: []*networking.HTTPRouteDestination{{ 2086 Destination: &networking.Destination{Host: "foo.baz"}, 2087 Headers: &networking.Headers{ 2088 Request: &networking.Headers_HeaderOperations{ 2089 Add: map[string]string{ 2090 "": "value", 2091 }, 2092 }, 2093 }, 2094 }}, 2095 }, valid: false}, 2096 {name: "empty header name - request set", route: &networking.HTTPRoute{ 2097 Route: []*networking.HTTPRouteDestination{{ 2098 Destination: &networking.Destination{Host: "foo.baz"}, 2099 Headers: &networking.Headers{ 2100 Request: &networking.Headers_HeaderOperations{ 2101 Set: map[string]string{ 2102 "": "value", 2103 }, 2104 }, 2105 }, 2106 }}, 2107 }, valid: false}, 2108 {name: "empty header name - request remove", route: &networking.HTTPRoute{ 2109 Route: []*networking.HTTPRouteDestination{{ 2110 Destination: &networking.Destination{Host: "foo.baz"}, 2111 Headers: &networking.Headers{ 2112 Request: &networking.Headers_HeaderOperations{ 2113 Remove: []string{ 2114 "", 2115 }, 2116 }, 2117 }, 2118 }}, 2119 }, valid: false}, 2120 {name: "empty header name - response add", route: &networking.HTTPRoute{ 2121 Route: []*networking.HTTPRouteDestination{{ 2122 Destination: &networking.Destination{Host: "foo.baz"}, 2123 Headers: &networking.Headers{ 2124 Response: &networking.Headers_HeaderOperations{ 2125 Add: map[string]string{ 2126 "": "value", 2127 }, 2128 }, 2129 }, 2130 }}, 2131 }, valid: false}, 2132 {name: "empty header name - response set", route: &networking.HTTPRoute{ 2133 Route: []*networking.HTTPRouteDestination{{ 2134 Destination: &networking.Destination{Host: "foo.baz"}, 2135 Headers: &networking.Headers{ 2136 Response: &networking.Headers_HeaderOperations{ 2137 Set: map[string]string{ 2138 "": "value", 2139 }, 2140 }, 2141 }, 2142 }}, 2143 }, valid: false}, 2144 {name: "empty header name - response remove", route: &networking.HTTPRoute{ 2145 Route: []*networking.HTTPRouteDestination{{ 2146 Destination: &networking.Destination{Host: "foo.baz"}, 2147 Headers: &networking.Headers{ 2148 Response: &networking.Headers_HeaderOperations{ 2149 Remove: []string{ 2150 "", 2151 }, 2152 }, 2153 }, 2154 }}, 2155 }, valid: false}, 2156 {name: "null header match", route: &networking.HTTPRoute{ 2157 Route: []*networking.HTTPRouteDestination{{ 2158 Destination: &networking.Destination{Host: "foo.bar"}, 2159 }}, 2160 Match: []*networking.HTTPMatchRequest{{ 2161 Headers: map[string]*networking.StringMatch{ 2162 "header": nil, 2163 }, 2164 }}, 2165 }, valid: false}, 2166 {name: "nil match", route: &networking.HTTPRoute{ 2167 Route: []*networking.HTTPRouteDestination{{ 2168 Destination: &networking.Destination{Host: "foo.bar"}, 2169 }}, 2170 Match: nil, 2171 }, valid: true}, 2172 {name: "match with nil element", route: &networking.HTTPRoute{ 2173 Route: []*networking.HTTPRouteDestination{{ 2174 Destination: &networking.Destination{Host: "foo.bar"}, 2175 }}, 2176 Match: []*networking.HTTPMatchRequest{nil}, 2177 }, valid: true}, 2178 {name: "invalid mirror percent", route: &networking.HTTPRoute{ 2179 MirrorPercent: &types.UInt32Value{Value: 101}, 2180 Route: []*networking.HTTPRouteDestination{{ 2181 Destination: &networking.Destination{Host: "foo.bar"}, 2182 }}, 2183 Match: []*networking.HTTPMatchRequest{nil}, 2184 }, valid: false}, 2185 {name: "invalid mirror percentage", route: &networking.HTTPRoute{ 2186 MirrorPercentage: &networking.Percent{ 2187 Value: 101, 2188 }, 2189 Route: []*networking.HTTPRouteDestination{{ 2190 Destination: &networking.Destination{Host: "foo.bar"}, 2191 }}, 2192 Match: []*networking.HTTPMatchRequest{nil}, 2193 }, valid: false}, 2194 {name: "valid mirror percentage", route: &networking.HTTPRoute{ 2195 MirrorPercentage: &networking.Percent{ 2196 Value: 1, 2197 }, 2198 Route: []*networking.HTTPRouteDestination{{ 2199 Destination: &networking.Destination{Host: "foo.bar"}, 2200 }}, 2201 Match: []*networking.HTTPMatchRequest{nil}, 2202 }, valid: true}, 2203 } 2204 2205 for _, tc := range testCases { 2206 t.Run(tc.name, func(t *testing.T) { 2207 if err := validateHTTPRoute(tc.route, false); (err == nil) != tc.valid { 2208 t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err) 2209 } 2210 }) 2211 } 2212} 2213 2214func TestValidateRouteDestination(t *testing.T) { 2215 testCases := []struct { 2216 name string 2217 routes []*networking.RouteDestination 2218 valid bool 2219 }{ 2220 {name: "simple", routes: []*networking.RouteDestination{{ 2221 Destination: &networking.Destination{Host: "foo.baz"}, 2222 }}, valid: true}, 2223 {name: "wildcard dash", routes: []*networking.RouteDestination{{ 2224 Destination: &networking.Destination{Host: "*-foo.baz"}, 2225 }}, valid: true}, 2226 {name: "wildcard prefix", routes: []*networking.RouteDestination{{ 2227 Destination: &networking.Destination{Host: "*foo.baz"}, 2228 }}, valid: true}, 2229 {name: "wildcard", routes: []*networking.RouteDestination{{ 2230 Destination: &networking.Destination{Host: "*"}, 2231 }}, valid: false}, 2232 {name: "bad wildcard", routes: []*networking.RouteDestination{{ 2233 Destination: &networking.Destination{Host: "foo.*"}, 2234 }}, valid: false}, 2235 {name: "bad fqdn", routes: []*networking.RouteDestination{{ 2236 Destination: &networking.Destination{Host: "default/baz"}, 2237 }}, valid: false}, 2238 {name: "no destination", routes: []*networking.RouteDestination{{ 2239 Destination: nil, 2240 }}, valid: false}, 2241 {name: "weighted", routes: []*networking.RouteDestination{{ 2242 Destination: &networking.Destination{Host: "foo.baz.south"}, 2243 Weight: 25, 2244 }, { 2245 Destination: &networking.Destination{Host: "foo.baz.east"}, 2246 Weight: 75, 2247 }}, valid: true}, 2248 {name: "weight < 0", routes: []*networking.RouteDestination{{ 2249 Destination: &networking.Destination{Host: "foo.baz.south"}, 2250 Weight: 5, 2251 }, { 2252 Destination: &networking.Destination{Host: "foo.baz.east"}, 2253 Weight: -1, 2254 }}, valid: false}, 2255 {name: "total weight > 100", routes: []*networking.RouteDestination{{ 2256 Destination: &networking.Destination{Host: "foo.baz.south"}, 2257 Weight: 55, 2258 }, { 2259 Destination: &networking.Destination{Host: "foo.baz.east"}, 2260 Weight: 50, 2261 }}, valid: false}, 2262 {name: "total weight < 100", routes: []*networking.RouteDestination{{ 2263 Destination: &networking.Destination{Host: "foo.baz.south"}, 2264 Weight: 49, 2265 }, { 2266 Destination: &networking.Destination{Host: "foo.baz.east"}, 2267 Weight: 50, 2268 }}, valid: false}, 2269 {name: "total weight = 100", routes: []*networking.RouteDestination{{ 2270 Destination: &networking.Destination{Host: "foo.baz.south"}, 2271 Weight: 100, 2272 }, { 2273 Destination: &networking.Destination{Host: "foo.baz.east"}, 2274 Weight: 0, 2275 }}, valid: true}, 2276 {name: "weight = 0", routes: []*networking.RouteDestination{{ 2277 Destination: &networking.Destination{Host: "foo.baz.south"}, 2278 Weight: 0, 2279 }}, valid: true}, 2280 {name: "total weight = 0 with multi RouteDestination", routes: []*networking.RouteDestination{{ 2281 Destination: &networking.Destination{Host: "foo.baz.south"}, 2282 Weight: 0, 2283 }, { 2284 Destination: &networking.Destination{Host: "foo.baz.east"}, 2285 Weight: 0, 2286 }}, valid: false}, 2287 } 2288 2289 for _, tc := range testCases { 2290 t.Run(tc.name, func(t *testing.T) { 2291 if err := validateRouteDestinations(tc.routes); (err == nil) != tc.valid { 2292 t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err) 2293 } 2294 }) 2295 } 2296} 2297 2298// TODO: add TCP test cases once it is implemented 2299func TestValidateVirtualService(t *testing.T) { 2300 testCases := []struct { 2301 name string 2302 in proto.Message 2303 valid bool 2304 }{ 2305 {name: "simple", in: &networking.VirtualService{ 2306 Hosts: []string{"foo.bar"}, 2307 Http: []*networking.HTTPRoute{{ 2308 Route: []*networking.HTTPRouteDestination{{ 2309 Destination: &networking.Destination{Host: "foo.baz"}, 2310 }}, 2311 }}, 2312 }, valid: true}, 2313 {name: "duplicate hosts", in: &networking.VirtualService{ 2314 Hosts: []string{"*.foo.bar", "*.bar"}, 2315 Http: []*networking.HTTPRoute{{ 2316 Route: []*networking.HTTPRouteDestination{{ 2317 Destination: &networking.Destination{Host: "foo.baz"}, 2318 }}, 2319 }}, 2320 }, valid: false}, 2321 {name: "with no destination", in: &networking.VirtualService{ 2322 Hosts: []string{"*.foo.bar", "*.bar"}, 2323 Http: []*networking.HTTPRoute{{ 2324 Route: []*networking.HTTPRouteDestination{{}}, 2325 }}, 2326 }, valid: false}, 2327 {name: "destination with out hosts", in: &networking.VirtualService{ 2328 Hosts: []string{"*.foo.bar", "*.bar"}, 2329 Http: []*networking.HTTPRoute{{ 2330 Route: []*networking.HTTPRouteDestination{{ 2331 Destination: &networking.Destination{}, 2332 }}, 2333 }}, 2334 }, valid: false}, 2335 {name: "no hosts", in: &networking.VirtualService{ 2336 Hosts: nil, 2337 Http: []*networking.HTTPRoute{{ 2338 Route: []*networking.HTTPRouteDestination{{ 2339 Destination: &networking.Destination{Host: "foo.baz"}, 2340 }}, 2341 }}, 2342 }, valid: false}, 2343 {name: "bad host", in: &networking.VirtualService{ 2344 Hosts: []string{"foo.ba!r"}, 2345 Http: []*networking.HTTPRoute{{ 2346 Route: []*networking.HTTPRouteDestination{{ 2347 Destination: &networking.Destination{Host: "foo.baz"}, 2348 }}, 2349 }}, 2350 }, valid: false}, 2351 {name: "no tcp or http routing", in: &networking.VirtualService{ 2352 Hosts: []string{"foo.bar"}, 2353 }, valid: false}, 2354 {name: "bad gateway", in: &networking.VirtualService{ 2355 Hosts: []string{"foo.bar"}, 2356 Gateways: []string{"b@dgateway"}, 2357 Http: []*networking.HTTPRoute{{ 2358 Route: []*networking.HTTPRouteDestination{{ 2359 Destination: &networking.Destination{Host: "foo.baz"}, 2360 }}, 2361 }}, 2362 }, valid: false}, 2363 {name: "FQDN for gateway", in: &networking.VirtualService{ 2364 Hosts: []string{"foo.bar"}, 2365 Gateways: []string{"gateway.example.com"}, 2366 Http: []*networking.HTTPRoute{{ 2367 Route: []*networking.HTTPRouteDestination{{ 2368 Destination: &networking.Destination{Host: "foo.baz"}, 2369 }}, 2370 }}, 2371 }, valid: true}, 2372 {name: "namespace/name for gateway", in: &networking.VirtualService{ 2373 Hosts: []string{"foo.bar"}, 2374 Gateways: []string{"ns1/gateway"}, 2375 Http: []*networking.HTTPRoute{{ 2376 Route: []*networking.HTTPRouteDestination{{ 2377 Destination: &networking.Destination{Host: "foo.baz"}, 2378 }}, 2379 }}, 2380 }, valid: true}, 2381 {name: "namespace/* for gateway", in: &networking.VirtualService{ 2382 Hosts: []string{"foo.bar"}, 2383 Gateways: []string{"ns1/*"}, 2384 Http: []*networking.HTTPRoute{{ 2385 Route: []*networking.HTTPRouteDestination{{ 2386 Destination: &networking.Destination{Host: "foo.baz"}, 2387 }}, 2388 }}, 2389 }, valid: false}, 2390 {name: "*/name for gateway", in: &networking.VirtualService{ 2391 Hosts: []string{"foo.bar"}, 2392 Gateways: []string{"*/gateway"}, 2393 Http: []*networking.HTTPRoute{{ 2394 Route: []*networking.HTTPRouteDestination{{ 2395 Destination: &networking.Destination{Host: "foo.baz"}, 2396 }}, 2397 }}, 2398 }, valid: false}, 2399 {name: "wildcard for mesh gateway", in: &networking.VirtualService{ 2400 Hosts: []string{"*"}, 2401 Http: []*networking.HTTPRoute{{ 2402 Route: []*networking.HTTPRouteDestination{{ 2403 Destination: &networking.Destination{Host: "foo.baz"}, 2404 }}, 2405 }}, 2406 }, valid: false}, 2407 {name: "wildcard for non-mesh gateway", in: &networking.VirtualService{ 2408 Hosts: []string{"*"}, 2409 Gateways: []string{"somegateway"}, 2410 Http: []*networking.HTTPRoute{{ 2411 Route: []*networking.HTTPRouteDestination{{ 2412 Destination: &networking.Destination{Host: "foo.baz"}, 2413 }}, 2414 }}, 2415 }, valid: true}, 2416 {name: "missing tcp route", in: &networking.VirtualService{ 2417 Hosts: []string{"foo.bar"}, 2418 Tcp: []*networking.TCPRoute{{ 2419 Match: []*networking.L4MatchAttributes{ 2420 {Port: 999}, 2421 }, 2422 }}, 2423 }, valid: false}, 2424 {name: "missing tls route", in: &networking.VirtualService{ 2425 Hosts: []string{"foo.bar"}, 2426 Tls: []*networking.TLSRoute{{ 2427 Match: []*networking.TLSMatchAttributes{ 2428 { 2429 Port: 999, 2430 SniHosts: []string{"foo.bar"}, 2431 }, 2432 }, 2433 }}, 2434 }, valid: false}, 2435 } 2436 2437 for _, tc := range testCases { 2438 t.Run(tc.name, func(t *testing.T) { 2439 if err := ValidateVirtualService("", "", tc.in); (err == nil) != tc.valid { 2440 t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err) 2441 } 2442 }) 2443 } 2444} 2445 2446func TestValidateDestinationRule(t *testing.T) { 2447 cases := []struct { 2448 name string 2449 in proto.Message 2450 valid bool 2451 }{ 2452 {name: "simple destination rule", in: &networking.DestinationRule{ 2453 Host: "reviews", 2454 Subsets: []*networking.Subset{ 2455 {Name: "v1", Labels: map[string]string{"version": "v1"}}, 2456 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2457 }, 2458 }, valid: true}, 2459 2460 {name: "missing destination name", in: &networking.DestinationRule{ 2461 Host: "", 2462 Subsets: []*networking.Subset{ 2463 {Name: "v1", Labels: map[string]string{"version": "v1"}}, 2464 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2465 }, 2466 }, valid: false}, 2467 2468 {name: "missing subset name", in: &networking.DestinationRule{ 2469 Host: "reviews", 2470 Subsets: []*networking.Subset{ 2471 {Name: "", Labels: map[string]string{"version": "v1"}}, 2472 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2473 }, 2474 }, valid: false}, 2475 2476 {name: "valid traffic policy, top level", in: &networking.DestinationRule{ 2477 Host: "reviews", 2478 TrafficPolicy: &networking.TrafficPolicy{ 2479 LoadBalancer: &networking.LoadBalancerSettings{ 2480 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2481 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2482 }, 2483 }, 2484 ConnectionPool: &networking.ConnectionPoolSettings{ 2485 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2486 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2487 }, 2488 OutlierDetection: &networking.OutlierDetection{ 2489 ConsecutiveErrors: 5, 2490 MinHealthPercent: 20, 2491 }, 2492 }, 2493 Subsets: []*networking.Subset{ 2494 {Name: "v1", Labels: map[string]string{"version": "v1"}}, 2495 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2496 }, 2497 }, valid: true}, 2498 2499 {name: "invalid traffic policy, top level", in: &networking.DestinationRule{ 2500 Host: "reviews", 2501 TrafficPolicy: &networking.TrafficPolicy{ 2502 LoadBalancer: &networking.LoadBalancerSettings{ 2503 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2504 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2505 }, 2506 }, 2507 ConnectionPool: &networking.ConnectionPoolSettings{}, 2508 OutlierDetection: &networking.OutlierDetection{ 2509 ConsecutiveErrors: 5, 2510 MinHealthPercent: 20, 2511 }, 2512 }, 2513 Subsets: []*networking.Subset{ 2514 {Name: "v1", Labels: map[string]string{"version": "v1"}}, 2515 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2516 }, 2517 }, valid: false}, 2518 2519 {name: "valid traffic policy, subset level", in: &networking.DestinationRule{ 2520 Host: "reviews", 2521 Subsets: []*networking.Subset{ 2522 {Name: "v1", Labels: map[string]string{"version": "v1"}, 2523 TrafficPolicy: &networking.TrafficPolicy{ 2524 LoadBalancer: &networking.LoadBalancerSettings{ 2525 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2526 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2527 }, 2528 }, 2529 ConnectionPool: &networking.ConnectionPoolSettings{ 2530 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2531 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2532 }, 2533 OutlierDetection: &networking.OutlierDetection{ 2534 ConsecutiveErrors: 5, 2535 MinHealthPercent: 20, 2536 }, 2537 }, 2538 }, 2539 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2540 }, 2541 }, valid: true}, 2542 2543 {name: "invalid traffic policy, subset level", in: &networking.DestinationRule{ 2544 Host: "reviews", 2545 Subsets: []*networking.Subset{ 2546 {Name: "v1", Labels: map[string]string{"version": "v1"}, 2547 TrafficPolicy: &networking.TrafficPolicy{ 2548 LoadBalancer: &networking.LoadBalancerSettings{ 2549 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2550 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2551 }, 2552 }, 2553 ConnectionPool: &networking.ConnectionPoolSettings{}, 2554 OutlierDetection: &networking.OutlierDetection{ 2555 ConsecutiveErrors: 5, 2556 MinHealthPercent: 20, 2557 }, 2558 }, 2559 }, 2560 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2561 }, 2562 }, valid: false}, 2563 2564 {name: "valid traffic policy, both levels", in: &networking.DestinationRule{ 2565 Host: "reviews", 2566 TrafficPolicy: &networking.TrafficPolicy{ 2567 LoadBalancer: &networking.LoadBalancerSettings{ 2568 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2569 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2570 }, 2571 }, 2572 ConnectionPool: &networking.ConnectionPoolSettings{ 2573 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2574 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2575 }, 2576 OutlierDetection: &networking.OutlierDetection{ 2577 ConsecutiveErrors: 5, 2578 MinHealthPercent: 20, 2579 }, 2580 }, 2581 Subsets: []*networking.Subset{ 2582 {Name: "v1", Labels: map[string]string{"version": "v1"}, 2583 TrafficPolicy: &networking.TrafficPolicy{ 2584 LoadBalancer: &networking.LoadBalancerSettings{ 2585 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2586 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2587 }, 2588 }, 2589 ConnectionPool: &networking.ConnectionPoolSettings{ 2590 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2591 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2592 }, 2593 OutlierDetection: &networking.OutlierDetection{ 2594 ConsecutiveErrors: 5, 2595 MinHealthPercent: 30, 2596 }, 2597 }, 2598 }, 2599 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2600 }, 2601 }, valid: true}, 2602 2603 {name: "negative consecutive errors", in: &networking.DestinationRule{ 2604 Host: "reviews", 2605 TrafficPolicy: &networking.TrafficPolicy{ 2606 OutlierDetection: &networking.OutlierDetection{ 2607 ConsecutiveErrors: -1, 2608 }, 2609 }, 2610 Subsets: []*networking.Subset{ 2611 {Name: "v1", Labels: map[string]string{"version": "v1"}}, 2612 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2613 }, 2614 }, valid: false}, 2615 2616 {name: "deprecated consecutive errors set together with consecutive 5xx errors", in: &networking.DestinationRule{ 2617 Host: "reviews", 2618 TrafficPolicy: &networking.TrafficPolicy{ 2619 OutlierDetection: &networking.OutlierDetection{ 2620 ConsecutiveErrors: 3, 2621 Consecutive_5XxErrors: &types.UInt32Value{Value: 3}, 2622 }, 2623 }, 2624 Subsets: []*networking.Subset{ 2625 {Name: "v1", Labels: map[string]string{"version": "v1"}}, 2626 {Name: "v2", Labels: map[string]string{"version": "v2"}}, 2627 }, 2628 }, valid: false}, 2629 } 2630 for _, c := range cases { 2631 if got := ValidateDestinationRule(someName, someNamespace, c.in); (got == nil) != c.valid { 2632 t.Errorf("ValidateDestinationRule failed on %v: got valid=%v but wanted valid=%v: %v", 2633 c.name, got == nil, c.valid, got) 2634 } 2635 } 2636} 2637 2638func TestValidateTrafficPolicy(t *testing.T) { 2639 cases := []struct { 2640 name string 2641 in networking.TrafficPolicy 2642 valid bool 2643 }{ 2644 {name: "valid traffic policy", in: networking.TrafficPolicy{ 2645 LoadBalancer: &networking.LoadBalancerSettings{ 2646 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2647 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2648 }, 2649 }, 2650 ConnectionPool: &networking.ConnectionPoolSettings{ 2651 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2652 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2653 }, 2654 OutlierDetection: &networking.OutlierDetection{ 2655 ConsecutiveErrors: 5, 2656 MinHealthPercent: 20, 2657 }, 2658 }, 2659 valid: true}, 2660 {name: "invalid traffic policy, nil entries", in: networking.TrafficPolicy{}, 2661 valid: false}, 2662 2663 {name: "invalid traffic policy, missing port in port level settings", in: networking.TrafficPolicy{ 2664 PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{ 2665 { 2666 LoadBalancer: &networking.LoadBalancerSettings{ 2667 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2668 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2669 }, 2670 }, 2671 ConnectionPool: &networking.ConnectionPoolSettings{ 2672 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2673 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2674 }, 2675 OutlierDetection: &networking.OutlierDetection{ 2676 ConsecutiveErrors: 5, 2677 MinHealthPercent: 20, 2678 }, 2679 }, 2680 }, 2681 }, 2682 valid: false}, 2683 {name: "invalid traffic policy, bad connection pool", in: networking.TrafficPolicy{ 2684 LoadBalancer: &networking.LoadBalancerSettings{ 2685 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2686 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2687 }, 2688 }, 2689 ConnectionPool: &networking.ConnectionPoolSettings{}, 2690 OutlierDetection: &networking.OutlierDetection{ 2691 ConsecutiveErrors: 5, 2692 MinHealthPercent: 20, 2693 }, 2694 }, 2695 valid: false}, 2696 {name: "invalid traffic policy, panic threshold too low", in: networking.TrafficPolicy{ 2697 LoadBalancer: &networking.LoadBalancerSettings{ 2698 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2699 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2700 }, 2701 }, 2702 ConnectionPool: &networking.ConnectionPoolSettings{ 2703 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7}, 2704 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11}, 2705 }, 2706 OutlierDetection: &networking.OutlierDetection{ 2707 ConsecutiveErrors: 5, 2708 MinHealthPercent: -1, 2709 }, 2710 }, 2711 valid: false}, 2712 } 2713 for _, c := range cases { 2714 if got := validateTrafficPolicy(&c.in); (got == nil) != c.valid { 2715 t.Errorf("ValidateTrafficPolicy failed on %v: got valid=%v but wanted valid=%v: %v", 2716 c.name, got == nil, c.valid, got) 2717 } 2718 } 2719} 2720 2721func TestValidateConnectionPool(t *testing.T) { 2722 cases := []struct { 2723 name string 2724 in networking.ConnectionPoolSettings 2725 valid bool 2726 }{ 2727 {name: "valid connection pool, tcp and http", in: networking.ConnectionPoolSettings{ 2728 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2729 MaxConnections: 7, 2730 ConnectTimeout: &types.Duration{Seconds: 2}, 2731 }, 2732 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2733 Http1MaxPendingRequests: 2, 2734 Http2MaxRequests: 11, 2735 MaxRequestsPerConnection: 5, 2736 MaxRetries: 4, 2737 IdleTimeout: &types.Duration{Seconds: 30}, 2738 }, 2739 }, 2740 valid: true}, 2741 2742 {name: "valid connection pool, tcp only", in: networking.ConnectionPoolSettings{ 2743 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2744 MaxConnections: 7, 2745 ConnectTimeout: &types.Duration{Seconds: 2}, 2746 }, 2747 }, 2748 valid: true}, 2749 2750 {name: "valid connection pool, http only", in: networking.ConnectionPoolSettings{ 2751 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2752 Http1MaxPendingRequests: 2, 2753 Http2MaxRequests: 11, 2754 MaxRequestsPerConnection: 5, 2755 MaxRetries: 4, 2756 IdleTimeout: &types.Duration{Seconds: 30}, 2757 }, 2758 }, 2759 valid: true}, 2760 2761 {name: "valid connection pool, http only with empty idle timeout", in: networking.ConnectionPoolSettings{ 2762 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2763 Http1MaxPendingRequests: 2, 2764 Http2MaxRequests: 11, 2765 MaxRequestsPerConnection: 5, 2766 MaxRetries: 4, 2767 }, 2768 }, 2769 valid: true}, 2770 2771 {name: "invalid connection pool, empty", in: networking.ConnectionPoolSettings{}, valid: false}, 2772 2773 {name: "invalid connection pool, bad max connections", in: networking.ConnectionPoolSettings{ 2774 Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: -1}}, 2775 valid: false}, 2776 2777 {name: "invalid connection pool, bad connect timeout", in: networking.ConnectionPoolSettings{ 2778 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2779 ConnectTimeout: &types.Duration{Seconds: 2, Nanos: 5}}}, 2780 valid: false}, 2781 2782 {name: "invalid connection pool, bad max pending requests", in: networking.ConnectionPoolSettings{ 2783 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http1MaxPendingRequests: -1}}, 2784 valid: false}, 2785 2786 {name: "invalid connection pool, bad max requests", in: networking.ConnectionPoolSettings{ 2787 Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: -1}}, 2788 valid: false}, 2789 2790 {name: "invalid connection pool, bad max requests per connection", in: networking.ConnectionPoolSettings{ 2791 Http: &networking.ConnectionPoolSettings_HTTPSettings{MaxRequestsPerConnection: -1}}, 2792 valid: false}, 2793 2794 {name: "invalid connection pool, bad max retries", in: networking.ConnectionPoolSettings{ 2795 Http: &networking.ConnectionPoolSettings_HTTPSettings{MaxRetries: -1}}, 2796 valid: false}, 2797 2798 {name: "invalid connection pool, bad idle timeout", in: networking.ConnectionPoolSettings{ 2799 Http: &networking.ConnectionPoolSettings_HTTPSettings{IdleTimeout: &types.Duration{Seconds: 30, Nanos: 5}}}, 2800 valid: false}, 2801 } 2802 2803 for _, c := range cases { 2804 if got := validateConnectionPool(&c.in); (got == nil) != c.valid { 2805 t.Errorf("ValidateConnectionSettings failed on %v: got valid=%v but wanted valid=%v: %v", 2806 c.name, got == nil, c.valid, got) 2807 } 2808 } 2809} 2810 2811func TestValidateLoadBalancer(t *testing.T) { 2812 duration := types.Duration{Seconds: int64(time.Hour / time.Second)} 2813 cases := []struct { 2814 name string 2815 in networking.LoadBalancerSettings 2816 valid bool 2817 }{ 2818 {name: "valid load balancer with simple load balancing", in: networking.LoadBalancerSettings{ 2819 LbPolicy: &networking.LoadBalancerSettings_Simple{ 2820 Simple: networking.LoadBalancerSettings_ROUND_ROBIN, 2821 }, 2822 }, 2823 valid: true}, 2824 2825 {name: "valid load balancer with consistentHash load balancing", in: networking.LoadBalancerSettings{ 2826 LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{ 2827 ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{ 2828 MinimumRingSize: 1024, 2829 HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{ 2830 HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{ 2831 Name: "test", 2832 Ttl: &duration, 2833 }, 2834 }, 2835 }, 2836 }, 2837 }, 2838 valid: true}, 2839 2840 {name: "invalid load balancer with consistentHash load balancing, missing ttl", in: networking.LoadBalancerSettings{ 2841 LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{ 2842 ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{ 2843 MinimumRingSize: 1024, 2844 HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{ 2845 HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{ 2846 Name: "test", 2847 }, 2848 }, 2849 }, 2850 }, 2851 }, 2852 valid: false}, 2853 2854 {name: "invalid load balancer with consistentHash load balancing, missing name", in: networking.LoadBalancerSettings{ 2855 LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{ 2856 ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{ 2857 MinimumRingSize: 1024, 2858 HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{ 2859 HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{ 2860 Ttl: &duration, 2861 }, 2862 }, 2863 }, 2864 }, 2865 }, 2866 valid: false}, 2867 } 2868 2869 for _, c := range cases { 2870 if got := validateLoadBalancer(&c.in); (got == nil) != c.valid { 2871 t.Errorf("validateLoadBalancer failed on %v: got valid=%v but wanted valid=%v: %v", 2872 c.name, got == nil, c.valid, got) 2873 } 2874 } 2875} 2876 2877func TestValidateOutlierDetection(t *testing.T) { 2878 cases := []struct { 2879 name string 2880 in networking.OutlierDetection 2881 valid bool 2882 }{ 2883 {name: "valid outlier detection", in: networking.OutlierDetection{ 2884 ConsecutiveErrors: 5, 2885 Interval: &types.Duration{Seconds: 2}, 2886 BaseEjectionTime: &types.Duration{Seconds: 2}, 2887 MaxEjectionPercent: 50, 2888 }, valid: true}, 2889 2890 {name: "invalid outlier detection, bad consecutive errors", in: networking.OutlierDetection{ 2891 ConsecutiveErrors: -1}, 2892 valid: false}, 2893 2894 {name: "invalid outlier detection, bad interval", in: networking.OutlierDetection{ 2895 Interval: &types.Duration{Seconds: 2, Nanos: 5}}, 2896 valid: false}, 2897 2898 {name: "invalid outlier detection, bad base ejection time", in: networking.OutlierDetection{ 2899 BaseEjectionTime: &types.Duration{Seconds: 2, Nanos: 5}}, 2900 valid: false}, 2901 2902 {name: "invalid outlier detection, bad max ejection percent", in: networking.OutlierDetection{ 2903 MaxEjectionPercent: 105}, 2904 valid: false}, 2905 {name: "invalid outlier detection, panic threshold too low", in: networking.OutlierDetection{ 2906 MinHealthPercent: -1, 2907 }, 2908 valid: false}, 2909 {name: "invalid outlier detection, panic threshold too high", in: networking.OutlierDetection{ 2910 MinHealthPercent: 101, 2911 }, 2912 valid: false}, 2913 } 2914 2915 for _, c := range cases { 2916 if got := validateOutlierDetection(&c.in); (got == nil) != c.valid { 2917 t.Errorf("ValidateOutlierDetection failed on %v: got valid=%v but wanted valid=%v: %v", 2918 c.name, got == nil, c.valid, got) 2919 } 2920 } 2921} 2922 2923func TestValidateEnvoyFilter(t *testing.T) { 2924 tests := []struct { 2925 name string 2926 in proto.Message 2927 error string 2928 }{ 2929 {name: "empty filters", in: &networking.EnvoyFilter{}, error: ""}, 2930 2931 {name: "invalid applyTo", in: &networking.EnvoyFilter{ 2932 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2933 { 2934 ApplyTo: 0, 2935 }, 2936 }, 2937 }, error: "Envoy filter: missing applyTo"}, 2938 {name: "nil patch", in: &networking.EnvoyFilter{ 2939 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2940 { 2941 ApplyTo: networking.EnvoyFilter_LISTENER, 2942 Patch: nil, 2943 }, 2944 }, 2945 }, error: "Envoy filter: missing patch"}, 2946 {name: "invalid patch operation", in: &networking.EnvoyFilter{ 2947 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2948 { 2949 ApplyTo: networking.EnvoyFilter_LISTENER, 2950 Patch: &networking.EnvoyFilter_Patch{}, 2951 }, 2952 }, 2953 }, error: "Envoy filter: missing patch operation"}, 2954 {name: "nil patch value", in: &networking.EnvoyFilter{ 2955 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2956 { 2957 ApplyTo: networking.EnvoyFilter_LISTENER, 2958 Patch: &networking.EnvoyFilter_Patch{ 2959 Operation: networking.EnvoyFilter_Patch_ADD, 2960 }, 2961 }, 2962 }, 2963 }, error: "Envoy filter: missing patch value for non-remove operation"}, 2964 {name: "match with invalid regex", in: &networking.EnvoyFilter{ 2965 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2966 { 2967 ApplyTo: networking.EnvoyFilter_LISTENER, 2968 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 2969 Proxy: &networking.EnvoyFilter_ProxyMatch{ 2970 ProxyVersion: "%#@~++==`24c234`", 2971 }, 2972 }, 2973 Patch: &networking.EnvoyFilter_Patch{ 2974 Operation: networking.EnvoyFilter_Patch_REMOVE, 2975 }, 2976 }, 2977 }, 2978 }, error: "Envoy filter: invalid regex for proxy version, [error parsing regexp: invalid nested repetition operator: `++`]"}, 2979 {name: "match with valid regex", in: &networking.EnvoyFilter{ 2980 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2981 { 2982 ApplyTo: networking.EnvoyFilter_LISTENER, 2983 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 2984 Proxy: &networking.EnvoyFilter_ProxyMatch{ 2985 ProxyVersion: `release-1\.2-23434`, 2986 }, 2987 }, 2988 Patch: &networking.EnvoyFilter_Patch{ 2989 Operation: networking.EnvoyFilter_Patch_REMOVE, 2990 }, 2991 }, 2992 }, 2993 }, error: ""}, 2994 {name: "listener with invalid match", in: &networking.EnvoyFilter{ 2995 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 2996 { 2997 ApplyTo: networking.EnvoyFilter_LISTENER, 2998 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 2999 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ 3000 Cluster: &networking.EnvoyFilter_ClusterMatch{}, 3001 }, 3002 }, 3003 Patch: &networking.EnvoyFilter_Patch{ 3004 Operation: networking.EnvoyFilter_Patch_REMOVE, 3005 }, 3006 }, 3007 }, 3008 }, error: "Envoy filter: applyTo for listener class objects cannot have non listener match"}, 3009 {name: "listener with invalid filter match", in: &networking.EnvoyFilter{ 3010 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3011 { 3012 ApplyTo: networking.EnvoyFilter_LISTENER, 3013 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3014 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3015 Listener: &networking.EnvoyFilter_ListenerMatch{ 3016 FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 3017 Sni: "124", 3018 Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{}, 3019 }, 3020 }, 3021 }, 3022 }, 3023 Patch: &networking.EnvoyFilter_Patch{ 3024 Operation: networking.EnvoyFilter_Patch_REMOVE, 3025 }, 3026 }, 3027 }, 3028 }, error: "Envoy filter: filter match has no name to match on"}, 3029 {name: "listener with sub filter match and invalid applyTo", in: &networking.EnvoyFilter{ 3030 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3031 { 3032 ApplyTo: networking.EnvoyFilter_LISTENER, 3033 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3034 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3035 Listener: &networking.EnvoyFilter_ListenerMatch{ 3036 FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 3037 Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ 3038 Name: "random", 3039 SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{}, 3040 }, 3041 }, 3042 }, 3043 }, 3044 }, 3045 Patch: &networking.EnvoyFilter_Patch{ 3046 Operation: networking.EnvoyFilter_Patch_REMOVE, 3047 }, 3048 }, 3049 }, 3050 }, error: "Envoy filter: subfilter match can be used with applyTo HTTP_FILTER only"}, 3051 {name: "listener with sub filter match and invalid filter name", in: &networking.EnvoyFilter{ 3052 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3053 { 3054 ApplyTo: networking.EnvoyFilter_HTTP_FILTER, 3055 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3056 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3057 Listener: &networking.EnvoyFilter_ListenerMatch{ 3058 FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 3059 Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ 3060 Name: "random", 3061 SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{}, 3062 }, 3063 }, 3064 }, 3065 }, 3066 }, 3067 Patch: &networking.EnvoyFilter_Patch{ 3068 Operation: networking.EnvoyFilter_Patch_REMOVE, 3069 }, 3070 }, 3071 }, 3072 }, error: "Envoy filter: subfilter match requires filter match with envoy.http_connection_manager"}, 3073 {name: "listener with sub filter match and no sub filter name", in: &networking.EnvoyFilter{ 3074 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3075 { 3076 ApplyTo: networking.EnvoyFilter_HTTP_FILTER, 3077 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3078 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3079 Listener: &networking.EnvoyFilter_ListenerMatch{ 3080 FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 3081 Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ 3082 Name: "envoy.http_connection_manager", 3083 SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{}, 3084 }, 3085 }, 3086 }, 3087 }, 3088 }, 3089 Patch: &networking.EnvoyFilter_Patch{ 3090 Operation: networking.EnvoyFilter_Patch_REMOVE, 3091 }, 3092 }, 3093 }, 3094 }, error: "Envoy filter: subfilter match has no name to match on"}, 3095 {name: "route configuration with invalid match", in: &networking.EnvoyFilter{ 3096 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3097 { 3098 ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST, 3099 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3100 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ 3101 Cluster: &networking.EnvoyFilter_ClusterMatch{}, 3102 }, 3103 }, 3104 Patch: &networking.EnvoyFilter_Patch{ 3105 Operation: networking.EnvoyFilter_Patch_REMOVE, 3106 }, 3107 }, 3108 }, 3109 }, error: "Envoy filter: applyTo for http route class objects cannot have non route configuration match"}, 3110 {name: "cluster with invalid match", in: &networking.EnvoyFilter{ 3111 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3112 { 3113 ApplyTo: networking.EnvoyFilter_CLUSTER, 3114 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3115 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3116 Listener: &networking.EnvoyFilter_ListenerMatch{}, 3117 }, 3118 }, 3119 Patch: &networking.EnvoyFilter_Patch{ 3120 Operation: networking.EnvoyFilter_Patch_REMOVE, 3121 }, 3122 }, 3123 }, 3124 }, error: "Envoy filter: applyTo for cluster class objects cannot have non cluster match"}, 3125 {name: "invalid patch value", in: &networking.EnvoyFilter{ 3126 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3127 { 3128 ApplyTo: networking.EnvoyFilter_CLUSTER, 3129 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3130 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ 3131 Cluster: &networking.EnvoyFilter_ClusterMatch{}, 3132 }, 3133 }, 3134 Patch: &networking.EnvoyFilter_Patch{ 3135 Operation: networking.EnvoyFilter_Patch_ADD, 3136 Value: &types.Struct{ 3137 Fields: map[string]*types.Value{ 3138 "foo": { 3139 Kind: &types.Value_BoolValue{BoolValue: false}, 3140 }, 3141 }, 3142 }, 3143 }, 3144 }, 3145 }, 3146 }, error: `Envoy filter: unknown field "foo" in envoy_api_v2.Cluster`}, 3147 {name: "happy config", in: &networking.EnvoyFilter{ 3148 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 3149 { 3150 ApplyTo: networking.EnvoyFilter_CLUSTER, 3151 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3152 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ 3153 Cluster: &networking.EnvoyFilter_ClusterMatch{}, 3154 }, 3155 }, 3156 Patch: &networking.EnvoyFilter_Patch{ 3157 Operation: networking.EnvoyFilter_Patch_ADD, 3158 Value: &types.Struct{ 3159 Fields: map[string]*types.Value{ 3160 "lb_policy": { 3161 Kind: &types.Value_StringValue{StringValue: "RING_HASH"}, 3162 }, 3163 }, 3164 }, 3165 }, 3166 }, 3167 { 3168 ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, 3169 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3170 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3171 Listener: &networking.EnvoyFilter_ListenerMatch{ 3172 FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 3173 Name: "envoy.tcp_proxy", 3174 }, 3175 }, 3176 }, 3177 }, 3178 Patch: &networking.EnvoyFilter_Patch{ 3179 Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE, 3180 Value: &types.Struct{ 3181 Fields: map[string]*types.Value{ 3182 "typed_config": { 3183 Kind: &types.Value_StructValue{StructValue: &types.Struct{ 3184 Fields: map[string]*types.Value{ 3185 "@type": { 3186 Kind: &types.Value_StringValue{ 3187 StringValue: "type.googleapis.com/envoy.config.filter.network.ext_authz.v2.ExtAuthz", 3188 }, 3189 }, 3190 }, 3191 }}, 3192 }, 3193 }, 3194 }, 3195 }, 3196 }, 3197 { 3198 ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, 3199 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 3200 ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ 3201 Listener: &networking.EnvoyFilter_ListenerMatch{ 3202 FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ 3203 Name: "envoy.tcp_proxy", 3204 }, 3205 }, 3206 }, 3207 }, 3208 Patch: &networking.EnvoyFilter_Patch{ 3209 Operation: networking.EnvoyFilter_Patch_INSERT_FIRST, 3210 Value: &types.Struct{ 3211 Fields: map[string]*types.Value{ 3212 "typed_config": { 3213 Kind: &types.Value_StructValue{StructValue: &types.Struct{ 3214 Fields: map[string]*types.Value{ 3215 "@type": { 3216 Kind: &types.Value_StringValue{ 3217 StringValue: "type.googleapis.com/envoy.config.filter.network.ext_authz.v2.ExtAuthz", 3218 }, 3219 }, 3220 }, 3221 }}, 3222 }, 3223 }, 3224 }, 3225 }, 3226 }, 3227 }, 3228 }, error: ""}, 3229 } 3230 for _, tt := range tests { 3231 t.Run(tt.name, func(t *testing.T) { 3232 err := ValidateEnvoyFilter(someName, someNamespace, tt.in) 3233 if err == nil && tt.error != "" { 3234 t.Fatalf("ValidateEnvoyFilter(%v) = nil, wanted %q", tt.in, tt.error) 3235 } else if err != nil && tt.error == "" { 3236 t.Fatalf("ValidateEnvoyFilter(%v) = %v, wanted nil", tt.in, err) 3237 } else if err != nil && !strings.Contains(err.Error(), tt.error) { 3238 t.Fatalf("ValidateEnvoyFilter(%v) = %v, wanted %q", tt.in, err, tt.error) 3239 } 3240 }) 3241 } 3242} 3243 3244func TestValidateServiceEntries(t *testing.T) { 3245 cases := []struct { 3246 name string 3247 in networking.ServiceEntry 3248 valid bool 3249 }{ 3250 {name: "discovery type DNS", in: networking.ServiceEntry{ 3251 Hosts: []string{"*.google.com"}, 3252 Ports: []*networking.Port{ 3253 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3254 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3255 }, 3256 Endpoints: []*networking.WorkloadEntry{ 3257 {Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}}, 3258 {Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}}, 3259 }, 3260 Resolution: networking.ServiceEntry_DNS, 3261 }, 3262 valid: true}, 3263 3264 {name: "discovery type DNS, label tlsMode: istio", in: networking.ServiceEntry{ 3265 Hosts: []string{"*.google.com"}, 3266 Ports: []*networking.Port{ 3267 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3268 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3269 }, 3270 Endpoints: []*networking.WorkloadEntry{ 3271 {Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}, Labels: map[string]string{"security.istio.io/tlsMode": "istio"}}, 3272 {Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}, Labels: map[string]string{"security.istio.io/tlsMode": "istio"}}, 3273 }, 3274 Resolution: networking.ServiceEntry_DNS, 3275 }, 3276 valid: true}, 3277 3278 {name: "discovery type DNS, IP in endpoints", in: networking.ServiceEntry{ 3279 Hosts: []string{"*.google.com"}, 3280 Ports: []*networking.Port{ 3281 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3282 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3283 }, 3284 Endpoints: []*networking.WorkloadEntry{ 3285 {Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}}, 3286 {Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}}, 3287 }, 3288 Resolution: networking.ServiceEntry_DNS, 3289 }, 3290 valid: true}, 3291 3292 {name: "empty hosts", in: networking.ServiceEntry{ 3293 Ports: []*networking.Port{ 3294 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3295 }, 3296 Endpoints: []*networking.WorkloadEntry{ 3297 {Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}}, 3298 }, 3299 Resolution: networking.ServiceEntry_DNS, 3300 }, 3301 valid: false}, 3302 3303 {name: "bad hosts", in: networking.ServiceEntry{ 3304 Hosts: []string{"-"}, 3305 Ports: []*networking.Port{ 3306 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3307 }, 3308 Endpoints: []*networking.WorkloadEntry{ 3309 {Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}}, 3310 }, 3311 Resolution: networking.ServiceEntry_DNS, 3312 }, 3313 valid: false}, 3314 {name: "full wildcard host", in: networking.ServiceEntry{ 3315 Hosts: []string{"foo.com", "*"}, 3316 Ports: []*networking.Port{ 3317 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3318 }, 3319 Endpoints: []*networking.WorkloadEntry{ 3320 {Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}}, 3321 }, 3322 Resolution: networking.ServiceEntry_DNS, 3323 }, 3324 valid: false}, 3325 {name: "short name host", in: networking.ServiceEntry{ 3326 Hosts: []string{"foo", "bar.com"}, 3327 Ports: []*networking.Port{ 3328 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3329 }, 3330 Endpoints: []*networking.WorkloadEntry{ 3331 {Address: "in.google.com", Ports: map[string]uint32{"http-valid1": 9080}}, 3332 }, 3333 Resolution: networking.ServiceEntry_DNS, 3334 }, 3335 valid: true}, 3336 {name: "undefined endpoint port", in: networking.ServiceEntry{ 3337 Hosts: []string{"google.com"}, 3338 Ports: []*networking.Port{ 3339 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3340 {Number: 80, Protocol: "http", Name: "http-valid2"}, 3341 }, 3342 Endpoints: []*networking.WorkloadEntry{ 3343 {Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}}, 3344 {Address: "in.google.com", Ports: map[string]uint32{"http-dne": 9080}}, 3345 }, 3346 Resolution: networking.ServiceEntry_DNS, 3347 }, 3348 valid: false}, 3349 3350 {name: "discovery type DNS, non-FQDN endpoint", in: networking.ServiceEntry{ 3351 Hosts: []string{"*.google.com"}, 3352 Ports: []*networking.Port{ 3353 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3354 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3355 }, 3356 Endpoints: []*networking.WorkloadEntry{ 3357 {Address: "*.lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}}, 3358 {Address: "in.google.com", Ports: map[string]uint32{"http-dne": 9080}}, 3359 }, 3360 Resolution: networking.ServiceEntry_DNS, 3361 }, 3362 valid: false}, 3363 3364 {name: "discovery type DNS, non-FQDN host", in: networking.ServiceEntry{ 3365 Hosts: []string{"*.google.com"}, 3366 Ports: []*networking.Port{ 3367 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3368 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3369 }, 3370 3371 Resolution: networking.ServiceEntry_DNS, 3372 }, 3373 valid: false}, 3374 3375 {name: "discovery type DNS, no endpoints", in: networking.ServiceEntry{ 3376 Hosts: []string{"google.com"}, 3377 Ports: []*networking.Port{ 3378 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3379 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3380 }, 3381 3382 Resolution: networking.ServiceEntry_DNS, 3383 }, 3384 valid: true}, 3385 3386 {name: "discovery type DNS, unix endpoint", in: networking.ServiceEntry{ 3387 Hosts: []string{"*.google.com"}, 3388 Ports: []*networking.Port{ 3389 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3390 }, 3391 Endpoints: []*networking.WorkloadEntry{ 3392 {Address: "unix:///lon/google/com"}, 3393 }, 3394 Resolution: networking.ServiceEntry_DNS, 3395 }, 3396 valid: false}, 3397 3398 {name: "discovery type none", in: networking.ServiceEntry{ 3399 Hosts: []string{"google.com"}, 3400 Ports: []*networking.Port{ 3401 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3402 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3403 }, 3404 Resolution: networking.ServiceEntry_NONE, 3405 }, 3406 valid: true}, 3407 3408 {name: "discovery type none, endpoints provided", in: networking.ServiceEntry{ 3409 Hosts: []string{"google.com"}, 3410 Ports: []*networking.Port{ 3411 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3412 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3413 }, 3414 Endpoints: []*networking.WorkloadEntry{ 3415 {Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}}, 3416 }, 3417 Resolution: networking.ServiceEntry_NONE, 3418 }, 3419 valid: false}, 3420 3421 {name: "discovery type none, cidr addresses", in: networking.ServiceEntry{ 3422 Hosts: []string{"google.com"}, 3423 Addresses: []string{"172.1.2.16/16"}, 3424 Ports: []*networking.Port{ 3425 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3426 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3427 }, 3428 Resolution: networking.ServiceEntry_NONE, 3429 }, 3430 valid: true}, 3431 3432 {name: "discovery type static, cidr addresses with endpoints", in: networking.ServiceEntry{ 3433 Hosts: []string{"google.com"}, 3434 Addresses: []string{"172.1.2.16/16"}, 3435 Ports: []*networking.Port{ 3436 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3437 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3438 }, 3439 Endpoints: []*networking.WorkloadEntry{ 3440 {Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}}, 3441 {Address: "2.2.2.2", Ports: map[string]uint32{"http-valid2": 9080}}, 3442 }, 3443 Resolution: networking.ServiceEntry_STATIC, 3444 }, 3445 valid: true}, 3446 3447 {name: "discovery type static", in: networking.ServiceEntry{ 3448 Hosts: []string{"google.com"}, 3449 Addresses: []string{"172.1.2.16"}, 3450 Ports: []*networking.Port{ 3451 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3452 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3453 }, 3454 Endpoints: []*networking.WorkloadEntry{ 3455 {Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}}, 3456 {Address: "2.2.2.2", Ports: map[string]uint32{"http-valid2": 9080}}, 3457 }, 3458 Resolution: networking.ServiceEntry_STATIC, 3459 }, 3460 valid: true}, 3461 3462 {name: "discovery type static, FQDN in endpoints", in: networking.ServiceEntry{ 3463 Hosts: []string{"google.com"}, 3464 Addresses: []string{"172.1.2.16"}, 3465 Ports: []*networking.Port{ 3466 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3467 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3468 }, 3469 Endpoints: []*networking.WorkloadEntry{ 3470 {Address: "google.com", Ports: map[string]uint32{"http-valid1": 8080}}, 3471 {Address: "2.2.2.2", Ports: map[string]uint32{"http-valid2": 9080}}, 3472 }, 3473 Resolution: networking.ServiceEntry_STATIC, 3474 }, 3475 valid: false}, 3476 3477 {name: "discovery type static, missing endpoints", in: networking.ServiceEntry{ 3478 Hosts: []string{"google.com"}, 3479 Addresses: []string{"172.1.2.16"}, 3480 Ports: []*networking.Port{ 3481 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3482 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3483 }, 3484 Resolution: networking.ServiceEntry_STATIC, 3485 }, 3486 valid: true}, 3487 3488 {name: "discovery type static, bad endpoint port name", in: networking.ServiceEntry{ 3489 Hosts: []string{"google.com"}, 3490 Addresses: []string{"172.1.2.16"}, 3491 Ports: []*networking.Port{ 3492 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3493 {Number: 8080, Protocol: "http", Name: "http-valid2"}, 3494 }, 3495 Endpoints: []*networking.WorkloadEntry{ 3496 {Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}}, 3497 {Address: "2.2.2.2", Ports: map[string]uint32{"http-dne": 9080}}, 3498 }, 3499 Resolution: networking.ServiceEntry_STATIC, 3500 }, 3501 valid: false}, 3502 3503 {name: "discovery type none, conflicting port names", in: networking.ServiceEntry{ 3504 Hosts: []string{"google.com"}, 3505 Ports: []*networking.Port{ 3506 {Number: 80, Protocol: "http", Name: "http-conflict"}, 3507 {Number: 8080, Protocol: "http", Name: "http-conflict"}, 3508 }, 3509 Resolution: networking.ServiceEntry_NONE, 3510 }, 3511 valid: false}, 3512 3513 {name: "discovery type none, conflicting port numbers", in: networking.ServiceEntry{ 3514 Hosts: []string{"google.com"}, 3515 Ports: []*networking.Port{ 3516 {Number: 80, Protocol: "http", Name: "http-conflict1"}, 3517 {Number: 80, Protocol: "http", Name: "http-conflict2"}, 3518 }, 3519 Resolution: networking.ServiceEntry_NONE, 3520 }, 3521 valid: false}, 3522 3523 {name: "unix socket", in: networking.ServiceEntry{ 3524 Hosts: []string{"uds.cluster.local"}, 3525 Ports: []*networking.Port{ 3526 {Number: 6553, Protocol: "grpc", Name: "grpc-service1"}, 3527 }, 3528 Resolution: networking.ServiceEntry_STATIC, 3529 Endpoints: []*networking.WorkloadEntry{ 3530 {Address: "unix:///path/to/socket"}, 3531 }, 3532 }, 3533 valid: true}, 3534 3535 {name: "unix socket, relative path", in: networking.ServiceEntry{ 3536 Hosts: []string{"uds.cluster.local"}, 3537 Ports: []*networking.Port{ 3538 {Number: 6553, Protocol: "grpc", Name: "grpc-service1"}, 3539 }, 3540 Resolution: networking.ServiceEntry_STATIC, 3541 Endpoints: []*networking.WorkloadEntry{ 3542 {Address: "unix://./relative/path.sock"}, 3543 }, 3544 }, 3545 valid: false}, 3546 3547 {name: "unix socket, endpoint ports", in: networking.ServiceEntry{ 3548 Hosts: []string{"uds.cluster.local"}, 3549 Ports: []*networking.Port{ 3550 {Number: 6553, Protocol: "grpc", Name: "grpc-service1"}, 3551 }, 3552 Resolution: networking.ServiceEntry_STATIC, 3553 Endpoints: []*networking.WorkloadEntry{ 3554 {Address: "unix:///path/to/socket", Ports: map[string]uint32{"grpc-service1": 6553}}, 3555 }, 3556 }, 3557 valid: false}, 3558 3559 {name: "unix socket, multiple service ports", in: networking.ServiceEntry{ 3560 Hosts: []string{"uds.cluster.local"}, 3561 Ports: []*networking.Port{ 3562 {Number: 6553, Protocol: "grpc", Name: "grpc-service1"}, 3563 {Number: 80, Protocol: "http", Name: "http-service2"}, 3564 }, 3565 Resolution: networking.ServiceEntry_STATIC, 3566 Endpoints: []*networking.WorkloadEntry{ 3567 {Address: "unix:///path/to/socket"}, 3568 }, 3569 }, 3570 valid: false}, 3571 {name: "empty protocol", in: networking.ServiceEntry{ 3572 Hosts: []string{"google.com"}, 3573 Addresses: []string{"172.1.2.16/16"}, 3574 Ports: []*networking.Port{ 3575 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3576 {Number: 8080, Name: "http-valid2"}, 3577 }, 3578 Resolution: networking.ServiceEntry_NONE, 3579 }, 3580 valid: true}, 3581 {name: "selector", in: networking.ServiceEntry{ 3582 Hosts: []string{"google.com"}, 3583 WorkloadSelector: &networking.WorkloadSelector{Labels: map[string]string{"foo": "bar"}}, 3584 Ports: []*networking.Port{ 3585 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3586 }, 3587 }, 3588 valid: true}, 3589 {name: "selector and endpoints", in: networking.ServiceEntry{ 3590 Hosts: []string{"google.com"}, 3591 WorkloadSelector: &networking.WorkloadSelector{Labels: map[string]string{"foo": "bar"}}, 3592 Ports: []*networking.Port{ 3593 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3594 }, 3595 Endpoints: []*networking.WorkloadEntry{ 3596 {Address: "1.1.1.1"}, 3597 }, 3598 }, 3599 valid: false}, 3600 {name: "bad selector key", in: networking.ServiceEntry{ 3601 Hosts: []string{"google.com"}, 3602 WorkloadSelector: &networking.WorkloadSelector{Labels: map[string]string{"": "bar"}}, 3603 Ports: []*networking.Port{ 3604 {Number: 80, Protocol: "http", Name: "http-valid1"}, 3605 }, 3606 }, 3607 valid: false}, 3608 } 3609 3610 for _, c := range cases { 3611 t.Run(c.name, func(t *testing.T) { 3612 if got := ValidateServiceEntry(someName, someNamespace, &c.in); (got == nil) != c.valid { 3613 t.Errorf("ValidateServiceEntry got valid=%v but wanted valid=%v: %v", 3614 got == nil, c.valid, got) 3615 } 3616 }) 3617 } 3618} 3619 3620func TestValidateAuthenticationPolicy(t *testing.T) { 3621 cases := []struct { 3622 name string 3623 configName string 3624 in proto.Message 3625 valid bool 3626 }{ 3627 { 3628 name: "empty policy with namespace-wide policy name", 3629 configName: constants.DefaultAuthenticationPolicyName, 3630 in: &authn.Policy{}, 3631 valid: true, 3632 }, 3633 { 3634 name: "empty policy with non-default name", 3635 configName: someName, 3636 in: &authn.Policy{}, 3637 valid: false, 3638 }, 3639 { 3640 name: "service-specific policy with namespace-wide name", 3641 configName: constants.DefaultAuthenticationPolicyName, 3642 in: &authn.Policy{ 3643 // nolint: staticcheck 3644 Targets: []*authn.TargetSelector{{ 3645 Name: "foo", 3646 }}, 3647 }, 3648 valid: false, 3649 }, 3650 { 3651 name: "Targets only policy", 3652 configName: someName, 3653 in: &authn.Policy{ 3654 // nolint: staticcheck 3655 Targets: []*authn.TargetSelector{{ 3656 Name: "foo", 3657 }}, 3658 }, 3659 valid: true, 3660 }, 3661 { 3662 name: "Source mTLS", 3663 configName: constants.DefaultAuthenticationPolicyName, 3664 in: &authn.Policy{ 3665 Peers: []*authn.PeerAuthenticationMethod{{ 3666 Params: &authn.PeerAuthenticationMethod_Mtls{}, 3667 }}, 3668 }, 3669 valid: true, 3670 }, 3671 { 3672 name: "Source JWT", 3673 configName: constants.DefaultAuthenticationPolicyName, 3674 in: &authn.Policy{ 3675 Peers: []*authn.PeerAuthenticationMethod{{ 3676 Params: &authn.PeerAuthenticationMethod_Jwt{ 3677 // nolint: staticcheck 3678 Jwt: &authn.Jwt{ 3679 Issuer: "istio.io", 3680 JwksUri: "https://secure.istio.io/oauth/v1/certs", 3681 JwtHeaders: []string{"x-goog-iap-jwt-assertion"}, 3682 }, 3683 }, 3684 }}, 3685 }, 3686 valid: true, 3687 }, 3688 { 3689 name: "Origin", 3690 configName: constants.DefaultAuthenticationPolicyName, 3691 in: &authn.Policy{ 3692 // nolint: staticcheck 3693 Origins: []*authn.OriginAuthenticationMethod{ 3694 { 3695 // nolint: staticcheck 3696 Jwt: &authn.Jwt{ 3697 Issuer: "istio.io", 3698 JwksUri: "https://secure.istio.io/oauth/v1/certs", 3699 JwtHeaders: []string{"x-goog-iap-jwt-assertion"}, 3700 }, 3701 }, 3702 }, 3703 }, 3704 valid: true, 3705 }, 3706 { 3707 name: "Bad JkwsURI", 3708 configName: constants.DefaultAuthenticationPolicyName, 3709 in: &authn.Policy{ 3710 // nolint: staticcheck 3711 Origins: []*authn.OriginAuthenticationMethod{ 3712 { 3713 // nolint: staticcheck 3714 Jwt: &authn.Jwt{ 3715 Issuer: "istio.io", 3716 JwksUri: "secure.istio.io/oauth/v1/certs", 3717 JwtHeaders: []string{"x-goog-iap-jwt-assertion"}, 3718 }, 3719 }, 3720 }, 3721 }, 3722 valid: false, 3723 }, 3724 { 3725 name: "Bad JkwsURI Port", 3726 configName: constants.DefaultAuthenticationPolicyName, 3727 in: &authn.Policy{ 3728 // nolint: staticcheck 3729 Origins: []*authn.OriginAuthenticationMethod{ 3730 { 3731 // nolint: staticcheck 3732 Jwt: &authn.Jwt{ 3733 Issuer: "istio.io", 3734 JwksUri: "https://secure.istio.io:not-a-number/oauth/v1/certs", 3735 JwtHeaders: []string{"x-goog-iap-jwt-assertion"}, 3736 }, 3737 }, 3738 }, 3739 }, 3740 valid: false, 3741 }, 3742 { 3743 name: "Duplicate Jwt issuers", 3744 configName: constants.DefaultAuthenticationPolicyName, 3745 in: &authn.Policy{ 3746 Peers: []*authn.PeerAuthenticationMethod{{ 3747 Params: &authn.PeerAuthenticationMethod_Jwt{ 3748 // nolint: staticcheck 3749 Jwt: &authn.Jwt{ 3750 Issuer: "istio.io", 3751 JwksUri: "https://secure.istio.io/oauth/v1/certs", 3752 JwtHeaders: []string{"x-goog-iap-jwt-assertion"}, 3753 }, 3754 }, 3755 }}, 3756 // nolint: staticcheck 3757 Origins: []*authn.OriginAuthenticationMethod{ 3758 { 3759 Jwt: &authn.Jwt{ 3760 Issuer: "istio.io", 3761 JwksUri: "https://secure.istio.io/oauth/v1/certs", 3762 JwtHeaders: []string{"x-goog-iap-jwt-assertion"}, 3763 }, 3764 }, 3765 }, 3766 }, 3767 valid: false, 3768 }, 3769 { 3770 name: "Just binding", 3771 configName: constants.DefaultAuthenticationPolicyName, 3772 in: &authn.Policy{ 3773 // nolint: staticcheck 3774 PrincipalBinding: authn.PrincipalBinding_USE_ORIGIN, 3775 }, 3776 valid: true, 3777 }, 3778 { 3779 name: "Bad target name", 3780 configName: someName, 3781 in: &authn.Policy{ 3782 // nolint: staticcheck 3783 Targets: []*authn.TargetSelector{ 3784 { 3785 Name: "foo.bar", 3786 }, 3787 }, 3788 }, 3789 valid: false, 3790 }, 3791 { 3792 name: "Good target name", 3793 configName: someName, 3794 in: &authn.Policy{ 3795 // nolint: staticcheck 3796 Targets: []*authn.TargetSelector{ 3797 { 3798 Name: "good-service-name", 3799 }, 3800 }, 3801 }, 3802 valid: true, 3803 }, 3804 } 3805 for _, c := range cases { 3806 if got := ValidateAuthenticationPolicy(c.configName, someNamespace, c.in); (got == nil) != c.valid { 3807 t.Errorf("ValidateAuthenticationPolicy(%v): got(%v) != want(%v): %v\n", c.name, got == nil, c.valid, got) 3808 } 3809 } 3810} 3811 3812func TestValidateAuthenticationMeshPolicy(t *testing.T) { 3813 cases := []struct { 3814 name string 3815 configName string 3816 in proto.Message 3817 valid bool 3818 }{ 3819 { 3820 name: "good name", 3821 configName: constants.DefaultAuthenticationPolicyName, 3822 in: &authn.Policy{}, 3823 valid: true, 3824 }, 3825 { 3826 name: "bad-name", 3827 configName: someName, 3828 in: &authn.Policy{}, 3829 valid: false, 3830 }, 3831 { 3832 name: "has targets", 3833 configName: constants.DefaultAuthenticationPolicyName, 3834 in: &authn.Policy{ 3835 Targets: []*authn.TargetSelector{{ 3836 Name: "foo", 3837 }}, 3838 }, 3839 valid: false, 3840 }, 3841 { 3842 name: "good", 3843 configName: constants.DefaultAuthenticationPolicyName, 3844 in: &authn.Policy{ 3845 Peers: []*authn.PeerAuthenticationMethod{{ 3846 Params: &authn.PeerAuthenticationMethod_Mtls{}, 3847 }}, 3848 }, 3849 valid: true, 3850 }, 3851 { 3852 name: "empty origin", 3853 configName: constants.DefaultAuthenticationPolicyName, 3854 in: &authn.Policy{ 3855 Origins: []*authn.OriginAuthenticationMethod{{}}, 3856 }, 3857 valid: false, 3858 }, 3859 { 3860 name: "nil origin", 3861 configName: constants.DefaultAuthenticationPolicyName, 3862 in: &authn.Policy{ 3863 Origins: []*authn.OriginAuthenticationMethod{nil}, 3864 }, 3865 valid: false, 3866 }, 3867 } 3868 for _, c := range cases { 3869 if got := ValidateAuthenticationPolicy(c.configName, "", c.in); (got == nil) != c.valid { 3870 t.Errorf("ValidateAuthenticationPolicy(%v): got(%v) != want(%v): %v\n", c.name, got == nil, c.valid, got) 3871 } 3872 } 3873} 3874 3875func TestValidateAuthorizationPolicy(t *testing.T) { 3876 cases := []struct { 3877 name string 3878 in proto.Message 3879 valid bool 3880 }{ 3881 { 3882 name: "good", 3883 in: &security_beta.AuthorizationPolicy{ 3884 Selector: &api.WorkloadSelector{ 3885 MatchLabels: map[string]string{ 3886 "app": "httpbin", 3887 "version": "v1", 3888 }, 3889 }, 3890 Rules: []*security_beta.Rule{ 3891 { 3892 From: []*security_beta.Rule_From{ 3893 { 3894 Source: &security_beta.Source{ 3895 Principals: []string{"sa1"}, 3896 }, 3897 }, 3898 { 3899 Source: &security_beta.Source{ 3900 Principals: []string{"sa2"}, 3901 }, 3902 }, 3903 }, 3904 To: []*security_beta.Rule_To{ 3905 { 3906 Operation: &security_beta.Operation{ 3907 Methods: []string{"GET"}, 3908 }, 3909 }, 3910 { 3911 Operation: &security_beta.Operation{ 3912 Methods: []string{"POST"}, 3913 }, 3914 }, 3915 }, 3916 When: []*security_beta.Condition{ 3917 { 3918 Key: "source.ip", 3919 Values: []string{"1.2.3.4", "5.6.7.0/24"}, 3920 }, 3921 { 3922 Key: "request.headers[:authority]", 3923 Values: []string{"v1", "v2"}, 3924 }, 3925 }, 3926 }, 3927 }, 3928 }, 3929 valid: true, 3930 }, 3931 { 3932 name: "allow-rules-nil", 3933 in: &security_beta.AuthorizationPolicy{ 3934 Action: security_beta.AuthorizationPolicy_ALLOW, 3935 }, 3936 valid: true, 3937 }, 3938 { 3939 name: "deny-rules-nil", 3940 in: &security_beta.AuthorizationPolicy{ 3941 Action: security_beta.AuthorizationPolicy_DENY, 3942 }, 3943 valid: false, 3944 }, 3945 { 3946 name: "selector-empty-value", 3947 in: &security_beta.AuthorizationPolicy{ 3948 Selector: &api.WorkloadSelector{ 3949 MatchLabels: map[string]string{ 3950 "app": "", 3951 "version": "v1", 3952 }, 3953 }, 3954 }, 3955 valid: true, 3956 }, 3957 { 3958 name: "selector-empty-key", 3959 in: &security_beta.AuthorizationPolicy{ 3960 Selector: &api.WorkloadSelector{ 3961 MatchLabels: map[string]string{ 3962 "app": "httpbin", 3963 "": "v1", 3964 }, 3965 }, 3966 }, 3967 valid: false, 3968 }, 3969 { 3970 name: "selector-wildcard-value", 3971 in: &security_beta.AuthorizationPolicy{ 3972 Selector: &api.WorkloadSelector{ 3973 MatchLabels: map[string]string{ 3974 "app": "httpbin-*", 3975 }, 3976 }, 3977 }, 3978 valid: false, 3979 }, 3980 { 3981 name: "selector-wildcard-key", 3982 in: &security_beta.AuthorizationPolicy{ 3983 Selector: &api.WorkloadSelector{ 3984 MatchLabels: map[string]string{ 3985 "app-*": "httpbin", 3986 }, 3987 }, 3988 }, 3989 valid: false, 3990 }, 3991 { 3992 name: "from-empty", 3993 in: &security_beta.AuthorizationPolicy{ 3994 Rules: []*security_beta.Rule{ 3995 { 3996 From: []*security_beta.Rule_From{}, 3997 }, 3998 }, 3999 }, 4000 valid: false, 4001 }, 4002 { 4003 name: "source-nil", 4004 in: &security_beta.AuthorizationPolicy{ 4005 Rules: []*security_beta.Rule{ 4006 { 4007 From: []*security_beta.Rule_From{ 4008 {}, 4009 }, 4010 }, 4011 }, 4012 }, 4013 valid: false, 4014 }, 4015 { 4016 name: "source-empty", 4017 in: &security_beta.AuthorizationPolicy{ 4018 Rules: []*security_beta.Rule{ 4019 { 4020 From: []*security_beta.Rule_From{ 4021 { 4022 Source: &security_beta.Source{}, 4023 }, 4024 }, 4025 }, 4026 }, 4027 }, 4028 valid: false, 4029 }, 4030 { 4031 name: "to-empty", 4032 in: &security_beta.AuthorizationPolicy{ 4033 Rules: []*security_beta.Rule{ 4034 { 4035 To: []*security_beta.Rule_To{}, 4036 }, 4037 }, 4038 }, 4039 valid: false, 4040 }, 4041 { 4042 name: "operation-nil", 4043 in: &security_beta.AuthorizationPolicy{ 4044 Rules: []*security_beta.Rule{ 4045 { 4046 To: []*security_beta.Rule_To{ 4047 {}, 4048 }, 4049 }, 4050 }, 4051 }, 4052 valid: false, 4053 }, 4054 { 4055 name: "operation-empty", 4056 in: &security_beta.AuthorizationPolicy{ 4057 Rules: []*security_beta.Rule{ 4058 { 4059 To: []*security_beta.Rule_To{ 4060 { 4061 Operation: &security_beta.Operation{}, 4062 }, 4063 }, 4064 }, 4065 }, 4066 }, 4067 valid: false, 4068 }, 4069 { 4070 name: "Principals-empty", 4071 in: &security_beta.AuthorizationPolicy{ 4072 Rules: []*security_beta.Rule{ 4073 { 4074 From: []*security_beta.Rule_From{ 4075 { 4076 Source: &security_beta.Source{ 4077 Principals: []string{"p1", ""}, 4078 }, 4079 }, 4080 }, 4081 }, 4082 }, 4083 }, 4084 valid: false, 4085 }, 4086 { 4087 name: "NotPrincipals-empty", 4088 in: &security_beta.AuthorizationPolicy{ 4089 Rules: []*security_beta.Rule{ 4090 { 4091 From: []*security_beta.Rule_From{ 4092 { 4093 Source: &security_beta.Source{ 4094 NotPrincipals: []string{"p1", ""}, 4095 }, 4096 }, 4097 }, 4098 }, 4099 }, 4100 }, 4101 valid: false, 4102 }, 4103 { 4104 name: "RequestPrincipals-empty", 4105 in: &security_beta.AuthorizationPolicy{ 4106 Rules: []*security_beta.Rule{ 4107 { 4108 From: []*security_beta.Rule_From{ 4109 { 4110 Source: &security_beta.Source{ 4111 RequestPrincipals: []string{"p1", ""}, 4112 }, 4113 }, 4114 }, 4115 }, 4116 }, 4117 }, 4118 valid: false, 4119 }, 4120 { 4121 name: "NotRequestPrincipals-empty", 4122 in: &security_beta.AuthorizationPolicy{ 4123 Rules: []*security_beta.Rule{ 4124 { 4125 From: []*security_beta.Rule_From{ 4126 { 4127 Source: &security_beta.Source{ 4128 NotRequestPrincipals: []string{"p1", ""}, 4129 }, 4130 }, 4131 }, 4132 }, 4133 }, 4134 }, 4135 valid: false, 4136 }, 4137 { 4138 name: "Namespaces-empty", 4139 in: &security_beta.AuthorizationPolicy{ 4140 Rules: []*security_beta.Rule{ 4141 { 4142 From: []*security_beta.Rule_From{ 4143 { 4144 Source: &security_beta.Source{ 4145 Namespaces: []string{"ns", ""}, 4146 }, 4147 }, 4148 }, 4149 }, 4150 }, 4151 }, 4152 valid: false, 4153 }, 4154 { 4155 name: "NotNamespaces-empty", 4156 in: &security_beta.AuthorizationPolicy{ 4157 Rules: []*security_beta.Rule{ 4158 { 4159 From: []*security_beta.Rule_From{ 4160 { 4161 Source: &security_beta.Source{ 4162 NotNamespaces: []string{"ns", ""}, 4163 }, 4164 }, 4165 }, 4166 }, 4167 }, 4168 }, 4169 valid: false, 4170 }, 4171 { 4172 name: "IpBlocks-empty", 4173 in: &security_beta.AuthorizationPolicy{ 4174 Rules: []*security_beta.Rule{ 4175 { 4176 From: []*security_beta.Rule_From{ 4177 { 4178 Source: &security_beta.Source{ 4179 IpBlocks: []string{"1.2.3.4", ""}, 4180 }, 4181 }, 4182 }, 4183 }, 4184 }, 4185 }, 4186 valid: false, 4187 }, 4188 { 4189 name: "NotIpBlocks-empty", 4190 in: &security_beta.AuthorizationPolicy{ 4191 Rules: []*security_beta.Rule{ 4192 { 4193 From: []*security_beta.Rule_From{ 4194 { 4195 Source: &security_beta.Source{ 4196 NotIpBlocks: []string{"1.2.3.4", ""}, 4197 }, 4198 }, 4199 }, 4200 }, 4201 }, 4202 }, 4203 valid: false, 4204 }, 4205 { 4206 name: "Hosts-empty", 4207 in: &security_beta.AuthorizationPolicy{ 4208 Rules: []*security_beta.Rule{ 4209 { 4210 To: []*security_beta.Rule_To{ 4211 { 4212 Operation: &security_beta.Operation{ 4213 Hosts: []string{"host", ""}, 4214 }, 4215 }, 4216 }, 4217 }, 4218 }, 4219 }, 4220 valid: false, 4221 }, 4222 { 4223 name: "NotHosts-empty", 4224 in: &security_beta.AuthorizationPolicy{ 4225 Rules: []*security_beta.Rule{ 4226 { 4227 To: []*security_beta.Rule_To{ 4228 { 4229 Operation: &security_beta.Operation{ 4230 NotHosts: []string{"host", ""}, 4231 }, 4232 }, 4233 }, 4234 }, 4235 }, 4236 }, 4237 valid: false, 4238 }, 4239 { 4240 name: "Ports-empty", 4241 in: &security_beta.AuthorizationPolicy{ 4242 Rules: []*security_beta.Rule{ 4243 { 4244 To: []*security_beta.Rule_To{ 4245 { 4246 Operation: &security_beta.Operation{ 4247 Ports: []string{"80", ""}, 4248 }, 4249 }, 4250 }, 4251 }, 4252 }, 4253 }, 4254 valid: false, 4255 }, 4256 { 4257 name: "NotPorts-empty", 4258 in: &security_beta.AuthorizationPolicy{ 4259 Rules: []*security_beta.Rule{ 4260 { 4261 To: []*security_beta.Rule_To{ 4262 { 4263 Operation: &security_beta.Operation{ 4264 NotPorts: []string{"80", ""}, 4265 }, 4266 }, 4267 }, 4268 }, 4269 }, 4270 }, 4271 valid: false, 4272 }, 4273 { 4274 name: "Methods-empty", 4275 in: &security_beta.AuthorizationPolicy{ 4276 Rules: []*security_beta.Rule{ 4277 { 4278 To: []*security_beta.Rule_To{ 4279 { 4280 Operation: &security_beta.Operation{ 4281 Methods: []string{"GET", ""}, 4282 }, 4283 }, 4284 }, 4285 }, 4286 }, 4287 }, 4288 valid: false, 4289 }, 4290 { 4291 name: "NotMethods-empty", 4292 in: &security_beta.AuthorizationPolicy{ 4293 Rules: []*security_beta.Rule{ 4294 { 4295 To: []*security_beta.Rule_To{ 4296 { 4297 Operation: &security_beta.Operation{ 4298 NotMethods: []string{"GET", ""}, 4299 }, 4300 }, 4301 }, 4302 }, 4303 }, 4304 }, 4305 valid: false, 4306 }, 4307 { 4308 name: "Paths-empty", 4309 in: &security_beta.AuthorizationPolicy{ 4310 Rules: []*security_beta.Rule{ 4311 { 4312 To: []*security_beta.Rule_To{ 4313 { 4314 Operation: &security_beta.Operation{ 4315 Paths: []string{"/path", ""}, 4316 }, 4317 }, 4318 }, 4319 }, 4320 }, 4321 }, 4322 valid: false, 4323 }, 4324 { 4325 name: "NotPaths-empty", 4326 in: &security_beta.AuthorizationPolicy{ 4327 Rules: []*security_beta.Rule{ 4328 { 4329 To: []*security_beta.Rule_To{ 4330 { 4331 Operation: &security_beta.Operation{ 4332 NotPaths: []string{"/path", ""}, 4333 }, 4334 }, 4335 }, 4336 }, 4337 }, 4338 }, 4339 valid: false, 4340 }, 4341 { 4342 name: "value-empty", 4343 in: &security_beta.AuthorizationPolicy{ 4344 Rules: []*security_beta.Rule{ 4345 { 4346 When: []*security_beta.Condition{ 4347 { 4348 Key: "request.headers[:authority]", 4349 Values: []string{"v1", ""}, 4350 }, 4351 }, 4352 }, 4353 }, 4354 }, 4355 valid: false, 4356 }, 4357 { 4358 name: "invalid ip and port", 4359 in: &security_beta.AuthorizationPolicy{ 4360 Rules: []*security_beta.Rule{ 4361 { 4362 From: []*security_beta.Rule_From{ 4363 { 4364 Source: &security_beta.Source{ 4365 IpBlocks: []string{"1.2.3.4", "ip1"}, 4366 NotIpBlocks: []string{"5.6.7.8", "ip2"}, 4367 }, 4368 }, 4369 }, 4370 To: []*security_beta.Rule_To{ 4371 { 4372 Operation: &security_beta.Operation{ 4373 Ports: []string{"80", "port1"}, 4374 NotPorts: []string{"90", "port2"}, 4375 }, 4376 }, 4377 }, 4378 }, 4379 }, 4380 }, 4381 valid: false, 4382 }, 4383 { 4384 name: "condition-key-missing", 4385 in: &security_beta.AuthorizationPolicy{ 4386 Selector: &api.WorkloadSelector{ 4387 MatchLabels: map[string]string{ 4388 "app": "httpbin", 4389 }, 4390 }, 4391 Rules: []*security_beta.Rule{ 4392 { 4393 When: []*security_beta.Condition{ 4394 { 4395 Values: []string{"v1", "v2"}, 4396 }, 4397 }, 4398 }, 4399 }, 4400 }, 4401 valid: false, 4402 }, 4403 { 4404 name: "condition-key-empty", 4405 in: &security_beta.AuthorizationPolicy{ 4406 Selector: &api.WorkloadSelector{ 4407 MatchLabels: map[string]string{ 4408 "app": "httpbin", 4409 }, 4410 }, 4411 Rules: []*security_beta.Rule{ 4412 { 4413 When: []*security_beta.Condition{ 4414 { 4415 Key: "", 4416 Values: []string{"v1", "v2"}, 4417 }, 4418 }, 4419 }, 4420 }, 4421 }, 4422 valid: false, 4423 }, 4424 { 4425 name: "condition-value-missing", 4426 in: &security_beta.AuthorizationPolicy{ 4427 Selector: &api.WorkloadSelector{ 4428 MatchLabels: map[string]string{ 4429 "app": "httpbin", 4430 }, 4431 }, 4432 Rules: []*security_beta.Rule{ 4433 { 4434 When: []*security_beta.Condition{ 4435 { 4436 Key: "source.principal", 4437 }, 4438 }, 4439 }, 4440 }, 4441 }, 4442 valid: false, 4443 }, 4444 { 4445 name: "condition-value-invalid", 4446 in: &security_beta.AuthorizationPolicy{ 4447 Rules: []*security_beta.Rule{ 4448 { 4449 When: []*security_beta.Condition{ 4450 { 4451 Key: "source.ip", 4452 Values: []string{"a.b.c.d"}, 4453 }, 4454 }, 4455 }, 4456 }, 4457 }, 4458 valid: false, 4459 }, 4460 { 4461 name: "condition-notValue-invalid", 4462 in: &security_beta.AuthorizationPolicy{ 4463 Rules: []*security_beta.Rule{ 4464 { 4465 When: []*security_beta.Condition{ 4466 { 4467 Key: "source.ip", 4468 NotValues: []string{"a.b.c.d"}, 4469 }, 4470 }, 4471 }, 4472 }, 4473 }, 4474 valid: false, 4475 }, 4476 { 4477 name: "condition-unknown", 4478 in: &security_beta.AuthorizationPolicy{ 4479 Selector: &api.WorkloadSelector{ 4480 MatchLabels: map[string]string{ 4481 "app": "httpbin", 4482 }, 4483 }, 4484 Rules: []*security_beta.Rule{ 4485 { 4486 When: []*security_beta.Condition{ 4487 { 4488 Key: "key1", 4489 Values: []string{"v1"}, 4490 }, 4491 }, 4492 }, 4493 }, 4494 }, 4495 valid: false, 4496 }, 4497 } 4498 4499 for _, c := range cases { 4500 t.Run(c.name, func(t *testing.T) { 4501 if got := ValidateAuthorizationPolicy("", "", c.in); (got == nil) != c.valid { 4502 t.Errorf("got: %v\nwant: %v", got, c.valid) 4503 } 4504 }) 4505 } 4506} 4507 4508func TestValidateServiceRole(t *testing.T) { 4509 cases := []struct { 4510 name string 4511 in proto.Message 4512 expectErrMsg string 4513 }{ 4514 { 4515 name: "invalid proto", 4516 expectErrMsg: "cannot cast to ServiceRole", 4517 }, 4518 { 4519 name: "empty rules", 4520 in: &rbac.ServiceRole{}, 4521 expectErrMsg: "at least 1 rule must be specified", 4522 }, 4523 { 4524 name: "has both methods and not_methods", 4525 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4526 { 4527 Methods: []string{"GET", "POST"}, 4528 NotMethods: []string{"DELETE"}, 4529 }, 4530 }}, 4531 expectErrMsg: "cannot have both regular and *not* attributes for the same kind (i.e. methods and not_methods) for rule 0", 4532 }, 4533 { 4534 name: "has both ports and not_ports", 4535 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4536 { 4537 Ports: []int32{9080}, 4538 NotPorts: []int32{443}, 4539 }, 4540 }}, 4541 expectErrMsg: "cannot have both regular and *not* attributes for the same kind (i.e. ports and not_ports) for rule 0", 4542 }, 4543 { 4544 name: "has out of range port", 4545 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4546 { 4547 Ports: []int32{9080, -80}, 4548 }, 4549 }}, 4550 expectErrMsg: "at least one port is not in the range of [0, 65535]", 4551 }, 4552 { 4553 name: "has both first-class field and constraints", 4554 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4555 { 4556 Ports: []int32{9080}, 4557 Constraints: []*rbac.AccessRule_Constraint{ 4558 {Key: "destination.port", Values: []string{"80"}}, 4559 }, 4560 }, 4561 }}, 4562 expectErrMsg: "cannot define destination.port for rule 0 because a similar first-class field has been defined", 4563 }, 4564 { 4565 name: "no key in constraint", 4566 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4567 { 4568 Methods: []string{"GET", "POST"}, 4569 Constraints: []*rbac.AccessRule_Constraint{ 4570 {Key: "key", Values: []string{"value"}}, 4571 {Key: "key", Values: []string{"value"}}, 4572 }, 4573 }, 4574 { 4575 Methods: []string{"GET", "POST"}, 4576 Constraints: []*rbac.AccessRule_Constraint{ 4577 {Key: "key", Values: []string{"value"}}, 4578 {Values: []string{"value"}}, 4579 }, 4580 }, 4581 }}, 4582 expectErrMsg: "key cannot be empty for constraint 1 in rule 1", 4583 }, 4584 { 4585 name: "no value in constraint", 4586 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4587 { 4588 Methods: []string{"GET", "POST"}, 4589 Constraints: []*rbac.AccessRule_Constraint{ 4590 {Key: "key", Values: []string{"value"}}, 4591 {Key: "key", Values: []string{"value"}}, 4592 }, 4593 }, 4594 { 4595 Methods: []string{"GET", "POST"}, 4596 Constraints: []*rbac.AccessRule_Constraint{ 4597 {Key: "key", Values: []string{"value"}}, 4598 {Key: "key", Values: []string{}}, 4599 }, 4600 }, 4601 }}, 4602 expectErrMsg: "at least 1 value must be specified for constraint 1 in rule 1", 4603 }, 4604 { 4605 name: "success proto", 4606 in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{ 4607 { 4608 Methods: []string{"GET", "POST"}, 4609 NotHosts: []string{"finances.google.com"}, 4610 Constraints: []*rbac.AccessRule_Constraint{ 4611 {Key: "key", Values: []string{"value"}}, 4612 {Key: "key", Values: []string{"value"}}, 4613 }, 4614 }, 4615 { 4616 Methods: []string{"GET", "POST"}, 4617 Constraints: []*rbac.AccessRule_Constraint{ 4618 {Key: "key", Values: []string{"value"}}, 4619 {Key: "key", Values: []string{"value"}}, 4620 }, 4621 }, 4622 }}, 4623 }, 4624 } 4625 for _, c := range cases { 4626 err := ValidateServiceRole(someName, someNamespace, c.in) 4627 if err == nil { 4628 if len(c.expectErrMsg) != 0 { 4629 t.Errorf("ValidateServiceRole(%v): got nil but want %q\n", c.name, c.expectErrMsg) 4630 } 4631 } else if err.Error() != c.expectErrMsg { 4632 t.Errorf("ValidateServiceRole(%v): got %q but want %q\n", c.name, err.Error(), c.expectErrMsg) 4633 } 4634 } 4635} 4636 4637func TestValidateServiceRoleBinding(t *testing.T) { 4638 cases := []struct { 4639 name string 4640 in proto.Message 4641 expectErrMsg string 4642 }{ 4643 { 4644 name: "invalid proto", 4645 expectErrMsg: "cannot cast to ServiceRoleBinding", 4646 }, 4647 { 4648 name: "no subject", 4649 in: &rbac.ServiceRoleBinding{ 4650 Subjects: []*rbac.Subject{}, 4651 RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}, 4652 }, 4653 expectErrMsg: "at least 1 subject must be specified", 4654 }, 4655 { 4656 name: "no user, group and properties", 4657 in: &rbac.ServiceRoleBinding{ 4658 Subjects: []*rbac.Subject{ 4659 {User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}}, 4660 {User: "", Group: "", Properties: map[string]string{}}, 4661 }, 4662 RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}, 4663 }, 4664 expectErrMsg: "empty subjects are not allowed. Found an empty subject at index 1", 4665 }, 4666 { 4667 name: "no roleRef", 4668 in: &rbac.ServiceRoleBinding{ 4669 Subjects: []*rbac.Subject{ 4670 {User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}}, 4671 {User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}}, 4672 }, 4673 }, 4674 expectErrMsg: "exactly one of `roleRef`, `role`, or `actions` must be specified", 4675 }, 4676 { 4677 name: "incorrect kind", 4678 in: &rbac.ServiceRoleBinding{ 4679 Subjects: []*rbac.Subject{ 4680 {User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}}, 4681 {User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}}, 4682 }, 4683 RoleRef: &rbac.RoleRef{Kind: "ServiceRoleTypo", Name: "ServiceRole001"}, 4684 }, 4685 expectErrMsg: `kind set to "ServiceRoleTypo", currently the only supported value is "ServiceRole"`, 4686 }, 4687 { 4688 name: "no name", 4689 in: &rbac.ServiceRoleBinding{ 4690 Subjects: []*rbac.Subject{ 4691 {User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}}, 4692 {User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}}, 4693 }, 4694 Role: "/", 4695 }, 4696 expectErrMsg: "`role` cannot have an empty ServiceRole name", 4697 }, 4698 { 4699 name: "no name", 4700 in: &rbac.ServiceRoleBinding{ 4701 Subjects: []*rbac.Subject{ 4702 {User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}}, 4703 {User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}}, 4704 }, 4705 RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: ""}, 4706 }, 4707 expectErrMsg: "`name` in `roleRef` cannot be empty", 4708 }, 4709 { 4710 name: "first-class field already exists", 4711 in: &rbac.ServiceRoleBinding{ 4712 Subjects: []*rbac.Subject{ 4713 {Namespaces: []string{"default"}, Properties: map[string]string{"source.namespace": "istio-system"}}, 4714 }, 4715 RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}, 4716 }, 4717 expectErrMsg: "cannot define source.namespace for binding 0 because a similar first-class field has been defined", 4718 }, 4719 { 4720 name: "use * for names", 4721 in: &rbac.ServiceRoleBinding{ 4722 Subjects: []*rbac.Subject{ 4723 {Names: []string{"*"}}, 4724 }, 4725 RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}, 4726 }, 4727 expectErrMsg: "do not use * for names or not_names (in rule 0)", 4728 }, 4729 { 4730 name: "success proto", 4731 in: &rbac.ServiceRoleBinding{ 4732 Subjects: []*rbac.Subject{ 4733 {User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}}, 4734 {User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}}, 4735 }, 4736 RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}, 4737 }, 4738 }, 4739 } 4740 for _, c := range cases { 4741 err := ValidateServiceRoleBinding(someName, someNamespace, c.in) 4742 if err == nil { 4743 if len(c.expectErrMsg) != 0 { 4744 t.Errorf("ValidateServiceRoleBinding(%v): got nil but want %q\n", c.name, c.expectErrMsg) 4745 } 4746 } else if err.Error() != c.expectErrMsg { 4747 t.Errorf("ValidateServiceRoleBinding(%v): got %q but want %q\n", c.name, err.Error(), c.expectErrMsg) 4748 } 4749 } 4750} 4751 4752func TestValidateClusterRbacConfig(t *testing.T) { 4753 cases := []struct { 4754 caseName string 4755 name string 4756 namespace string 4757 in proto.Message 4758 expectErrMsg string 4759 }{ 4760 { 4761 caseName: "invalid proto", 4762 expectErrMsg: "cannot cast to ClusterRbacConfig", 4763 }, 4764 { 4765 caseName: "invalid name", 4766 name: "cluster-rbac-config", 4767 in: &rbac.RbacConfig{Mode: rbac.RbacConfig_ON_WITH_INCLUSION}, 4768 expectErrMsg: fmt.Sprintf("ClusterRbacConfig has invalid name(cluster-rbac-config), name must be %q", 4769 constants.DefaultRbacConfigName), 4770 }, 4771 { 4772 caseName: "success proto", 4773 name: constants.DefaultRbacConfigName, 4774 in: &rbac.RbacConfig{Mode: rbac.RbacConfig_ON}, 4775 }, 4776 { 4777 caseName: "empty exclusion", 4778 name: constants.DefaultRbacConfigName, 4779 in: &rbac.RbacConfig{Mode: rbac.RbacConfig_ON_WITH_EXCLUSION}, 4780 expectErrMsg: "exclusion cannot be null (use 'exclusion: {}' for none)", 4781 }, 4782 { 4783 caseName: "empty inclusion", 4784 name: constants.DefaultRbacConfigName, 4785 in: &rbac.RbacConfig{Mode: rbac.RbacConfig_ON_WITH_INCLUSION}, 4786 expectErrMsg: "inclusion cannot be null (use 'inclusion: {}' for none)", 4787 }, 4788 } 4789 for _, c := range cases { 4790 err := ValidateClusterRbacConfig(c.name, c.namespace, c.in) 4791 if err == nil { 4792 if len(c.expectErrMsg) != 0 { 4793 t.Errorf("ValidateClusterRbacConfig(%v): got nil but want %q\n", c.caseName, c.expectErrMsg) 4794 } 4795 } else if err.Error() != c.expectErrMsg { 4796 t.Errorf("ValidateClusterRbacConfig(%v): got %q but want %q\n", c.caseName, err.Error(), c.expectErrMsg) 4797 } 4798 } 4799} 4800 4801func TestValidateMixerService(t *testing.T) { 4802 cases := []struct { 4803 name string 4804 in *mccpb.IstioService 4805 valid bool 4806 }{ 4807 { 4808 name: "no name and service", 4809 in: &mccpb.IstioService{}, 4810 }, 4811 { 4812 name: "specify both name and service", 4813 in: &mccpb.IstioService{Service: "test-service-service", Name: "test-service-name"}, 4814 }, 4815 { 4816 name: "specify both namespace and service", 4817 in: &mccpb.IstioService{Service: "test-service-service", Namespace: "test-service-namespace"}, 4818 }, 4819 { 4820 name: "specify both domain and service", 4821 in: &mccpb.IstioService{Service: "test-service-service", Domain: "test-service-domain"}, 4822 }, 4823 { 4824 name: "invalid name label", 4825 in: &mccpb.IstioService{Name: strings.Repeat("x", 64)}, 4826 }, 4827 { 4828 name: "invalid namespace label", 4829 in: &mccpb.IstioService{Name: "test-service-name", Namespace: strings.Repeat("x", 64)}, 4830 }, 4831 { 4832 name: "invalid domain or labels", 4833 in: &mccpb.IstioService{Name: "test-service-name", Domain: strings.Repeat("x", 256)}, 4834 }, 4835 { 4836 name: "valid", 4837 in: validService, 4838 valid: true, 4839 }, 4840 } 4841 4842 for _, c := range cases { 4843 t.Run(c.name, func(t *testing.T) { 4844 if got := ValidateMixerService(c.in); (got == nil) != c.valid { 4845 t.Errorf("ValidateMixerService(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got) 4846 } 4847 }) 4848 } 4849} 4850 4851func TestValidateSidecar(t *testing.T) { 4852 tests := []struct { 4853 name string 4854 in *networking.Sidecar 4855 valid bool 4856 }{ 4857 {"empty ingress and egress", &networking.Sidecar{}, false}, 4858 {"default", &networking.Sidecar{ 4859 Egress: []*networking.IstioEgressListener{ 4860 { 4861 Hosts: []string{"*/*"}, 4862 }, 4863 }, 4864 }, true}, 4865 {"import local namespace with wildcard", &networking.Sidecar{ 4866 Egress: []*networking.IstioEgressListener{ 4867 { 4868 Hosts: []string{"./*"}, 4869 }, 4870 }, 4871 }, true}, 4872 {"import local namespace with fqdn", &networking.Sidecar{ 4873 Egress: []*networking.IstioEgressListener{ 4874 { 4875 Hosts: []string{"./foo.com"}, 4876 }, 4877 }, 4878 }, true}, 4879 {"import nothing", &networking.Sidecar{ 4880 Egress: []*networking.IstioEgressListener{ 4881 { 4882 Hosts: []string{"~/*"}, 4883 }, 4884 }, 4885 }, true}, 4886 {"bad egress host 1", &networking.Sidecar{ 4887 Egress: []*networking.IstioEgressListener{ 4888 { 4889 Hosts: []string{"*"}, 4890 }, 4891 }, 4892 }, false}, 4893 {"bad egress host 2", &networking.Sidecar{ 4894 Egress: []*networking.IstioEgressListener{ 4895 { 4896 Hosts: []string{"/"}, 4897 }, 4898 }, 4899 }, false}, 4900 {"empty egress host", &networking.Sidecar{ 4901 Egress: []*networking.IstioEgressListener{ 4902 { 4903 Hosts: []string{}, 4904 }, 4905 }, 4906 }, false}, 4907 {"multiple wildcard egress", &networking.Sidecar{ 4908 Egress: []*networking.IstioEgressListener{ 4909 { 4910 Hosts: []string{ 4911 "*/foo.com", 4912 }, 4913 }, 4914 { 4915 Hosts: []string{ 4916 "ns1/bar.com", 4917 }, 4918 }, 4919 }, 4920 }, false}, 4921 {"wildcard egress not in end", &networking.Sidecar{ 4922 Egress: []*networking.IstioEgressListener{ 4923 { 4924 Hosts: []string{ 4925 "*/foo.com", 4926 }, 4927 }, 4928 { 4929 Port: &networking.Port{ 4930 Protocol: "http", 4931 Number: 8080, 4932 Name: "h8080", 4933 }, 4934 Hosts: []string{ 4935 "ns1/bar.com", 4936 }, 4937 }, 4938 }, 4939 }, false}, 4940 {"invalid Port", &networking.Sidecar{ 4941 Egress: []*networking.IstioEgressListener{ 4942 { 4943 Port: &networking.Port{ 4944 Protocol: "http1", 4945 Number: 1000000, 4946 Name: "", 4947 }, 4948 Hosts: []string{ 4949 "ns1/bar.com", 4950 }, 4951 }, 4952 }, 4953 }, false}, 4954 {"Port without name", &networking.Sidecar{ 4955 Egress: []*networking.IstioEgressListener{ 4956 { 4957 Port: &networking.Port{ 4958 Protocol: "http", 4959 Number: 8080, 4960 }, 4961 Hosts: []string{ 4962 "ns1/bar.com", 4963 }, 4964 }, 4965 }, 4966 }, true}, 4967 {"UDS bind in outbound", &networking.Sidecar{ 4968 Egress: []*networking.IstioEgressListener{ 4969 { 4970 Port: &networking.Port{ 4971 Protocol: "http", 4972 Number: 0, 4973 Name: "uds", 4974 }, 4975 Hosts: []string{ 4976 "ns1/bar.com", 4977 }, 4978 Bind: "unix:///@foo/bar/com", 4979 }, 4980 }, 4981 }, true}, 4982 {"UDS bind in inbound", &networking.Sidecar{ 4983 Ingress: []*networking.IstioIngressListener{ 4984 { 4985 Port: &networking.Port{ 4986 Protocol: "http", 4987 Number: 0, 4988 Name: "uds", 4989 }, 4990 Bind: "unix:///@foo/bar/com", 4991 DefaultEndpoint: "127.0.0.1:9999", 4992 }, 4993 }, 4994 }, false}, 4995 {"UDS bind in outbound 2", &networking.Sidecar{ 4996 Egress: []*networking.IstioEgressListener{ 4997 { 4998 Port: &networking.Port{ 4999 Protocol: "http", 5000 Number: 0, 5001 Name: "uds", 5002 }, 5003 Hosts: []string{ 5004 "ns1/bar.com", 5005 }, 5006 Bind: "unix:///foo/bar/com", 5007 }, 5008 }, 5009 }, true}, 5010 {"invalid bind", &networking.Sidecar{ 5011 Egress: []*networking.IstioEgressListener{ 5012 { 5013 Port: &networking.Port{ 5014 Protocol: "http", 5015 Number: 0, 5016 Name: "uds", 5017 }, 5018 Hosts: []string{ 5019 "ns1/bar.com", 5020 }, 5021 Bind: "foobar:///@foo/bar/com", 5022 }, 5023 }, 5024 }, false}, 5025 {"invalid capture mode with uds bind", &networking.Sidecar{ 5026 Egress: []*networking.IstioEgressListener{ 5027 { 5028 Port: &networking.Port{ 5029 Protocol: "http", 5030 Number: 0, 5031 Name: "uds", 5032 }, 5033 Hosts: []string{ 5034 "ns1/bar.com", 5035 }, 5036 Bind: "unix:///@foo/bar/com", 5037 CaptureMode: networking.CaptureMode_IPTABLES, 5038 }, 5039 }, 5040 }, false}, 5041 {"duplicate UDS bind", &networking.Sidecar{ 5042 Egress: []*networking.IstioEgressListener{ 5043 { 5044 Port: &networking.Port{ 5045 Protocol: "http", 5046 Number: 0, 5047 Name: "uds", 5048 }, 5049 Hosts: []string{ 5050 "ns1/bar.com", 5051 }, 5052 Bind: "unix:///@foo/bar/com", 5053 }, 5054 { 5055 Port: &networking.Port{ 5056 Protocol: "http", 5057 Number: 0, 5058 Name: "uds", 5059 }, 5060 Hosts: []string{ 5061 "ns1/bar.com", 5062 }, 5063 Bind: "unix:///@foo/bar/com", 5064 }, 5065 }, 5066 }, false}, 5067 {"duplicate ports", &networking.Sidecar{ 5068 Egress: []*networking.IstioEgressListener{ 5069 { 5070 Port: &networking.Port{ 5071 Protocol: "http", 5072 Number: 90, 5073 Name: "foo", 5074 }, 5075 Hosts: []string{ 5076 "ns1/bar.com", 5077 }, 5078 }, 5079 { 5080 Port: &networking.Port{ 5081 Protocol: "tcp", 5082 Number: 90, 5083 Name: "tcp", 5084 }, 5085 Hosts: []string{ 5086 "ns2/bar.com", 5087 }, 5088 }, 5089 }, 5090 }, false}, 5091 {"ingress without port", &networking.Sidecar{ 5092 Ingress: []*networking.IstioIngressListener{ 5093 { 5094 DefaultEndpoint: "127.0.0.1:110", 5095 }, 5096 }, 5097 Egress: []*networking.IstioEgressListener{ 5098 { 5099 Hosts: []string{"*/*"}, 5100 }, 5101 }, 5102 }, false}, 5103 {"ingress with duplicate ports", &networking.Sidecar{ 5104 Ingress: []*networking.IstioIngressListener{ 5105 { 5106 Port: &networking.Port{ 5107 Protocol: "http", 5108 Number: 90, 5109 Name: "foo", 5110 }, 5111 DefaultEndpoint: "127.0.0.1:110", 5112 }, 5113 { 5114 Port: &networking.Port{ 5115 Protocol: "tcp", 5116 Number: 90, 5117 Name: "bar", 5118 }, 5119 DefaultEndpoint: "127.0.0.1:110", 5120 }, 5121 }, 5122 Egress: []*networking.IstioEgressListener{ 5123 { 5124 Hosts: []string{"*/*"}, 5125 }, 5126 }, 5127 }, false}, 5128 {"ingress without default endpoint", &networking.Sidecar{ 5129 Ingress: []*networking.IstioIngressListener{ 5130 { 5131 Port: &networking.Port{ 5132 Protocol: "http", 5133 Number: 90, 5134 Name: "foo", 5135 }, 5136 }, 5137 }, 5138 Egress: []*networking.IstioEgressListener{ 5139 { 5140 Hosts: []string{"*/*"}, 5141 }, 5142 }, 5143 }, false}, 5144 {"ingress with invalid default endpoint IP", &networking.Sidecar{ 5145 Ingress: []*networking.IstioIngressListener{ 5146 { 5147 Port: &networking.Port{ 5148 Protocol: "http", 5149 Number: 90, 5150 Name: "foo", 5151 }, 5152 DefaultEndpoint: "1.1.1.1:90", 5153 }, 5154 }, 5155 }, false}, 5156 {"ingress with invalid default endpoint uds", &networking.Sidecar{ 5157 Ingress: []*networking.IstioIngressListener{ 5158 { 5159 Port: &networking.Port{ 5160 Protocol: "http", 5161 Number: 90, 5162 Name: "foo", 5163 }, 5164 DefaultEndpoint: "unix:///", 5165 }, 5166 }, 5167 Egress: []*networking.IstioEgressListener{ 5168 { 5169 Hosts: []string{"*/*"}, 5170 }, 5171 }, 5172 }, false}, 5173 {"ingress with invalid default endpoint port", &networking.Sidecar{ 5174 Ingress: []*networking.IstioIngressListener{ 5175 { 5176 Port: &networking.Port{ 5177 Protocol: "http", 5178 Number: 90, 5179 Name: "foo", 5180 }, 5181 DefaultEndpoint: "127.0.0.1:hi", 5182 }, 5183 }, 5184 Egress: []*networking.IstioEgressListener{ 5185 { 5186 Hosts: []string{"*/*"}, 5187 }, 5188 }, 5189 }, false}, 5190 {"valid ingress and egress", &networking.Sidecar{ 5191 Ingress: []*networking.IstioIngressListener{ 5192 { 5193 Port: &networking.Port{ 5194 Protocol: "http", 5195 Number: 90, 5196 Name: "foo", 5197 }, 5198 DefaultEndpoint: "127.0.0.1:9999", 5199 }, 5200 }, 5201 Egress: []*networking.IstioEgressListener{ 5202 { 5203 Hosts: []string{"*/*"}, 5204 }, 5205 }, 5206 }, true}, 5207 {"valid ingress and empty egress", &networking.Sidecar{ 5208 Ingress: []*networking.IstioIngressListener{ 5209 { 5210 Port: &networking.Port{ 5211 Protocol: "http", 5212 Number: 90, 5213 Name: "foo", 5214 }, 5215 DefaultEndpoint: "127.0.0.1:9999", 5216 }, 5217 }, 5218 }, false}, 5219 {"empty protocol", &networking.Sidecar{ 5220 Ingress: []*networking.IstioIngressListener{ 5221 { 5222 Port: &networking.Port{ 5223 Number: 90, 5224 Name: "foo", 5225 }, 5226 DefaultEndpoint: "127.0.0.1:9999", 5227 }, 5228 }, 5229 Egress: []*networking.IstioEgressListener{ 5230 { 5231 Hosts: []string{"*/*"}, 5232 }, 5233 }, 5234 }, true}, 5235 {"ALLOW_ANY sidecar egress policy with no egress proxy ", &networking.Sidecar{ 5236 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 5237 Mode: networking.OutboundTrafficPolicy_ALLOW_ANY, 5238 }, 5239 Egress: []*networking.IstioEgressListener{ 5240 { 5241 Hosts: []string{"*/*"}, 5242 }, 5243 }, 5244 }, true}, 5245 {"sidecar egress proxy with RESGISTRY_ONLY(default)", &networking.Sidecar{ 5246 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 5247 EgressProxy: &networking.Destination{ 5248 Host: "foo.bar", 5249 Subset: "shiny", 5250 Port: &networking.PortSelector{ 5251 Number: 5000, 5252 }, 5253 }, 5254 }, 5255 Egress: []*networking.IstioEgressListener{ 5256 { 5257 Hosts: []string{"*/*"}, 5258 }, 5259 }, 5260 }, false}, 5261 {"sidecar egress proxy with ALLOW_ANY", &networking.Sidecar{ 5262 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 5263 Mode: networking.OutboundTrafficPolicy_ALLOW_ANY, 5264 EgressProxy: &networking.Destination{ 5265 Host: "foo.bar", 5266 Subset: "shiny", 5267 Port: &networking.PortSelector{ 5268 Number: 5000, 5269 }, 5270 }, 5271 }, 5272 Egress: []*networking.IstioEgressListener{ 5273 { 5274 Hosts: []string{"*/*"}, 5275 }, 5276 }, 5277 }, true}, 5278 {"sidecar egress proxy with ALLOW_ANY, service hostname invalid fqdn", &networking.Sidecar{ 5279 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 5280 Mode: networking.OutboundTrafficPolicy_ALLOW_ANY, 5281 EgressProxy: &networking.Destination{ 5282 Host: "foobar*123", 5283 Subset: "shiny", 5284 Port: &networking.PortSelector{ 5285 Number: 5000, 5286 }, 5287 }, 5288 }, 5289 Egress: []*networking.IstioEgressListener{ 5290 { 5291 Hosts: []string{"*/*"}, 5292 }, 5293 }, 5294 }, false}, 5295 {"sidecar egress proxy(without Port) with ALLOW_ANY", &networking.Sidecar{ 5296 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 5297 Mode: networking.OutboundTrafficPolicy_ALLOW_ANY, 5298 EgressProxy: &networking.Destination{ 5299 Host: "foo.bar", 5300 Subset: "shiny", 5301 }, 5302 }, 5303 Egress: []*networking.IstioEgressListener{ 5304 { 5305 Hosts: []string{"*/*"}, 5306 }, 5307 }, 5308 }, false}, 5309 } 5310 5311 for _, tt := range tests { 5312 t.Run(tt.name, func(t *testing.T) { 5313 err := ValidateSidecar("foo", "bar", tt.in) 5314 if err == nil && !tt.valid { 5315 t.Fatalf("ValidateSidecar(%v) = true, wanted false", tt.in) 5316 } else if err != nil && tt.valid { 5317 t.Fatalf("ValidateSidecar(%v) = %v, wanted true", tt.in, err) 5318 } 5319 }) 5320 } 5321} 5322 5323func TestValidateLocalityLbSetting(t *testing.T) { 5324 cases := []struct { 5325 name string 5326 in *networking.LocalityLoadBalancerSetting 5327 valid bool 5328 }{ 5329 { 5330 name: "valid mesh config without LocalityLoadBalancerSetting", 5331 in: nil, 5332 valid: true, 5333 }, 5334 5335 { 5336 name: "invalid LocalityLoadBalancerSetting_Distribute total weight > 100", 5337 in: &networking.LocalityLoadBalancerSetting{ 5338 Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{ 5339 { 5340 From: "a/b/c", 5341 To: map[string]uint32{ 5342 "a/b/c": 80, 5343 "a/b1": 25, 5344 }, 5345 }, 5346 }, 5347 }, 5348 valid: false, 5349 }, 5350 { 5351 name: "invalid LocalityLoadBalancerSetting_Distribute total weight < 100", 5352 in: &networking.LocalityLoadBalancerSetting{ 5353 Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{ 5354 { 5355 From: "a/b/c", 5356 To: map[string]uint32{ 5357 "a/b/c": 80, 5358 "a/b1": 15, 5359 }, 5360 }, 5361 }, 5362 }, 5363 valid: false, 5364 }, 5365 { 5366 name: "invalid LocalityLoadBalancerSetting_Distribute weight = 0", 5367 in: &networking.LocalityLoadBalancerSetting{ 5368 Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{ 5369 { 5370 From: "a/b/c", 5371 To: map[string]uint32{ 5372 "a/b/c": 0, 5373 "a/b1": 100, 5374 }, 5375 }, 5376 }, 5377 }, 5378 valid: false, 5379 }, 5380 { 5381 name: "invalid LocalityLoadBalancerSetting specify both distribute and failover", 5382 in: &networking.LocalityLoadBalancerSetting{ 5383 Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{ 5384 { 5385 From: "a/b/c", 5386 To: map[string]uint32{ 5387 "a/b/c": 80, 5388 "a/b1": 20, 5389 }, 5390 }, 5391 }, 5392 Failover: []*networking.LocalityLoadBalancerSetting_Failover{ 5393 { 5394 From: "region1", 5395 To: "region2", 5396 }, 5397 }, 5398 }, 5399 valid: false, 5400 }, 5401 5402 { 5403 name: "invalid failover src and dst have same region", 5404 in: &networking.LocalityLoadBalancerSetting{ 5405 Failover: []*networking.LocalityLoadBalancerSetting_Failover{ 5406 { 5407 From: "region1", 5408 To: "region1", 5409 }, 5410 }, 5411 }, 5412 valid: false, 5413 }, 5414 } 5415 5416 for _, c := range cases { 5417 if got := validateLocalityLbSetting(c.in); (got == nil) != c.valid { 5418 t.Errorf("ValidateLocalityLbSetting failed on %v: got valid=%v but wanted valid=%v: %v", 5419 c.name, got == nil, c.valid, got) 5420 } 5421 } 5422} 5423 5424func TestValidateLocalities(t *testing.T) { 5425 cases := []struct { 5426 name string 5427 localities []string 5428 valid bool 5429 }{ 5430 { 5431 name: "multi wildcard locality", 5432 localities: []string{"*/zone/*"}, 5433 valid: false, 5434 }, 5435 { 5436 name: "wildcard not in suffix", 5437 localities: []string{"*/zone"}, 5438 valid: false, 5439 }, 5440 { 5441 name: "explicit wildcard region overlap", 5442 localities: []string{"*", "a/b/c"}, 5443 valid: false, 5444 }, 5445 { 5446 name: "implicit wildcard region overlap", 5447 localities: []string{"a", "a/b/c"}, 5448 valid: false, 5449 }, 5450 { 5451 name: "explicit wildcard zone overlap", 5452 localities: []string{"a/*", "a/b/c"}, 5453 valid: false, 5454 }, 5455 { 5456 name: "implicit wildcard zone overlap", 5457 localities: []string{"a/b", "a/b/c"}, 5458 valid: false, 5459 }, 5460 { 5461 name: "explicit wildcard subzone overlap", 5462 localities: []string{"a/b/*", "a/b/c"}, 5463 valid: false, 5464 }, 5465 { 5466 name: "implicit wildcard subzone overlap", 5467 localities: []string{"a/b", "a/b/c"}, 5468 valid: false, 5469 }, 5470 { 5471 name: "valid localities", 5472 localities: []string{"a1/*", "a2/*", "a3/b3/c3", "a4/b4", "a5/b5/*"}, 5473 valid: true, 5474 }, 5475 } 5476 for _, c := range cases { 5477 t.Run(c.name, func(t *testing.T) { 5478 err := validateLocalities(c.localities) 5479 if !c.valid && err == nil { 5480 t.Errorf("expect invalid localities") 5481 } 5482 5483 if c.valid && err != nil { 5484 t.Errorf("expect valid localities. but got err %v", err) 5485 } 5486 }) 5487 } 5488} 5489 5490func TestValidationIPAddress(t *testing.T) { 5491 tests := []struct { 5492 name string 5493 addr string 5494 ok bool 5495 }{ 5496 { 5497 name: "valid ipv4 address", 5498 addr: "1.1.1.1", 5499 ok: true, 5500 }, 5501 { 5502 name: "invalid ipv4 address", 5503 addr: "1.1.A.1", 5504 ok: false, 5505 }, 5506 { 5507 name: "valid ipv6 subnet", 5508 addr: "2001:1::1", 5509 ok: true, 5510 }, 5511 { 5512 name: "invalid ipv6 address", 5513 addr: "2001:1:G::1", 5514 ok: false, 5515 }, 5516 } 5517 for _, tt := range tests { 5518 if err := ValidateIPAddress(tt.addr); err != nil { 5519 if tt.ok { 5520 t.Errorf("test: \"%s\" expected to succeed but failed with error: %+v", tt.name, err) 5521 } 5522 } else { 5523 if !tt.ok { 5524 t.Errorf("test: \"%s\" expected to fail but succeeded", tt.name) 5525 } 5526 } 5527 } 5528} 5529 5530func TestValidationIPSubnet(t *testing.T) { 5531 tests := []struct { 5532 name string 5533 subnet string 5534 ok bool 5535 }{ 5536 { 5537 name: "valid ipv4 subnet", 5538 subnet: "1.1.1.1/24", 5539 ok: true, 5540 }, 5541 { 5542 name: "invalid ipv4 subnet", 5543 subnet: "1.1.1.1/48", 5544 ok: false, 5545 }, 5546 { 5547 name: "valid ipv6 subnet", 5548 subnet: "2001:1::1/64", 5549 ok: true, 5550 }, 5551 { 5552 name: "invalid ipv6 subnet", 5553 subnet: "2001:1::1/132", 5554 ok: false, 5555 }, 5556 } 5557 5558 for _, tt := range tests { 5559 if err := ValidateIPSubnet(tt.subnet); err != nil { 5560 if tt.ok { 5561 t.Errorf("test: \"%s\" expected to succeed but failed with error: %+v", tt.name, err) 5562 } 5563 } else { 5564 if !tt.ok { 5565 t.Errorf("test: \"%s\" expected to fail but succeeded", tt.name) 5566 } 5567 } 5568 } 5569} 5570 5571func TestValidateRequestAuthentication(t *testing.T) { 5572 cases := []struct { 5573 name string 5574 configName string 5575 in proto.Message 5576 valid bool 5577 }{ 5578 { 5579 name: "empty spec", 5580 configName: constants.DefaultAuthenticationPolicyName, 5581 in: &security_beta.RequestAuthentication{}, 5582 valid: true, 5583 }, 5584 { 5585 name: "another empty spec", 5586 configName: constants.DefaultAuthenticationPolicyName, 5587 in: &security_beta.RequestAuthentication{ 5588 Selector: &api.WorkloadSelector{}, 5589 }, 5590 valid: true, 5591 }, 5592 { 5593 name: "empty spec with non default name", 5594 configName: someName, 5595 in: &security_beta.RequestAuthentication{}, 5596 valid: true, 5597 }, 5598 { 5599 name: "default name with non empty selector", 5600 configName: constants.DefaultAuthenticationPolicyName, 5601 in: &security_beta.RequestAuthentication{ 5602 Selector: &api.WorkloadSelector{ 5603 MatchLabels: map[string]string{ 5604 "app": "httpbin", 5605 }, 5606 }, 5607 }, 5608 valid: true, 5609 }, 5610 { 5611 name: "empty jwt rule", 5612 configName: "foo", 5613 in: &security_beta.RequestAuthentication{ 5614 JwtRules: []*security_beta.JWTRule{ 5615 {}, 5616 }, 5617 }, 5618 valid: false, 5619 }, 5620 { 5621 name: "empty issuer", 5622 configName: "foo", 5623 in: &security_beta.RequestAuthentication{ 5624 JwtRules: []*security_beta.JWTRule{ 5625 { 5626 Issuer: "", 5627 }, 5628 }, 5629 }, 5630 valid: false, 5631 }, 5632 { 5633 name: "bad JwksUri - no protocol", 5634 configName: "foo", 5635 in: &security_beta.RequestAuthentication{ 5636 JwtRules: []*security_beta.JWTRule{ 5637 { 5638 Issuer: "foo.com", 5639 JwksUri: "foo.com", 5640 }, 5641 }, 5642 }, 5643 valid: false, 5644 }, 5645 { 5646 name: "bad JwksUri - invalid port", 5647 configName: "foo", 5648 in: &security_beta.RequestAuthentication{ 5649 JwtRules: []*security_beta.JWTRule{ 5650 { 5651 Issuer: "foo.com", 5652 JwksUri: "https://foo.com:not-a-number", 5653 }, 5654 }, 5655 }, 5656 valid: false, 5657 }, 5658 { 5659 name: "empy value", 5660 configName: "foo", 5661 in: &security_beta.RequestAuthentication{ 5662 Selector: &api.WorkloadSelector{ 5663 MatchLabels: map[string]string{ 5664 "app": "httpbin", 5665 "version": "", 5666 }, 5667 }, 5668 JwtRules: []*security_beta.JWTRule{ 5669 { 5670 Issuer: "foo.com", 5671 JwksUri: "https://foo.com/cert", 5672 }, 5673 }, 5674 }, 5675 valid: true, 5676 }, 5677 { 5678 name: "bad selector - empy key", 5679 configName: "foo", 5680 in: &security_beta.RequestAuthentication{ 5681 Selector: &api.WorkloadSelector{ 5682 MatchLabels: map[string]string{ 5683 "app": "httpbin", 5684 "": "v1", 5685 }, 5686 }, 5687 JwtRules: []*security_beta.JWTRule{ 5688 { 5689 Issuer: "foo.com", 5690 JwksUri: "https://foo.com/cert", 5691 }, 5692 }, 5693 }, 5694 valid: false, 5695 }, 5696 { 5697 name: "bad header location", 5698 configName: constants.DefaultAuthenticationPolicyName, 5699 in: &security_beta.RequestAuthentication{ 5700 JwtRules: []*security_beta.JWTRule{ 5701 { 5702 Issuer: "foo.com", 5703 JwksUri: "https://foo.com", 5704 FromHeaders: []*security_beta.JWTHeader{ 5705 { 5706 Name: "", 5707 Prefix: "Bearer ", 5708 }, 5709 }, 5710 }, 5711 }, 5712 }, 5713 valid: false, 5714 }, 5715 { 5716 name: "bad param location", 5717 configName: constants.DefaultAuthenticationPolicyName, 5718 in: &security_beta.RequestAuthentication{ 5719 JwtRules: []*security_beta.JWTRule{ 5720 { 5721 Issuer: "foo.com", 5722 JwksUri: "https://foo.com", 5723 FromParams: []string{""}, 5724 }, 5725 }, 5726 }, 5727 valid: false, 5728 }, 5729 { 5730 name: "good", 5731 configName: constants.DefaultAuthenticationPolicyName, 5732 in: &security_beta.RequestAuthentication{ 5733 JwtRules: []*security_beta.JWTRule{ 5734 { 5735 Issuer: "foo.com", 5736 JwksUri: "https://foo.com", 5737 FromHeaders: []*security_beta.JWTHeader{ 5738 { 5739 Name: "x-foo", 5740 Prefix: "Bearer ", 5741 }, 5742 }, 5743 }, 5744 }, 5745 }, 5746 valid: true, 5747 }, 5748 } 5749 5750 for _, c := range cases { 5751 t.Run(c.name, func(t *testing.T) { 5752 if got := ValidateRequestAuthentication(c.configName, someNamespace, c.in); (got == nil) != c.valid { 5753 t.Errorf("got(%v) != want(%v)\n", got, c.valid) 5754 } 5755 }) 5756 } 5757} 5758 5759func TestValidatePeerAuthentication(t *testing.T) { 5760 cases := []struct { 5761 name string 5762 configName string 5763 in proto.Message 5764 valid bool 5765 }{ 5766 { 5767 name: "empty spec", 5768 configName: constants.DefaultAuthenticationPolicyName, 5769 in: &security_beta.PeerAuthentication{}, 5770 valid: true, 5771 }, 5772 { 5773 name: "empty mtls", 5774 configName: constants.DefaultAuthenticationPolicyName, 5775 in: &security_beta.PeerAuthentication{ 5776 Selector: &api.WorkloadSelector{}, 5777 }, 5778 valid: true, 5779 }, 5780 { 5781 name: "empty spec with non default name", 5782 configName: someName, 5783 in: &security_beta.PeerAuthentication{}, 5784 valid: true, 5785 }, 5786 { 5787 name: "default name with non empty selector", 5788 configName: constants.DefaultAuthenticationPolicyName, 5789 in: &security_beta.PeerAuthentication{ 5790 Selector: &api.WorkloadSelector{ 5791 MatchLabels: map[string]string{ 5792 "app": "httpbin", 5793 }, 5794 }, 5795 }, 5796 valid: true, 5797 }, 5798 { 5799 name: "empty port level mtls", 5800 configName: "foo", 5801 in: &security_beta.PeerAuthentication{ 5802 Selector: &api.WorkloadSelector{ 5803 MatchLabels: map[string]string{ 5804 "app": "httpbin", 5805 }, 5806 }, 5807 PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{}, 5808 }, 5809 valid: false, 5810 }, 5811 { 5812 name: "empty selector with port level mtls", 5813 configName: constants.DefaultAuthenticationPolicyName, 5814 in: &security_beta.PeerAuthentication{ 5815 PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{ 5816 8080: { 5817 Mode: security_beta.PeerAuthentication_MutualTLS_UNSET, 5818 }, 5819 }, 5820 }, 5821 valid: false, 5822 }, 5823 { 5824 name: "port 0", 5825 configName: "foo", 5826 in: &security_beta.PeerAuthentication{ 5827 PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{ 5828 0: { 5829 Mode: security_beta.PeerAuthentication_MutualTLS_UNSET, 5830 }, 5831 }, 5832 }, 5833 valid: false, 5834 }, 5835 { 5836 name: "unset mode", 5837 configName: constants.DefaultAuthenticationPolicyName, 5838 in: &security_beta.PeerAuthentication{ 5839 Mtls: &security_beta.PeerAuthentication_MutualTLS{ 5840 Mode: security_beta.PeerAuthentication_MutualTLS_UNSET, 5841 }, 5842 }, 5843 valid: true, 5844 }, 5845 { 5846 name: "port level", 5847 configName: "port-level", 5848 in: &security_beta.PeerAuthentication{ 5849 Selector: &api.WorkloadSelector{ 5850 MatchLabels: map[string]string{ 5851 "app": "httpbin", 5852 }, 5853 }, 5854 PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{ 5855 8080: { 5856 Mode: security_beta.PeerAuthentication_MutualTLS_UNSET, 5857 }, 5858 }, 5859 }, 5860 valid: true, 5861 }, 5862 } 5863 5864 for _, c := range cases { 5865 t.Run(c.name, func(t *testing.T) { 5866 if got := ValidatePeerAuthentication(c.configName, someNamespace, c.in); (got == nil) != c.valid { 5867 t.Errorf("got(%v) != want(%v)\n", got, c.valid) 5868 } 5869 }) 5870 } 5871} 5872 5873func TestServiceSettings(t *testing.T) { 5874 cases := []struct { 5875 name string 5876 hosts []string 5877 valid bool 5878 }{ 5879 { 5880 name: "good", 5881 hosts: []string{ 5882 "*.foo.bar", 5883 "bar.baz.svc.cluster.local", 5884 }, 5885 valid: true, 5886 }, 5887 { 5888 name: "bad", 5889 hosts: []string{ 5890 "foo.bar.*", 5891 }, 5892 valid: false, 5893 }, 5894 } 5895 5896 for _, c := range cases { 5897 t.Run(c.name, func(t *testing.T) { 5898 m := meshconfig.MeshConfig{ 5899 ServiceSettings: []*meshconfig.MeshConfig_ServiceSettings{ 5900 { 5901 Hosts: c.hosts, 5902 }, 5903 }, 5904 } 5905 5906 if got := validateServiceSettings(&m); (got == nil) != c.valid { 5907 t.Errorf("got(%v) != want(%v)\n", got, c.valid) 5908 } 5909 }) 5910 } 5911} 5912 5913func TestValidateMeshNetworks(t *testing.T) { 5914 testcases := []struct { 5915 name string 5916 mn *meshconfig.MeshNetworks 5917 valid bool 5918 }{ 5919 { 5920 name: "Empty MeshNetworks", 5921 mn: &meshconfig.MeshNetworks{}, 5922 valid: true, 5923 }, 5924 { 5925 name: "Valid MeshNetworks", 5926 mn: &meshconfig.MeshNetworks{ 5927 Networks: map[string]*meshconfig.Network{ 5928 "n1": { 5929 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 5930 { 5931 Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{ 5932 FromRegistry: "Kubernetes", 5933 }, 5934 }, 5935 }, 5936 Gateways: []*meshconfig.Network_IstioNetworkGateway{ 5937 { 5938 Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{ 5939 RegistryServiceName: "istio-ingressgateway.istio-system.svc.cluster.local", 5940 }, 5941 Port: 80, 5942 }, 5943 }, 5944 }, 5945 "n2": { 5946 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 5947 { 5948 Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{ 5949 FromRegistry: "cluster1", 5950 }, 5951 }, 5952 }, 5953 Gateways: []*meshconfig.Network_IstioNetworkGateway{ 5954 { 5955 Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{ 5956 RegistryServiceName: "istio-ingressgateway.istio-system.svc.cluster.local", 5957 }, 5958 Port: 443, 5959 }, 5960 }, 5961 }, 5962 }, 5963 }, 5964 valid: true, 5965 }, 5966 { 5967 name: "Invalid registry name", 5968 mn: &meshconfig.MeshNetworks{ 5969 Networks: map[string]*meshconfig.Network{ 5970 "n1": { 5971 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 5972 { 5973 Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{ 5974 FromRegistry: "cluster.local", 5975 }, 5976 }, 5977 }, 5978 Gateways: []*meshconfig.Network_IstioNetworkGateway{ 5979 { 5980 Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{ 5981 RegistryServiceName: "istio-ingressgateway.istio-system.svc.cluster.local", 5982 }, 5983 Port: 80, 5984 }, 5985 }, 5986 }, 5987 }, 5988 }, 5989 valid: false, 5990 }, 5991 } 5992 5993 for _, tc := range testcases { 5994 t.Run(tc.name, func(t *testing.T) { 5995 err := ValidateMeshNetworks(tc.mn) 5996 if err != nil && tc.valid { 5997 t.Errorf("error not expected on valid meshnetworks: %v", *tc.mn) 5998 } 5999 if err == nil && !tc.valid { 6000 t.Errorf("expected an error on invalid meshnetworks: %v", *tc.mn) 6001 } 6002 }) 6003 } 6004} 6005