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