1/* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19package client 20 21import ( 22 "regexp" 23 "testing" 24 25 v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" 26 v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 27 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 28 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 29 v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 30 v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 31 "github.com/golang/protobuf/proto" 32 anypb "github.com/golang/protobuf/ptypes/any" 33 "github.com/google/go-cmp/cmp" 34 "github.com/google/go-cmp/cmp/cmpopts" 35 xdsinternal "google.golang.org/grpc/internal/xds" 36 "google.golang.org/grpc/internal/xds/env" 37 "google.golang.org/grpc/xds/internal/version" 38 "google.golang.org/protobuf/types/known/wrapperspb" 39) 40 41const ( 42 clusterName = "clusterName" 43 serviceName = "service" 44) 45 46var emptyUpdate = ClusterUpdate{ServiceName: "", EnableLRS: false} 47 48func (s) TestValidateCluster_Failure(t *testing.T) { 49 tests := []struct { 50 name string 51 cluster *v3clusterpb.Cluster 52 wantUpdate ClusterUpdate 53 wantErr bool 54 }{ 55 { 56 name: "non-eds-cluster-type", 57 cluster: &v3clusterpb.Cluster{ 58 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC}, 59 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 60 EdsConfig: &v3corepb.ConfigSource{ 61 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 62 Ads: &v3corepb.AggregatedConfigSource{}, 63 }, 64 }, 65 }, 66 LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST, 67 }, 68 wantUpdate: emptyUpdate, 69 wantErr: true, 70 }, 71 { 72 name: "no-eds-config", 73 cluster: &v3clusterpb.Cluster{ 74 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 75 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 76 }, 77 wantUpdate: emptyUpdate, 78 wantErr: true, 79 }, 80 { 81 name: "no-ads-config-source", 82 cluster: &v3clusterpb.Cluster{ 83 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 84 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{}, 85 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 86 }, 87 wantUpdate: emptyUpdate, 88 wantErr: true, 89 }, 90 { 91 name: "non-round-robin-lb-policy", 92 cluster: &v3clusterpb.Cluster{ 93 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 94 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 95 EdsConfig: &v3corepb.ConfigSource{ 96 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 97 Ads: &v3corepb.AggregatedConfigSource{}, 98 }, 99 }, 100 }, 101 LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST, 102 }, 103 wantUpdate: emptyUpdate, 104 wantErr: true, 105 }, 106 } 107 108 for _, test := range tests { 109 t.Run(test.name, func(t *testing.T) { 110 if update, err := validateCluster(test.cluster); err == nil { 111 t.Errorf("validateCluster(%+v) = %v, wanted error", test.cluster, update) 112 } 113 }) 114 } 115} 116 117func (s) TestValidateCluster_Success(t *testing.T) { 118 tests := []struct { 119 name string 120 cluster *v3clusterpb.Cluster 121 wantUpdate ClusterUpdate 122 }{ 123 { 124 name: "happy-case-no-service-name-no-lrs", 125 cluster: &v3clusterpb.Cluster{ 126 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 127 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 128 EdsConfig: &v3corepb.ConfigSource{ 129 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 130 Ads: &v3corepb.AggregatedConfigSource{}, 131 }, 132 }, 133 }, 134 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 135 }, 136 wantUpdate: emptyUpdate, 137 }, 138 { 139 name: "happy-case-no-lrs", 140 cluster: &v3clusterpb.Cluster{ 141 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 142 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 143 EdsConfig: &v3corepb.ConfigSource{ 144 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 145 Ads: &v3corepb.AggregatedConfigSource{}, 146 }, 147 }, 148 ServiceName: serviceName, 149 }, 150 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 151 }, 152 wantUpdate: ClusterUpdate{ServiceName: serviceName, EnableLRS: false}, 153 }, 154 { 155 name: "happiest-case", 156 cluster: &v3clusterpb.Cluster{ 157 Name: clusterName, 158 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 159 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 160 EdsConfig: &v3corepb.ConfigSource{ 161 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 162 Ads: &v3corepb.AggregatedConfigSource{}, 163 }, 164 }, 165 ServiceName: serviceName, 166 }, 167 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 168 LrsServer: &v3corepb.ConfigSource{ 169 ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ 170 Self: &v3corepb.SelfConfigSource{}, 171 }, 172 }, 173 }, 174 wantUpdate: ClusterUpdate{ServiceName: serviceName, EnableLRS: true}, 175 }, 176 { 177 name: "happiest-case-with-circuitbreakers", 178 cluster: &v3clusterpb.Cluster{ 179 Name: clusterName, 180 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 181 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 182 EdsConfig: &v3corepb.ConfigSource{ 183 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 184 Ads: &v3corepb.AggregatedConfigSource{}, 185 }, 186 }, 187 ServiceName: serviceName, 188 }, 189 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 190 CircuitBreakers: &v3clusterpb.CircuitBreakers{ 191 Thresholds: []*v3clusterpb.CircuitBreakers_Thresholds{ 192 { 193 Priority: v3corepb.RoutingPriority_DEFAULT, 194 MaxRequests: wrapperspb.UInt32(512), 195 }, 196 { 197 Priority: v3corepb.RoutingPriority_HIGH, 198 MaxRequests: nil, 199 }, 200 }, 201 }, 202 LrsServer: &v3corepb.ConfigSource{ 203 ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ 204 Self: &v3corepb.SelfConfigSource{}, 205 }, 206 }, 207 }, 208 wantUpdate: ClusterUpdate{ServiceName: serviceName, EnableLRS: true, MaxRequests: func() *uint32 { i := uint32(512); return &i }()}, 209 }, 210 } 211 212 origCircuitBreakingSupport := env.CircuitBreakingSupport 213 env.CircuitBreakingSupport = true 214 defer func() { env.CircuitBreakingSupport = origCircuitBreakingSupport }() 215 for _, test := range tests { 216 t.Run(test.name, func(t *testing.T) { 217 update, err := validateCluster(test.cluster) 218 if err != nil { 219 t.Errorf("validateCluster(%+v) failed: %v", test.cluster, err) 220 } 221 if !cmp.Equal(update, test.wantUpdate, cmpopts.EquateEmpty()) { 222 t.Errorf("validateCluster(%+v) = %v, want: %v", test.cluster, update, test.wantUpdate) 223 } 224 }) 225 } 226} 227 228func (s) TestValidateClusterWithSecurityConfig_EnvVarOff(t *testing.T) { 229 // Turn off the env var protection for client-side security. 230 origClientSideSecurityEnvVar := env.ClientSideSecuritySupport 231 env.ClientSideSecuritySupport = false 232 defer func() { env.ClientSideSecuritySupport = origClientSideSecurityEnvVar }() 233 234 cluster := &v3clusterpb.Cluster{ 235 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 236 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 237 EdsConfig: &v3corepb.ConfigSource{ 238 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 239 Ads: &v3corepb.AggregatedConfigSource{}, 240 }, 241 }, 242 ServiceName: serviceName, 243 }, 244 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 245 TransportSocket: &v3corepb.TransportSocket{ 246 Name: "envoy.transport_sockets.tls", 247 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 248 TypedConfig: &anypb.Any{ 249 TypeUrl: version.V3UpstreamTLSContextURL, 250 Value: func() []byte { 251 tls := &v3tlspb.UpstreamTlsContext{ 252 CommonTlsContext: &v3tlspb.CommonTlsContext{ 253 ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ 254 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 255 InstanceName: "rootInstance", 256 CertificateName: "rootCert", 257 }, 258 }, 259 }, 260 } 261 mtls, _ := proto.Marshal(tls) 262 return mtls 263 }(), 264 }, 265 }, 266 }, 267 } 268 wantUpdate := ClusterUpdate{ 269 ServiceName: serviceName, 270 EnableLRS: false, 271 } 272 gotUpdate, err := validateCluster(cluster) 273 if err != nil { 274 t.Errorf("validateCluster() failed: %v", err) 275 } 276 if diff := cmp.Diff(wantUpdate, gotUpdate); diff != "" { 277 t.Errorf("validateCluster() returned unexpected diff (-want, got):\n%s", diff) 278 } 279} 280 281func (s) TestValidateClusterWithSecurityConfig(t *testing.T) { 282 // Turn on the env var protection for client-side security. 283 origClientSideSecurityEnvVar := env.ClientSideSecuritySupport 284 env.ClientSideSecuritySupport = true 285 defer func() { env.ClientSideSecuritySupport = origClientSideSecurityEnvVar }() 286 287 const ( 288 identityPluginInstance = "identityPluginInstance" 289 identityCertName = "identityCert" 290 rootPluginInstance = "rootPluginInstance" 291 rootCertName = "rootCert" 292 serviceName = "service" 293 sanExact = "san-exact" 294 sanPrefix = "san-prefix" 295 sanSuffix = "san-suffix" 296 sanRegexBad = "??" 297 sanRegexGood = "san?regex?" 298 sanContains = "san-contains" 299 ) 300 var sanRE = regexp.MustCompile(sanRegexGood) 301 302 tests := []struct { 303 name string 304 cluster *v3clusterpb.Cluster 305 wantUpdate ClusterUpdate 306 wantErr bool 307 }{ 308 { 309 name: "transport-socket-unsupported-name", 310 cluster: &v3clusterpb.Cluster{ 311 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 312 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 313 EdsConfig: &v3corepb.ConfigSource{ 314 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 315 Ads: &v3corepb.AggregatedConfigSource{}, 316 }, 317 }, 318 ServiceName: serviceName, 319 }, 320 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 321 TransportSocket: &v3corepb.TransportSocket{ 322 Name: "unsupported-foo", 323 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 324 TypedConfig: &anypb.Any{ 325 TypeUrl: version.V3UpstreamTLSContextURL, 326 }, 327 }, 328 }, 329 }, 330 wantErr: true, 331 }, 332 { 333 name: "transport-socket-unsupported-typeURL", 334 cluster: &v3clusterpb.Cluster{ 335 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 336 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 337 EdsConfig: &v3corepb.ConfigSource{ 338 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 339 Ads: &v3corepb.AggregatedConfigSource{}, 340 }, 341 }, 342 ServiceName: serviceName, 343 }, 344 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 345 TransportSocket: &v3corepb.TransportSocket{ 346 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 347 TypedConfig: &anypb.Any{ 348 TypeUrl: version.V3HTTPConnManagerURL, 349 }, 350 }, 351 }, 352 }, 353 wantErr: true, 354 }, 355 { 356 name: "transport-socket-unsupported-type", 357 cluster: &v3clusterpb.Cluster{ 358 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 359 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 360 EdsConfig: &v3corepb.ConfigSource{ 361 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 362 Ads: &v3corepb.AggregatedConfigSource{}, 363 }, 364 }, 365 ServiceName: serviceName, 366 }, 367 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 368 TransportSocket: &v3corepb.TransportSocket{ 369 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 370 TypedConfig: &anypb.Any{ 371 TypeUrl: version.V3UpstreamTLSContextURL, 372 Value: []byte{1, 2, 3, 4}, 373 }, 374 }, 375 }, 376 }, 377 wantErr: true, 378 }, 379 { 380 name: "transport-socket-unsupported-validation-context", 381 cluster: &v3clusterpb.Cluster{ 382 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 383 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 384 EdsConfig: &v3corepb.ConfigSource{ 385 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 386 Ads: &v3corepb.AggregatedConfigSource{}, 387 }, 388 }, 389 ServiceName: serviceName, 390 }, 391 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 392 TransportSocket: &v3corepb.TransportSocket{ 393 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 394 TypedConfig: &anypb.Any{ 395 TypeUrl: version.V3UpstreamTLSContextURL, 396 Value: func() []byte { 397 tls := &v3tlspb.UpstreamTlsContext{ 398 CommonTlsContext: &v3tlspb.CommonTlsContext{ 399 ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{ 400 ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{ 401 Name: "foo-sds-secret", 402 }, 403 }, 404 }, 405 } 406 mtls, _ := proto.Marshal(tls) 407 return mtls 408 }(), 409 }, 410 }, 411 }, 412 }, 413 wantErr: true, 414 }, 415 { 416 name: "transport-socket-without-validation-context", 417 cluster: &v3clusterpb.Cluster{ 418 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 419 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 420 EdsConfig: &v3corepb.ConfigSource{ 421 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 422 Ads: &v3corepb.AggregatedConfigSource{}, 423 }, 424 }, 425 ServiceName: serviceName, 426 }, 427 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 428 TransportSocket: &v3corepb.TransportSocket{ 429 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 430 TypedConfig: &anypb.Any{ 431 TypeUrl: version.V3UpstreamTLSContextURL, 432 Value: func() []byte { 433 tls := &v3tlspb.UpstreamTlsContext{ 434 CommonTlsContext: &v3tlspb.CommonTlsContext{}, 435 } 436 mtls, _ := proto.Marshal(tls) 437 return mtls 438 }(), 439 }, 440 }, 441 }, 442 }, 443 wantErr: true, 444 }, 445 { 446 name: "empty-prefix-in-matching-SAN", 447 cluster: &v3clusterpb.Cluster{ 448 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 449 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 450 EdsConfig: &v3corepb.ConfigSource{ 451 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 452 Ads: &v3corepb.AggregatedConfigSource{}, 453 }, 454 }, 455 ServiceName: serviceName, 456 }, 457 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 458 TransportSocket: &v3corepb.TransportSocket{ 459 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 460 TypedConfig: &anypb.Any{ 461 TypeUrl: version.V3UpstreamTLSContextURL, 462 Value: func() []byte { 463 tls := &v3tlspb.UpstreamTlsContext{ 464 CommonTlsContext: &v3tlspb.CommonTlsContext{ 465 ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ 466 CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ 467 DefaultValidationContext: &v3tlspb.CertificateValidationContext{ 468 MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ 469 {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}}, 470 }, 471 }, 472 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 473 InstanceName: rootPluginInstance, 474 CertificateName: rootCertName, 475 }, 476 }, 477 }, 478 }, 479 } 480 mtls, _ := proto.Marshal(tls) 481 return mtls 482 }(), 483 }, 484 }, 485 }, 486 }, 487 wantErr: true, 488 }, 489 { 490 name: "empty-suffix-in-matching-SAN", 491 cluster: &v3clusterpb.Cluster{ 492 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 493 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 494 EdsConfig: &v3corepb.ConfigSource{ 495 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 496 Ads: &v3corepb.AggregatedConfigSource{}, 497 }, 498 }, 499 ServiceName: serviceName, 500 }, 501 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 502 TransportSocket: &v3corepb.TransportSocket{ 503 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 504 TypedConfig: &anypb.Any{ 505 TypeUrl: version.V3UpstreamTLSContextURL, 506 Value: func() []byte { 507 tls := &v3tlspb.UpstreamTlsContext{ 508 CommonTlsContext: &v3tlspb.CommonTlsContext{ 509 ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ 510 CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ 511 DefaultValidationContext: &v3tlspb.CertificateValidationContext{ 512 MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ 513 {MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: ""}}, 514 }, 515 }, 516 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 517 InstanceName: rootPluginInstance, 518 CertificateName: rootCertName, 519 }, 520 }, 521 }, 522 }, 523 } 524 mtls, _ := proto.Marshal(tls) 525 return mtls 526 }(), 527 }, 528 }, 529 }, 530 }, 531 wantErr: true, 532 }, 533 { 534 name: "empty-contains-in-matching-SAN", 535 cluster: &v3clusterpb.Cluster{ 536 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 537 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 538 EdsConfig: &v3corepb.ConfigSource{ 539 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 540 Ads: &v3corepb.AggregatedConfigSource{}, 541 }, 542 }, 543 ServiceName: serviceName, 544 }, 545 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 546 TransportSocket: &v3corepb.TransportSocket{ 547 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 548 TypedConfig: &anypb.Any{ 549 TypeUrl: version.V3UpstreamTLSContextURL, 550 Value: func() []byte { 551 tls := &v3tlspb.UpstreamTlsContext{ 552 CommonTlsContext: &v3tlspb.CommonTlsContext{ 553 ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ 554 CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ 555 DefaultValidationContext: &v3tlspb.CertificateValidationContext{ 556 MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ 557 {MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: ""}}, 558 }, 559 }, 560 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 561 InstanceName: rootPluginInstance, 562 CertificateName: rootCertName, 563 }, 564 }, 565 }, 566 }, 567 } 568 mtls, _ := proto.Marshal(tls) 569 return mtls 570 }(), 571 }, 572 }, 573 }, 574 }, 575 wantErr: true, 576 }, 577 { 578 name: "invalid-regex-in-matching-SAN", 579 cluster: &v3clusterpb.Cluster{ 580 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 581 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 582 EdsConfig: &v3corepb.ConfigSource{ 583 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 584 Ads: &v3corepb.AggregatedConfigSource{}, 585 }, 586 }, 587 ServiceName: serviceName, 588 }, 589 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 590 TransportSocket: &v3corepb.TransportSocket{ 591 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 592 TypedConfig: &anypb.Any{ 593 TypeUrl: version.V3UpstreamTLSContextURL, 594 Value: func() []byte { 595 tls := &v3tlspb.UpstreamTlsContext{ 596 CommonTlsContext: &v3tlspb.CommonTlsContext{ 597 ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ 598 CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ 599 DefaultValidationContext: &v3tlspb.CertificateValidationContext{ 600 MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ 601 {MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}}, 602 }, 603 }, 604 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 605 InstanceName: rootPluginInstance, 606 CertificateName: rootCertName, 607 }, 608 }, 609 }, 610 }, 611 } 612 mtls, _ := proto.Marshal(tls) 613 return mtls 614 }(), 615 }, 616 }, 617 }, 618 }, 619 wantErr: true, 620 }, 621 { 622 name: "happy-case-with-no-identity-certs", 623 cluster: &v3clusterpb.Cluster{ 624 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 625 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 626 EdsConfig: &v3corepb.ConfigSource{ 627 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 628 Ads: &v3corepb.AggregatedConfigSource{}, 629 }, 630 }, 631 ServiceName: serviceName, 632 }, 633 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 634 TransportSocket: &v3corepb.TransportSocket{ 635 Name: "envoy.transport_sockets.tls", 636 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 637 TypedConfig: &anypb.Any{ 638 TypeUrl: version.V3UpstreamTLSContextURL, 639 Value: func() []byte { 640 tls := &v3tlspb.UpstreamTlsContext{ 641 CommonTlsContext: &v3tlspb.CommonTlsContext{ 642 ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ 643 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 644 InstanceName: rootPluginInstance, 645 CertificateName: rootCertName, 646 }, 647 }, 648 }, 649 } 650 mtls, _ := proto.Marshal(tls) 651 return mtls 652 }(), 653 }, 654 }, 655 }, 656 }, 657 wantUpdate: ClusterUpdate{ 658 ServiceName: serviceName, 659 EnableLRS: false, 660 SecurityCfg: &SecurityConfig{ 661 RootInstanceName: rootPluginInstance, 662 RootCertName: rootCertName, 663 }, 664 }, 665 }, 666 { 667 name: "happy-case-with-validation-context-provider-instance", 668 cluster: &v3clusterpb.Cluster{ 669 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 670 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 671 EdsConfig: &v3corepb.ConfigSource{ 672 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 673 Ads: &v3corepb.AggregatedConfigSource{}, 674 }, 675 }, 676 ServiceName: serviceName, 677 }, 678 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 679 TransportSocket: &v3corepb.TransportSocket{ 680 Name: "envoy.transport_sockets.tls", 681 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 682 TypedConfig: &anypb.Any{ 683 TypeUrl: version.V3UpstreamTLSContextURL, 684 Value: func() []byte { 685 tls := &v3tlspb.UpstreamTlsContext{ 686 CommonTlsContext: &v3tlspb.CommonTlsContext{ 687 TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 688 InstanceName: identityPluginInstance, 689 CertificateName: identityCertName, 690 }, 691 ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ 692 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 693 InstanceName: rootPluginInstance, 694 CertificateName: rootCertName, 695 }, 696 }, 697 }, 698 } 699 mtls, _ := proto.Marshal(tls) 700 return mtls 701 }(), 702 }, 703 }, 704 }, 705 }, 706 wantUpdate: ClusterUpdate{ 707 ServiceName: serviceName, 708 EnableLRS: false, 709 SecurityCfg: &SecurityConfig{ 710 RootInstanceName: rootPluginInstance, 711 RootCertName: rootCertName, 712 IdentityInstanceName: identityPluginInstance, 713 IdentityCertName: identityCertName, 714 }, 715 }, 716 }, 717 { 718 name: "happy-case-with-combined-validation-context", 719 cluster: &v3clusterpb.Cluster{ 720 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 721 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 722 EdsConfig: &v3corepb.ConfigSource{ 723 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 724 Ads: &v3corepb.AggregatedConfigSource{}, 725 }, 726 }, 727 ServiceName: serviceName, 728 }, 729 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 730 TransportSocket: &v3corepb.TransportSocket{ 731 Name: "envoy.transport_sockets.tls", 732 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 733 TypedConfig: &anypb.Any{ 734 TypeUrl: version.V3UpstreamTLSContextURL, 735 Value: func() []byte { 736 tls := &v3tlspb.UpstreamTlsContext{ 737 CommonTlsContext: &v3tlspb.CommonTlsContext{ 738 TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 739 InstanceName: identityPluginInstance, 740 CertificateName: identityCertName, 741 }, 742 ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ 743 CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ 744 DefaultValidationContext: &v3tlspb.CertificateValidationContext{ 745 MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ 746 { 747 MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact}, 748 IgnoreCase: true, 749 }, 750 {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}}, 751 {MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}}, 752 {MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}}, 753 {MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}}, 754 }, 755 }, 756 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 757 InstanceName: rootPluginInstance, 758 CertificateName: rootCertName, 759 }, 760 }, 761 }, 762 }, 763 } 764 mtls, _ := proto.Marshal(tls) 765 return mtls 766 }(), 767 }, 768 }, 769 }, 770 }, 771 wantUpdate: ClusterUpdate{ 772 ServiceName: serviceName, 773 EnableLRS: false, 774 SecurityCfg: &SecurityConfig{ 775 RootInstanceName: rootPluginInstance, 776 RootCertName: rootCertName, 777 IdentityInstanceName: identityPluginInstance, 778 IdentityCertName: identityCertName, 779 SubjectAltNameMatchers: []xdsinternal.StringMatcher{ 780 xdsinternal.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true), 781 xdsinternal.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false), 782 xdsinternal.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false), 783 xdsinternal.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false), 784 xdsinternal.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false), 785 }, 786 }, 787 }, 788 }, 789 } 790 791 for _, test := range tests { 792 t.Run(test.name, func(t *testing.T) { 793 update, err := validateCluster(test.cluster) 794 if (err != nil) != test.wantErr { 795 t.Errorf("validateCluster() returned err %v wantErr %v)", err, test.wantErr) 796 } 797 if diff := cmp.Diff(test.wantUpdate, update, cmpopts.EquateEmpty(), cmp.AllowUnexported(regexp.Regexp{})); diff != "" { 798 t.Errorf("validateCluster() returned unexpected diff (-want, +got):\n%s", diff) 799 } 800 }) 801 } 802} 803 804func (s) TestUnmarshalCluster(t *testing.T) { 805 const ( 806 v2ClusterName = "v2clusterName" 807 v3ClusterName = "v3clusterName" 808 v2Service = "v2Service" 809 v3Service = "v2Service" 810 ) 811 var ( 812 v2Cluster = &v2xdspb.Cluster{ 813 Name: v2ClusterName, 814 ClusterDiscoveryType: &v2xdspb.Cluster_Type{Type: v2xdspb.Cluster_EDS}, 815 EdsClusterConfig: &v2xdspb.Cluster_EdsClusterConfig{ 816 EdsConfig: &v2corepb.ConfigSource{ 817 ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{ 818 Ads: &v2corepb.AggregatedConfigSource{}, 819 }, 820 }, 821 ServiceName: v2Service, 822 }, 823 LbPolicy: v2xdspb.Cluster_ROUND_ROBIN, 824 LrsServer: &v2corepb.ConfigSource{ 825 ConfigSourceSpecifier: &v2corepb.ConfigSource_Self{ 826 Self: &v2corepb.SelfConfigSource{}, 827 }, 828 }, 829 } 830 v2ClusterAny = &anypb.Any{ 831 TypeUrl: version.V2ClusterURL, 832 Value: func() []byte { 833 mcl, _ := proto.Marshal(v2Cluster) 834 return mcl 835 }(), 836 } 837 838 v3Cluster = &v3clusterpb.Cluster{ 839 Name: v3ClusterName, 840 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, 841 EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ 842 EdsConfig: &v3corepb.ConfigSource{ 843 ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ 844 Ads: &v3corepb.AggregatedConfigSource{}, 845 }, 846 }, 847 ServiceName: v3Service, 848 }, 849 LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, 850 LrsServer: &v3corepb.ConfigSource{ 851 ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ 852 Self: &v3corepb.SelfConfigSource{}, 853 }, 854 }, 855 } 856 v3ClusterAny = &anypb.Any{ 857 TypeUrl: version.V3ClusterURL, 858 Value: func() []byte { 859 mcl, _ := proto.Marshal(v3Cluster) 860 return mcl 861 }(), 862 } 863 ) 864 const testVersion = "test-version-cds" 865 866 tests := []struct { 867 name string 868 resources []*anypb.Any 869 wantUpdate map[string]ClusterUpdate 870 wantMD UpdateMetadata 871 wantErr bool 872 }{ 873 { 874 name: "non-cluster resource type", 875 resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}}, 876 wantMD: UpdateMetadata{ 877 Status: ServiceStatusNACKed, 878 Version: testVersion, 879 ErrState: &UpdateErrorMetadata{ 880 Version: testVersion, 881 Err: errPlaceHolder, 882 }, 883 }, 884 wantErr: true, 885 }, 886 { 887 name: "badly marshaled cluster resource", 888 resources: []*anypb.Any{ 889 { 890 TypeUrl: version.V3ClusterURL, 891 Value: []byte{1, 2, 3, 4}, 892 }, 893 }, 894 wantMD: UpdateMetadata{ 895 Status: ServiceStatusNACKed, 896 Version: testVersion, 897 ErrState: &UpdateErrorMetadata{ 898 Version: testVersion, 899 Err: errPlaceHolder, 900 }, 901 }, 902 wantErr: true, 903 }, 904 { 905 name: "bad cluster resource", 906 resources: []*anypb.Any{ 907 { 908 TypeUrl: version.V3ClusterURL, 909 Value: func() []byte { 910 cl := &v3clusterpb.Cluster{ 911 Name: "test", 912 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC}, 913 } 914 mcl, _ := proto.Marshal(cl) 915 return mcl 916 }(), 917 }, 918 }, 919 wantUpdate: map[string]ClusterUpdate{"test": {}}, 920 wantMD: UpdateMetadata{ 921 Status: ServiceStatusNACKed, 922 Version: testVersion, 923 ErrState: &UpdateErrorMetadata{ 924 Version: testVersion, 925 Err: errPlaceHolder, 926 }, 927 }, 928 wantErr: true, 929 }, 930 { 931 name: "v2 cluster", 932 resources: []*anypb.Any{v2ClusterAny}, 933 wantUpdate: map[string]ClusterUpdate{ 934 v2ClusterName: { 935 ServiceName: v2Service, EnableLRS: true, 936 Raw: v2ClusterAny, 937 }, 938 }, 939 wantMD: UpdateMetadata{ 940 Status: ServiceStatusACKed, 941 Version: testVersion, 942 }, 943 }, 944 { 945 name: "v3 cluster", 946 resources: []*anypb.Any{v3ClusterAny}, 947 wantUpdate: map[string]ClusterUpdate{ 948 v3ClusterName: { 949 ServiceName: v3Service, EnableLRS: true, 950 Raw: v3ClusterAny, 951 }, 952 }, 953 wantMD: UpdateMetadata{ 954 Status: ServiceStatusACKed, 955 Version: testVersion, 956 }, 957 }, 958 { 959 name: "multiple clusters", 960 resources: []*anypb.Any{v2ClusterAny, v3ClusterAny}, 961 wantUpdate: map[string]ClusterUpdate{ 962 v2ClusterName: { 963 ServiceName: v2Service, EnableLRS: true, 964 Raw: v2ClusterAny, 965 }, 966 v3ClusterName: { 967 ServiceName: v3Service, EnableLRS: true, 968 Raw: v3ClusterAny, 969 }, 970 }, 971 wantMD: UpdateMetadata{ 972 Status: ServiceStatusACKed, 973 Version: testVersion, 974 }, 975 }, 976 { 977 // To test that unmarshal keeps processing on errors. 978 name: "good and bad clusters", 979 resources: []*anypb.Any{ 980 v2ClusterAny, 981 { 982 // bad cluster resource 983 TypeUrl: version.V3ClusterURL, 984 Value: func() []byte { 985 cl := &v3clusterpb.Cluster{ 986 Name: "bad", 987 ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC}, 988 } 989 mcl, _ := proto.Marshal(cl) 990 return mcl 991 }(), 992 }, 993 v3ClusterAny, 994 }, 995 wantUpdate: map[string]ClusterUpdate{ 996 v2ClusterName: { 997 ServiceName: v2Service, EnableLRS: true, 998 Raw: v2ClusterAny, 999 }, 1000 v3ClusterName: { 1001 ServiceName: v3Service, EnableLRS: true, 1002 Raw: v3ClusterAny, 1003 }, 1004 "bad": {}, 1005 }, 1006 wantMD: UpdateMetadata{ 1007 Status: ServiceStatusNACKed, 1008 Version: testVersion, 1009 ErrState: &UpdateErrorMetadata{ 1010 Version: testVersion, 1011 Err: errPlaceHolder, 1012 }, 1013 }, 1014 wantErr: true, 1015 }, 1016 } 1017 for _, test := range tests { 1018 t.Run(test.name, func(t *testing.T) { 1019 update, md, err := UnmarshalCluster(testVersion, test.resources, nil) 1020 if (err != nil) != test.wantErr { 1021 t.Fatalf("UnmarshalCluster(), got err: %v, wantErr: %v", err, test.wantErr) 1022 } 1023 if diff := cmp.Diff(update, test.wantUpdate, cmpOpts); diff != "" { 1024 t.Errorf("got unexpected update, diff (-got +want): %v", diff) 1025 } 1026 if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" { 1027 t.Errorf("got unexpected metadata, diff (-got +want): %v", diff) 1028 } 1029 }) 1030 } 1031} 1032