google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/xdsresource/unmarshal_cds_test.go (about)

     1  /*
     2   *
     3   * Copyright 2021 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  package xdsresource
    19  
    20  import (
    21  	"encoding/json"
    22  	"regexp"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/google/go-cmp/cmp/cmpopts"
    28  	"google.golang.org/grpc/internal/envconfig"
    29  	"google.golang.org/grpc/internal/pretty"
    30  	"google.golang.org/grpc/internal/testutils"
    31  	"google.golang.org/grpc/internal/xds/bootstrap"
    32  	"google.golang.org/grpc/internal/xds/matcher"
    33  	"google.golang.org/grpc/xds/internal"
    34  	"google.golang.org/grpc/xds/internal/xdsclient/xdsresource/version"
    35  
    36  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    37  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    38  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    39  	v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3"
    40  	v3leastrequestpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/least_request/v3"
    41  	v3ringhashpb "github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ring_hash/v3"
    42  	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    43  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    44  	v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
    45  	"google.golang.org/protobuf/types/known/anypb"
    46  	"google.golang.org/protobuf/types/known/durationpb"
    47  	"google.golang.org/protobuf/types/known/structpb"
    48  	"google.golang.org/protobuf/types/known/wrapperspb"
    49  )
    50  
    51  const (
    52  	clusterName = "clusterName"
    53  	serviceName = "service"
    54  )
    55  
    56  func (s) TestValidateCluster_Failure(t *testing.T) {
    57  	tests := []struct {
    58  		name    string
    59  		cluster *v3clusterpb.Cluster
    60  		wantErr bool
    61  	}{
    62  		{
    63  			name: "non-supported-cluster-type-static",
    64  			cluster: &v3clusterpb.Cluster{
    65  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
    66  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
    67  					EdsConfig: &v3corepb.ConfigSource{
    68  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
    69  							Ads: &v3corepb.AggregatedConfigSource{},
    70  						},
    71  					},
    72  				},
    73  				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
    74  			},
    75  			wantErr: true,
    76  		},
    77  		{
    78  			name: "non-supported-cluster-type-original-dst",
    79  			cluster: &v3clusterpb.Cluster{
    80  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_ORIGINAL_DST},
    81  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
    82  					EdsConfig: &v3corepb.ConfigSource{
    83  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
    84  							Ads: &v3corepb.AggregatedConfigSource{},
    85  						},
    86  					},
    87  				},
    88  				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
    89  			},
    90  			wantErr: true,
    91  		},
    92  		{
    93  			name: "no-eds-config",
    94  			cluster: &v3clusterpb.Cluster{
    95  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
    96  				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
    97  			},
    98  			wantErr: true,
    99  		},
   100  		{
   101  			name: "no-ads-config-source",
   102  			cluster: &v3clusterpb.Cluster{
   103  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   104  				EdsClusterConfig:     &v3clusterpb.Cluster_EdsClusterConfig{},
   105  				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
   106  			},
   107  			wantErr: true,
   108  		},
   109  		{
   110  			name: "non-round-robin-or-ring-hash-lb-policy",
   111  			cluster: &v3clusterpb.Cluster{
   112  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   113  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   114  					EdsConfig: &v3corepb.ConfigSource{
   115  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   116  							Ads: &v3corepb.AggregatedConfigSource{},
   117  						},
   118  					},
   119  				},
   120  				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
   121  			},
   122  			wantErr: true,
   123  		},
   124  		{
   125  			name: "logical-dns-multiple-localities",
   126  			cluster: &v3clusterpb.Cluster{
   127  				Name:                 clusterName,
   128  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS},
   129  				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
   130  				LoadAssignment: &v3endpointpb.ClusterLoadAssignment{
   131  					Endpoints: []*v3endpointpb.LocalityLbEndpoints{
   132  						// Invalid if there are more than one locality.
   133  						{LbEndpoints: nil},
   134  						{LbEndpoints: nil},
   135  					},
   136  				},
   137  			},
   138  			wantErr: true,
   139  		},
   140  		{
   141  			name: "ring-hash-hash-function-not-xx-hash",
   142  			cluster: &v3clusterpb.Cluster{
   143  				LbPolicy: v3clusterpb.Cluster_RING_HASH,
   144  				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
   145  					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
   146  						HashFunction: v3clusterpb.Cluster_RingHashLbConfig_MURMUR_HASH_2,
   147  					},
   148  				},
   149  			},
   150  			wantErr: true,
   151  		},
   152  		{
   153  			name: "least-request-choice-count-less-than-two",
   154  			cluster: &v3clusterpb.Cluster{
   155  				LbPolicy: v3clusterpb.Cluster_RING_HASH,
   156  				LbConfig: &v3clusterpb.Cluster_LeastRequestLbConfig_{
   157  					LeastRequestLbConfig: &v3clusterpb.Cluster_LeastRequestLbConfig{
   158  						ChoiceCount: wrapperspb.UInt32(1),
   159  					},
   160  				},
   161  			},
   162  			wantErr: true,
   163  		},
   164  		{
   165  			name: "ring-hash-max-bound-greater-than-upper-bound",
   166  			cluster: &v3clusterpb.Cluster{
   167  				LbPolicy: v3clusterpb.Cluster_RING_HASH,
   168  				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
   169  					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
   170  						MaximumRingSize: wrapperspb.UInt64(ringHashSizeUpperBound + 1),
   171  					},
   172  				},
   173  			},
   174  			wantErr: true,
   175  		},
   176  		{
   177  			name: "ring-hash-max-bound-greater-than-upper-bound-load-balancing-policy",
   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  				LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
   190  					Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
   191  						{
   192  							TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
   193  								TypedConfig: testutils.MarshalAny(t, &v3ringhashpb.RingHash{
   194  									HashFunction:    v3ringhashpb.RingHash_XX_HASH,
   195  									MinimumRingSize: wrapperspb.UInt64(10),
   196  									MaximumRingSize: wrapperspb.UInt64(ringHashSizeUpperBound + 1),
   197  								}),
   198  							},
   199  						},
   200  					},
   201  				},
   202  			},
   203  			wantErr: true,
   204  		},
   205  		{
   206  			name: "least-request-unsupported-in-converter-since-env-var-unset",
   207  			cluster: &v3clusterpb.Cluster{
   208  				Name:                 clusterName,
   209  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   210  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   211  					EdsConfig: &v3corepb.ConfigSource{
   212  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   213  							Ads: &v3corepb.AggregatedConfigSource{},
   214  						},
   215  					},
   216  					ServiceName: serviceName,
   217  				},
   218  				LoadBalancingPolicy: &v3clusterpb.LoadBalancingPolicy{
   219  					Policies: []*v3clusterpb.LoadBalancingPolicy_Policy{
   220  						{
   221  							TypedExtensionConfig: &v3corepb.TypedExtensionConfig{
   222  								TypedConfig: testutils.MarshalAny(t, &v3leastrequestpb.LeastRequest{}),
   223  							},
   224  						},
   225  					},
   226  				},
   227  			},
   228  			wantErr: true,
   229  		},
   230  		{
   231  			name: "aggregate-nil-clusters",
   232  			cluster: &v3clusterpb.Cluster{
   233  				Name: clusterName,
   234  				ClusterDiscoveryType: &v3clusterpb.Cluster_ClusterType{
   235  					ClusterType: &v3clusterpb.Cluster_CustomClusterType{
   236  						Name:        "envoy.clusters.aggregate",
   237  						TypedConfig: testutils.MarshalAny(t, &v3aggregateclusterpb.ClusterConfig{}),
   238  					},
   239  				},
   240  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   241  			},
   242  			wantErr: true,
   243  		},
   244  		{
   245  			name: "aggregate-empty-clusters",
   246  			cluster: &v3clusterpb.Cluster{
   247  				Name: clusterName,
   248  				ClusterDiscoveryType: &v3clusterpb.Cluster_ClusterType{
   249  					ClusterType: &v3clusterpb.Cluster_CustomClusterType{
   250  						Name: "envoy.clusters.aggregate",
   251  						TypedConfig: testutils.MarshalAny(t, &v3aggregateclusterpb.ClusterConfig{
   252  							Clusters: []string{},
   253  						}),
   254  					},
   255  				},
   256  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   257  			},
   258  			wantErr: true,
   259  		},
   260  	}
   261  
   262  	for _, test := range tests {
   263  		t.Run(test.name, func(t *testing.T) {
   264  			if update, err := validateClusterAndConstructClusterUpdate(test.cluster, nil); err == nil {
   265  				t.Errorf("validateClusterAndConstructClusterUpdate(%+v) = %v, wanted error", test.cluster, update)
   266  			}
   267  		})
   268  	}
   269  }
   270  
   271  func (s) TestSecurityConfigFromCommonTLSContextUsingNewFields_ErrorCases(t *testing.T) {
   272  	tests := []struct {
   273  		name                      string
   274  		common                    *v3tlspb.CommonTlsContext
   275  		server                    bool
   276  		wantErr                   string
   277  		enableSystemRootCertsFlag bool
   278  	}{
   279  		{
   280  			name: "unsupported-tls_certificates-field-for-identity-certs",
   281  			common: &v3tlspb.CommonTlsContext{
   282  				TlsCertificates: []*v3tlspb.TlsCertificate{
   283  					{CertificateChain: &v3corepb.DataSource{}},
   284  				},
   285  			},
   286  			wantErr: "unsupported field tls_certificates is set in CommonTlsContext message",
   287  		},
   288  		{
   289  			name: "unsupported-tls_certificates_sds_secret_configs-field-for-identity-certs",
   290  			common: &v3tlspb.CommonTlsContext{
   291  				TlsCertificateSdsSecretConfigs: []*v3tlspb.SdsSecretConfig{
   292  					{Name: "sds-secrets-config"},
   293  				},
   294  			},
   295  			wantErr: "unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message",
   296  		},
   297  		{
   298  			name: "unsupported-sds-validation-context",
   299  			common: &v3tlspb.CommonTlsContext{
   300  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{
   301  					ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{
   302  						Name: "foo-sds-secret",
   303  					},
   304  				},
   305  			},
   306  			wantErr: "validation context contains unexpected type",
   307  		},
   308  		{
   309  			name: "missing-ca_certificate_provider_instance-in-validation-context",
   310  			common: &v3tlspb.CommonTlsContext{
   311  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   312  					ValidationContext: &v3tlspb.CertificateValidationContext{},
   313  				},
   314  			},
   315  			wantErr: "expected field ca_certificate_provider_instance is missing in CommonTlsContext message",
   316  		},
   317  		{
   318  			name: "unsupported-field-verify_certificate_spki-in-validation-context",
   319  			common: &v3tlspb.CommonTlsContext{
   320  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   321  					ValidationContext: &v3tlspb.CertificateValidationContext{
   322  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   323  							InstanceName:    "rootPluginInstance",
   324  							CertificateName: "rootCertName",
   325  						},
   326  						VerifyCertificateSpki: []string{"spki"},
   327  					},
   328  				},
   329  			},
   330  			wantErr: "unsupported verify_certificate_spki field in CommonTlsContext message",
   331  		},
   332  		{
   333  			name: "unsupported-field-verify_certificate_hash-in-validation-context",
   334  			common: &v3tlspb.CommonTlsContext{
   335  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   336  					ValidationContext: &v3tlspb.CertificateValidationContext{
   337  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   338  							InstanceName:    "rootPluginInstance",
   339  							CertificateName: "rootCertName",
   340  						},
   341  						VerifyCertificateHash: []string{"hash"},
   342  					},
   343  				},
   344  			},
   345  			wantErr: "unsupported verify_certificate_hash field in CommonTlsContext message",
   346  		},
   347  		{
   348  			name: "unsupported-field-require_signed_certificate_timestamp-in-validation-context",
   349  			common: &v3tlspb.CommonTlsContext{
   350  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   351  					ValidationContext: &v3tlspb.CertificateValidationContext{
   352  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   353  							InstanceName:    "rootPluginInstance",
   354  							CertificateName: "rootCertName",
   355  						},
   356  						RequireSignedCertificateTimestamp: &wrapperspb.BoolValue{Value: true},
   357  					},
   358  				},
   359  			},
   360  			wantErr: "unsupported require_signed_certificate_timestamp field in CommonTlsContext message",
   361  		},
   362  		{
   363  			name: "unsupported-field-crl-in-validation-context",
   364  			common: &v3tlspb.CommonTlsContext{
   365  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   366  					ValidationContext: &v3tlspb.CertificateValidationContext{
   367  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   368  							InstanceName:    "rootPluginInstance",
   369  							CertificateName: "rootCertName",
   370  						},
   371  						Crl: &v3corepb.DataSource{},
   372  					},
   373  				},
   374  			},
   375  			wantErr: "unsupported crl field in CommonTlsContext message",
   376  		},
   377  		{
   378  			name: "unsupported-field-custom_validator_config-in-validation-context",
   379  			common: &v3tlspb.CommonTlsContext{
   380  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   381  					ValidationContext: &v3tlspb.CertificateValidationContext{
   382  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   383  							InstanceName:    "rootPluginInstance",
   384  							CertificateName: "rootCertName",
   385  						},
   386  						CustomValidatorConfig: &v3corepb.TypedExtensionConfig{},
   387  					},
   388  				},
   389  			},
   390  			wantErr: "unsupported custom_validator_config field in CommonTlsContext message",
   391  		},
   392  		{
   393  			name: "invalid-match_subject_alt_names-field-in-validation-context",
   394  			common: &v3tlspb.CommonTlsContext{
   395  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   396  					ValidationContext: &v3tlspb.CertificateValidationContext{
   397  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   398  							InstanceName:    "rootPluginInstance",
   399  							CertificateName: "rootCertName",
   400  						},
   401  						MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   402  							{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}},
   403  						},
   404  					},
   405  				},
   406  			},
   407  			wantErr: "empty prefix is not allowed in StringMatcher",
   408  		},
   409  		{
   410  			name: "unsupported-field-matching-subject-alt-names-in-validation-context-of-server",
   411  			common: &v3tlspb.CommonTlsContext{
   412  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   413  					ValidationContext: &v3tlspb.CertificateValidationContext{
   414  						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   415  							InstanceName:    "rootPluginInstance",
   416  							CertificateName: "rootCertName",
   417  						},
   418  						MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   419  							{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "sanPrefix"}},
   420  						},
   421  					},
   422  				},
   423  			},
   424  			server:  true,
   425  			wantErr: "match_subject_alt_names field in validation context is not supported on the server",
   426  		},
   427  		{
   428  			name:                      "client-missing-root-cert-provider-and-use-system-certs-fields",
   429  			enableSystemRootCertsFlag: true,
   430  			common: &v3tlspb.CommonTlsContext{
   431  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   432  					ValidationContext: &v3tlspb.CertificateValidationContext{},
   433  				},
   434  			},
   435  			wantErr: "expected fields ca_certificate_provider_instance and system_root_certs are missing",
   436  		},
   437  		{
   438  			name:                      "server-missing-root-cert-provider-and-use-system-certs-fields",
   439  			enableSystemRootCertsFlag: true,
   440  			common: &v3tlspb.CommonTlsContext{
   441  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   442  					ValidationContext: &v3tlspb.CertificateValidationContext{},
   443  				},
   444  			},
   445  			server:  true,
   446  			wantErr: "expected field ca_certificate_provider_instance is missing",
   447  		},
   448  		{
   449  			name: "client-missing-root-cert-provider-and-use-system-certs-fields-env-var-unset",
   450  			common: &v3tlspb.CommonTlsContext{
   451  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   452  					ValidationContext: &v3tlspb.CertificateValidationContext{},
   453  				},
   454  			},
   455  			wantErr: "expected field ca_certificate_provider_instance is missing",
   456  		},
   457  		{
   458  			name: "server-missing-root-cert-provider-and-use-system-certs-fields-env-var-unset",
   459  			common: &v3tlspb.CommonTlsContext{
   460  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   461  					ValidationContext: &v3tlspb.CertificateValidationContext{},
   462  				},
   463  			},
   464  			server:  true,
   465  			wantErr: "expected field ca_certificate_provider_instance is missing",
   466  		},
   467  		{
   468  			name:                      "server-missing-root-cert-provider-and-set-use-system-certs-fields",
   469  			enableSystemRootCertsFlag: true,
   470  			common: &v3tlspb.CommonTlsContext{
   471  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   472  					ValidationContext: &v3tlspb.CertificateValidationContext{
   473  						SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
   474  					},
   475  				},
   476  			},
   477  			server:  true,
   478  			wantErr: "expected field ca_certificate_provider_instance is missing and unexpected field system_root_certs is set",
   479  		},
   480  	}
   481  
   482  	for _, test := range tests {
   483  		t.Run(test.name, func(t *testing.T) {
   484  			origFlag := envconfig.XDSSystemRootCertsEnabled
   485  			defer func() {
   486  				envconfig.XDSSystemRootCertsEnabled = origFlag
   487  			}()
   488  			envconfig.XDSSystemRootCertsEnabled = test.enableSystemRootCertsFlag
   489  			_, err := securityConfigFromCommonTLSContextUsingNewFields(test.common, test.server)
   490  			if err == nil {
   491  				t.Fatal("securityConfigFromCommonTLSContextUsingNewFields() succeeded when expected to fail")
   492  			}
   493  			if !strings.Contains(err.Error(), test.wantErr) {
   494  				t.Fatalf("securityConfigFromCommonTLSContextUsingNewFields() returned err: %v, wantErr: %v", err, test.wantErr)
   495  			}
   496  		})
   497  	}
   498  }
   499  
   500  func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
   501  	const (
   502  		identityPluginInstance = "identityPluginInstance"
   503  		identityCertName       = "identityCert"
   504  		rootPluginInstance     = "rootPluginInstance"
   505  		rootCertName           = "rootCert"
   506  		clusterName            = "cluster"
   507  		serviceName            = "service"
   508  		sanExact               = "san-exact"
   509  		sanPrefix              = "san-prefix"
   510  		sanSuffix              = "san-suffix"
   511  		sanRegexBad            = "??"
   512  		sanRegexGood           = "san?regex?"
   513  		sanContains            = "san-contains"
   514  	)
   515  	var sanRE = regexp.MustCompile(sanRegexGood)
   516  
   517  	tests := []struct {
   518  		name                      string
   519  		cluster                   *v3clusterpb.Cluster
   520  		wantUpdate                ClusterUpdate
   521  		wantErr                   bool
   522  		enableSystemRootCertsFlag bool
   523  	}{
   524  		{
   525  			name: "transport-socket-matches",
   526  			cluster: &v3clusterpb.Cluster{
   527  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   528  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   529  					EdsConfig: &v3corepb.ConfigSource{
   530  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   531  							Ads: &v3corepb.AggregatedConfigSource{},
   532  						},
   533  					},
   534  					ServiceName: serviceName,
   535  				},
   536  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   537  				TransportSocketMatches: []*v3clusterpb.Cluster_TransportSocketMatch{
   538  					{Name: "transport-socket-match-1"},
   539  				},
   540  			},
   541  			wantErr: true,
   542  		},
   543  		{
   544  			name: "transport-socket-unsupported-name",
   545  			cluster: &v3clusterpb.Cluster{
   546  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   547  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   548  					EdsConfig: &v3corepb.ConfigSource{
   549  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   550  							Ads: &v3corepb.AggregatedConfigSource{},
   551  						},
   552  					},
   553  					ServiceName: serviceName,
   554  				},
   555  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   556  				TransportSocket: &v3corepb.TransportSocket{
   557  					Name: "unsupported-foo",
   558  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   559  						TypedConfig: &anypb.Any{
   560  							TypeUrl: version.V3UpstreamTLSContextURL,
   561  						},
   562  					},
   563  				},
   564  			},
   565  			wantErr: true,
   566  		},
   567  		{
   568  			name: "transport-socket-unsupported-typeURL",
   569  			cluster: &v3clusterpb.Cluster{
   570  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   571  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   572  					EdsConfig: &v3corepb.ConfigSource{
   573  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   574  							Ads: &v3corepb.AggregatedConfigSource{},
   575  						},
   576  					},
   577  					ServiceName: serviceName,
   578  				},
   579  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   580  				TransportSocket: &v3corepb.TransportSocket{
   581  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   582  						TypedConfig: &anypb.Any{
   583  							TypeUrl: version.V3HTTPConnManagerURL,
   584  						},
   585  					},
   586  				},
   587  			},
   588  			wantErr: true,
   589  		},
   590  		{
   591  			name: "transport-socket-unsupported-type",
   592  			cluster: &v3clusterpb.Cluster{
   593  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   594  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   595  					EdsConfig: &v3corepb.ConfigSource{
   596  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   597  							Ads: &v3corepb.AggregatedConfigSource{},
   598  						},
   599  					},
   600  					ServiceName: serviceName,
   601  				},
   602  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   603  				TransportSocket: &v3corepb.TransportSocket{
   604  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   605  						TypedConfig: &anypb.Any{
   606  							TypeUrl: version.V3UpstreamTLSContextURL,
   607  							Value:   []byte{1, 2, 3, 4},
   608  						},
   609  					},
   610  				},
   611  			},
   612  			wantErr: true,
   613  		},
   614  		{
   615  			name: "transport-socket-unsupported-tls-params-field",
   616  			cluster: &v3clusterpb.Cluster{
   617  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   618  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   619  					EdsConfig: &v3corepb.ConfigSource{
   620  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   621  							Ads: &v3corepb.AggregatedConfigSource{},
   622  						},
   623  					},
   624  					ServiceName: serviceName,
   625  				},
   626  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   627  				TransportSocket: &v3corepb.TransportSocket{
   628  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   629  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   630  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   631  								TlsParams: &v3tlspb.TlsParameters{},
   632  							},
   633  						}),
   634  					},
   635  				},
   636  			},
   637  			wantErr: true,
   638  		},
   639  		{
   640  			name: "transport-socket-unsupported-custom-handshaker-field",
   641  			cluster: &v3clusterpb.Cluster{
   642  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   643  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   644  					EdsConfig: &v3corepb.ConfigSource{
   645  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   646  							Ads: &v3corepb.AggregatedConfigSource{},
   647  						},
   648  					},
   649  					ServiceName: serviceName,
   650  				},
   651  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   652  				TransportSocket: &v3corepb.TransportSocket{
   653  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   654  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   655  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   656  								CustomHandshaker: &v3corepb.TypedExtensionConfig{},
   657  							},
   658  						}),
   659  					},
   660  				},
   661  			},
   662  			wantErr: true,
   663  		},
   664  		{
   665  			name: "transport-socket-unsupported-validation-context",
   666  			cluster: &v3clusterpb.Cluster{
   667  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   668  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   669  					EdsConfig: &v3corepb.ConfigSource{
   670  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   671  							Ads: &v3corepb.AggregatedConfigSource{},
   672  						},
   673  					},
   674  					ServiceName: serviceName,
   675  				},
   676  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   677  				TransportSocket: &v3corepb.TransportSocket{
   678  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   679  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   680  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   681  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{
   682  									ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{
   683  										Name: "foo-sds-secret",
   684  									},
   685  								},
   686  							},
   687  						}),
   688  					},
   689  				},
   690  			},
   691  			wantErr: true,
   692  		},
   693  		{
   694  			name: "transport-socket-without-validation-context",
   695  			cluster: &v3clusterpb.Cluster{
   696  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   697  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   698  					EdsConfig: &v3corepb.ConfigSource{
   699  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   700  							Ads: &v3corepb.AggregatedConfigSource{},
   701  						},
   702  					},
   703  					ServiceName: serviceName,
   704  				},
   705  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   706  				TransportSocket: &v3corepb.TransportSocket{
   707  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   708  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   709  							CommonTlsContext: &v3tlspb.CommonTlsContext{},
   710  						}),
   711  					},
   712  				},
   713  			},
   714  			wantErr: true,
   715  		},
   716  		{
   717  			name: "empty-prefix-in-matching-SAN",
   718  			cluster: &v3clusterpb.Cluster{
   719  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   720  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   721  					EdsConfig: &v3corepb.ConfigSource{
   722  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   723  							Ads: &v3corepb.AggregatedConfigSource{},
   724  						},
   725  					},
   726  					ServiceName: serviceName,
   727  				},
   728  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   729  				TransportSocket: &v3corepb.TransportSocket{
   730  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   731  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   732  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   733  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
   734  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
   735  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
   736  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   737  												{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}},
   738  											},
   739  										},
   740  										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   741  											InstanceName:    rootPluginInstance,
   742  											CertificateName: rootCertName,
   743  										},
   744  									},
   745  								},
   746  							},
   747  						}),
   748  					},
   749  				},
   750  			},
   751  			wantErr: true,
   752  		},
   753  		{
   754  			name: "empty-suffix-in-matching-SAN",
   755  			cluster: &v3clusterpb.Cluster{
   756  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   757  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   758  					EdsConfig: &v3corepb.ConfigSource{
   759  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   760  							Ads: &v3corepb.AggregatedConfigSource{},
   761  						},
   762  					},
   763  					ServiceName: serviceName,
   764  				},
   765  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   766  				TransportSocket: &v3corepb.TransportSocket{
   767  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   768  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   769  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   770  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
   771  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
   772  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
   773  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   774  												{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: ""}},
   775  											},
   776  										},
   777  										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   778  											InstanceName:    rootPluginInstance,
   779  											CertificateName: rootCertName,
   780  										},
   781  									},
   782  								},
   783  							},
   784  						}),
   785  					},
   786  				},
   787  			},
   788  			wantErr: true,
   789  		},
   790  		{
   791  			name: "empty-contains-in-matching-SAN",
   792  			cluster: &v3clusterpb.Cluster{
   793  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   794  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   795  					EdsConfig: &v3corepb.ConfigSource{
   796  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   797  							Ads: &v3corepb.AggregatedConfigSource{},
   798  						},
   799  					},
   800  					ServiceName: serviceName,
   801  				},
   802  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   803  				TransportSocket: &v3corepb.TransportSocket{
   804  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   805  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   806  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   807  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
   808  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
   809  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
   810  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   811  												{MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: ""}},
   812  											},
   813  										},
   814  										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   815  											InstanceName:    rootPluginInstance,
   816  											CertificateName: rootCertName,
   817  										},
   818  									},
   819  								},
   820  							},
   821  						}),
   822  					},
   823  				},
   824  			},
   825  			wantErr: true,
   826  		},
   827  		{
   828  			name: "invalid-regex-in-matching-SAN",
   829  			cluster: &v3clusterpb.Cluster{
   830  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   831  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   832  					EdsConfig: &v3corepb.ConfigSource{
   833  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   834  							Ads: &v3corepb.AggregatedConfigSource{},
   835  						},
   836  					},
   837  					ServiceName: serviceName,
   838  				},
   839  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   840  				TransportSocket: &v3corepb.TransportSocket{
   841  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   842  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   843  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   844  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
   845  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
   846  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
   847  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   848  												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}},
   849  											},
   850  										},
   851  										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   852  											InstanceName:    rootPluginInstance,
   853  											CertificateName: rootCertName,
   854  										},
   855  									},
   856  								},
   857  							},
   858  						}),
   859  					},
   860  				},
   861  			},
   862  			wantErr: true,
   863  		},
   864  		{
   865  			name: "invalid-regex-in-matching-SAN-with-new-fields",
   866  			cluster: &v3clusterpb.Cluster{
   867  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   868  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   869  					EdsConfig: &v3corepb.ConfigSource{
   870  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   871  							Ads: &v3corepb.AggregatedConfigSource{},
   872  						},
   873  					},
   874  					ServiceName: serviceName,
   875  				},
   876  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   877  				TransportSocket: &v3corepb.TransportSocket{
   878  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   879  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   880  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   881  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
   882  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
   883  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
   884  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
   885  												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}},
   886  											},
   887  											CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   888  												InstanceName:    rootPluginInstance,
   889  												CertificateName: rootCertName,
   890  											},
   891  										},
   892  									},
   893  								},
   894  							},
   895  						}),
   896  					},
   897  				},
   898  			},
   899  			wantErr: true,
   900  		},
   901  		{
   902  			name: "happy-case-with-no-identity-certs-using-deprecated-fields",
   903  			cluster: &v3clusterpb.Cluster{
   904  				Name:                 clusterName,
   905  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   906  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   907  					EdsConfig: &v3corepb.ConfigSource{
   908  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   909  							Ads: &v3corepb.AggregatedConfigSource{},
   910  						},
   911  					},
   912  					ServiceName: serviceName,
   913  				},
   914  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   915  				TransportSocket: &v3corepb.TransportSocket{
   916  					Name: "envoy.transport_sockets.tls",
   917  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   918  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   919  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   920  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
   921  									ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   922  										InstanceName:    rootPluginInstance,
   923  										CertificateName: rootCertName,
   924  									},
   925  								},
   926  							},
   927  						}),
   928  					},
   929  				},
   930  			},
   931  			wantUpdate: ClusterUpdate{
   932  				ClusterName:    clusterName,
   933  				EDSServiceName: serviceName,
   934  				SecurityCfg: &SecurityConfig{
   935  					RootInstanceName: rootPluginInstance,
   936  					RootCertName:     rootCertName,
   937  				},
   938  				TelemetryLabels: internal.UnknownCSMLabels,
   939  			},
   940  		},
   941  		{
   942  			name: "happy-case-with-no-identity-certs-using-new-fields",
   943  			cluster: &v3clusterpb.Cluster{
   944  				Name:                 clusterName,
   945  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   946  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   947  					EdsConfig: &v3corepb.ConfigSource{
   948  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   949  							Ads: &v3corepb.AggregatedConfigSource{},
   950  						},
   951  					},
   952  					ServiceName: serviceName,
   953  				},
   954  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   955  				TransportSocket: &v3corepb.TransportSocket{
   956  					Name: "envoy.transport_sockets.tls",
   957  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
   958  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
   959  							CommonTlsContext: &v3tlspb.CommonTlsContext{
   960  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   961  									ValidationContext: &v3tlspb.CertificateValidationContext{
   962  										CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
   963  											InstanceName:    rootPluginInstance,
   964  											CertificateName: rootCertName,
   965  										},
   966  									},
   967  								},
   968  							},
   969  						}),
   970  					},
   971  				},
   972  			},
   973  			wantUpdate: ClusterUpdate{
   974  				ClusterName:    clusterName,
   975  				EDSServiceName: serviceName,
   976  				SecurityCfg: &SecurityConfig{
   977  					RootInstanceName: rootPluginInstance,
   978  					RootCertName:     rootCertName,
   979  				},
   980  				TelemetryLabels: internal.UnknownCSMLabels,
   981  			},
   982  		},
   983  		{
   984  			name: "happy-case-with-validation-context-provider-instance-using-deprecated-fields",
   985  			cluster: &v3clusterpb.Cluster{
   986  				Name:                 clusterName,
   987  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
   988  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
   989  					EdsConfig: &v3corepb.ConfigSource{
   990  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   991  							Ads: &v3corepb.AggregatedConfigSource{},
   992  						},
   993  					},
   994  					ServiceName: serviceName,
   995  				},
   996  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
   997  				TransportSocket: &v3corepb.TransportSocket{
   998  					Name: "envoy.transport_sockets.tls",
   999  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1000  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1001  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1002  								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
  1003  									InstanceName:    identityPluginInstance,
  1004  									CertificateName: identityCertName,
  1005  								},
  1006  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
  1007  									ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
  1008  										InstanceName:    rootPluginInstance,
  1009  										CertificateName: rootCertName,
  1010  									},
  1011  								},
  1012  							},
  1013  						}),
  1014  					},
  1015  				},
  1016  			},
  1017  			wantUpdate: ClusterUpdate{
  1018  				ClusterName:    clusterName,
  1019  				EDSServiceName: serviceName,
  1020  				SecurityCfg: &SecurityConfig{
  1021  					RootInstanceName:     rootPluginInstance,
  1022  					RootCertName:         rootCertName,
  1023  					IdentityInstanceName: identityPluginInstance,
  1024  					IdentityCertName:     identityCertName,
  1025  				},
  1026  				TelemetryLabels: internal.UnknownCSMLabels,
  1027  			},
  1028  		},
  1029  		{
  1030  			name:                      "happy-case-with-validation-context-provider-instance-using-new-fields",
  1031  			enableSystemRootCertsFlag: true,
  1032  			cluster: &v3clusterpb.Cluster{
  1033  				Name:                 clusterName,
  1034  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1035  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1036  					EdsConfig: &v3corepb.ConfigSource{
  1037  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1038  							Ads: &v3corepb.AggregatedConfigSource{},
  1039  						},
  1040  					},
  1041  					ServiceName: serviceName,
  1042  				},
  1043  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1044  				TransportSocket: &v3corepb.TransportSocket{
  1045  					Name: "envoy.transport_sockets.tls",
  1046  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1047  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1048  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1049  								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1050  									InstanceName:    identityPluginInstance,
  1051  									CertificateName: identityCertName,
  1052  								},
  1053  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
  1054  									ValidationContext: &v3tlspb.CertificateValidationContext{
  1055  										CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1056  											InstanceName:    rootPluginInstance,
  1057  											CertificateName: rootCertName,
  1058  										},
  1059  										// SystemRootCerts will be ignored due
  1060  										// to the presence of
  1061  										// CaCertificateProviderInstance.
  1062  										SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
  1063  									},
  1064  								},
  1065  							},
  1066  						}),
  1067  					},
  1068  				},
  1069  			},
  1070  			wantUpdate: ClusterUpdate{
  1071  				ClusterName:    clusterName,
  1072  				EDSServiceName: serviceName,
  1073  				SecurityCfg: &SecurityConfig{
  1074  					RootInstanceName:     rootPluginInstance,
  1075  					RootCertName:         rootCertName,
  1076  					IdentityInstanceName: identityPluginInstance,
  1077  					IdentityCertName:     identityCertName,
  1078  				},
  1079  				TelemetryLabels: internal.UnknownCSMLabels,
  1080  			},
  1081  		},
  1082  		{
  1083  			name:                      "happy-case-with-validation-context-provider-instance-using-new-fields-and-system-root-certs",
  1084  			enableSystemRootCertsFlag: true,
  1085  			cluster: &v3clusterpb.Cluster{
  1086  				Name:                 clusterName,
  1087  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1088  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1089  					EdsConfig: &v3corepb.ConfigSource{
  1090  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1091  							Ads: &v3corepb.AggregatedConfigSource{},
  1092  						},
  1093  					},
  1094  					ServiceName: serviceName,
  1095  				},
  1096  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1097  				TransportSocket: &v3corepb.TransportSocket{
  1098  					Name: "envoy.transport_sockets.tls",
  1099  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1100  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1101  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1102  								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1103  									InstanceName:    identityPluginInstance,
  1104  									CertificateName: identityCertName,
  1105  								},
  1106  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
  1107  									ValidationContext: &v3tlspb.CertificateValidationContext{
  1108  										SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
  1109  									},
  1110  								},
  1111  							},
  1112  						}),
  1113  					},
  1114  				},
  1115  			},
  1116  			wantUpdate: ClusterUpdate{
  1117  				ClusterName:    clusterName,
  1118  				EDSServiceName: serviceName,
  1119  				SecurityCfg: &SecurityConfig{
  1120  					UseSystemRootCerts:   true,
  1121  					IdentityInstanceName: identityPluginInstance,
  1122  					IdentityCertName:     identityCertName,
  1123  				},
  1124  				TelemetryLabels: internal.UnknownCSMLabels,
  1125  			},
  1126  		},
  1127  		{
  1128  			name: "failure-case-with-validation-context-provider-instance-using-new-fields-and-system-root-certs-env-flag-disabled",
  1129  			cluster: &v3clusterpb.Cluster{
  1130  				Name:                 clusterName,
  1131  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1132  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1133  					EdsConfig: &v3corepb.ConfigSource{
  1134  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1135  							Ads: &v3corepb.AggregatedConfigSource{},
  1136  						},
  1137  					},
  1138  					ServiceName: serviceName,
  1139  				},
  1140  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1141  				TransportSocket: &v3corepb.TransportSocket{
  1142  					Name: "envoy.transport_sockets.tls",
  1143  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1144  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1145  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1146  								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1147  									InstanceName:    identityPluginInstance,
  1148  									CertificateName: identityCertName,
  1149  								},
  1150  								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
  1151  									ValidationContext: &v3tlspb.CertificateValidationContext{
  1152  										SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
  1153  									},
  1154  								},
  1155  							},
  1156  						}),
  1157  					},
  1158  				},
  1159  			},
  1160  			wantErr: true,
  1161  		},
  1162  		{
  1163  			name: "happy-case-with-combined-validation-context-using-deprecated-fields",
  1164  			cluster: &v3clusterpb.Cluster{
  1165  				Name:                 clusterName,
  1166  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1167  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1168  					EdsConfig: &v3corepb.ConfigSource{
  1169  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1170  							Ads: &v3corepb.AggregatedConfigSource{},
  1171  						},
  1172  					},
  1173  					ServiceName: serviceName,
  1174  				},
  1175  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1176  				TransportSocket: &v3corepb.TransportSocket{
  1177  					Name: "envoy.transport_sockets.tls",
  1178  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1179  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1180  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1181  								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
  1182  									InstanceName:    identityPluginInstance,
  1183  									CertificateName: identityCertName,
  1184  								},
  1185  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
  1186  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
  1187  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
  1188  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
  1189  												{
  1190  													MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact},
  1191  													IgnoreCase:   true,
  1192  												},
  1193  												{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}},
  1194  												{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}},
  1195  												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}},
  1196  												{MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}},
  1197  											},
  1198  										},
  1199  										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
  1200  											InstanceName:    rootPluginInstance,
  1201  											CertificateName: rootCertName,
  1202  										},
  1203  									},
  1204  								},
  1205  							},
  1206  						}),
  1207  					},
  1208  				},
  1209  			},
  1210  			wantUpdate: ClusterUpdate{
  1211  				ClusterName:    clusterName,
  1212  				EDSServiceName: serviceName,
  1213  				SecurityCfg: &SecurityConfig{
  1214  					RootInstanceName:     rootPluginInstance,
  1215  					RootCertName:         rootCertName,
  1216  					IdentityInstanceName: identityPluginInstance,
  1217  					IdentityCertName:     identityCertName,
  1218  					SubjectAltNameMatchers: []matcher.StringMatcher{
  1219  						matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true),
  1220  						matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false),
  1221  						matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false),
  1222  						matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false),
  1223  						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
  1224  					},
  1225  				},
  1226  				TelemetryLabels: internal.UnknownCSMLabels,
  1227  			},
  1228  		},
  1229  		{
  1230  			name: "happy-case-with-combined-validation-context-using-new-fields",
  1231  			cluster: &v3clusterpb.Cluster{
  1232  				Name:                 clusterName,
  1233  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1234  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1235  					EdsConfig: &v3corepb.ConfigSource{
  1236  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1237  							Ads: &v3corepb.AggregatedConfigSource{},
  1238  						},
  1239  					},
  1240  					ServiceName: serviceName,
  1241  				},
  1242  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1243  				TransportSocket: &v3corepb.TransportSocket{
  1244  					Name: "envoy.transport_sockets.tls",
  1245  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1246  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1247  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1248  								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1249  									InstanceName:    identityPluginInstance,
  1250  									CertificateName: identityCertName,
  1251  								},
  1252  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
  1253  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
  1254  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
  1255  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
  1256  												{
  1257  													MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact},
  1258  													IgnoreCase:   true,
  1259  												},
  1260  												{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}},
  1261  												{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}},
  1262  												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}},
  1263  												{MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}},
  1264  											},
  1265  											CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1266  												InstanceName:    rootPluginInstance,
  1267  												CertificateName: rootCertName,
  1268  											},
  1269  										},
  1270  									},
  1271  								},
  1272  							},
  1273  						}),
  1274  					},
  1275  				},
  1276  			},
  1277  			wantUpdate: ClusterUpdate{
  1278  				ClusterName:    clusterName,
  1279  				EDSServiceName: serviceName,
  1280  				SecurityCfg: &SecurityConfig{
  1281  					RootInstanceName:     rootPluginInstance,
  1282  					RootCertName:         rootCertName,
  1283  					IdentityInstanceName: identityPluginInstance,
  1284  					IdentityCertName:     identityCertName,
  1285  					SubjectAltNameMatchers: []matcher.StringMatcher{
  1286  						matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true),
  1287  						matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false),
  1288  						matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false),
  1289  						matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false),
  1290  						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
  1291  					},
  1292  				},
  1293  				TelemetryLabels: internal.UnknownCSMLabels,
  1294  			},
  1295  		},
  1296  		{
  1297  			name:                      "happy-case-with-combined-validation-context-using-new-fields-and-system-root-certs",
  1298  			enableSystemRootCertsFlag: true,
  1299  			cluster: &v3clusterpb.Cluster{
  1300  				Name:                 clusterName,
  1301  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1302  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1303  					EdsConfig: &v3corepb.ConfigSource{
  1304  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1305  							Ads: &v3corepb.AggregatedConfigSource{},
  1306  						},
  1307  					},
  1308  					ServiceName: serviceName,
  1309  				},
  1310  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1311  				TransportSocket: &v3corepb.TransportSocket{
  1312  					Name: "envoy.transport_sockets.tls",
  1313  					ConfigType: &v3corepb.TransportSocket_TypedConfig{
  1314  						TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{
  1315  							CommonTlsContext: &v3tlspb.CommonTlsContext{
  1316  								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
  1317  									InstanceName:    identityPluginInstance,
  1318  									CertificateName: identityCertName,
  1319  								},
  1320  								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
  1321  									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
  1322  										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
  1323  											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
  1324  												{
  1325  													MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact},
  1326  													IgnoreCase:   true,
  1327  												},
  1328  												{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}},
  1329  												{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}},
  1330  												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}},
  1331  												{MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}},
  1332  											},
  1333  											SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
  1334  										},
  1335  									},
  1336  								},
  1337  							},
  1338  						}),
  1339  					},
  1340  				},
  1341  			},
  1342  			wantUpdate: ClusterUpdate{
  1343  				ClusterName:    clusterName,
  1344  				EDSServiceName: serviceName,
  1345  				SecurityCfg: &SecurityConfig{
  1346  					UseSystemRootCerts:   true,
  1347  					IdentityInstanceName: identityPluginInstance,
  1348  					IdentityCertName:     identityCertName,
  1349  					SubjectAltNameMatchers: []matcher.StringMatcher{
  1350  						matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true),
  1351  						matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false),
  1352  						matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false),
  1353  						matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false),
  1354  						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
  1355  					},
  1356  				},
  1357  				TelemetryLabels: internal.UnknownCSMLabels,
  1358  			},
  1359  		},
  1360  	}
  1361  
  1362  	for _, test := range tests {
  1363  		t.Run(test.name, func(t *testing.T) {
  1364  			origFlag := envconfig.XDSSystemRootCertsEnabled
  1365  			defer func() {
  1366  				envconfig.XDSSystemRootCertsEnabled = origFlag
  1367  			}()
  1368  			envconfig.XDSSystemRootCertsEnabled = test.enableSystemRootCertsFlag
  1369  			update, err := validateClusterAndConstructClusterUpdate(test.cluster, nil)
  1370  			if (err != nil) != test.wantErr {
  1371  				t.Errorf("validateClusterAndConstructClusterUpdate() returned err %v wantErr %v)", err, test.wantErr)
  1372  			}
  1373  			if diff := cmp.Diff(test.wantUpdate, update, cmpopts.EquateEmpty(), cmp.AllowUnexported(regexp.Regexp{}), cmpopts.IgnoreFields(ClusterUpdate{}, "LBPolicy")); diff != "" {
  1374  				t.Errorf("validateClusterAndConstructClusterUpdate() returned unexpected diff (-want, +got):\n%s", diff)
  1375  			}
  1376  		})
  1377  	}
  1378  }
  1379  
  1380  func (s) TestUnmarshalCluster(t *testing.T) {
  1381  	const (
  1382  		v3ClusterName = "v3clusterName"
  1383  		v3Service     = "v3Service"
  1384  	)
  1385  	var (
  1386  		v3ClusterAny = testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1387  			Name:                 v3ClusterName,
  1388  			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1389  			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1390  				EdsConfig: &v3corepb.ConfigSource{
  1391  					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1392  						Ads: &v3corepb.AggregatedConfigSource{},
  1393  					},
  1394  				},
  1395  				ServiceName: v3Service,
  1396  			},
  1397  			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1398  			LrsServer: &v3corepb.ConfigSource{
  1399  				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
  1400  					Self: &v3corepb.SelfConfigSource{},
  1401  				},
  1402  			},
  1403  		})
  1404  
  1405  		v3ClusterAnyWithEDSConfigSourceSelf = testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1406  			Name:                 v3ClusterName,
  1407  			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1408  			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1409  				EdsConfig: &v3corepb.ConfigSource{
  1410  					ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{},
  1411  				},
  1412  				ServiceName: v3Service,
  1413  			},
  1414  			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1415  			LrsServer: &v3corepb.ConfigSource{
  1416  				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
  1417  					Self: &v3corepb.SelfConfigSource{},
  1418  				},
  1419  			},
  1420  		})
  1421  
  1422  		v3ClusterAnyWithTelemetryLabels = testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1423  			Name:                 v3ClusterName,
  1424  			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1425  			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1426  				EdsConfig: &v3corepb.ConfigSource{
  1427  					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1428  						Ads: &v3corepb.AggregatedConfigSource{},
  1429  					},
  1430  				},
  1431  				ServiceName: v3Service,
  1432  			},
  1433  			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1434  			LrsServer: &v3corepb.ConfigSource{
  1435  				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
  1436  					Self: &v3corepb.SelfConfigSource{},
  1437  				},
  1438  			},
  1439  			Metadata: &v3corepb.Metadata{
  1440  				FilterMetadata: map[string]*structpb.Struct{
  1441  					"com.google.csm.telemetry_labels": {
  1442  						Fields: map[string]*structpb.Value{
  1443  							"service_name":      structpb.NewStringValue("grpc-service"),
  1444  							"service_namespace": structpb.NewStringValue("grpc-service-namespace"),
  1445  						},
  1446  					},
  1447  				},
  1448  			},
  1449  		})
  1450  		v3ClusterAnyWithTelemetryLabelsIgnoreSome = testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1451  			Name:                 v3ClusterName,
  1452  			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1453  			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1454  				EdsConfig: &v3corepb.ConfigSource{
  1455  					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1456  						Ads: &v3corepb.AggregatedConfigSource{},
  1457  					},
  1458  				},
  1459  				ServiceName: v3Service,
  1460  			},
  1461  			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1462  			LrsServer: &v3corepb.ConfigSource{
  1463  				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
  1464  					Self: &v3corepb.SelfConfigSource{},
  1465  				},
  1466  			},
  1467  			Metadata: &v3corepb.Metadata{
  1468  				FilterMetadata: map[string]*structpb.Struct{
  1469  					"com.google.csm.telemetry_labels": {
  1470  						Fields: map[string]*structpb.Value{
  1471  							"string-value-should-ignore": structpb.NewStringValue("string-val"),
  1472  							"float-value-ignore":         structpb.NewNumberValue(3),
  1473  							"bool-value-ignore":          structpb.NewBoolValue(false),
  1474  							"service_name":               structpb.NewStringValue("grpc-service"), // shouldn't ignore
  1475  							"service_namespace":          structpb.NewNullValue(),                 // should ignore - wrong type
  1476  						},
  1477  					},
  1478  					"ignore-this-metadata": { // should ignore this filter_metadata
  1479  						Fields: map[string]*structpb.Value{
  1480  							"service_namespace": structpb.NewStringValue("string-val-should-ignore"),
  1481  						},
  1482  					},
  1483  				},
  1484  			},
  1485  		})
  1486  	)
  1487  
  1488  	serverCfg, err := bootstrap.ServerConfigForTesting(bootstrap.ServerConfigTestingOptions{URI: "test-server"})
  1489  	if err != nil {
  1490  		t.Fatalf("Failed to create server config for testing: %v", err)
  1491  	}
  1492  
  1493  	tests := []struct {
  1494  		name       string
  1495  		resource   *anypb.Any
  1496  		serverCfg  *bootstrap.ServerConfig
  1497  		wantName   string
  1498  		wantUpdate ClusterUpdate
  1499  		wantErr    bool
  1500  	}{
  1501  		{
  1502  			name:     "non-cluster resource type",
  1503  			resource: &anypb.Any{TypeUrl: version.V3HTTPConnManagerURL},
  1504  			wantErr:  true,
  1505  		},
  1506  		{
  1507  			name: "badly marshaled cluster resource",
  1508  			resource: &anypb.Any{
  1509  				TypeUrl: version.V3ClusterURL,
  1510  				Value:   []byte{1, 2, 3, 4},
  1511  			},
  1512  			wantErr: true,
  1513  		},
  1514  		{
  1515  			name: "bad cluster resource",
  1516  			resource: testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1517  				Name:                 "test",
  1518  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
  1519  			}),
  1520  			wantName: "test",
  1521  			wantErr:  true,
  1522  		},
  1523  		{
  1524  			name: "cluster resource with non-self lrs_server field",
  1525  			resource: testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1526  				Name:                 "test",
  1527  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1528  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1529  					EdsConfig: &v3corepb.ConfigSource{
  1530  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1531  							Ads: &v3corepb.AggregatedConfigSource{},
  1532  						},
  1533  					},
  1534  					ServiceName: v3Service,
  1535  				},
  1536  				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
  1537  				LrsServer: &v3corepb.ConfigSource{
  1538  					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1539  						Ads: &v3corepb.AggregatedConfigSource{},
  1540  					},
  1541  				},
  1542  			}),
  1543  			wantName: "test",
  1544  			wantErr:  true,
  1545  		},
  1546  		{
  1547  			name:      "v3 cluster",
  1548  			resource:  v3ClusterAny,
  1549  			serverCfg: serverCfg,
  1550  			wantName:  v3ClusterName,
  1551  			wantUpdate: ClusterUpdate{
  1552  				ClusterName:     v3ClusterName,
  1553  				EDSServiceName:  v3Service,
  1554  				LRSServerConfig: serverCfg,
  1555  				Raw:             v3ClusterAny,
  1556  				TelemetryLabels: internal.UnknownCSMLabels,
  1557  			},
  1558  		},
  1559  		{
  1560  			name:      "v3 cluster wrapped",
  1561  			resource:  testutils.MarshalAny(t, &v3discoverypb.Resource{Resource: v3ClusterAny}),
  1562  			serverCfg: serverCfg,
  1563  			wantName:  v3ClusterName,
  1564  			wantUpdate: ClusterUpdate{
  1565  				ClusterName:     v3ClusterName,
  1566  				EDSServiceName:  v3Service,
  1567  				LRSServerConfig: serverCfg,
  1568  				Raw:             v3ClusterAny,
  1569  				TelemetryLabels: internal.UnknownCSMLabels,
  1570  			},
  1571  		},
  1572  		{
  1573  			name:      "v3 cluster with EDS config source self",
  1574  			resource:  v3ClusterAnyWithEDSConfigSourceSelf,
  1575  			serverCfg: serverCfg,
  1576  			wantName:  v3ClusterName,
  1577  			wantUpdate: ClusterUpdate{
  1578  				ClusterName:     v3ClusterName,
  1579  				EDSServiceName:  v3Service,
  1580  				LRSServerConfig: serverCfg,
  1581  				Raw:             v3ClusterAnyWithEDSConfigSourceSelf,
  1582  				TelemetryLabels: internal.UnknownCSMLabels,
  1583  			},
  1584  		},
  1585  		{
  1586  			name:      "v3 cluster with telemetry case",
  1587  			resource:  v3ClusterAnyWithTelemetryLabels,
  1588  			serverCfg: serverCfg,
  1589  			wantName:  v3ClusterName,
  1590  			wantUpdate: ClusterUpdate{
  1591  				ClusterName:     v3ClusterName,
  1592  				EDSServiceName:  v3Service,
  1593  				LRSServerConfig: serverCfg,
  1594  				Raw:             v3ClusterAnyWithTelemetryLabels,
  1595  				TelemetryLabels: map[string]string{
  1596  					"csm.service_name":           "grpc-service",
  1597  					"csm.service_namespace_name": "grpc-service-namespace",
  1598  				},
  1599  			},
  1600  		},
  1601  		{
  1602  			name:      "v3 metadata ignore other types not string and not com.google.csm.telemetry_labels",
  1603  			resource:  v3ClusterAnyWithTelemetryLabelsIgnoreSome,
  1604  			serverCfg: serverCfg,
  1605  			wantName:  v3ClusterName,
  1606  			wantUpdate: ClusterUpdate{
  1607  				ClusterName:     v3ClusterName,
  1608  				EDSServiceName:  v3Service,
  1609  				LRSServerConfig: serverCfg,
  1610  				Raw:             v3ClusterAnyWithTelemetryLabelsIgnoreSome,
  1611  				TelemetryLabels: map[string]string{
  1612  					"csm.service_name":           "grpc-service",
  1613  					"csm.service_namespace_name": "unknown",
  1614  				},
  1615  			},
  1616  		},
  1617  		{
  1618  			name: "xdstp cluster resource with unset EDS service name",
  1619  			resource: testutils.MarshalAny(t, &v3clusterpb.Cluster{
  1620  				Name:                 "xdstp:foo",
  1621  				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1622  				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1623  					EdsConfig: &v3corepb.ConfigSource{
  1624  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1625  							Ads: &v3corepb.AggregatedConfigSource{},
  1626  						},
  1627  					},
  1628  					ServiceName: "",
  1629  				},
  1630  			}),
  1631  			wantName: "xdstp:foo",
  1632  			wantErr:  true,
  1633  		},
  1634  	}
  1635  	for _, test := range tests {
  1636  		t.Run(test.name, func(t *testing.T) {
  1637  			name, update, err := unmarshalClusterResource(test.resource, test.serverCfg)
  1638  			if (err != nil) != test.wantErr {
  1639  				t.Fatalf("unmarshalClusterResource(%s), got err: %v, wantErr: %v", pretty.ToJSON(test.resource), err, test.wantErr)
  1640  			}
  1641  			if name != test.wantName {
  1642  				t.Errorf("unmarshalClusterResource(%s), got name: %s, want: %s", pretty.ToJSON(test.resource), name, test.wantName)
  1643  			}
  1644  			if diff := cmp.Diff(update, test.wantUpdate, cmpOpts, cmpopts.IgnoreFields(ClusterUpdate{}, "LBPolicy")); diff != "" {
  1645  				t.Errorf("unmarshalClusterResource(%s), got unexpected update, diff (-got +want): %v", pretty.ToJSON(test.resource), diff)
  1646  			}
  1647  		})
  1648  	}
  1649  }
  1650  
  1651  func (s) TestValidateClusterWithOutlierDetection(t *testing.T) {
  1652  	odToClusterProto := func(od *v3clusterpb.OutlierDetection) *v3clusterpb.Cluster {
  1653  		// Cluster parsing doesn't fail with respect to fields orthogonal to
  1654  		// outlier detection.
  1655  		return &v3clusterpb.Cluster{
  1656  			Name:                 clusterName,
  1657  			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
  1658  			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
  1659  				EdsConfig: &v3corepb.ConfigSource{
  1660  					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
  1661  						Ads: &v3corepb.AggregatedConfigSource{},
  1662  					},
  1663  				},
  1664  			},
  1665  			LbPolicy:         v3clusterpb.Cluster_ROUND_ROBIN,
  1666  			OutlierDetection: od,
  1667  		}
  1668  	}
  1669  
  1670  	tests := []struct {
  1671  		name      string
  1672  		cluster   *v3clusterpb.Cluster
  1673  		wantODCfg string
  1674  		wantErr   bool
  1675  	}{
  1676  		{
  1677  			name:      "success-and-failure-null",
  1678  			cluster:   odToClusterProto(&v3clusterpb.OutlierDetection{}),
  1679  			wantODCfg: `{"successRateEjection": {}}`,
  1680  		},
  1681  		{
  1682  			name: "success-and-failure-zero",
  1683  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
  1684  				EnforcingSuccessRate:       &wrapperspb.UInt32Value{Value: 0}, // Thus doesn't create sre - to focus on fpe
  1685  				EnforcingFailurePercentage: &wrapperspb.UInt32Value{Value: 0},
  1686  			}),
  1687  			wantODCfg: `{}`,
  1688  		},
  1689  		{
  1690  			name: "some-fields-set",
  1691  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
  1692  				Interval:                       &durationpb.Duration{Seconds: 1},
  1693  				MaxEjectionTime:                &durationpb.Duration{Seconds: 3},
  1694  				EnforcingSuccessRate:           &wrapperspb.UInt32Value{Value: 3},
  1695  				SuccessRateRequestVolume:       &wrapperspb.UInt32Value{Value: 5},
  1696  				EnforcingFailurePercentage:     &wrapperspb.UInt32Value{Value: 7},
  1697  				FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 9},
  1698  			}),
  1699  			wantODCfg: `{
  1700  				"interval": "1s",
  1701  				"maxEjectionTime": "3s",
  1702  				"successRateEjection": {
  1703  					"enforcementPercentage": 3,
  1704  					"requestVolume": 5
  1705  				},
  1706  				"failurePercentageEjection": {
  1707  					"enforcementPercentage": 7,
  1708  					"requestVolume": 9
  1709  				}
  1710  			}`,
  1711  		},
  1712  		{
  1713  			name: "every-field-set-non-zero",
  1714  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{
  1715  				// all fields set (including ones that will be layered) should
  1716  				// pick up those too and explicitly all fields, including those
  1717  				// put in layers, in the JSON generated.
  1718  				Interval:                       &durationpb.Duration{Seconds: 1},
  1719  				BaseEjectionTime:               &durationpb.Duration{Seconds: 2},
  1720  				MaxEjectionTime:                &durationpb.Duration{Seconds: 3},
  1721  				MaxEjectionPercent:             &wrapperspb.UInt32Value{Value: 1},
  1722  				SuccessRateStdevFactor:         &wrapperspb.UInt32Value{Value: 2},
  1723  				EnforcingSuccessRate:           &wrapperspb.UInt32Value{Value: 3},
  1724  				SuccessRateMinimumHosts:        &wrapperspb.UInt32Value{Value: 4},
  1725  				SuccessRateRequestVolume:       &wrapperspb.UInt32Value{Value: 5},
  1726  				FailurePercentageThreshold:     &wrapperspb.UInt32Value{Value: 6},
  1727  				EnforcingFailurePercentage:     &wrapperspb.UInt32Value{Value: 7},
  1728  				FailurePercentageMinimumHosts:  &wrapperspb.UInt32Value{Value: 8},
  1729  				FailurePercentageRequestVolume: &wrapperspb.UInt32Value{Value: 9},
  1730  			}),
  1731  			wantODCfg: `{
  1732  				"interval": "1s",
  1733  				"baseEjectionTime": "2s",
  1734  				"maxEjectionTime": "3s",
  1735  				"maxEjectionPercent": 1,
  1736  				"successRateEjection": {
  1737  					"stdevFactor": 2,
  1738  					"enforcementPercentage": 3,
  1739  					"minimumHosts": 4,
  1740  					"requestVolume": 5
  1741  				},
  1742  				"failurePercentageEjection": {
  1743  					"threshold": 6,
  1744  					"enforcementPercentage": 7,
  1745  					"minimumHosts": 8,
  1746  					"requestVolume": 9
  1747  				}
  1748  			}`,
  1749  		},
  1750  		{
  1751  			name:    "interval-is-negative",
  1752  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{Interval: &durationpb.Duration{Seconds: -10}}),
  1753  			wantErr: true,
  1754  		},
  1755  		{
  1756  			name:    "interval-overflows",
  1757  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{Interval: &durationpb.Duration{Seconds: 315576000001}}),
  1758  			wantErr: true,
  1759  		},
  1760  		{
  1761  			name:    "base-ejection-time-is-negative",
  1762  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{BaseEjectionTime: &durationpb.Duration{Seconds: -10}}),
  1763  			wantErr: true,
  1764  		},
  1765  		{
  1766  			name:    "base-ejection-time-overflows",
  1767  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{BaseEjectionTime: &durationpb.Duration{Seconds: 315576000001}}),
  1768  			wantErr: true,
  1769  		},
  1770  		{
  1771  			name:    "max-ejection-time-is-negative",
  1772  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{MaxEjectionTime: &durationpb.Duration{Seconds: -10}}),
  1773  			wantErr: true,
  1774  		},
  1775  		{
  1776  			name:    "max-ejection-time-overflows",
  1777  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{MaxEjectionTime: &durationpb.Duration{Seconds: 315576000001}}),
  1778  			wantErr: true,
  1779  		},
  1780  		{
  1781  			name:    "max-ejection-percent-is-greater-than-100",
  1782  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{MaxEjectionPercent: &wrapperspb.UInt32Value{Value: 150}}),
  1783  			wantErr: true,
  1784  		},
  1785  		{
  1786  			name:    "enforcing-success-rate-is-greater-than-100",
  1787  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{EnforcingSuccessRate: &wrapperspb.UInt32Value{Value: 150}}),
  1788  			wantErr: true,
  1789  		},
  1790  		{
  1791  			name:    "failure-percentage-threshold-is-greater-than-100",
  1792  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{FailurePercentageThreshold: &wrapperspb.UInt32Value{Value: 150}}),
  1793  			wantErr: true,
  1794  		},
  1795  		{
  1796  			name:    "enforcing-failure-percentage-is-greater-than-100",
  1797  			cluster: odToClusterProto(&v3clusterpb.OutlierDetection{EnforcingFailurePercentage: &wrapperspb.UInt32Value{Value: 150}}),
  1798  			wantErr: true,
  1799  		},
  1800  		// A Outlier Detection proto not present should lead to a nil
  1801  		// OutlierDetection field in the ClusterUpdate, which is implicitly
  1802  		// tested in every other test in this file.
  1803  	}
  1804  	for _, test := range tests {
  1805  		t.Run(test.name, func(t *testing.T) {
  1806  			update, err := validateClusterAndConstructClusterUpdate(test.cluster, nil)
  1807  			if (err != nil) != test.wantErr {
  1808  				t.Errorf("validateClusterAndConstructClusterUpdate() returned err %v wantErr %v)", err, test.wantErr)
  1809  			}
  1810  			if test.wantErr {
  1811  				return
  1812  			}
  1813  			// got and want must be unmarshalled since JSON strings shouldn't
  1814  			// generally be directly compared.
  1815  			var got map[string]any
  1816  			if err := json.Unmarshal(update.OutlierDetection, &got); err != nil {
  1817  				t.Fatalf("Error unmarshalling update.OutlierDetection (%q): %v", update.OutlierDetection, err)
  1818  			}
  1819  			var want map[string]any
  1820  			if err := json.Unmarshal(json.RawMessage(test.wantODCfg), &want); err != nil {
  1821  				t.Fatalf("Error unmarshalling wantODCfg (%q): %v", test.wantODCfg, err)
  1822  			}
  1823  			if diff := cmp.Diff(got, want); diff != "" {
  1824  				t.Fatalf("cluster.OutlierDetection got unexpected output, diff (-got, +want): %v", diff)
  1825  			}
  1826  		})
  1827  	}
  1828  }