/* * * Copyright 2020 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package xdsclient import ( "regexp" "strings" "testing" v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2" v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3" v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" anypb "github.com/golang/protobuf/ptypes/any" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/grpc/internal/testutils" "google.golang.org/grpc/internal/xds/env" "google.golang.org/grpc/internal/xds/matcher" "google.golang.org/grpc/xds/internal/version" "google.golang.org/protobuf/types/known/wrapperspb" ) const ( clusterName = "clusterName" serviceName = "service" ) var emptyUpdate = ClusterUpdate{ClusterName: clusterName, EnableLRS: false} func (s) TestValidateCluster_Failure(t *testing.T) { tests := []struct { name string cluster *v3clusterpb.Cluster wantUpdate ClusterUpdate wantErr bool }{ { name: "non-supported-cluster-type-static", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, }, LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "non-supported-cluster-type-original-dst", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_ORIGINAL_DST}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, }, LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "no-eds-config", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "no-ads-config-source", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{}, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "non-round-robin-or-ring-hash-lb-policy", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, }, LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "logical-dns-multiple-localities", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS}, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, LoadAssignment: &v3endpointpb.ClusterLoadAssignment{ Endpoints: []*v3endpointpb.LocalityLbEndpoints{ // Invalid if there are more than one locality. {LbEndpoints: nil}, {LbEndpoints: nil}, }, }, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "ring-hash-hash-function-not-xx-hash", cluster: &v3clusterpb.Cluster{ LbPolicy: v3clusterpb.Cluster_RING_HASH, LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{ RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{ HashFunction: v3clusterpb.Cluster_RingHashLbConfig_MURMUR_HASH_2, }, }, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "ring-hash-min-bound-greater-than-max", cluster: &v3clusterpb.Cluster{ LbPolicy: v3clusterpb.Cluster_RING_HASH, LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{ RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{ MinimumRingSize: wrapperspb.UInt64(100), MaximumRingSize: wrapperspb.UInt64(10), }, }, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "ring-hash-min-bound-greater-than-upper-bound", cluster: &v3clusterpb.Cluster{ LbPolicy: v3clusterpb.Cluster_RING_HASH, LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{ RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{ MinimumRingSize: wrapperspb.UInt64(ringHashSizeUpperBound + 1), }, }, }, wantUpdate: emptyUpdate, wantErr: true, }, { name: "ring-hash-max-bound-greater-than-upper-bound", cluster: &v3clusterpb.Cluster{ LbPolicy: v3clusterpb.Cluster_RING_HASH, LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{ RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{ MaximumRingSize: wrapperspb.UInt64(ringHashSizeUpperBound + 1), }, }, }, wantUpdate: emptyUpdate, wantErr: true, }, } oldAggregateAndDNSSupportEnv := env.AggregateAndDNSSupportEnv env.AggregateAndDNSSupportEnv = true defer func() { env.AggregateAndDNSSupportEnv = oldAggregateAndDNSSupportEnv }() oldRingHashSupport := env.RingHashSupport env.RingHashSupport = true defer func() { env.RingHashSupport = oldRingHashSupport }() for _, test := range tests { t.Run(test.name, func(t *testing.T) { if update, err := validateClusterAndConstructClusterUpdate(test.cluster); err == nil { t.Errorf("validateClusterAndConstructClusterUpdate(%+v) = %v, wanted error", test.cluster, update) } }) } } func (s) TestValidateCluster_Success(t *testing.T) { tests := []struct { name string cluster *v3clusterpb.Cluster wantUpdate ClusterUpdate }{ { name: "happy-case-logical-dns", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS}, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, LoadAssignment: &v3endpointpb.ClusterLoadAssignment{ Endpoints: []*v3endpointpb.LocalityLbEndpoints{{ LbEndpoints: []*v3endpointpb.LbEndpoint{{ HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{ Endpoint: &v3endpointpb.Endpoint{ Address: &v3corepb.Address{ Address: &v3corepb.Address_SocketAddress{ SocketAddress: &v3corepb.SocketAddress{ Address: "dns_host", PortSpecifier: &v3corepb.SocketAddress_PortValue{ PortValue: 8080, }, }, }, }, }, }, }}, }}, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, ClusterType: ClusterTypeLogicalDNS, DNSHostName: "dns_host:8080", }, }, { name: "happy-case-aggregate-v3", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_ClusterType{ ClusterType: &v3clusterpb.Cluster_CustomClusterType{ Name: "envoy.clusters.aggregate", TypedConfig: testutils.MarshalAny(&v3aggregateclusterpb.ClusterConfig{ Clusters: []string{"a", "b", "c"}, }), }, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EnableLRS: false, ClusterType: ClusterTypeAggregate, PrioritizedClusterNames: []string{"a", "b", "c"}, }, }, { name: "happy-case-no-service-name-no-lrs", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, }, wantUpdate: emptyUpdate, }, { name: "happy-case-no-lrs", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, }, wantUpdate: ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false}, }, { name: "happiest-case", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, LrsServer: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ Self: &v3corepb.SelfConfigSource{}, }, }, }, wantUpdate: ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true}, }, { name: "happiest-case-with-circuitbreakers", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, CircuitBreakers: &v3clusterpb.CircuitBreakers{ Thresholds: []*v3clusterpb.CircuitBreakers_Thresholds{ { Priority: v3corepb.RoutingPriority_DEFAULT, MaxRequests: wrapperspb.UInt32(512), }, { Priority: v3corepb.RoutingPriority_HIGH, MaxRequests: nil, }, }, }, LrsServer: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ Self: &v3corepb.SelfConfigSource{}, }, }, }, wantUpdate: ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true, MaxRequests: func() *uint32 { i := uint32(512); return &i }()}, }, { name: "happiest-case-with-ring-hash-lb-policy-with-default-config", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_RING_HASH, LrsServer: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ Self: &v3corepb.SelfConfigSource{}, }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true, LBPolicy: &ClusterLBPolicyRingHash{MinimumRingSize: defaultRingHashMinSize, MaximumRingSize: defaultRingHashMaxSize}, }, }, { name: "happiest-case-with-ring-hash-lb-policy-with-none-default-config", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_RING_HASH, LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{ RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{ MinimumRingSize: wrapperspb.UInt64(10), MaximumRingSize: wrapperspb.UInt64(100), }, }, LrsServer: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ Self: &v3corepb.SelfConfigSource{}, }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true, LBPolicy: &ClusterLBPolicyRingHash{MinimumRingSize: 10, MaximumRingSize: 100}, }, }, } oldAggregateAndDNSSupportEnv := env.AggregateAndDNSSupportEnv env.AggregateAndDNSSupportEnv = true defer func() { env.AggregateAndDNSSupportEnv = oldAggregateAndDNSSupportEnv }() oldRingHashSupport := env.RingHashSupport env.RingHashSupport = true defer func() { env.RingHashSupport = oldRingHashSupport }() for _, test := range tests { t.Run(test.name, func(t *testing.T) { update, err := validateClusterAndConstructClusterUpdate(test.cluster) if err != nil { t.Errorf("validateClusterAndConstructClusterUpdate(%+v) failed: %v", test.cluster, err) } if diff := cmp.Diff(update, test.wantUpdate, cmpopts.EquateEmpty()); diff != "" { t.Errorf("validateClusterAndConstructClusterUpdate(%+v) got diff: %v (-got, +want)", test.cluster, diff) } }) } } func (s) TestValidateClusterWithSecurityConfig_EnvVarOff(t *testing.T) { // Turn off the env var protection for client-side security. origClientSideSecurityEnvVar := env.ClientSideSecuritySupport env.ClientSideSecuritySupport = false defer func() { env.ClientSideSecuritySupport = origClientSideSecurityEnvVar }() cluster := &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: "rootInstance", CertificateName: "rootCert", }, }, }, }), }, }, } wantUpdate := ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, } gotUpdate, err := validateClusterAndConstructClusterUpdate(cluster) if err != nil { t.Errorf("validateClusterAndConstructClusterUpdate() failed: %v", err) } if diff := cmp.Diff(wantUpdate, gotUpdate); diff != "" { t.Errorf("validateClusterAndConstructClusterUpdate() returned unexpected diff (-want, got):\n%s", diff) } } func (s) TestSecurityConfigFromCommonTLSContextUsingNewFields_ErrorCases(t *testing.T) { tests := []struct { name string common *v3tlspb.CommonTlsContext server bool wantErr string }{ { name: "unsupported-tls_certificates-field-for-identity-certs", common: &v3tlspb.CommonTlsContext{ TlsCertificates: []*v3tlspb.TlsCertificate{ {CertificateChain: &v3corepb.DataSource{}}, }, }, wantErr: "unsupported field tls_certificates is set in CommonTlsContext message", }, { name: "unsupported-tls_certificates_sds_secret_configs-field-for-identity-certs", common: &v3tlspb.CommonTlsContext{ TlsCertificateSdsSecretConfigs: []*v3tlspb.SdsSecretConfig{ {Name: "sds-secrets-config"}, }, }, wantErr: "unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message", }, { name: "unsupported-sds-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{ ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{ Name: "foo-sds-secret", }, }, }, wantErr: "validation context contains unexpected type", }, { name: "missing-ca_certificate_provider_instance-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{}, }, }, wantErr: "expected field ca_certificate_provider_instance is missing in CommonTlsContext message", }, { name: "unsupported-field-verify_certificate_spki-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, VerifyCertificateSpki: []string{"spki"}, }, }, }, wantErr: "unsupported verify_certificate_spki field in CommonTlsContext message", }, { name: "unsupported-field-verify_certificate_hash-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, VerifyCertificateHash: []string{"hash"}, }, }, }, wantErr: "unsupported verify_certificate_hash field in CommonTlsContext message", }, { name: "unsupported-field-require_signed_certificate_timestamp-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, RequireSignedCertificateTimestamp: &wrapperspb.BoolValue{Value: true}, }, }, }, wantErr: "unsupported require_sugned_ceritificate_timestamp field in CommonTlsContext message", }, { name: "unsupported-field-crl-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, Crl: &v3corepb.DataSource{}, }, }, }, wantErr: "unsupported crl field in CommonTlsContext message", }, { name: "unsupported-field-custom_validator_config-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, CustomValidatorConfig: &v3corepb.TypedExtensionConfig{}, }, }, }, wantErr: "unsupported custom_validator_config field in CommonTlsContext message", }, { name: "invalid-match_subject_alt_names-field-in-validation-context", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}}, }, }, }, }, wantErr: "empty prefix is not allowed in StringMatcher", }, { name: "unsupported-field-matching-subject-alt-names-in-validation-context-of-server", common: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: "rootPluginInstance", CertificateName: "rootCertName", }, MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "sanPrefix"}}, }, }, }, }, server: true, wantErr: "match_subject_alt_names field in validation context is not supported on the server", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { _, err := securityConfigFromCommonTLSContextUsingNewFields(test.common, test.server) if err == nil { t.Fatal("securityConfigFromCommonTLSContextUsingNewFields() succeeded when expected to fail") } if !strings.Contains(err.Error(), test.wantErr) { t.Fatalf("securityConfigFromCommonTLSContextUsingNewFields() returned err: %v, wantErr: %v", err, test.wantErr) } }) } } func (s) TestValidateClusterWithSecurityConfig(t *testing.T) { const ( identityPluginInstance = "identityPluginInstance" identityCertName = "identityCert" rootPluginInstance = "rootPluginInstance" rootCertName = "rootCert" clusterName = "cluster" serviceName = "service" sanExact = "san-exact" sanPrefix = "san-prefix" sanSuffix = "san-suffix" sanRegexBad = "??" sanRegexGood = "san?regex?" sanContains = "san-contains" ) var sanRE = regexp.MustCompile(sanRegexGood) tests := []struct { name string cluster *v3clusterpb.Cluster wantUpdate ClusterUpdate wantErr bool }{ { name: "transport-socket-matches", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocketMatches: []*v3clusterpb.Cluster_TransportSocketMatch{ {Name: "transport-socket-match-1"}, }, }, wantErr: true, }, { name: "transport-socket-unsupported-name", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "unsupported-foo", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: &anypb.Any{ TypeUrl: version.V3UpstreamTLSContextURL, }, }, }, }, wantErr: true, }, { name: "transport-socket-unsupported-typeURL", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: &anypb.Any{ TypeUrl: version.V3HTTPConnManagerURL, }, }, }, }, wantErr: true, }, { name: "transport-socket-unsupported-type", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: &anypb.Any{ TypeUrl: version.V3UpstreamTLSContextURL, Value: []byte{1, 2, 3, 4}, }, }, }, }, wantErr: true, }, { name: "transport-socket-unsupported-tls-params-field", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ TlsParams: &v3tlspb.TlsParameters{}, }, }), }, }, }, wantErr: true, }, { name: "transport-socket-unsupported-custom-handshaker-field", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ CustomHandshaker: &v3corepb.TypedExtensionConfig{}, }, }), }, }, }, wantErr: true, }, { name: "transport-socket-unsupported-validation-context", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{ ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{ Name: "foo-sds-secret", }, }, }, }), }, }, }, wantErr: true, }, { name: "transport-socket-without-validation-context", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{}, }), }, }, }, wantErr: true, }, { name: "empty-prefix-in-matching-SAN", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}}, }, }, ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantErr: true, }, { name: "empty-suffix-in-matching-SAN", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: ""}}, }, }, ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantErr: true, }, { name: "empty-contains-in-matching-SAN", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: ""}}, }, }, ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantErr: true, }, { name: "invalid-regex-in-matching-SAN", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}}, }, }, ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantErr: true, }, { name: "invalid-regex-in-matching-SAN-with-new-fields", cluster: &v3clusterpb.Cluster{ ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ {MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}}, }, CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }, }), }, }, }, wantErr: true, }, { name: "happy-case-with-no-identity-certs-using-deprecated-fields", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }), }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, SecurityCfg: &SecurityConfig{ RootInstanceName: rootPluginInstance, RootCertName: rootCertName, }, }, }, { name: "happy-case-with-no-identity-certs-using-new-fields", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, SecurityCfg: &SecurityConfig{ RootInstanceName: rootPluginInstance, RootCertName: rootCertName, }, }, }, { name: "happy-case-with-validation-context-provider-instance-using-deprecated-fields", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: identityPluginInstance, CertificateName: identityCertName, }, ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }), }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, SecurityCfg: &SecurityConfig{ RootInstanceName: rootPluginInstance, RootCertName: rootCertName, IdentityInstanceName: identityPluginInstance, IdentityCertName: identityCertName, }, }, }, { name: "happy-case-with-validation-context-provider-instance-using-new-fields", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: identityPluginInstance, CertificateName: identityCertName, }, ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{ ValidationContext: &v3tlspb.CertificateValidationContext{ CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, SecurityCfg: &SecurityConfig{ RootInstanceName: rootPluginInstance, RootCertName: rootCertName, IdentityInstanceName: identityPluginInstance, IdentityCertName: identityCertName, }, }, }, { name: "happy-case-with-combined-validation-context-using-deprecated-fields", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: identityPluginInstance, CertificateName: identityCertName, }, ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ { MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact}, IgnoreCase: true, }, {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}}, {MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}}, {MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}}, {MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}}, }, }, ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }), }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, SecurityCfg: &SecurityConfig{ RootInstanceName: rootPluginInstance, RootCertName: rootCertName, IdentityInstanceName: identityPluginInstance, IdentityCertName: identityCertName, SubjectAltNameMatchers: []matcher.StringMatcher{ matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true), matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false), matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false), matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false), matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false), }, }, }, }, { name: "happy-case-with-combined-validation-context-using-new-fields", cluster: &v3clusterpb.Cluster{ Name: clusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: serviceName, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, TransportSocket: &v3corepb.TransportSocket{ Name: "envoy.transport_sockets.tls", ConfigType: &v3corepb.TransportSocket_TypedConfig{ TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{ CommonTlsContext: &v3tlspb.CommonTlsContext{ TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: identityPluginInstance, CertificateName: identityCertName, }, ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{ CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{ DefaultValidationContext: &v3tlspb.CertificateValidationContext{ MatchSubjectAltNames: []*v3matcherpb.StringMatcher{ { MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact}, IgnoreCase: true, }, {MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}}, {MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}}, {MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}}, {MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}}, }, CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{ InstanceName: rootPluginInstance, CertificateName: rootCertName, }, }, }, }, }, }), }, }, }, wantUpdate: ClusterUpdate{ ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false, SecurityCfg: &SecurityConfig{ RootInstanceName: rootPluginInstance, RootCertName: rootCertName, IdentityInstanceName: identityPluginInstance, IdentityCertName: identityCertName, SubjectAltNameMatchers: []matcher.StringMatcher{ matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true), matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false), matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false), matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false), matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false), }, }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { update, err := validateClusterAndConstructClusterUpdate(test.cluster) if (err != nil) != test.wantErr { t.Errorf("validateClusterAndConstructClusterUpdate() returned err %v wantErr %v)", err, test.wantErr) } if diff := cmp.Diff(test.wantUpdate, update, cmpopts.EquateEmpty(), cmp.AllowUnexported(regexp.Regexp{})); diff != "" { t.Errorf("validateClusterAndConstructClusterUpdate() returned unexpected diff (-want, +got):\n%s", diff) } }) } } func (s) TestUnmarshalCluster(t *testing.T) { const ( v2ClusterName = "v2clusterName" v3ClusterName = "v3clusterName" v2Service = "v2Service" v3Service = "v2Service" ) var ( v2ClusterAny = testutils.MarshalAny(&v2xdspb.Cluster{ Name: v2ClusterName, ClusterDiscoveryType: &v2xdspb.Cluster_Type{Type: v2xdspb.Cluster_EDS}, EdsClusterConfig: &v2xdspb.Cluster_EdsClusterConfig{ EdsConfig: &v2corepb.ConfigSource{ ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{ Ads: &v2corepb.AggregatedConfigSource{}, }, }, ServiceName: v2Service, }, LbPolicy: v2xdspb.Cluster_ROUND_ROBIN, LrsServer: &v2corepb.ConfigSource{ ConfigSourceSpecifier: &v2corepb.ConfigSource_Self{ Self: &v2corepb.SelfConfigSource{}, }, }, }) v3ClusterAny = testutils.MarshalAny(&v3clusterpb.Cluster{ Name: v3ClusterName, ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}, EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{ EdsConfig: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{ Ads: &v3corepb.AggregatedConfigSource{}, }, }, ServiceName: v3Service, }, LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN, LrsServer: &v3corepb.ConfigSource{ ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{ Self: &v3corepb.SelfConfigSource{}, }, }, }) ) const testVersion = "test-version-cds" tests := []struct { name string resources []*anypb.Any wantUpdate map[string]ClusterUpdateErrTuple wantMD UpdateMetadata wantErr bool }{ { name: "non-cluster resource type", resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}}, wantMD: UpdateMetadata{ Status: ServiceStatusNACKed, Version: testVersion, ErrState: &UpdateErrorMetadata{ Version: testVersion, Err: cmpopts.AnyError, }, }, wantErr: true, }, { name: "badly marshaled cluster resource", resources: []*anypb.Any{ { TypeUrl: version.V3ClusterURL, Value: []byte{1, 2, 3, 4}, }, }, wantMD: UpdateMetadata{ Status: ServiceStatusNACKed, Version: testVersion, ErrState: &UpdateErrorMetadata{ Version: testVersion, Err: cmpopts.AnyError, }, }, wantErr: true, }, { name: "bad cluster resource", resources: []*anypb.Any{ testutils.MarshalAny(&v3clusterpb.Cluster{ Name: "test", ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC}, }), }, wantUpdate: map[string]ClusterUpdateErrTuple{ "test": {Err: cmpopts.AnyError}, }, wantMD: UpdateMetadata{ Status: ServiceStatusNACKed, Version: testVersion, ErrState: &UpdateErrorMetadata{ Version: testVersion, Err: cmpopts.AnyError, }, }, wantErr: true, }, { name: "v2 cluster", resources: []*anypb.Any{v2ClusterAny}, wantUpdate: map[string]ClusterUpdateErrTuple{ v2ClusterName: {Update: ClusterUpdate{ ClusterName: v2ClusterName, EDSServiceName: v2Service, EnableLRS: true, Raw: v2ClusterAny, }}, }, wantMD: UpdateMetadata{ Status: ServiceStatusACKed, Version: testVersion, }, }, { name: "v3 cluster", resources: []*anypb.Any{v3ClusterAny}, wantUpdate: map[string]ClusterUpdateErrTuple{ v3ClusterName: {Update: ClusterUpdate{ ClusterName: v3ClusterName, EDSServiceName: v3Service, EnableLRS: true, Raw: v3ClusterAny, }}, }, wantMD: UpdateMetadata{ Status: ServiceStatusACKed, Version: testVersion, }, }, { name: "multiple clusters", resources: []*anypb.Any{v2ClusterAny, v3ClusterAny}, wantUpdate: map[string]ClusterUpdateErrTuple{ v2ClusterName: {Update: ClusterUpdate{ ClusterName: v2ClusterName, EDSServiceName: v2Service, EnableLRS: true, Raw: v2ClusterAny, }}, v3ClusterName: {Update: ClusterUpdate{ ClusterName: v3ClusterName, EDSServiceName: v3Service, EnableLRS: true, Raw: v3ClusterAny, }}, }, wantMD: UpdateMetadata{ Status: ServiceStatusACKed, Version: testVersion, }, }, { // To test that unmarshal keeps processing on errors. name: "good and bad clusters", resources: []*anypb.Any{ v2ClusterAny, // bad cluster resource testutils.MarshalAny(&v3clusterpb.Cluster{ Name: "bad", ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC}, }), v3ClusterAny, }, wantUpdate: map[string]ClusterUpdateErrTuple{ v2ClusterName: {Update: ClusterUpdate{ ClusterName: v2ClusterName, EDSServiceName: v2Service, EnableLRS: true, Raw: v2ClusterAny, }}, v3ClusterName: {Update: ClusterUpdate{ ClusterName: v3ClusterName, EDSServiceName: v3Service, EnableLRS: true, Raw: v3ClusterAny, }}, "bad": {Err: cmpopts.AnyError}, }, wantMD: UpdateMetadata{ Status: ServiceStatusNACKed, Version: testVersion, ErrState: &UpdateErrorMetadata{ Version: testVersion, Err: cmpopts.AnyError, }, }, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { opts := &UnmarshalOptions{ Version: testVersion, Resources: test.resources, } update, md, err := UnmarshalCluster(opts) if (err != nil) != test.wantErr { t.Fatalf("UnmarshalCluster(%+v), got err: %v, wantErr: %v", opts, err, test.wantErr) } if diff := cmp.Diff(update, test.wantUpdate, cmpOpts); diff != "" { t.Errorf("got unexpected update, diff (-got +want): %v", diff) } if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" { t.Errorf("got unexpected metadata, diff (-got +want): %v", diff) } }) } }