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