1/*
2 *
3 * Copyright 2020 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package xdsclient
20
21import (
22	"regexp"
23	"strings"
24	"testing"
25
26	v2xdspb "github.com/envoyproxy/go-control-plane/envoy/api/v2"
27	v2corepb "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
28	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
29	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
30	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
31	v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3"
32	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
33	v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
34	anypb "github.com/golang/protobuf/ptypes/any"
35	"github.com/google/go-cmp/cmp"
36	"github.com/google/go-cmp/cmp/cmpopts"
37	"google.golang.org/grpc/internal/testutils"
38	"google.golang.org/grpc/internal/xds/env"
39	"google.golang.org/grpc/internal/xds/matcher"
40	"google.golang.org/grpc/xds/internal/version"
41	"google.golang.org/protobuf/types/known/wrapperspb"
42)
43
44const (
45	clusterName = "clusterName"
46	serviceName = "service"
47)
48
49var emptyUpdate = ClusterUpdate{ClusterName: clusterName, EnableLRS: false}
50
51func (s) TestValidateCluster_Failure(t *testing.T) {
52	tests := []struct {
53		name       string
54		cluster    *v3clusterpb.Cluster
55		wantUpdate ClusterUpdate
56		wantErr    bool
57	}{
58		{
59			name: "non-supported-cluster-type-static",
60			cluster: &v3clusterpb.Cluster{
61				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
62				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
63					EdsConfig: &v3corepb.ConfigSource{
64						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
65							Ads: &v3corepb.AggregatedConfigSource{},
66						},
67					},
68				},
69				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
70			},
71			wantUpdate: emptyUpdate,
72			wantErr:    true,
73		},
74		{
75			name: "non-supported-cluster-type-original-dst",
76			cluster: &v3clusterpb.Cluster{
77				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_ORIGINAL_DST},
78				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
79					EdsConfig: &v3corepb.ConfigSource{
80						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
81							Ads: &v3corepb.AggregatedConfigSource{},
82						},
83					},
84				},
85				LbPolicy: v3clusterpb.Cluster_LEAST_REQUEST,
86			},
87			wantUpdate: emptyUpdate,
88			wantErr:    true,
89		},
90		{
91			name: "no-eds-config",
92			cluster: &v3clusterpb.Cluster{
93				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
94				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
95			},
96			wantUpdate: emptyUpdate,
97			wantErr:    true,
98		},
99		{
100			name: "no-ads-config-source",
101			cluster: &v3clusterpb.Cluster{
102				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
103				EdsClusterConfig:     &v3clusterpb.Cluster_EdsClusterConfig{},
104				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
105			},
106			wantUpdate: emptyUpdate,
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			wantUpdate: emptyUpdate,
123			wantErr:    true,
124		},
125		{
126			name: "logical-dns-multiple-localities",
127			cluster: &v3clusterpb.Cluster{
128				Name:                 clusterName,
129				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS},
130				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
131				LoadAssignment: &v3endpointpb.ClusterLoadAssignment{
132					Endpoints: []*v3endpointpb.LocalityLbEndpoints{
133						// Invalid if there are more than one locality.
134						{LbEndpoints: nil},
135						{LbEndpoints: nil},
136					},
137				},
138			},
139			wantUpdate: emptyUpdate,
140			wantErr:    true,
141		},
142		{
143			name: "ring-hash-hash-function-not-xx-hash",
144			cluster: &v3clusterpb.Cluster{
145				LbPolicy: v3clusterpb.Cluster_RING_HASH,
146				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
147					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
148						HashFunction: v3clusterpb.Cluster_RingHashLbConfig_MURMUR_HASH_2,
149					},
150				},
151			},
152			wantUpdate: emptyUpdate,
153			wantErr:    true,
154		},
155		{
156			name: "ring-hash-min-bound-greater-than-max",
157			cluster: &v3clusterpb.Cluster{
158				LbPolicy: v3clusterpb.Cluster_RING_HASH,
159				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
160					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
161						MinimumRingSize: wrapperspb.UInt64(100),
162						MaximumRingSize: wrapperspb.UInt64(10),
163					},
164				},
165			},
166			wantUpdate: emptyUpdate,
167			wantErr:    true,
168		},
169		{
170			name: "ring-hash-min-bound-greater-than-upper-bound",
171			cluster: &v3clusterpb.Cluster{
172				LbPolicy: v3clusterpb.Cluster_RING_HASH,
173				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
174					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
175						MinimumRingSize: wrapperspb.UInt64(ringHashSizeUpperBound + 1),
176					},
177				},
178			},
179			wantUpdate: emptyUpdate,
180			wantErr:    true,
181		},
182		{
183			name: "ring-hash-max-bound-greater-than-upper-bound",
184			cluster: &v3clusterpb.Cluster{
185				LbPolicy: v3clusterpb.Cluster_RING_HASH,
186				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
187					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
188						MaximumRingSize: wrapperspb.UInt64(ringHashSizeUpperBound + 1),
189					},
190				},
191			},
192			wantUpdate: emptyUpdate,
193			wantErr:    true,
194		},
195	}
196
197	oldAggregateAndDNSSupportEnv := env.AggregateAndDNSSupportEnv
198	env.AggregateAndDNSSupportEnv = true
199	defer func() { env.AggregateAndDNSSupportEnv = oldAggregateAndDNSSupportEnv }()
200	oldRingHashSupport := env.RingHashSupport
201	env.RingHashSupport = true
202	defer func() { env.RingHashSupport = oldRingHashSupport }()
203	for _, test := range tests {
204		t.Run(test.name, func(t *testing.T) {
205			if update, err := validateClusterAndConstructClusterUpdate(test.cluster); err == nil {
206				t.Errorf("validateClusterAndConstructClusterUpdate(%+v) = %v, wanted error", test.cluster, update)
207			}
208		})
209	}
210}
211
212func (s) TestValidateCluster_Success(t *testing.T) {
213	tests := []struct {
214		name       string
215		cluster    *v3clusterpb.Cluster
216		wantUpdate ClusterUpdate
217	}{
218		{
219			name: "happy-case-logical-dns",
220			cluster: &v3clusterpb.Cluster{
221				Name:                 clusterName,
222				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS},
223				LbPolicy:             v3clusterpb.Cluster_ROUND_ROBIN,
224				LoadAssignment: &v3endpointpb.ClusterLoadAssignment{
225					Endpoints: []*v3endpointpb.LocalityLbEndpoints{{
226						LbEndpoints: []*v3endpointpb.LbEndpoint{{
227							HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{
228								Endpoint: &v3endpointpb.Endpoint{
229									Address: &v3corepb.Address{
230										Address: &v3corepb.Address_SocketAddress{
231											SocketAddress: &v3corepb.SocketAddress{
232												Address: "dns_host",
233												PortSpecifier: &v3corepb.SocketAddress_PortValue{
234													PortValue: 8080,
235												},
236											},
237										},
238									},
239								},
240							},
241						}},
242					}},
243				},
244			},
245			wantUpdate: ClusterUpdate{
246				ClusterName: clusterName,
247				ClusterType: ClusterTypeLogicalDNS,
248				DNSHostName: "dns_host:8080",
249			},
250		},
251		{
252			name: "happy-case-aggregate-v3",
253			cluster: &v3clusterpb.Cluster{
254				Name: clusterName,
255				ClusterDiscoveryType: &v3clusterpb.Cluster_ClusterType{
256					ClusterType: &v3clusterpb.Cluster_CustomClusterType{
257						Name: "envoy.clusters.aggregate",
258						TypedConfig: testutils.MarshalAny(&v3aggregateclusterpb.ClusterConfig{
259							Clusters: []string{"a", "b", "c"},
260						}),
261					},
262				},
263				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
264			},
265			wantUpdate: ClusterUpdate{
266				ClusterName: clusterName, EnableLRS: false, ClusterType: ClusterTypeAggregate,
267				PrioritizedClusterNames: []string{"a", "b", "c"},
268			},
269		},
270		{
271			name: "happy-case-no-service-name-no-lrs",
272			cluster: &v3clusterpb.Cluster{
273				Name:                 clusterName,
274				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
275				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
276					EdsConfig: &v3corepb.ConfigSource{
277						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
278							Ads: &v3corepb.AggregatedConfigSource{},
279						},
280					},
281				},
282				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
283			},
284			wantUpdate: emptyUpdate,
285		},
286		{
287			name: "happy-case-no-lrs",
288			cluster: &v3clusterpb.Cluster{
289				Name:                 clusterName,
290				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
291				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
292					EdsConfig: &v3corepb.ConfigSource{
293						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
294							Ads: &v3corepb.AggregatedConfigSource{},
295						},
296					},
297					ServiceName: serviceName,
298				},
299				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
300			},
301			wantUpdate: ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: false},
302		},
303		{
304			name: "happiest-case",
305			cluster: &v3clusterpb.Cluster{
306				Name:                 clusterName,
307				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
308				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
309					EdsConfig: &v3corepb.ConfigSource{
310						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
311							Ads: &v3corepb.AggregatedConfigSource{},
312						},
313					},
314					ServiceName: serviceName,
315				},
316				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
317				LrsServer: &v3corepb.ConfigSource{
318					ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
319						Self: &v3corepb.SelfConfigSource{},
320					},
321				},
322			},
323			wantUpdate: ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true},
324		},
325		{
326			name: "happiest-case-with-circuitbreakers",
327			cluster: &v3clusterpb.Cluster{
328				Name:                 clusterName,
329				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
330				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
331					EdsConfig: &v3corepb.ConfigSource{
332						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
333							Ads: &v3corepb.AggregatedConfigSource{},
334						},
335					},
336					ServiceName: serviceName,
337				},
338				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
339				CircuitBreakers: &v3clusterpb.CircuitBreakers{
340					Thresholds: []*v3clusterpb.CircuitBreakers_Thresholds{
341						{
342							Priority:    v3corepb.RoutingPriority_DEFAULT,
343							MaxRequests: wrapperspb.UInt32(512),
344						},
345						{
346							Priority:    v3corepb.RoutingPriority_HIGH,
347							MaxRequests: nil,
348						},
349					},
350				},
351				LrsServer: &v3corepb.ConfigSource{
352					ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
353						Self: &v3corepb.SelfConfigSource{},
354					},
355				},
356			},
357			wantUpdate: ClusterUpdate{ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true, MaxRequests: func() *uint32 { i := uint32(512); return &i }()},
358		},
359		{
360			name: "happiest-case-with-ring-hash-lb-policy-with-default-config",
361			cluster: &v3clusterpb.Cluster{
362				Name:                 clusterName,
363				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
364				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
365					EdsConfig: &v3corepb.ConfigSource{
366						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
367							Ads: &v3corepb.AggregatedConfigSource{},
368						},
369					},
370					ServiceName: serviceName,
371				},
372				LbPolicy: v3clusterpb.Cluster_RING_HASH,
373				LrsServer: &v3corepb.ConfigSource{
374					ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
375						Self: &v3corepb.SelfConfigSource{},
376					},
377				},
378			},
379			wantUpdate: ClusterUpdate{
380				ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true,
381				LBPolicy: &ClusterLBPolicyRingHash{MinimumRingSize: defaultRingHashMinSize, MaximumRingSize: defaultRingHashMaxSize},
382			},
383		},
384		{
385			name: "happiest-case-with-ring-hash-lb-policy-with-none-default-config",
386			cluster: &v3clusterpb.Cluster{
387				Name:                 clusterName,
388				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
389				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
390					EdsConfig: &v3corepb.ConfigSource{
391						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
392							Ads: &v3corepb.AggregatedConfigSource{},
393						},
394					},
395					ServiceName: serviceName,
396				},
397				LbPolicy: v3clusterpb.Cluster_RING_HASH,
398				LbConfig: &v3clusterpb.Cluster_RingHashLbConfig_{
399					RingHashLbConfig: &v3clusterpb.Cluster_RingHashLbConfig{
400						MinimumRingSize: wrapperspb.UInt64(10),
401						MaximumRingSize: wrapperspb.UInt64(100),
402					},
403				},
404				LrsServer: &v3corepb.ConfigSource{
405					ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
406						Self: &v3corepb.SelfConfigSource{},
407					},
408				},
409			},
410			wantUpdate: ClusterUpdate{
411				ClusterName: clusterName, EDSServiceName: serviceName, EnableLRS: true,
412				LBPolicy: &ClusterLBPolicyRingHash{MinimumRingSize: 10, MaximumRingSize: 100},
413			},
414		},
415	}
416
417	oldAggregateAndDNSSupportEnv := env.AggregateAndDNSSupportEnv
418	env.AggregateAndDNSSupportEnv = true
419	defer func() { env.AggregateAndDNSSupportEnv = oldAggregateAndDNSSupportEnv }()
420	oldRingHashSupport := env.RingHashSupport
421	env.RingHashSupport = true
422	defer func() { env.RingHashSupport = oldRingHashSupport }()
423	for _, test := range tests {
424		t.Run(test.name, func(t *testing.T) {
425			update, err := validateClusterAndConstructClusterUpdate(test.cluster)
426			if err != nil {
427				t.Errorf("validateClusterAndConstructClusterUpdate(%+v) failed: %v", test.cluster, err)
428			}
429			if diff := cmp.Diff(update, test.wantUpdate, cmpopts.EquateEmpty()); diff != "" {
430				t.Errorf("validateClusterAndConstructClusterUpdate(%+v) got diff: %v (-got, +want)", test.cluster, diff)
431			}
432		})
433	}
434}
435
436func (s) TestValidateClusterWithSecurityConfig_EnvVarOff(t *testing.T) {
437	// Turn off the env var protection for client-side security.
438	origClientSideSecurityEnvVar := env.ClientSideSecuritySupport
439	env.ClientSideSecuritySupport = false
440	defer func() { env.ClientSideSecuritySupport = origClientSideSecurityEnvVar }()
441
442	cluster := &v3clusterpb.Cluster{
443		Name:                 clusterName,
444		ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
445		EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
446			EdsConfig: &v3corepb.ConfigSource{
447				ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
448					Ads: &v3corepb.AggregatedConfigSource{},
449				},
450			},
451			ServiceName: serviceName,
452		},
453		LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
454		TransportSocket: &v3corepb.TransportSocket{
455			Name: "envoy.transport_sockets.tls",
456			ConfigType: &v3corepb.TransportSocket_TypedConfig{
457				TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
458					CommonTlsContext: &v3tlspb.CommonTlsContext{
459						ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
460							ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
461								InstanceName:    "rootInstance",
462								CertificateName: "rootCert",
463							},
464						},
465					},
466				}),
467			},
468		},
469	}
470	wantUpdate := ClusterUpdate{
471		ClusterName:    clusterName,
472		EDSServiceName: serviceName,
473		EnableLRS:      false,
474	}
475	gotUpdate, err := validateClusterAndConstructClusterUpdate(cluster)
476	if err != nil {
477		t.Errorf("validateClusterAndConstructClusterUpdate() failed: %v", err)
478	}
479	if diff := cmp.Diff(wantUpdate, gotUpdate); diff != "" {
480		t.Errorf("validateClusterAndConstructClusterUpdate() returned unexpected diff (-want, got):\n%s", diff)
481	}
482}
483
484func (s) TestSecurityConfigFromCommonTLSContextUsingNewFields_ErrorCases(t *testing.T) {
485	tests := []struct {
486		name    string
487		common  *v3tlspb.CommonTlsContext
488		server  bool
489		wantErr string
490	}{
491		{
492			name: "unsupported-tls_certificates-field-for-identity-certs",
493			common: &v3tlspb.CommonTlsContext{
494				TlsCertificates: []*v3tlspb.TlsCertificate{
495					{CertificateChain: &v3corepb.DataSource{}},
496				},
497			},
498			wantErr: "unsupported field tls_certificates is set in CommonTlsContext message",
499		},
500		{
501			name: "unsupported-tls_certificates_sds_secret_configs-field-for-identity-certs",
502			common: &v3tlspb.CommonTlsContext{
503				TlsCertificateSdsSecretConfigs: []*v3tlspb.SdsSecretConfig{
504					{Name: "sds-secrets-config"},
505				},
506			},
507			wantErr: "unsupported field tls_certificate_sds_secret_configs is set in CommonTlsContext message",
508		},
509		{
510			name: "unsupported-sds-validation-context",
511			common: &v3tlspb.CommonTlsContext{
512				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{
513					ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{
514						Name: "foo-sds-secret",
515					},
516				},
517			},
518			wantErr: "validation context contains unexpected type",
519		},
520		{
521			name: "missing-ca_certificate_provider_instance-in-validation-context",
522			common: &v3tlspb.CommonTlsContext{
523				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
524					ValidationContext: &v3tlspb.CertificateValidationContext{},
525				},
526			},
527			wantErr: "expected field ca_certificate_provider_instance is missing in CommonTlsContext message",
528		},
529		{
530			name: "unsupported-field-verify_certificate_spki-in-validation-context",
531			common: &v3tlspb.CommonTlsContext{
532				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
533					ValidationContext: &v3tlspb.CertificateValidationContext{
534						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
535							InstanceName:    "rootPluginInstance",
536							CertificateName: "rootCertName",
537						},
538						VerifyCertificateSpki: []string{"spki"},
539					},
540				},
541			},
542			wantErr: "unsupported verify_certificate_spki field in CommonTlsContext message",
543		},
544		{
545			name: "unsupported-field-verify_certificate_hash-in-validation-context",
546			common: &v3tlspb.CommonTlsContext{
547				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
548					ValidationContext: &v3tlspb.CertificateValidationContext{
549						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
550							InstanceName:    "rootPluginInstance",
551							CertificateName: "rootCertName",
552						},
553						VerifyCertificateHash: []string{"hash"},
554					},
555				},
556			},
557			wantErr: "unsupported verify_certificate_hash field in CommonTlsContext message",
558		},
559		{
560			name: "unsupported-field-require_signed_certificate_timestamp-in-validation-context",
561			common: &v3tlspb.CommonTlsContext{
562				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
563					ValidationContext: &v3tlspb.CertificateValidationContext{
564						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
565							InstanceName:    "rootPluginInstance",
566							CertificateName: "rootCertName",
567						},
568						RequireSignedCertificateTimestamp: &wrapperspb.BoolValue{Value: true},
569					},
570				},
571			},
572			wantErr: "unsupported require_sugned_ceritificate_timestamp field in CommonTlsContext message",
573		},
574		{
575			name: "unsupported-field-crl-in-validation-context",
576			common: &v3tlspb.CommonTlsContext{
577				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
578					ValidationContext: &v3tlspb.CertificateValidationContext{
579						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
580							InstanceName:    "rootPluginInstance",
581							CertificateName: "rootCertName",
582						},
583						Crl: &v3corepb.DataSource{},
584					},
585				},
586			},
587			wantErr: "unsupported crl field in CommonTlsContext message",
588		},
589		{
590			name: "unsupported-field-custom_validator_config-in-validation-context",
591			common: &v3tlspb.CommonTlsContext{
592				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
593					ValidationContext: &v3tlspb.CertificateValidationContext{
594						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
595							InstanceName:    "rootPluginInstance",
596							CertificateName: "rootCertName",
597						},
598						CustomValidatorConfig: &v3corepb.TypedExtensionConfig{},
599					},
600				},
601			},
602			wantErr: "unsupported custom_validator_config field in CommonTlsContext message",
603		},
604		{
605			name: "invalid-match_subject_alt_names-field-in-validation-context",
606			common: &v3tlspb.CommonTlsContext{
607				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
608					ValidationContext: &v3tlspb.CertificateValidationContext{
609						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
610							InstanceName:    "rootPluginInstance",
611							CertificateName: "rootCertName",
612						},
613						MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
614							{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}},
615						},
616					},
617				},
618			},
619			wantErr: "empty prefix is not allowed in StringMatcher",
620		},
621		{
622			name: "unsupported-field-matching-subject-alt-names-in-validation-context-of-server",
623			common: &v3tlspb.CommonTlsContext{
624				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
625					ValidationContext: &v3tlspb.CertificateValidationContext{
626						CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
627							InstanceName:    "rootPluginInstance",
628							CertificateName: "rootCertName",
629						},
630						MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
631							{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "sanPrefix"}},
632						},
633					},
634				},
635			},
636			server:  true,
637			wantErr: "match_subject_alt_names field in validation context is not supported on the server",
638		},
639	}
640
641	for _, test := range tests {
642		t.Run(test.name, func(t *testing.T) {
643			_, err := securityConfigFromCommonTLSContextUsingNewFields(test.common, test.server)
644			if err == nil {
645				t.Fatal("securityConfigFromCommonTLSContextUsingNewFields() succeeded when expected to fail")
646			}
647			if !strings.Contains(err.Error(), test.wantErr) {
648				t.Fatalf("securityConfigFromCommonTLSContextUsingNewFields() returned err: %v, wantErr: %v", err, test.wantErr)
649			}
650		})
651	}
652}
653
654func (s) TestValidateClusterWithSecurityConfig(t *testing.T) {
655	const (
656		identityPluginInstance = "identityPluginInstance"
657		identityCertName       = "identityCert"
658		rootPluginInstance     = "rootPluginInstance"
659		rootCertName           = "rootCert"
660		clusterName            = "cluster"
661		serviceName            = "service"
662		sanExact               = "san-exact"
663		sanPrefix              = "san-prefix"
664		sanSuffix              = "san-suffix"
665		sanRegexBad            = "??"
666		sanRegexGood           = "san?regex?"
667		sanContains            = "san-contains"
668	)
669	var sanRE = regexp.MustCompile(sanRegexGood)
670
671	tests := []struct {
672		name       string
673		cluster    *v3clusterpb.Cluster
674		wantUpdate ClusterUpdate
675		wantErr    bool
676	}{
677		{
678			name: "transport-socket-matches",
679			cluster: &v3clusterpb.Cluster{
680				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
681				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
682					EdsConfig: &v3corepb.ConfigSource{
683						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
684							Ads: &v3corepb.AggregatedConfigSource{},
685						},
686					},
687					ServiceName: serviceName,
688				},
689				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
690				TransportSocketMatches: []*v3clusterpb.Cluster_TransportSocketMatch{
691					{Name: "transport-socket-match-1"},
692				},
693			},
694			wantErr: true,
695		},
696		{
697			name: "transport-socket-unsupported-name",
698			cluster: &v3clusterpb.Cluster{
699				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
700				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
701					EdsConfig: &v3corepb.ConfigSource{
702						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
703							Ads: &v3corepb.AggregatedConfigSource{},
704						},
705					},
706					ServiceName: serviceName,
707				},
708				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
709				TransportSocket: &v3corepb.TransportSocket{
710					Name: "unsupported-foo",
711					ConfigType: &v3corepb.TransportSocket_TypedConfig{
712						TypedConfig: &anypb.Any{
713							TypeUrl: version.V3UpstreamTLSContextURL,
714						},
715					},
716				},
717			},
718			wantErr: true,
719		},
720		{
721			name: "transport-socket-unsupported-typeURL",
722			cluster: &v3clusterpb.Cluster{
723				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
724				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
725					EdsConfig: &v3corepb.ConfigSource{
726						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
727							Ads: &v3corepb.AggregatedConfigSource{},
728						},
729					},
730					ServiceName: serviceName,
731				},
732				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
733				TransportSocket: &v3corepb.TransportSocket{
734					ConfigType: &v3corepb.TransportSocket_TypedConfig{
735						TypedConfig: &anypb.Any{
736							TypeUrl: version.V3HTTPConnManagerURL,
737						},
738					},
739				},
740			},
741			wantErr: true,
742		},
743		{
744			name: "transport-socket-unsupported-type",
745			cluster: &v3clusterpb.Cluster{
746				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
747				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
748					EdsConfig: &v3corepb.ConfigSource{
749						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
750							Ads: &v3corepb.AggregatedConfigSource{},
751						},
752					},
753					ServiceName: serviceName,
754				},
755				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
756				TransportSocket: &v3corepb.TransportSocket{
757					ConfigType: &v3corepb.TransportSocket_TypedConfig{
758						TypedConfig: &anypb.Any{
759							TypeUrl: version.V3UpstreamTLSContextURL,
760							Value:   []byte{1, 2, 3, 4},
761						},
762					},
763				},
764			},
765			wantErr: true,
766		},
767		{
768			name: "transport-socket-unsupported-tls-params-field",
769			cluster: &v3clusterpb.Cluster{
770				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
771				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
772					EdsConfig: &v3corepb.ConfigSource{
773						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
774							Ads: &v3corepb.AggregatedConfigSource{},
775						},
776					},
777					ServiceName: serviceName,
778				},
779				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
780				TransportSocket: &v3corepb.TransportSocket{
781					ConfigType: &v3corepb.TransportSocket_TypedConfig{
782						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
783							CommonTlsContext: &v3tlspb.CommonTlsContext{
784								TlsParams: &v3tlspb.TlsParameters{},
785							},
786						}),
787					},
788				},
789			},
790			wantErr: true,
791		},
792		{
793			name: "transport-socket-unsupported-custom-handshaker-field",
794			cluster: &v3clusterpb.Cluster{
795				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
796				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
797					EdsConfig: &v3corepb.ConfigSource{
798						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
799							Ads: &v3corepb.AggregatedConfigSource{},
800						},
801					},
802					ServiceName: serviceName,
803				},
804				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
805				TransportSocket: &v3corepb.TransportSocket{
806					ConfigType: &v3corepb.TransportSocket_TypedConfig{
807						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
808							CommonTlsContext: &v3tlspb.CommonTlsContext{
809								CustomHandshaker: &v3corepb.TypedExtensionConfig{},
810							},
811						}),
812					},
813				},
814			},
815			wantErr: true,
816		},
817		{
818			name: "transport-socket-unsupported-validation-context",
819			cluster: &v3clusterpb.Cluster{
820				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
821				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
822					EdsConfig: &v3corepb.ConfigSource{
823						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
824							Ads: &v3corepb.AggregatedConfigSource{},
825						},
826					},
827					ServiceName: serviceName,
828				},
829				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
830				TransportSocket: &v3corepb.TransportSocket{
831					ConfigType: &v3corepb.TransportSocket_TypedConfig{
832						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
833							CommonTlsContext: &v3tlspb.CommonTlsContext{
834								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextSdsSecretConfig{
835									ValidationContextSdsSecretConfig: &v3tlspb.SdsSecretConfig{
836										Name: "foo-sds-secret",
837									},
838								},
839							},
840						}),
841					},
842				},
843			},
844			wantErr: true,
845		},
846		{
847			name: "transport-socket-without-validation-context",
848			cluster: &v3clusterpb.Cluster{
849				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
850				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
851					EdsConfig: &v3corepb.ConfigSource{
852						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
853							Ads: &v3corepb.AggregatedConfigSource{},
854						},
855					},
856					ServiceName: serviceName,
857				},
858				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
859				TransportSocket: &v3corepb.TransportSocket{
860					ConfigType: &v3corepb.TransportSocket_TypedConfig{
861						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
862							CommonTlsContext: &v3tlspb.CommonTlsContext{},
863						}),
864					},
865				},
866			},
867			wantErr: true,
868		},
869		{
870			name: "empty-prefix-in-matching-SAN",
871			cluster: &v3clusterpb.Cluster{
872				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
873				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
874					EdsConfig: &v3corepb.ConfigSource{
875						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
876							Ads: &v3corepb.AggregatedConfigSource{},
877						},
878					},
879					ServiceName: serviceName,
880				},
881				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
882				TransportSocket: &v3corepb.TransportSocket{
883					ConfigType: &v3corepb.TransportSocket_TypedConfig{
884						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
885							CommonTlsContext: &v3tlspb.CommonTlsContext{
886								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
887									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
888										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
889											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
890												{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: ""}},
891											},
892										},
893										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
894											InstanceName:    rootPluginInstance,
895											CertificateName: rootCertName,
896										},
897									},
898								},
899							},
900						}),
901					},
902				},
903			},
904			wantErr: true,
905		},
906		{
907			name: "empty-suffix-in-matching-SAN",
908			cluster: &v3clusterpb.Cluster{
909				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
910				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
911					EdsConfig: &v3corepb.ConfigSource{
912						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
913							Ads: &v3corepb.AggregatedConfigSource{},
914						},
915					},
916					ServiceName: serviceName,
917				},
918				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
919				TransportSocket: &v3corepb.TransportSocket{
920					ConfigType: &v3corepb.TransportSocket_TypedConfig{
921						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
922							CommonTlsContext: &v3tlspb.CommonTlsContext{
923								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
924									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
925										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
926											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
927												{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: ""}},
928											},
929										},
930										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
931											InstanceName:    rootPluginInstance,
932											CertificateName: rootCertName,
933										},
934									},
935								},
936							},
937						}),
938					},
939				},
940			},
941			wantErr: true,
942		},
943		{
944			name: "empty-contains-in-matching-SAN",
945			cluster: &v3clusterpb.Cluster{
946				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
947				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
948					EdsConfig: &v3corepb.ConfigSource{
949						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
950							Ads: &v3corepb.AggregatedConfigSource{},
951						},
952					},
953					ServiceName: serviceName,
954				},
955				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
956				TransportSocket: &v3corepb.TransportSocket{
957					ConfigType: &v3corepb.TransportSocket_TypedConfig{
958						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
959							CommonTlsContext: &v3tlspb.CommonTlsContext{
960								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
961									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
962										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
963											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
964												{MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: ""}},
965											},
966										},
967										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
968											InstanceName:    rootPluginInstance,
969											CertificateName: rootCertName,
970										},
971									},
972								},
973							},
974						}),
975					},
976				},
977			},
978			wantErr: true,
979		},
980		{
981			name: "invalid-regex-in-matching-SAN",
982			cluster: &v3clusterpb.Cluster{
983				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
984				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
985					EdsConfig: &v3corepb.ConfigSource{
986						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
987							Ads: &v3corepb.AggregatedConfigSource{},
988						},
989					},
990					ServiceName: serviceName,
991				},
992				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
993				TransportSocket: &v3corepb.TransportSocket{
994					ConfigType: &v3corepb.TransportSocket_TypedConfig{
995						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
996							CommonTlsContext: &v3tlspb.CommonTlsContext{
997								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
998									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
999										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
1000											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
1001												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}},
1002											},
1003										},
1004										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1005											InstanceName:    rootPluginInstance,
1006											CertificateName: rootCertName,
1007										},
1008									},
1009								},
1010							},
1011						}),
1012					},
1013				},
1014			},
1015			wantErr: true,
1016		},
1017		{
1018			name: "invalid-regex-in-matching-SAN-with-new-fields",
1019			cluster: &v3clusterpb.Cluster{
1020				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1021				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1022					EdsConfig: &v3corepb.ConfigSource{
1023						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1024							Ads: &v3corepb.AggregatedConfigSource{},
1025						},
1026					},
1027					ServiceName: serviceName,
1028				},
1029				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1030				TransportSocket: &v3corepb.TransportSocket{
1031					ConfigType: &v3corepb.TransportSocket_TypedConfig{
1032						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
1033							CommonTlsContext: &v3tlspb.CommonTlsContext{
1034								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
1035									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
1036										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
1037											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
1038												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexBad}}},
1039											},
1040											CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
1041												InstanceName:    rootPluginInstance,
1042												CertificateName: rootCertName,
1043											},
1044										},
1045									},
1046								},
1047							},
1048						}),
1049					},
1050				},
1051			},
1052			wantErr: true,
1053		},
1054		{
1055			name: "happy-case-with-no-identity-certs-using-deprecated-fields",
1056			cluster: &v3clusterpb.Cluster{
1057				Name:                 clusterName,
1058				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1059				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1060					EdsConfig: &v3corepb.ConfigSource{
1061						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1062							Ads: &v3corepb.AggregatedConfigSource{},
1063						},
1064					},
1065					ServiceName: serviceName,
1066				},
1067				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1068				TransportSocket: &v3corepb.TransportSocket{
1069					Name: "envoy.transport_sockets.tls",
1070					ConfigType: &v3corepb.TransportSocket_TypedConfig{
1071						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
1072							CommonTlsContext: &v3tlspb.CommonTlsContext{
1073								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
1074									ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1075										InstanceName:    rootPluginInstance,
1076										CertificateName: rootCertName,
1077									},
1078								},
1079							},
1080						}),
1081					},
1082				},
1083			},
1084			wantUpdate: ClusterUpdate{
1085				ClusterName:    clusterName,
1086				EDSServiceName: serviceName,
1087				EnableLRS:      false,
1088				SecurityCfg: &SecurityConfig{
1089					RootInstanceName: rootPluginInstance,
1090					RootCertName:     rootCertName,
1091				},
1092			},
1093		},
1094		{
1095			name: "happy-case-with-no-identity-certs-using-new-fields",
1096			cluster: &v3clusterpb.Cluster{
1097				Name:                 clusterName,
1098				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1099				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1100					EdsConfig: &v3corepb.ConfigSource{
1101						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1102							Ads: &v3corepb.AggregatedConfigSource{},
1103						},
1104					},
1105					ServiceName: serviceName,
1106				},
1107				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1108				TransportSocket: &v3corepb.TransportSocket{
1109					Name: "envoy.transport_sockets.tls",
1110					ConfigType: &v3corepb.TransportSocket_TypedConfig{
1111						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
1112							CommonTlsContext: &v3tlspb.CommonTlsContext{
1113								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
1114									ValidationContext: &v3tlspb.CertificateValidationContext{
1115										CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
1116											InstanceName:    rootPluginInstance,
1117											CertificateName: rootCertName,
1118										},
1119									},
1120								},
1121							},
1122						}),
1123					},
1124				},
1125			},
1126			wantUpdate: ClusterUpdate{
1127				ClusterName:    clusterName,
1128				EDSServiceName: serviceName,
1129				EnableLRS:      false,
1130				SecurityCfg: &SecurityConfig{
1131					RootInstanceName: rootPluginInstance,
1132					RootCertName:     rootCertName,
1133				},
1134			},
1135		},
1136		{
1137			name: "happy-case-with-validation-context-provider-instance-using-deprecated-fields",
1138			cluster: &v3clusterpb.Cluster{
1139				Name:                 clusterName,
1140				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1141				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1142					EdsConfig: &v3corepb.ConfigSource{
1143						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1144							Ads: &v3corepb.AggregatedConfigSource{},
1145						},
1146					},
1147					ServiceName: serviceName,
1148				},
1149				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1150				TransportSocket: &v3corepb.TransportSocket{
1151					Name: "envoy.transport_sockets.tls",
1152					ConfigType: &v3corepb.TransportSocket_TypedConfig{
1153						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
1154							CommonTlsContext: &v3tlspb.CommonTlsContext{
1155								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1156									InstanceName:    identityPluginInstance,
1157									CertificateName: identityCertName,
1158								},
1159								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
1160									ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1161										InstanceName:    rootPluginInstance,
1162										CertificateName: rootCertName,
1163									},
1164								},
1165							},
1166						}),
1167					},
1168				},
1169			},
1170			wantUpdate: ClusterUpdate{
1171				ClusterName:    clusterName,
1172				EDSServiceName: serviceName,
1173				EnableLRS:      false,
1174				SecurityCfg: &SecurityConfig{
1175					RootInstanceName:     rootPluginInstance,
1176					RootCertName:         rootCertName,
1177					IdentityInstanceName: identityPluginInstance,
1178					IdentityCertName:     identityCertName,
1179				},
1180			},
1181		},
1182		{
1183			name: "happy-case-with-validation-context-provider-instance-using-new-fields",
1184			cluster: &v3clusterpb.Cluster{
1185				Name:                 clusterName,
1186				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1187				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1188					EdsConfig: &v3corepb.ConfigSource{
1189						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1190							Ads: &v3corepb.AggregatedConfigSource{},
1191						},
1192					},
1193					ServiceName: serviceName,
1194				},
1195				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1196				TransportSocket: &v3corepb.TransportSocket{
1197					Name: "envoy.transport_sockets.tls",
1198					ConfigType: &v3corepb.TransportSocket_TypedConfig{
1199						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
1200							CommonTlsContext: &v3tlspb.CommonTlsContext{
1201								TlsCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
1202									InstanceName:    identityPluginInstance,
1203									CertificateName: identityCertName,
1204								},
1205								ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
1206									ValidationContext: &v3tlspb.CertificateValidationContext{
1207										CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
1208											InstanceName:    rootPluginInstance,
1209											CertificateName: rootCertName,
1210										},
1211									},
1212								},
1213							},
1214						}),
1215					},
1216				},
1217			},
1218			wantUpdate: ClusterUpdate{
1219				ClusterName:    clusterName,
1220				EDSServiceName: serviceName,
1221				EnableLRS:      false,
1222				SecurityCfg: &SecurityConfig{
1223					RootInstanceName:     rootPluginInstance,
1224					RootCertName:         rootCertName,
1225					IdentityInstanceName: identityPluginInstance,
1226					IdentityCertName:     identityCertName,
1227				},
1228			},
1229		},
1230		{
1231			name: "happy-case-with-combined-validation-context-using-deprecated-fields",
1232			cluster: &v3clusterpb.Cluster{
1233				Name:                 clusterName,
1234				ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1235				EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1236					EdsConfig: &v3corepb.ConfigSource{
1237						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1238							Ads: &v3corepb.AggregatedConfigSource{},
1239						},
1240					},
1241					ServiceName: serviceName,
1242				},
1243				LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1244				TransportSocket: &v3corepb.TransportSocket{
1245					Name: "envoy.transport_sockets.tls",
1246					ConfigType: &v3corepb.TransportSocket_TypedConfig{
1247						TypedConfig: testutils.MarshalAny(&v3tlspb.UpstreamTlsContext{
1248							CommonTlsContext: &v3tlspb.CommonTlsContext{
1249								TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1250									InstanceName:    identityPluginInstance,
1251									CertificateName: identityCertName,
1252								},
1253								ValidationContextType: &v3tlspb.CommonTlsContext_CombinedValidationContext{
1254									CombinedValidationContext: &v3tlspb.CommonTlsContext_CombinedCertificateValidationContext{
1255										DefaultValidationContext: &v3tlspb.CertificateValidationContext{
1256											MatchSubjectAltNames: []*v3matcherpb.StringMatcher{
1257												{
1258													MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: sanExact},
1259													IgnoreCase:   true,
1260												},
1261												{MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: sanPrefix}},
1262												{MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: sanSuffix}},
1263												{MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: sanRegexGood}}},
1264												{MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: sanContains}},
1265											},
1266										},
1267										ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
1268											InstanceName:    rootPluginInstance,
1269											CertificateName: rootCertName,
1270										},
1271									},
1272								},
1273							},
1274						}),
1275					},
1276				},
1277			},
1278			wantUpdate: ClusterUpdate{
1279				ClusterName:    clusterName,
1280				EDSServiceName: serviceName,
1281				EnableLRS:      false,
1282				SecurityCfg: &SecurityConfig{
1283					RootInstanceName:     rootPluginInstance,
1284					RootCertName:         rootCertName,
1285					IdentityInstanceName: identityPluginInstance,
1286					IdentityCertName:     identityCertName,
1287					SubjectAltNameMatchers: []matcher.StringMatcher{
1288						matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true),
1289						matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false),
1290						matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false),
1291						matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false),
1292						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
1293					},
1294				},
1295			},
1296		},
1297		{
1298			name: "happy-case-with-combined-validation-context-using-new-fields",
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(&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											CaCertificateProviderInstance: &v3tlspb.CertificateProviderPluginInstance{
1334												InstanceName:    rootPluginInstance,
1335												CertificateName: rootCertName,
1336											},
1337										},
1338									},
1339								},
1340							},
1341						}),
1342					},
1343				},
1344			},
1345			wantUpdate: ClusterUpdate{
1346				ClusterName:    clusterName,
1347				EDSServiceName: serviceName,
1348				EnableLRS:      false,
1349				SecurityCfg: &SecurityConfig{
1350					RootInstanceName:     rootPluginInstance,
1351					RootCertName:         rootCertName,
1352					IdentityInstanceName: identityPluginInstance,
1353					IdentityCertName:     identityCertName,
1354					SubjectAltNameMatchers: []matcher.StringMatcher{
1355						matcher.StringMatcherForTesting(newStringP(sanExact), nil, nil, nil, nil, true),
1356						matcher.StringMatcherForTesting(nil, newStringP(sanPrefix), nil, nil, nil, false),
1357						matcher.StringMatcherForTesting(nil, nil, newStringP(sanSuffix), nil, nil, false),
1358						matcher.StringMatcherForTesting(nil, nil, nil, nil, sanRE, false),
1359						matcher.StringMatcherForTesting(nil, nil, nil, newStringP(sanContains), nil, false),
1360					},
1361				},
1362			},
1363		},
1364	}
1365
1366	for _, test := range tests {
1367		t.Run(test.name, func(t *testing.T) {
1368			update, err := validateClusterAndConstructClusterUpdate(test.cluster)
1369			if (err != nil) != test.wantErr {
1370				t.Errorf("validateClusterAndConstructClusterUpdate() returned err %v wantErr %v)", err, test.wantErr)
1371			}
1372			if diff := cmp.Diff(test.wantUpdate, update, cmpopts.EquateEmpty(), cmp.AllowUnexported(regexp.Regexp{})); diff != "" {
1373				t.Errorf("validateClusterAndConstructClusterUpdate() returned unexpected diff (-want, +got):\n%s", diff)
1374			}
1375		})
1376	}
1377}
1378
1379func (s) TestUnmarshalCluster(t *testing.T) {
1380	const (
1381		v2ClusterName = "v2clusterName"
1382		v3ClusterName = "v3clusterName"
1383		v2Service     = "v2Service"
1384		v3Service     = "v2Service"
1385	)
1386	var (
1387		v2ClusterAny = testutils.MarshalAny(&v2xdspb.Cluster{
1388			Name:                 v2ClusterName,
1389			ClusterDiscoveryType: &v2xdspb.Cluster_Type{Type: v2xdspb.Cluster_EDS},
1390			EdsClusterConfig: &v2xdspb.Cluster_EdsClusterConfig{
1391				EdsConfig: &v2corepb.ConfigSource{
1392					ConfigSourceSpecifier: &v2corepb.ConfigSource_Ads{
1393						Ads: &v2corepb.AggregatedConfigSource{},
1394					},
1395				},
1396				ServiceName: v2Service,
1397			},
1398			LbPolicy: v2xdspb.Cluster_ROUND_ROBIN,
1399			LrsServer: &v2corepb.ConfigSource{
1400				ConfigSourceSpecifier: &v2corepb.ConfigSource_Self{
1401					Self: &v2corepb.SelfConfigSource{},
1402				},
1403			},
1404		})
1405
1406		v3ClusterAny = testutils.MarshalAny(&v3clusterpb.Cluster{
1407			Name:                 v3ClusterName,
1408			ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS},
1409			EdsClusterConfig: &v3clusterpb.Cluster_EdsClusterConfig{
1410				EdsConfig: &v3corepb.ConfigSource{
1411					ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
1412						Ads: &v3corepb.AggregatedConfigSource{},
1413					},
1414				},
1415				ServiceName: v3Service,
1416			},
1417			LbPolicy: v3clusterpb.Cluster_ROUND_ROBIN,
1418			LrsServer: &v3corepb.ConfigSource{
1419				ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
1420					Self: &v3corepb.SelfConfigSource{},
1421				},
1422			},
1423		})
1424	)
1425	const testVersion = "test-version-cds"
1426
1427	tests := []struct {
1428		name       string
1429		resources  []*anypb.Any
1430		wantUpdate map[string]ClusterUpdateErrTuple
1431		wantMD     UpdateMetadata
1432		wantErr    bool
1433	}{
1434		{
1435			name:      "non-cluster resource type",
1436			resources: []*anypb.Any{{TypeUrl: version.V3HTTPConnManagerURL}},
1437			wantMD: UpdateMetadata{
1438				Status:  ServiceStatusNACKed,
1439				Version: testVersion,
1440				ErrState: &UpdateErrorMetadata{
1441					Version: testVersion,
1442					Err:     cmpopts.AnyError,
1443				},
1444			},
1445			wantErr: true,
1446		},
1447		{
1448			name: "badly marshaled cluster resource",
1449			resources: []*anypb.Any{
1450				{
1451					TypeUrl: version.V3ClusterURL,
1452					Value:   []byte{1, 2, 3, 4},
1453				},
1454			},
1455			wantMD: UpdateMetadata{
1456				Status:  ServiceStatusNACKed,
1457				Version: testVersion,
1458				ErrState: &UpdateErrorMetadata{
1459					Version: testVersion,
1460					Err:     cmpopts.AnyError,
1461				},
1462			},
1463			wantErr: true,
1464		},
1465		{
1466			name: "bad cluster resource",
1467			resources: []*anypb.Any{
1468				testutils.MarshalAny(&v3clusterpb.Cluster{
1469					Name:                 "test",
1470					ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
1471				}),
1472			},
1473			wantUpdate: map[string]ClusterUpdateErrTuple{
1474				"test": {Err: cmpopts.AnyError},
1475			},
1476			wantMD: UpdateMetadata{
1477				Status:  ServiceStatusNACKed,
1478				Version: testVersion,
1479				ErrState: &UpdateErrorMetadata{
1480					Version: testVersion,
1481					Err:     cmpopts.AnyError,
1482				},
1483			},
1484			wantErr: true,
1485		},
1486		{
1487			name:      "v2 cluster",
1488			resources: []*anypb.Any{v2ClusterAny},
1489			wantUpdate: map[string]ClusterUpdateErrTuple{
1490				v2ClusterName: {Update: ClusterUpdate{
1491					ClusterName:    v2ClusterName,
1492					EDSServiceName: v2Service, EnableLRS: true,
1493					Raw: v2ClusterAny,
1494				}},
1495			},
1496			wantMD: UpdateMetadata{
1497				Status:  ServiceStatusACKed,
1498				Version: testVersion,
1499			},
1500		},
1501		{
1502			name:      "v3 cluster",
1503			resources: []*anypb.Any{v3ClusterAny},
1504			wantUpdate: map[string]ClusterUpdateErrTuple{
1505				v3ClusterName: {Update: ClusterUpdate{
1506					ClusterName:    v3ClusterName,
1507					EDSServiceName: v3Service, EnableLRS: true,
1508					Raw: v3ClusterAny,
1509				}},
1510			},
1511			wantMD: UpdateMetadata{
1512				Status:  ServiceStatusACKed,
1513				Version: testVersion,
1514			},
1515		},
1516		{
1517			name:      "multiple clusters",
1518			resources: []*anypb.Any{v2ClusterAny, v3ClusterAny},
1519			wantUpdate: map[string]ClusterUpdateErrTuple{
1520				v2ClusterName: {Update: ClusterUpdate{
1521					ClusterName:    v2ClusterName,
1522					EDSServiceName: v2Service, EnableLRS: true,
1523					Raw: v2ClusterAny,
1524				}},
1525				v3ClusterName: {Update: ClusterUpdate{
1526					ClusterName:    v3ClusterName,
1527					EDSServiceName: v3Service, EnableLRS: true,
1528					Raw: v3ClusterAny,
1529				}},
1530			},
1531			wantMD: UpdateMetadata{
1532				Status:  ServiceStatusACKed,
1533				Version: testVersion,
1534			},
1535		},
1536		{
1537			// To test that unmarshal keeps processing on errors.
1538			name: "good and bad clusters",
1539			resources: []*anypb.Any{
1540				v2ClusterAny,
1541				// bad cluster resource
1542				testutils.MarshalAny(&v3clusterpb.Cluster{
1543					Name:                 "bad",
1544					ClusterDiscoveryType: &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_STATIC},
1545				}),
1546				v3ClusterAny,
1547			},
1548			wantUpdate: map[string]ClusterUpdateErrTuple{
1549				v2ClusterName: {Update: ClusterUpdate{
1550					ClusterName:    v2ClusterName,
1551					EDSServiceName: v2Service, EnableLRS: true,
1552					Raw: v2ClusterAny,
1553				}},
1554				v3ClusterName: {Update: ClusterUpdate{
1555					ClusterName:    v3ClusterName,
1556					EDSServiceName: v3Service, EnableLRS: true,
1557					Raw: v3ClusterAny,
1558				}},
1559				"bad": {Err: cmpopts.AnyError},
1560			},
1561			wantMD: UpdateMetadata{
1562				Status:  ServiceStatusNACKed,
1563				Version: testVersion,
1564				ErrState: &UpdateErrorMetadata{
1565					Version: testVersion,
1566					Err:     cmpopts.AnyError,
1567				},
1568			},
1569			wantErr: true,
1570		},
1571	}
1572	for _, test := range tests {
1573		t.Run(test.name, func(t *testing.T) {
1574			update, md, err := UnmarshalCluster(testVersion, test.resources, nil)
1575			if (err != nil) != test.wantErr {
1576				t.Fatalf("UnmarshalCluster(), got err: %v, wantErr: %v", err, test.wantErr)
1577			}
1578			if diff := cmp.Diff(update, test.wantUpdate, cmpOpts); diff != "" {
1579				t.Errorf("got unexpected update, diff (-got +want): %v", diff)
1580			}
1581			if diff := cmp.Diff(md, test.wantMD, cmpOptsIgnoreDetails); diff != "" {
1582				t.Errorf("got unexpected metadata, diff (-got +want): %v", diff)
1583			}
1584		})
1585	}
1586}
1587