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