1// Copyright 2017 Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package validation
16
17import (
18	"fmt"
19	"strings"
20	"testing"
21	"time"
22
23	"github.com/gogo/protobuf/proto"
24	"github.com/gogo/protobuf/types"
25	"github.com/hashicorp/go-multierror"
26
27	authn "istio.io/api/authentication/v1alpha1"
28	meshconfig "istio.io/api/mesh/v1alpha1"
29	mpb "istio.io/api/mixer/v1"
30	mccpb "istio.io/api/mixer/v1/config/client"
31	networking "istio.io/api/networking/v1alpha3"
32	rbac "istio.io/api/rbac/v1alpha1"
33	security_beta "istio.io/api/security/v1beta1"
34	api "istio.io/api/type/v1beta1"
35
36	"istio.io/istio/pkg/config/constants"
37)
38
39const (
40	// Config name for testing
41	someName = "foo"
42	// Config namespace for testing.
43	someNamespace = "bar"
44)
45
46func TestValidateFQDN(t *testing.T) {
47	tests := []struct {
48		fqdn  string
49		valid bool
50		name  string
51	}{
52		{
53			fqdn:  strings.Repeat("x", 256),
54			valid: false,
55			name:  "long FQDN",
56		},
57		{
58			fqdn:  "",
59			valid: false,
60			name:  "empty FQDN",
61		},
62		{
63			fqdn:  "istio.io",
64			valid: true,
65			name:  "standard FQDN",
66		},
67		{
68			fqdn:  "istio.io.",
69			valid: true,
70			name:  "unambiguous FQDN",
71		},
72		{
73			fqdn:  "istio-pilot.istio-system.svc.cluster.local",
74			valid: true,
75			name:  "standard kubernetes FQDN",
76		},
77		{
78			fqdn:  "istio-pilot.istio-system.svc.cluster.local.",
79			valid: true,
80			name:  "unambiguous kubernetes FQDN",
81		},
82	}
83	for _, tt := range tests {
84		tt := tt
85		t.Run(tt.name, func(t *testing.T) {
86			err := ValidateFQDN(tt.fqdn)
87			valid := err == nil
88			if valid != tt.valid {
89				t.Errorf("Expected valid=%v, got valid=%v for %v", tt.valid, valid, tt.fqdn)
90			}
91		})
92
93	}
94}
95
96func TestValidateWildcardDomain(t *testing.T) {
97	tests := []struct {
98		name string
99		in   string
100		out  string
101	}{
102		{"empty", "", "empty"},
103		{"too long", strings.Repeat("x", 256), "too long"},
104		{"happy", strings.Repeat("x", 63), ""},
105		{"wildcard", "*", ""},
106		{"wildcard multi-segment", "*.bar.com", ""},
107		{"wildcard single segment", "*foo", ""},
108		{"wildcard prefix", "*foo.bar.com", ""},
109		{"wildcard prefix dash", "*-foo.bar.com", ""},
110		{"bad wildcard", "foo.*.com", "invalid"},
111		{"bad wildcard", "foo*.bar.com", "invalid"},
112		{"IP address", "1.1.1.1", "invalid"},
113	}
114	for _, tt := range tests {
115		t.Run(tt.name, func(t *testing.T) {
116			err := ValidateWildcardDomain(tt.in)
117			if err == nil && tt.out != "" {
118				t.Fatalf("ValidateWildcardDomain(%v) = nil, wanted %q", tt.in, tt.out)
119			} else if err != nil && tt.out == "" {
120				t.Fatalf("ValidateWildcardDomain(%v) = %v, wanted nil", tt.in, err)
121			} else if err != nil && !strings.Contains(err.Error(), tt.out) {
122				t.Fatalf("ValidateWildcardDomain(%v) = %v, wanted %q", tt.in, err, tt.out)
123			}
124		})
125	}
126}
127
128func TestValidatePort(t *testing.T) {
129	ports := map[int]bool{
130		0:     false,
131		65536: false,
132		-1:    false,
133		100:   true,
134		1000:  true,
135		65535: true,
136	}
137	for port, valid := range ports {
138		if got := ValidatePort(port); (got == nil) != valid {
139			t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %d", got == nil, valid, got, port)
140		}
141	}
142}
143
144func TestValidateProxyAddress(t *testing.T) {
145	addresses := map[string]bool{
146		"istio-pilot:80":        true,
147		"istio-pilot":           false,
148		"isti..:80":             false,
149		"10.0.0.100:9090":       true,
150		"10.0.0.100":            false,
151		"istio-pilot:port":      false,
152		"istio-pilot:100000":    false,
153		"[2001:db8::100]:80":    true,
154		"[2001:db8::10::20]:80": false,
155		"[2001:db8::100]":       false,
156		"[2001:db8::100]:port":  false,
157		"2001:db8::100:80":      false,
158	}
159	for addr, valid := range addresses {
160		if got := ValidateProxyAddress(addr); (got == nil) != valid {
161			t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %s", got == nil, valid, got, addr)
162		}
163	}
164}
165
166func TestValidateDuration(t *testing.T) {
167	type durationCheck struct {
168		duration *types.Duration
169		isValid  bool
170	}
171
172	checks := []durationCheck{
173		{
174			duration: &types.Duration{Seconds: 1},
175			isValid:  true,
176		},
177		{
178			duration: &types.Duration{Seconds: 1, Nanos: -1},
179			isValid:  false,
180		},
181		{
182			duration: &types.Duration{Seconds: -11, Nanos: -1},
183			isValid:  false,
184		},
185		{
186			duration: &types.Duration{Nanos: 1},
187			isValid:  false,
188		},
189		{
190			duration: &types.Duration{Seconds: 1, Nanos: 1},
191			isValid:  false,
192		},
193	}
194
195	for _, check := range checks {
196		if got := ValidateDuration(check.duration); (got == nil) != check.isValid {
197			t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %v", got == nil, check.isValid, got, check.duration)
198		}
199	}
200}
201
202func TestValidateParentAndDrain(t *testing.T) {
203	type ParentDrainTime struct {
204		Parent types.Duration
205		Drain  types.Duration
206		Valid  bool
207	}
208
209	combinations := []ParentDrainTime{
210		{
211			Parent: types.Duration{Seconds: 2},
212			Drain:  types.Duration{Seconds: 1},
213			Valid:  true,
214		},
215		{
216			Parent: types.Duration{Seconds: 1},
217			Drain:  types.Duration{Seconds: 1},
218			Valid:  false,
219		},
220		{
221			Parent: types.Duration{Seconds: 1},
222			Drain:  types.Duration{Seconds: 2},
223			Valid:  false,
224		},
225		{
226			Parent: types.Duration{Seconds: 2},
227			Drain:  types.Duration{Seconds: 1, Nanos: 1000000},
228			Valid:  false,
229		},
230		{
231			Parent: types.Duration{Seconds: 2, Nanos: 1000000},
232			Drain:  types.Duration{Seconds: 1},
233			Valid:  false,
234		},
235		{
236			Parent: types.Duration{Seconds: -2},
237			Drain:  types.Duration{Seconds: 1},
238			Valid:  false,
239		},
240		{
241			Parent: types.Duration{Seconds: 2},
242			Drain:  types.Duration{Seconds: -1},
243			Valid:  false,
244		},
245		{
246			Parent: types.Duration{Seconds: 1 + int64(time.Hour/time.Second)},
247			Drain:  types.Duration{Seconds: 10},
248			Valid:  false,
249		},
250		{
251			Parent: types.Duration{Seconds: 10},
252			Drain:  types.Duration{Seconds: 1 + int64(time.Hour/time.Second)},
253			Valid:  false,
254		},
255	}
256	for _, combo := range combinations {
257		if got := ValidateParentAndDrain(&combo.Drain, &combo.Parent); (got == nil) != combo.Valid {
258			t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for Parent:%v Drain:%v",
259				got == nil, combo.Valid, got, combo.Parent, combo.Drain)
260		}
261	}
262}
263
264func TestValidateConnectTimeout(t *testing.T) {
265	type durationCheck struct {
266		duration *types.Duration
267		isValid  bool
268	}
269
270	checks := []durationCheck{
271		{
272			duration: &types.Duration{Seconds: 1},
273			isValid:  true,
274		},
275		{
276			duration: &types.Duration{Seconds: 31},
277			isValid:  false,
278		},
279		{
280			duration: &types.Duration{Nanos: 99999},
281			isValid:  false,
282		},
283	}
284
285	for _, check := range checks {
286		if got := ValidateConnectTimeout(check.duration); (got == nil) != check.isValid {
287			t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %v", got == nil, check.isValid, got, check.duration)
288		}
289	}
290}
291
292func TestValidateProtocolDetectionTimeout(t *testing.T) {
293	type durationCheck struct {
294		duration *types.Duration
295		isValid  bool
296	}
297
298	checks := []durationCheck{
299		{
300			duration: &types.Duration{Seconds: 1},
301			isValid:  true,
302		},
303		{
304			duration: &types.Duration{Nanos: 99999},
305			isValid:  false,
306		},
307		{
308			duration: &types.Duration{Nanos: 0},
309			isValid:  true,
310		},
311	}
312
313	for _, check := range checks {
314		if got := ValidateProtocolDetectionTimeout(check.duration); (got == nil) != check.isValid {
315			t.Errorf("Failed: got valid=%t but wanted valid=%t: %v for %v", got == nil, check.isValid, got, check.duration)
316		}
317	}
318}
319
320func TestValidateMeshConfig(t *testing.T) {
321	if ValidateMeshConfig(&meshconfig.MeshConfig{}) == nil {
322		t.Error("expected an error on an empty mesh config")
323	}
324
325	invalid := meshconfig.MeshConfig{
326		MixerCheckServer:  "10.0.0.100",
327		MixerReportServer: "10.0.0.100",
328		ProxyListenPort:   0,
329		ConnectTimeout:    types.DurationProto(-1 * time.Second),
330		DefaultConfig:     &meshconfig.ProxyConfig{},
331	}
332
333	err := ValidateMeshConfig(&invalid)
334	if err == nil {
335		t.Errorf("expected an error on invalid proxy mesh config: %v", invalid)
336	} else {
337		switch err := err.(type) {
338		case *multierror.Error:
339			// each field must cause an error in the field
340			if len(err.Errors) < 6 {
341				t.Errorf("expected an error for each field %v", err)
342			}
343		default:
344			t.Errorf("expected a multi error as output")
345		}
346	}
347}
348
349func TestValidateProxyConfig(t *testing.T) {
350	valid := &meshconfig.ProxyConfig{
351		ConfigPath:             "/etc/istio/proxy",
352		BinaryPath:             "/usr/local/bin/envoy",
353		DiscoveryAddress:       "istio-pilot.istio-system:15010",
354		ProxyAdminPort:         15000,
355		DrainDuration:          types.DurationProto(45 * time.Second),
356		ParentShutdownDuration: types.DurationProto(60 * time.Second),
357		ServiceCluster:         "istio-proxy",
358		StatsdUdpAddress:       "istio-statsd-prom-bridge.istio-system:9125",
359		EnvoyMetricsService:    &meshconfig.RemoteService{Address: "metrics-service.istio-system:15000"},
360		EnvoyAccessLogService:  &meshconfig.RemoteService{Address: "accesslog-service.istio-system:15000"},
361		ControlPlaneAuthPolicy: 1,
362		Tracing:                nil,
363	}
364
365	modify := func(config *meshconfig.ProxyConfig, fieldSetter func(*meshconfig.ProxyConfig)) *meshconfig.ProxyConfig {
366		clone := proto.Clone(config).(*meshconfig.ProxyConfig)
367		fieldSetter(clone)
368		return clone
369	}
370
371	cases := []struct {
372		name    string
373		in      *meshconfig.ProxyConfig
374		isValid bool
375	}{
376		{
377			name:    "empty proxy config",
378			in:      &meshconfig.ProxyConfig{},
379			isValid: false,
380		},
381		{
382			name:    "valid proxy config",
383			in:      valid,
384			isValid: true,
385		},
386		{
387			name:    "config path invalid",
388			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.ConfigPath = "" }),
389			isValid: false,
390		},
391		{
392			name:    "binary path invalid",
393			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.BinaryPath = "" }),
394			isValid: false,
395		},
396		{
397			name:    "discovery address invalid",
398			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.DiscoveryAddress = "10.0.0.100" }),
399			isValid: false,
400		},
401		{
402			name:    "proxy admin port invalid",
403			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.ProxyAdminPort = 0 }),
404			isValid: false,
405		},
406		{
407			name:    "proxy admin port invalid",
408			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.ProxyAdminPort = 0 }),
409			isValid: false,
410		},
411		{
412			name:    "drain duration invalid",
413			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.DrainDuration = types.DurationProto(-1 * time.Second) }),
414			isValid: false,
415		},
416		{
417			name:    "parent shutdown duration invalid",
418			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.ParentShutdownDuration = types.DurationProto(-1 * time.Second) }),
419			isValid: false,
420		},
421		{
422			name:    "service cluster invalid",
423			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.ServiceCluster = "" }),
424			isValid: false,
425		},
426		{
427			name:    "statsd udp address invalid",
428			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.StatsdUdpAddress = "10.0.0.100" }),
429			isValid: false,
430		},
431		{
432			name: "envoy metrics service address invalid",
433			in: modify(valid, func(c *meshconfig.ProxyConfig) {
434				c.EnvoyMetricsService = &meshconfig.RemoteService{Address: "metrics-service.istio-system"}
435			}),
436			isValid: false,
437		},
438		{
439			name: "envoy access log service address invalid",
440			in: modify(valid, func(c *meshconfig.ProxyConfig) {
441				c.EnvoyAccessLogService = &meshconfig.RemoteService{Address: "accesslog-service.istio-system"}
442			}),
443			isValid: false,
444		},
445		{
446			name:    "control plane auth policy invalid",
447			in:      modify(valid, func(c *meshconfig.ProxyConfig) { c.ControlPlaneAuthPolicy = -1 }),
448			isValid: false,
449		},
450		{
451			name: "zipkin address is valid",
452			in: modify(valid,
453				func(c *meshconfig.ProxyConfig) {
454					c.Tracing = &meshconfig.Tracing{
455						Tracer: &meshconfig.Tracing_Zipkin_{
456							Zipkin: &meshconfig.Tracing_Zipkin{
457								Address: "zipkin.istio-system:9411",
458							},
459						},
460					}
461				},
462			),
463			isValid: true,
464		},
465		{
466			name: "zipkin config invalid",
467			in: modify(valid,
468				func(c *meshconfig.ProxyConfig) {
469					c.Tracing = &meshconfig.Tracing{
470						Tracer: &meshconfig.Tracing_Zipkin_{
471							Zipkin: &meshconfig.Tracing_Zipkin{
472								Address: "10.0.0.100",
473							},
474						},
475					}
476				},
477			),
478			isValid: false,
479		},
480		{
481			name: "lightstep config is valid",
482			in: modify(valid,
483				func(c *meshconfig.ProxyConfig) {
484					c.Tracing = &meshconfig.Tracing{
485						Tracer: &meshconfig.Tracing_Lightstep_{
486							Lightstep: &meshconfig.Tracing_Lightstep{
487								Address:     "collector.lightstep:8080",
488								AccessToken: "abcdefg1234567",
489							},
490						},
491					}
492				},
493			),
494			isValid: true,
495		},
496		{
497			name: "lightstep address invalid",
498			in: modify(valid,
499				func(c *meshconfig.ProxyConfig) {
500					c.Tracing = &meshconfig.Tracing{
501						Tracer: &meshconfig.Tracing_Lightstep_{
502							Lightstep: &meshconfig.Tracing_Lightstep{
503								Address:     "10.0.0.100",
504								AccessToken: "abcdefg1234567",
505							},
506						},
507					}
508				},
509			),
510			isValid: false,
511		},
512		{
513			name: "lightstep address empty but lightstep access token is not",
514			in: modify(valid,
515				func(c *meshconfig.ProxyConfig) {
516					c.Tracing = &meshconfig.Tracing{
517						Tracer: &meshconfig.Tracing_Lightstep_{
518							Lightstep: &meshconfig.Tracing_Lightstep{
519								Address:     "",
520								AccessToken: "abcdefg1234567",
521							},
522						},
523					}
524				},
525			),
526			isValid: false,
527		},
528		{
529			name: "lightstep address is valid but access token is empty",
530			in: modify(valid,
531				func(c *meshconfig.ProxyConfig) {
532					c.Tracing = &meshconfig.Tracing{
533						Tracer: &meshconfig.Tracing_Lightstep_{
534							Lightstep: &meshconfig.Tracing_Lightstep{
535								Address:     "collector.lightstep:8080",
536								AccessToken: "",
537							},
538						},
539					}
540				},
541			),
542			isValid: false,
543		},
544		{
545			name: "lightstep access token empty but lightstep address is not",
546			in: modify(valid,
547				func(c *meshconfig.ProxyConfig) {
548					c.Tracing = &meshconfig.Tracing{
549						Tracer: &meshconfig.Tracing_Lightstep_{
550							Lightstep: &meshconfig.Tracing_Lightstep{
551								Address:     "10.0.0.100",
552								AccessToken: "",
553							},
554						},
555					}
556				},
557			),
558			isValid: false,
559		},
560		{
561			name: "lightstep address and lightstep token both empty",
562			in: modify(valid,
563				func(c *meshconfig.ProxyConfig) {
564					c.Tracing = &meshconfig.Tracing{
565						Tracer: &meshconfig.Tracing_Lightstep_{
566							Lightstep: &meshconfig.Tracing_Lightstep{
567								Address:     "",
568								AccessToken: "",
569							},
570						},
571					}
572				},
573			),
574			isValid: false,
575		},
576		{
577			name: "datadog without address",
578			in: modify(valid,
579				func(c *meshconfig.ProxyConfig) {
580					c.Tracing = &meshconfig.Tracing{
581						Tracer: &meshconfig.Tracing_Datadog_{
582							Datadog: &meshconfig.Tracing_Datadog{},
583						},
584					}
585				},
586			),
587			isValid: false,
588		},
589		{
590			name: "datadog with correct address",
591			in: modify(valid,
592				func(c *meshconfig.ProxyConfig) {
593					c.Tracing = &meshconfig.Tracing{
594						Tracer: &meshconfig.Tracing_Datadog_{
595							Datadog: &meshconfig.Tracing_Datadog{
596								Address: "datadog-agent:8126",
597							},
598						},
599					}
600				},
601			),
602			isValid: true,
603		},
604		{
605			name: "datadog with invalid address",
606			in: modify(valid,
607				func(c *meshconfig.ProxyConfig) {
608					c.Tracing = &meshconfig.Tracing{
609						Tracer: &meshconfig.Tracing_Datadog_{
610							Datadog: &meshconfig.Tracing_Datadog{
611								Address: "address-missing-port-number",
612							},
613						},
614					}
615				},
616			),
617			isValid: false,
618		},
619	}
620	for _, c := range cases {
621		t.Run(c.name, func(t *testing.T) {
622			if got := ValidateProxyConfig(c.in); (got == nil) != c.isValid {
623				if c.isValid {
624					t.Errorf("got error %v, wanted none", got)
625				} else {
626					t.Error("got no error, wanted one")
627				}
628			}
629		})
630	}
631
632	invalid := meshconfig.ProxyConfig{
633		ConfigPath:             "",
634		BinaryPath:             "",
635		DiscoveryAddress:       "10.0.0.100",
636		ProxyAdminPort:         0,
637		DrainDuration:          types.DurationProto(-1 * time.Second),
638		ParentShutdownDuration: types.DurationProto(-1 * time.Second),
639		ServiceCluster:         "",
640		StatsdUdpAddress:       "10.0.0.100",
641		EnvoyMetricsService:    &meshconfig.RemoteService{Address: "metrics-service"},
642		EnvoyAccessLogService:  &meshconfig.RemoteService{Address: "accesslog-service"},
643		ControlPlaneAuthPolicy: -1,
644		Tracing: &meshconfig.Tracing{
645			Tracer: &meshconfig.Tracing_Zipkin_{
646				Zipkin: &meshconfig.Tracing_Zipkin{
647					Address: "10.0.0.100",
648				},
649			},
650		},
651	}
652
653	err := ValidateProxyConfig(&invalid)
654	if err == nil {
655		t.Errorf("expected an error on invalid proxy mesh config: %v", invalid)
656	} else {
657		switch err := err.(type) {
658		case *multierror.Error:
659			// each field must cause an error in the field
660			if len(err.Errors) != 12 {
661				t.Errorf("expected an error for each field %v", err)
662			}
663		default:
664			t.Errorf("expected a multi error as output")
665		}
666	}
667}
668
669var (
670	validService    = &mccpb.IstioService{Service: "*cnn.com"}
671	validAttributes = &mpb.Attributes{
672		Attributes: map[string]*mpb.Attributes_AttributeValue{
673			"api.service": {Value: &mpb.Attributes_AttributeValue_StringValue{"my-service"}},
674		},
675	}
676	invalidAttributes = &mpb.Attributes{
677		Attributes: map[string]*mpb.Attributes_AttributeValue{
678			"api.service": {Value: &mpb.Attributes_AttributeValue_StringValue{""}},
679		},
680	}
681)
682
683func TestValidateMixerAttributes(t *testing.T) {
684	cases := []struct {
685		name  string
686		in    *mpb.Attributes_AttributeValue
687		valid bool
688	}{
689		{"happy string",
690			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringValue{"my-service"}},
691			true},
692		{"invalid string",
693			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringValue{""}},
694			false},
695		{"happy duration",
696			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_DurationValue{&types.Duration{Seconds: 1}}},
697			true},
698		{"invalid duration",
699			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_DurationValue{&types.Duration{Nanos: -1e9}}},
700			false},
701		{"happy bytes",
702			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_BytesValue{[]byte{1, 2, 3}}},
703			true},
704		{"invalid bytes",
705			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_BytesValue{[]byte{}}},
706			false},
707		{"happy timestamp",
708			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_TimestampValue{&types.Timestamp{}}},
709			true},
710		{"invalid timestamp",
711			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_TimestampValue{&types.Timestamp{Nanos: -1}}},
712			false},
713		{"nil timestamp",
714			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_TimestampValue{nil}},
715			false},
716		{"happy stringmap",
717			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringMapValue{
718				&mpb.Attributes_StringMap{Entries: map[string]string{"foo": "bar"}}}},
719			true},
720		{"invalid stringmap",
721			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringMapValue{
722				&mpb.Attributes_StringMap{Entries: nil}}},
723			false},
724		{"nil stringmap",
725			&mpb.Attributes_AttributeValue{Value: &mpb.Attributes_AttributeValue_StringMapValue{nil}},
726			false},
727	}
728	for _, c := range cases {
729		t.Run(c.name, func(t *testing.T) {
730			attrs := &mpb.Attributes{
731				Attributes: map[string]*mpb.Attributes_AttributeValue{"key": c.in},
732			}
733			if got := ValidateMixerAttributes(attrs); (got == nil) != c.valid {
734				if c.valid {
735					t.Fatal("got error, wanted none")
736				} else {
737					t.Fatal("got no error, wanted one")
738				}
739			}
740		})
741	}
742}
743
744func TestValidateHTTPAPISpec(t *testing.T) {
745	var (
746		validPattern = &mccpb.HTTPAPISpecPattern{
747			Attributes: validAttributes,
748			HttpMethod: "POST",
749			Pattern: &mccpb.HTTPAPISpecPattern_UriTemplate{
750				UriTemplate: "/pet/{id}",
751			},
752		}
753		invalidPatternHTTPMethod = &mccpb.HTTPAPISpecPattern{
754			Attributes: validAttributes,
755			Pattern: &mccpb.HTTPAPISpecPattern_UriTemplate{
756				UriTemplate: "/pet/{id}",
757			},
758		}
759		invalidPatternURITemplate = &mccpb.HTTPAPISpecPattern{
760			Attributes: validAttributes,
761			HttpMethod: "POST",
762			Pattern:    &mccpb.HTTPAPISpecPattern_UriTemplate{},
763		}
764		invalidPatternRegex = &mccpb.HTTPAPISpecPattern{
765			Attributes: validAttributes,
766			HttpMethod: "POST",
767			Pattern:    &mccpb.HTTPAPISpecPattern_Regex{},
768		}
769		validAPIKey         = &mccpb.APIKey{Key: &mccpb.APIKey_Query{"api_key"}}
770		invalidAPIKeyQuery  = &mccpb.APIKey{Key: &mccpb.APIKey_Query{}}
771		invalidAPIKeyHeader = &mccpb.APIKey{Key: &mccpb.APIKey_Header{}}
772		invalidAPIKeyCookie = &mccpb.APIKey{Key: &mccpb.APIKey_Cookie{}}
773	)
774
775	cases := []struct {
776		name  string
777		in    proto.Message
778		valid bool
779	}{
780		{
781			name: "missing pattern",
782			in: &mccpb.HTTPAPISpec{
783				Attributes: validAttributes,
784				ApiKeys:    []*mccpb.APIKey{validAPIKey},
785			},
786		},
787		{
788			name: "invalid pattern (bad attributes)",
789			in: &mccpb.HTTPAPISpec{
790				Attributes: invalidAttributes,
791				Patterns:   []*mccpb.HTTPAPISpecPattern{validPattern},
792				ApiKeys:    []*mccpb.APIKey{validAPIKey},
793			},
794		},
795		{
796			name: "invalid pattern (bad http_method)",
797			in: &mccpb.HTTPAPISpec{
798				Attributes: validAttributes,
799				Patterns:   []*mccpb.HTTPAPISpecPattern{invalidPatternHTTPMethod},
800				ApiKeys:    []*mccpb.APIKey{validAPIKey},
801			},
802		},
803		{
804			name: "invalid pattern (missing uri_template)",
805			in: &mccpb.HTTPAPISpec{
806				Attributes: validAttributes,
807				Patterns:   []*mccpb.HTTPAPISpecPattern{invalidPatternURITemplate},
808				ApiKeys:    []*mccpb.APIKey{validAPIKey},
809			},
810		},
811		{
812			name: "invalid pattern (missing regex)",
813			in: &mccpb.HTTPAPISpec{
814				Attributes: validAttributes,
815				Patterns:   []*mccpb.HTTPAPISpecPattern{invalidPatternRegex},
816				ApiKeys:    []*mccpb.APIKey{validAPIKey},
817			},
818		},
819		{
820			name: "invalid api-key (missing query)",
821			in: &mccpb.HTTPAPISpec{
822				Attributes: validAttributes,
823				Patterns:   []*mccpb.HTTPAPISpecPattern{validPattern},
824				ApiKeys:    []*mccpb.APIKey{invalidAPIKeyQuery},
825			},
826		},
827		{
828			name: "invalid api-key (missing header)",
829			in: &mccpb.HTTPAPISpec{
830				Attributes: validAttributes,
831				Patterns:   []*mccpb.HTTPAPISpecPattern{validPattern},
832				ApiKeys:    []*mccpb.APIKey{invalidAPIKeyHeader},
833			},
834		},
835		{
836			name: "invalid api-key (missing cookie)",
837			in: &mccpb.HTTPAPISpec{
838				Attributes: validAttributes,
839				Patterns:   []*mccpb.HTTPAPISpecPattern{validPattern},
840				ApiKeys:    []*mccpb.APIKey{invalidAPIKeyCookie},
841			},
842		},
843		{
844			name: "valid",
845			in: &mccpb.HTTPAPISpec{
846				Attributes: validAttributes,
847				Patterns:   []*mccpb.HTTPAPISpecPattern{validPattern},
848				ApiKeys:    []*mccpb.APIKey{validAPIKey},
849			},
850			valid: true,
851		},
852		{
853			name: "invalid attribute (nil)",
854			in: &mccpb.HTTPAPISpec{
855				Attributes: &mpb.Attributes{
856					Attributes: map[string]*mpb.Attributes_AttributeValue{"": nil},
857				},
858			},
859			valid: false,
860		},
861	}
862	for _, c := range cases {
863		if got := ValidateHTTPAPISpec(someName, someNamespace, c.in); (got == nil) != c.valid {
864			t.Errorf("ValidateHTTPAPISpec(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got)
865		}
866	}
867}
868
869func TestValidateHTTPAPISpecBinding(t *testing.T) {
870	var (
871		validHTTPAPISpecRef   = &mccpb.HTTPAPISpecReference{Name: "foo", Namespace: "bar"}
872		invalidHTTPAPISpecRef = &mccpb.HTTPAPISpecReference{Name: "foo", Namespace: "--bar"}
873	)
874	cases := []struct {
875		name  string
876		in    proto.Message
877		valid bool
878	}{
879		{
880			name: "no service",
881			in: &mccpb.HTTPAPISpecBinding{
882				Services: []*mccpb.IstioService{},
883				ApiSpecs: []*mccpb.HTTPAPISpecReference{validHTTPAPISpecRef},
884			},
885		},
886		{
887			name: "no spec",
888			in: &mccpb.HTTPAPISpecBinding{
889				Services: []*mccpb.IstioService{validService},
890				ApiSpecs: []*mccpb.HTTPAPISpecReference{},
891			},
892		},
893		{
894			name: "invalid spec",
895			in: &mccpb.HTTPAPISpecBinding{
896				Services: []*mccpb.IstioService{validService},
897				ApiSpecs: []*mccpb.HTTPAPISpecReference{invalidHTTPAPISpecRef},
898			},
899		},
900		{
901			name: "valid",
902			in: &mccpb.HTTPAPISpecBinding{
903				Services: []*mccpb.IstioService{validService},
904				ApiSpecs: []*mccpb.HTTPAPISpecReference{validHTTPAPISpecRef},
905			},
906			valid: true,
907		},
908	}
909	for _, c := range cases {
910		if got := ValidateHTTPAPISpecBinding(someName, someNamespace, c.in); (got == nil) != c.valid {
911			t.Errorf("ValidateHTTPAPISpecBinding(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got)
912		}
913	}
914}
915
916func TestValidateQuotaSpec(t *testing.T) {
917	var (
918		validMatch = &mccpb.AttributeMatch{
919			Clause: map[string]*mccpb.StringMatch{
920				"api.operation": {
921					MatchType: &mccpb.StringMatch_Exact{
922						Exact: "getPet",
923					},
924				},
925			},
926		}
927		invalidMatchExact = &mccpb.AttributeMatch{
928			Clause: map[string]*mccpb.StringMatch{
929				"api.operation": {
930					MatchType: &mccpb.StringMatch_Exact{Exact: ""},
931				},
932			},
933		}
934		invalidMatchPrefix = &mccpb.AttributeMatch{
935			Clause: map[string]*mccpb.StringMatch{
936				"api.operation": {
937					MatchType: &mccpb.StringMatch_Prefix{Prefix: ""},
938				},
939			},
940		}
941		invalidMatchRegex = &mccpb.AttributeMatch{
942			Clause: map[string]*mccpb.StringMatch{
943				"api.operation": {
944					MatchType: &mccpb.StringMatch_Regex{Regex: ""},
945				},
946			},
947		}
948		invalidQuota = &mccpb.Quota{
949			Quota:  "",
950			Charge: 0,
951		}
952		validQuota = &mccpb.Quota{
953			Quota:  "myQuota",
954			Charge: 2,
955		}
956	)
957	cases := []struct {
958		name  string
959		in    proto.Message
960		valid bool
961	}{
962		{
963			name: "no rules",
964			in: &mccpb.QuotaSpec{
965				Rules: []*mccpb.QuotaRule{{}},
966			},
967		},
968		{
969			name: "invalid match (exact)",
970			in: &mccpb.QuotaSpec{
971				Rules: []*mccpb.QuotaRule{{
972					Match:  []*mccpb.AttributeMatch{invalidMatchExact},
973					Quotas: []*mccpb.Quota{validQuota},
974				}},
975			},
976		},
977		{
978			name: "invalid match (prefix)",
979			in: &mccpb.QuotaSpec{
980				Rules: []*mccpb.QuotaRule{{
981					Match:  []*mccpb.AttributeMatch{invalidMatchPrefix},
982					Quotas: []*mccpb.Quota{validQuota},
983				}},
984			},
985		},
986		{
987			name: "invalid match (regex)",
988			in: &mccpb.QuotaSpec{
989				Rules: []*mccpb.QuotaRule{{
990					Match:  []*mccpb.AttributeMatch{invalidMatchRegex},
991					Quotas: []*mccpb.Quota{validQuota},
992				}},
993			},
994		},
995		{
996			name: "no quota",
997			in: &mccpb.QuotaSpec{
998				Rules: []*mccpb.QuotaRule{{
999					Match:  []*mccpb.AttributeMatch{validMatch},
1000					Quotas: []*mccpb.Quota{},
1001				}},
1002			},
1003		},
1004		{
1005			name: "invalid quota/charge",
1006			in: &mccpb.QuotaSpec{
1007				Rules: []*mccpb.QuotaRule{{
1008					Match:  []*mccpb.AttributeMatch{validMatch},
1009					Quotas: []*mccpb.Quota{invalidQuota},
1010				}},
1011			},
1012		},
1013		{
1014			name: "valid",
1015			in: &mccpb.QuotaSpec{
1016				Rules: []*mccpb.QuotaRule{{
1017					Match:  []*mccpb.AttributeMatch{validMatch},
1018					Quotas: []*mccpb.Quota{validQuota},
1019				}},
1020			},
1021			valid: true,
1022		},
1023		{
1024			name: "regression test - nil clause",
1025			in: &mccpb.QuotaSpec{
1026				Rules: []*mccpb.QuotaRule{{
1027					Match: []*mccpb.AttributeMatch{{
1028						Clause: map[string]*mccpb.StringMatch{
1029							"": nil,
1030						},
1031					}},
1032				}},
1033			},
1034			valid: false,
1035		},
1036	}
1037	for _, c := range cases {
1038		if got := ValidateQuotaSpec(someName, someNamespace, c.in); (got == nil) != c.valid {
1039			t.Errorf("ValidateQuotaSpec(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got)
1040		}
1041	}
1042}
1043
1044func TestValidateQuotaSpecBinding(t *testing.T) {
1045	var (
1046		validQuotaSpecRef   = &mccpb.QuotaSpecBinding_QuotaSpecReference{Name: "foo", Namespace: "bar"}
1047		invalidQuotaSpecRef = &mccpb.QuotaSpecBinding_QuotaSpecReference{Name: "foo", Namespace: "--bar"}
1048	)
1049	cases := []struct {
1050		name  string
1051		in    proto.Message
1052		valid bool
1053	}{
1054		{
1055			name: "no service",
1056			in: &mccpb.QuotaSpecBinding{
1057				Services:   []*mccpb.IstioService{},
1058				QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{validQuotaSpecRef},
1059			},
1060		},
1061		{
1062			name: "no spec",
1063			in: &mccpb.QuotaSpecBinding{
1064				Services:   []*mccpb.IstioService{validService},
1065				QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{},
1066			},
1067		},
1068		{
1069			name: "invalid spec",
1070			in: &mccpb.QuotaSpecBinding{
1071				Services:   []*mccpb.IstioService{validService},
1072				QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{invalidQuotaSpecRef},
1073			},
1074		},
1075		{
1076			name: "valid",
1077			in: &mccpb.QuotaSpecBinding{
1078				Services:   []*mccpb.IstioService{validService},
1079				QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{validQuotaSpecRef},
1080			},
1081			valid: true,
1082		},
1083	}
1084	for _, c := range cases {
1085		if got := ValidateQuotaSpecBinding(someName, someNamespace, c.in); (got == nil) != c.valid {
1086			t.Errorf("ValidateQuotaSpecBinding(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got)
1087		}
1088	}
1089}
1090
1091func TestValidateGateway(t *testing.T) {
1092	tests := []struct {
1093		name string
1094		in   proto.Message
1095		out  string
1096	}{
1097		{"empty", &networking.Gateway{}, "server"},
1098		{"invalid message", &networking.Server{}, "cannot cast"},
1099		{"happy domain",
1100			&networking.Gateway{
1101				Servers: []*networking.Server{{
1102					Hosts: []string{"foo.bar.com"},
1103					Port:  &networking.Port{Name: "name1", Number: 7, Protocol: "http"},
1104				}},
1105			},
1106			""},
1107		{"happy multiple servers",
1108			&networking.Gateway{
1109				Servers: []*networking.Server{
1110					{
1111						Hosts: []string{"foo.bar.com"},
1112						Port:  &networking.Port{Name: "name1", Number: 7, Protocol: "http"},
1113					}},
1114			},
1115			""},
1116		{"invalid port",
1117			&networking.Gateway{
1118				Servers: []*networking.Server{
1119					{
1120						Hosts: []string{"foo.bar.com"},
1121						Port:  &networking.Port{Name: "name1", Number: 66000, Protocol: "http"},
1122					}},
1123			},
1124			"port"},
1125		{"duplicate port names",
1126			&networking.Gateway{
1127				Servers: []*networking.Server{
1128					{
1129						Hosts: []string{"foo.bar.com"},
1130						Port:  &networking.Port{Name: "foo", Number: 80, Protocol: "http"},
1131					},
1132					{
1133						Hosts: []string{"scooby.doo.com"},
1134						Port:  &networking.Port{Name: "foo", Number: 8080, Protocol: "http"},
1135					}},
1136			},
1137			"port names"},
1138		{"invalid domain",
1139			&networking.Gateway{
1140				Servers: []*networking.Server{
1141					{
1142						Hosts: []string{"foo.*.bar.com"},
1143						Port:  &networking.Port{Number: 7, Protocol: "http"},
1144					}},
1145			},
1146			"domain"},
1147	}
1148	for _, tt := range tests {
1149		t.Run(tt.name, func(t *testing.T) {
1150			err := ValidateGateway(someName, someNamespace, tt.in)
1151			if err == nil && tt.out != "" {
1152				t.Fatalf("ValidateGateway(%v) = nil, wanted %q", tt.in, tt.out)
1153			} else if err != nil && tt.out == "" {
1154				t.Fatalf("ValidateGateway(%v) = %v, wanted nil", tt.in, err)
1155			} else if err != nil && !strings.Contains(err.Error(), tt.out) {
1156				t.Fatalf("ValidateGateway(%v) = %v, wanted %q", tt.in, err, tt.out)
1157			}
1158		})
1159	}
1160}
1161
1162func TestValidateServer(t *testing.T) {
1163	tests := []struct {
1164		name string
1165		in   *networking.Server
1166		out  string
1167	}{
1168		{"empty", &networking.Server{}, "host"},
1169		{"empty", &networking.Server{}, "port"},
1170		{"happy",
1171			&networking.Server{
1172				Hosts: []string{"foo.bar.com"},
1173				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1174			},
1175			""},
1176		{"happy ip",
1177			&networking.Server{
1178				Hosts: []string{"1.1.1.1"},
1179				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1180			},
1181			""},
1182		{"happy ns/name",
1183			&networking.Server{
1184				Hosts: []string{"ns1/foo.bar.com"},
1185				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1186			},
1187			""},
1188		{"happy */name",
1189			&networking.Server{
1190				Hosts: []string{"*/foo.bar.com"},
1191				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1192			},
1193			""},
1194		{"happy ./name",
1195			&networking.Server{
1196				Hosts: []string{"./foo.bar.com"},
1197				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1198			},
1199			""},
1200		{"invalid domain ns/name format",
1201			&networking.Server{
1202				Hosts: []string{"ns1/foo.*.bar.com"},
1203				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1204			},
1205			"domain"},
1206		{"invalid domain",
1207			&networking.Server{
1208				Hosts: []string{"foo.*.bar.com"},
1209				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1210			},
1211			"domain"},
1212		{"invalid short name host",
1213			&networking.Server{
1214				Hosts: []string{"foo"},
1215				Port:  &networking.Port{Number: 7, Name: "http", Protocol: "http"},
1216			},
1217			"short names"},
1218		{"invalid port",
1219			&networking.Server{
1220				Hosts: []string{"foo.bar.com"},
1221				Port:  &networking.Port{Number: 66000, Name: "http", Protocol: "http"},
1222			},
1223			"port"},
1224		{"invalid tls options",
1225			&networking.Server{
1226				Hosts: []string{"foo.bar.com"},
1227				Port:  &networking.Port{Number: 1, Name: "http", Protocol: "http"},
1228				Tls:   &networking.ServerTLSSettings{Mode: networking.ServerTLSSettings_SIMPLE},
1229			},
1230			"TLS"},
1231		{"no tls on HTTPS",
1232			&networking.Server{
1233				Hosts: []string{"foo.bar.com"},
1234				Port:  &networking.Port{Number: 10000, Name: "https", Protocol: "https"},
1235			},
1236			"must have TLS"},
1237		{"tls on HTTP",
1238			&networking.Server{
1239				Hosts: []string{"foo.bar.com"},
1240				Port:  &networking.Port{Number: 10000, Name: "http", Protocol: "http"},
1241				Tls:   &networking.ServerTLSSettings{Mode: networking.ServerTLSSettings_SIMPLE},
1242			},
1243			"cannot have TLS"},
1244		{"tls redirect on HTTP",
1245			&networking.Server{
1246				Hosts: []string{"foo.bar.com"},
1247				Port:  &networking.Port{Number: 10000, Name: "http", Protocol: "http"},
1248				Tls: &networking.ServerTLSSettings{
1249					HttpsRedirect: true,
1250				},
1251			},
1252			""},
1253	}
1254	for _, tt := range tests {
1255		t.Run(tt.name, func(t *testing.T) {
1256			err := validateServer(tt.in)
1257			if err == nil && tt.out != "" {
1258				t.Fatalf("validateServer(%v) = nil, wanted %q", tt.in, tt.out)
1259			} else if err != nil && tt.out == "" {
1260				t.Fatalf("validateServer(%v) = %v, wanted nil", tt.in, err)
1261			} else if err != nil && !strings.Contains(err.Error(), tt.out) {
1262				t.Fatalf("validateServer(%v) = %v, wanted %q", tt.in, err, tt.out)
1263			}
1264		})
1265	}
1266}
1267
1268func TestValidateServerPort(t *testing.T) {
1269	tests := []struct {
1270		name string
1271		in   *networking.Port
1272		out  string
1273	}{
1274		{"empty", &networking.Port{}, "invalid protocol"},
1275		{"empty", &networking.Port{}, "port name"},
1276		{"happy",
1277			&networking.Port{
1278				Protocol: "http",
1279				Number:   1,
1280				Name:     "Henry",
1281			},
1282			""},
1283		{"invalid protocol",
1284			&networking.Port{
1285				Protocol: "kafka",
1286				Number:   1,
1287				Name:     "Henry",
1288			},
1289			"invalid protocol"},
1290		{"invalid number",
1291			&networking.Port{
1292				Protocol: "http",
1293				Number:   uint32(1 << 30),
1294				Name:     "http",
1295			},
1296			"port number"},
1297		{"name, no number",
1298			&networking.Port{
1299				Protocol: "http",
1300				Number:   0,
1301				Name:     "Henry",
1302			},
1303			""},
1304	}
1305	for _, tt := range tests {
1306		t.Run(tt.name, func(t *testing.T) {
1307			err := validateServerPort(tt.in)
1308			if err == nil && tt.out != "" {
1309				t.Fatalf("validateServerPort(%v) = nil, wanted %q", tt.in, tt.out)
1310			} else if err != nil && tt.out == "" {
1311				t.Fatalf("validateServerPort(%v) = %v, wanted nil", tt.in, err)
1312			} else if err != nil && !strings.Contains(err.Error(), tt.out) {
1313				t.Fatalf("validateServerPort(%v) = %v, wanted %q", tt.in, err, tt.out)
1314			}
1315		})
1316	}
1317}
1318
1319func TestValidateTlsOptions(t *testing.T) {
1320	tests := []struct {
1321		name string
1322		in   *networking.ServerTLSSettings
1323		out  string
1324	}{
1325		{"empty", &networking.ServerTLSSettings{}, ""},
1326		{"simple",
1327			&networking.ServerTLSSettings{
1328				Mode:              networking.ServerTLSSettings_SIMPLE,
1329				ServerCertificate: "Captain Jean-Luc Picard",
1330				PrivateKey:        "Khan Noonien Singh"},
1331			""},
1332		{"simple with client bundle",
1333			&networking.ServerTLSSettings{
1334				Mode:              networking.ServerTLSSettings_SIMPLE,
1335				ServerCertificate: "Captain Jean-Luc Picard",
1336				PrivateKey:        "Khan Noonien Singh",
1337				CaCertificates:    "Commander William T. Riker"},
1338			""},
1339		{"simple sds with client bundle",
1340			&networking.ServerTLSSettings{
1341				Mode:              networking.ServerTLSSettings_SIMPLE,
1342				ServerCertificate: "Captain Jean-Luc Picard",
1343				PrivateKey:        "Khan Noonien Singh",
1344				CaCertificates:    "Commander William T. Riker",
1345				CredentialName:    "sds-name"},
1346			""},
1347		{"simple no server cert",
1348			&networking.ServerTLSSettings{
1349				Mode:              networking.ServerTLSSettings_SIMPLE,
1350				ServerCertificate: "",
1351				PrivateKey:        "Khan Noonien Singh",
1352			},
1353			"server certificate"},
1354		{"simple no private key",
1355			&networking.ServerTLSSettings{
1356				Mode:              networking.ServerTLSSettings_SIMPLE,
1357				ServerCertificate: "Captain Jean-Luc Picard",
1358				PrivateKey:        ""},
1359			"private key"},
1360		{"simple sds no server cert",
1361			&networking.ServerTLSSettings{
1362				Mode:              networking.ServerTLSSettings_SIMPLE,
1363				ServerCertificate: "",
1364				PrivateKey:        "Khan Noonien Singh",
1365				CredentialName:    "sds-name",
1366			},
1367			""},
1368		{"simple sds no private key",
1369			&networking.ServerTLSSettings{
1370				Mode:              networking.ServerTLSSettings_SIMPLE,
1371				ServerCertificate: "Captain Jean-Luc Picard",
1372				PrivateKey:        "",
1373				CredentialName:    "sds-name",
1374			},
1375			""},
1376		{"mutual",
1377			&networking.ServerTLSSettings{
1378				Mode:              networking.ServerTLSSettings_MUTUAL,
1379				ServerCertificate: "Captain Jean-Luc Picard",
1380				PrivateKey:        "Khan Noonien Singh",
1381				CaCertificates:    "Commander William T. Riker"},
1382			""},
1383		{"mutual sds",
1384			&networking.ServerTLSSettings{
1385				Mode:              networking.ServerTLSSettings_MUTUAL,
1386				ServerCertificate: "Captain Jean-Luc Picard",
1387				PrivateKey:        "Khan Noonien Singh",
1388				CaCertificates:    "Commander William T. Riker",
1389				CredentialName:    "sds-name",
1390			},
1391			""},
1392		{"mutual no server cert",
1393			&networking.ServerTLSSettings{
1394				Mode:              networking.ServerTLSSettings_MUTUAL,
1395				ServerCertificate: "",
1396				PrivateKey:        "Khan Noonien Singh",
1397				CaCertificates:    "Commander William T. Riker"},
1398			"server certificate"},
1399		{"mutual sds no server cert",
1400			&networking.ServerTLSSettings{
1401				Mode:              networking.ServerTLSSettings_MUTUAL,
1402				ServerCertificate: "",
1403				PrivateKey:        "Khan Noonien Singh",
1404				CaCertificates:    "Commander William T. Riker",
1405				CredentialName:    "sds-name"},
1406			""},
1407		{"mutual no client CA bundle",
1408			&networking.ServerTLSSettings{
1409				Mode:              networking.ServerTLSSettings_MUTUAL,
1410				ServerCertificate: "Captain Jean-Luc Picard",
1411				PrivateKey:        "Khan Noonien Singh",
1412				CaCertificates:    ""},
1413			"client CA bundle"},
1414		// this pair asserts we get errors about both client and server certs missing when in mutual mode
1415		// and both are absent, but requires less rewriting of the testing harness than merging the cases
1416		{"mutual no certs",
1417			&networking.ServerTLSSettings{
1418				Mode:              networking.ServerTLSSettings_MUTUAL,
1419				ServerCertificate: "",
1420				PrivateKey:        "",
1421				CaCertificates:    ""},
1422			"server certificate"},
1423		{"mutual no certs",
1424			&networking.ServerTLSSettings{
1425				Mode:              networking.ServerTLSSettings_MUTUAL,
1426				ServerCertificate: "",
1427				PrivateKey:        "",
1428				CaCertificates:    ""},
1429			"private key"},
1430		{"mutual no certs",
1431			&networking.ServerTLSSettings{
1432				Mode:              networking.ServerTLSSettings_MUTUAL,
1433				ServerCertificate: "",
1434				PrivateKey:        "",
1435				CaCertificates:    ""},
1436			"client CA bundle"},
1437		{"pass through sds no certs",
1438			&networking.ServerTLSSettings{
1439				Mode:              networking.ServerTLSSettings_PASSTHROUGH,
1440				ServerCertificate: "",
1441				CaCertificates:    "",
1442				CredentialName:    "sds-name"},
1443			""},
1444		{"istio_mutual no certs",
1445			&networking.ServerTLSSettings{
1446				Mode:              networking.ServerTLSSettings_ISTIO_MUTUAL,
1447				ServerCertificate: "",
1448				PrivateKey:        "",
1449				CaCertificates:    ""},
1450			""},
1451		{"istio_mutual with server cert",
1452			&networking.ServerTLSSettings{
1453				Mode:              networking.ServerTLSSettings_ISTIO_MUTUAL,
1454				ServerCertificate: "Captain Jean-Luc Picard"},
1455			"cannot have associated server cert"},
1456		{"istio_mutual with client bundle",
1457			&networking.ServerTLSSettings{
1458				Mode:              networking.ServerTLSSettings_ISTIO_MUTUAL,
1459				ServerCertificate: "Captain Jean-Luc Picard",
1460				PrivateKey:        "Khan Noonien Singh",
1461				CaCertificates:    "Commander William T. Riker"},
1462			"cannot have associated"},
1463		{"istio_mutual with private key",
1464			&networking.ServerTLSSettings{
1465				Mode:       networking.ServerTLSSettings_ISTIO_MUTUAL,
1466				PrivateKey: "Khan Noonien Singh"},
1467			"cannot have associated private key"},
1468	}
1469
1470	for _, tt := range tests {
1471		t.Run(tt.name, func(t *testing.T) {
1472			err := validateTLSOptions(tt.in)
1473			if err == nil && tt.out != "" {
1474				t.Fatalf("validateTlsOptions(%v) = nil, wanted %q", tt.in, tt.out)
1475			} else if err != nil && tt.out == "" {
1476				t.Fatalf("validateTlsOptions(%v) = %v, wanted nil", tt.in, err)
1477			} else if err != nil && !strings.Contains(err.Error(), tt.out) {
1478				t.Fatalf("validateTlsOptions(%v) = %v, wanted %q", tt.in, err, tt.out)
1479			}
1480		})
1481	}
1482}
1483
1484func TestValidateHTTPHeaderName(t *testing.T) {
1485	testCases := []struct {
1486		name  string
1487		valid bool
1488	}{
1489		{name: "header1", valid: true},
1490		{name: "X-Requested-With", valid: true},
1491		{name: "", valid: false},
1492	}
1493
1494	for _, tc := range testCases {
1495		if got := ValidateHTTPHeaderName(tc.name); (got == nil) != tc.valid {
1496			t.Errorf("ValidateHTTPHeaderName(%q) => got valid=%v, want valid=%v",
1497				tc.name, got == nil, tc.valid)
1498		}
1499	}
1500}
1501
1502func TestValidateCORSPolicy(t *testing.T) {
1503	testCases := []struct {
1504		name  string
1505		in    *networking.CorsPolicy
1506		valid bool
1507	}{
1508		{name: "valid", in: &networking.CorsPolicy{
1509			AllowMethods:  []string{"GET", "POST"},
1510			AllowHeaders:  []string{"header1", "header2"},
1511			ExposeHeaders: []string{"header3"},
1512			MaxAge:        &types.Duration{Seconds: 2},
1513		}, valid: true},
1514		{name: "bad method", in: &networking.CorsPolicy{
1515			AllowMethods:  []string{"GET", "PUTT"},
1516			AllowHeaders:  []string{"header1", "header2"},
1517			ExposeHeaders: []string{"header3"},
1518			MaxAge:        &types.Duration{Seconds: 2},
1519		}, valid: false},
1520		{name: "bad header", in: &networking.CorsPolicy{
1521			AllowMethods:  []string{"GET", "POST"},
1522			AllowHeaders:  []string{"header1", "header2"},
1523			ExposeHeaders: []string{""},
1524			MaxAge:        &types.Duration{Seconds: 2},
1525		}, valid: false},
1526		{name: "bad max age", in: &networking.CorsPolicy{
1527			AllowMethods:  []string{"GET", "POST"},
1528			AllowHeaders:  []string{"header1", "header2"},
1529			ExposeHeaders: []string{"header3"},
1530			MaxAge:        &types.Duration{Seconds: 2, Nanos: 42},
1531		}, valid: false},
1532		{name: "empty matchType AllowOrigins", in: &networking.CorsPolicy{
1533			AllowOrigins: []*networking.StringMatch{
1534				{MatchType: &networking.StringMatch_Exact{Exact: ""}},
1535				{MatchType: &networking.StringMatch_Prefix{Prefix: ""}},
1536				{MatchType: &networking.StringMatch_Regex{Regex: ""}},
1537			},
1538			AllowMethods:  []string{"GET", "POST"},
1539			AllowHeaders:  []string{"header1", "header2"},
1540			ExposeHeaders: []string{"header3"},
1541			MaxAge:        &types.Duration{Seconds: 2},
1542		}, valid: false},
1543		{name: "non empty matchType AllowOrigins", in: &networking.CorsPolicy{
1544			AllowOrigins: []*networking.StringMatch{
1545				{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
1546				{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}},
1547				{MatchType: &networking.StringMatch_Regex{Regex: "regex"}},
1548			},
1549			AllowMethods:  []string{"GET", "POST"},
1550			AllowHeaders:  []string{"header1", "header2"},
1551			ExposeHeaders: []string{"header3"},
1552			MaxAge:        &types.Duration{Seconds: 2},
1553		}, valid: true},
1554	}
1555
1556	for _, tc := range testCases {
1557		t.Run(tc.name, func(t *testing.T) {
1558			if got := validateCORSPolicy(tc.in); (got == nil) != tc.valid {
1559				t.Errorf("got valid=%v, want valid=%v: %v",
1560					got == nil, tc.valid, got)
1561			}
1562		})
1563	}
1564}
1565
1566func TestValidateHTTPStatus(t *testing.T) {
1567	testCases := []struct {
1568		in    int32
1569		valid bool
1570	}{
1571		{-100, false},
1572		{0, false},
1573		{200, true},
1574		{600, true},
1575		{601, false},
1576	}
1577
1578	for _, tc := range testCases {
1579		if got := validateHTTPStatus(tc.in); (got == nil) != tc.valid {
1580			t.Errorf("validateHTTPStatus(%d) => got valid=%v, want valid=%v",
1581				tc.in, got, tc.valid)
1582		}
1583	}
1584}
1585
1586func TestValidateHTTPFaultInjectionAbort(t *testing.T) {
1587	testCases := []struct {
1588		name  string
1589		in    *networking.HTTPFaultInjection_Abort
1590		valid bool
1591	}{
1592		{name: "nil", in: nil, valid: true},
1593		{name: "valid", in: &networking.HTTPFaultInjection_Abort{
1594			Percentage: &networking.Percent{
1595				Value: 20,
1596			},
1597			ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{
1598				HttpStatus: 200,
1599			},
1600		}, valid: true},
1601		{name: "valid default", in: &networking.HTTPFaultInjection_Abort{
1602			ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{
1603				HttpStatus: 200,
1604			},
1605		}, valid: true},
1606		{name: "invalid http status", in: &networking.HTTPFaultInjection_Abort{
1607			Percentage: &networking.Percent{
1608				Value: 20,
1609			},
1610			ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{
1611				HttpStatus: 9000,
1612			},
1613		}, valid: false},
1614		{name: "invalid low http status", in: &networking.HTTPFaultInjection_Abort{
1615			Percentage: &networking.Percent{
1616				Value: 20,
1617			},
1618			ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{
1619				HttpStatus: 100,
1620			},
1621		}, valid: false},
1622		{name: "valid percentage", in: &networking.HTTPFaultInjection_Abort{
1623			Percentage: &networking.Percent{
1624				Value: 0.001,
1625			},
1626			ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{
1627				HttpStatus: 200,
1628			},
1629		}, valid: true},
1630		{name: "invalid fractional percent", in: &networking.HTTPFaultInjection_Abort{
1631			Percentage: &networking.Percent{
1632				Value: -10.0,
1633			},
1634			ErrorType: &networking.HTTPFaultInjection_Abort_HttpStatus{
1635				HttpStatus: 200,
1636			},
1637		}, valid: false},
1638	}
1639
1640	for _, tc := range testCases {
1641		t.Run(tc.name, func(t *testing.T) {
1642			if got := validateHTTPFaultInjectionAbort(tc.in); (got == nil) != tc.valid {
1643				t.Errorf("got valid=%v, want valid=%v: %v",
1644					got == nil, tc.valid, got)
1645			}
1646		})
1647	}
1648}
1649
1650func TestValidateHTTPFaultInjectionDelay(t *testing.T) {
1651	testCases := []struct {
1652		name  string
1653		in    *networking.HTTPFaultInjection_Delay
1654		valid bool
1655	}{
1656		{name: "nil", in: nil, valid: true},
1657		{name: "valid fixed", in: &networking.HTTPFaultInjection_Delay{
1658			Percentage: &networking.Percent{
1659				Value: 20,
1660			},
1661			HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
1662				FixedDelay: &types.Duration{Seconds: 3},
1663			},
1664		}, valid: true},
1665		{name: "valid default", in: &networking.HTTPFaultInjection_Delay{
1666			HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
1667				FixedDelay: &types.Duration{Seconds: 3},
1668			},
1669		}, valid: true},
1670		{name: "invalid percent", in: &networking.HTTPFaultInjection_Delay{
1671			Percentage: &networking.Percent{
1672				Value: 101,
1673			},
1674			HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
1675				FixedDelay: &types.Duration{Seconds: 3},
1676			},
1677		}, valid: false},
1678		{name: "invalid delay", in: &networking.HTTPFaultInjection_Delay{
1679			Percentage: &networking.Percent{
1680				Value: 20,
1681			},
1682			HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
1683				FixedDelay: &types.Duration{Seconds: 3, Nanos: 42},
1684			},
1685		}, valid: false},
1686		{name: "valid fractional percentage", in: &networking.HTTPFaultInjection_Delay{
1687			Percentage: &networking.Percent{
1688				Value: 0.001,
1689			},
1690			HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
1691				FixedDelay: &types.Duration{Seconds: 3},
1692			},
1693		}, valid: true},
1694		{name: "invalid fractional percentage", in: &networking.HTTPFaultInjection_Delay{
1695			Percentage: &networking.Percent{
1696				Value: -10.0,
1697			},
1698			HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
1699				FixedDelay: &types.Duration{Seconds: 3},
1700			},
1701		}, valid: false},
1702	}
1703
1704	for _, tc := range testCases {
1705		t.Run(tc.name, func(t *testing.T) {
1706			if got := validateHTTPFaultInjectionDelay(tc.in); (got == nil) != tc.valid {
1707				t.Errorf("got valid=%v, want valid=%v: %v",
1708					got == nil, tc.valid, got)
1709			}
1710		})
1711	}
1712}
1713
1714func TestValidateHTTPRetry(t *testing.T) {
1715	testCases := []struct {
1716		name  string
1717		in    *networking.HTTPRetry
1718		valid bool
1719	}{
1720		{name: "valid", in: &networking.HTTPRetry{
1721			Attempts:      10,
1722			PerTryTimeout: &types.Duration{Seconds: 2},
1723			RetryOn:       "5xx,gateway-error",
1724		}, valid: true},
1725		{name: "disable retries", in: &networking.HTTPRetry{
1726			Attempts: 0,
1727		}, valid: true},
1728		{name: "invalid, retry policy configured but attempts set to zero", in: &networking.HTTPRetry{
1729			Attempts:      0,
1730			PerTryTimeout: &types.Duration{Seconds: 2},
1731			RetryOn:       "5xx,gateway-error",
1732		}, valid: false},
1733		{name: "valid default", in: &networking.HTTPRetry{
1734			Attempts: 10,
1735		}, valid: true},
1736		{name: "valid http status retryOn", in: &networking.HTTPRetry{
1737			Attempts:      10,
1738			PerTryTimeout: &types.Duration{Seconds: 2},
1739			RetryOn:       "503,connect-failure",
1740		}, valid: true},
1741		{name: "invalid attempts", in: &networking.HTTPRetry{
1742			Attempts:      -1,
1743			PerTryTimeout: &types.Duration{Seconds: 2},
1744		}, valid: false},
1745		{name: "invalid timeout", in: &networking.HTTPRetry{
1746			Attempts:      10,
1747			PerTryTimeout: &types.Duration{Seconds: 2, Nanos: 1},
1748		}, valid: false},
1749		{name: "timeout too small", in: &networking.HTTPRetry{
1750			Attempts:      10,
1751			PerTryTimeout: &types.Duration{Nanos: 999},
1752		}, valid: false},
1753		{name: "invalid policy retryOn", in: &networking.HTTPRetry{
1754			Attempts:      10,
1755			PerTryTimeout: &types.Duration{Seconds: 2},
1756			RetryOn:       "5xx,invalid policy",
1757		}, valid: false},
1758		{name: "invalid http status retryOn", in: &networking.HTTPRetry{
1759			Attempts:      10,
1760			PerTryTimeout: &types.Duration{Seconds: 2},
1761			RetryOn:       "600,connect-failure",
1762		}, valid: false},
1763		{name: "invalid, retryRemoteLocalities configured but attempts set to zero", in: &networking.HTTPRetry{
1764			Attempts:              0,
1765			RetryRemoteLocalities: &types.BoolValue{Value: false},
1766		}, valid: false},
1767	}
1768
1769	for _, tc := range testCases {
1770		t.Run(tc.name, func(t *testing.T) {
1771			if got := validateHTTPRetry(tc.in); (got == nil) != tc.valid {
1772				t.Errorf("got valid=%v, want valid=%v: %v",
1773					got == nil, tc.valid, got)
1774			}
1775		})
1776	}
1777}
1778
1779func TestValidateHTTPRewrite(t *testing.T) {
1780	testCases := []struct {
1781		name  string
1782		in    *networking.HTTPRewrite
1783		valid bool
1784	}{
1785		{
1786			name:  "nil in",
1787			in:    nil,
1788			valid: true,
1789		},
1790		{
1791			name: "uri and authority",
1792			in: &networking.HTTPRewrite{
1793				Uri:       "/path/to/resource",
1794				Authority: "foobar.org",
1795			},
1796			valid: true,
1797		},
1798		{
1799			name: "uri",
1800			in: &networking.HTTPRewrite{
1801				Uri: "/path/to/resource",
1802			},
1803			valid: true,
1804		},
1805		{
1806			name: "authority",
1807			in: &networking.HTTPRewrite{
1808				Authority: "foobar.org",
1809			},
1810			valid: true,
1811		},
1812		{
1813			name:  "no uri or authority",
1814			in:    &networking.HTTPRewrite{},
1815			valid: false,
1816		},
1817	}
1818
1819	for _, tc := range testCases {
1820		t.Run(tc.name, func(t *testing.T) {
1821			if got := validateHTTPRewrite(tc.in); (got == nil) != tc.valid {
1822				t.Errorf("got valid=%v, want valid=%v: %v",
1823					got == nil, tc.valid, got)
1824			}
1825		})
1826	}
1827}
1828
1829func TestValidatePortName(t *testing.T) {
1830	testCases := []struct {
1831		name  string
1832		valid bool
1833	}{
1834		{
1835			name:  "",
1836			valid: false,
1837		},
1838		{
1839			name:  "simple",
1840			valid: true,
1841		},
1842		{
1843			name:  "full",
1844			valid: true,
1845		},
1846		{
1847			name:  "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong",
1848			valid: false,
1849		},
1850	}
1851
1852	for _, tc := range testCases {
1853		t.Run(tc.name, func(t *testing.T) {
1854			if err := ValidatePortName(tc.name); (err == nil) != tc.valid {
1855				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
1856			}
1857		})
1858	}
1859}
1860
1861func TestValidateHTTPRedirect(t *testing.T) {
1862	testCases := []struct {
1863		name     string
1864		redirect *networking.HTTPRedirect
1865		valid    bool
1866	}{
1867		{
1868			name:     "nil redirect",
1869			redirect: nil,
1870			valid:    true,
1871		},
1872		{
1873			name: "empty uri and authority",
1874			redirect: &networking.HTTPRedirect{
1875				Uri:       "",
1876				Authority: "",
1877			},
1878			valid: false,
1879		},
1880		{
1881			name: "too small redirect code",
1882			redirect: &networking.HTTPRedirect{
1883				Uri:          "t",
1884				Authority:    "",
1885				RedirectCode: 299,
1886			},
1887			valid: false,
1888		},
1889		{
1890			name: "too large redirect code",
1891			redirect: &networking.HTTPRedirect{
1892				Uri:          "t",
1893				Authority:    "",
1894				RedirectCode: 400,
1895			},
1896			valid: false,
1897		},
1898		{
1899			name: "empty authority",
1900			redirect: &networking.HTTPRedirect{
1901				Uri:       "t",
1902				Authority: "",
1903			},
1904			valid: true,
1905		},
1906		{
1907			name: "empty uri",
1908			redirect: &networking.HTTPRedirect{
1909				Uri:       "",
1910				Authority: "t",
1911			},
1912			valid: true,
1913		},
1914		{
1915			name: "empty redirect code",
1916			redirect: &networking.HTTPRedirect{
1917				Uri:          "t",
1918				Authority:    "t",
1919				RedirectCode: 0,
1920			},
1921			valid: true,
1922		},
1923		{
1924			name: "normal redirect",
1925			redirect: &networking.HTTPRedirect{
1926				Uri:          "t",
1927				Authority:    "t",
1928				RedirectCode: 308,
1929			},
1930			valid: true,
1931		},
1932	}
1933
1934	for _, tc := range testCases {
1935		t.Run(tc.name, func(t *testing.T) {
1936			if err := validateHTTPRedirect(tc.redirect); (err == nil) != tc.valid {
1937				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
1938			}
1939		})
1940	}
1941}
1942
1943func TestValidateDestination(t *testing.T) {
1944	testCases := []struct {
1945		name        string
1946		destination *networking.Destination
1947		valid       bool
1948	}{
1949		{
1950			name:        "empty",
1951			destination: &networking.Destination{}, // nothing
1952			valid:       false,
1953		},
1954		{
1955			name: "simple",
1956			destination: &networking.Destination{
1957				Host: "foo.bar",
1958			},
1959			valid: true,
1960		},
1961		{name: "full",
1962			destination: &networking.Destination{
1963				Host:   "foo.bar",
1964				Subset: "shiny",
1965				Port: &networking.PortSelector{
1966					Number: 5000,
1967				},
1968			},
1969			valid: true,
1970		},
1971		{name: "unnumbered-selector",
1972			destination: &networking.Destination{
1973				Host:   "foo.bar",
1974				Subset: "shiny",
1975				Port:   &networking.PortSelector{},
1976			},
1977			valid: false,
1978		},
1979	}
1980
1981	for _, tc := range testCases {
1982		t.Run(tc.name, func(t *testing.T) {
1983			if err := validateDestination(tc.destination); (err == nil) != tc.valid {
1984				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
1985			}
1986		})
1987	}
1988}
1989
1990func TestValidateHTTPRoute(t *testing.T) {
1991	testCases := []struct {
1992		name  string
1993		route *networking.HTTPRoute
1994		valid bool
1995	}{
1996		{name: "empty", route: &networking.HTTPRoute{ // nothing
1997		}, valid:                                     false},
1998		{name: "simple", route: &networking.HTTPRoute{
1999			Route: []*networking.HTTPRouteDestination{{
2000				Destination: &networking.Destination{Host: "foo.baz"},
2001			}},
2002		}, valid: true},
2003		{name: "no destination", route: &networking.HTTPRoute{
2004			Route: []*networking.HTTPRouteDestination{{
2005				Destination: nil,
2006			}},
2007		}, valid: false},
2008		{name: "weighted", route: &networking.HTTPRoute{
2009			Route: []*networking.HTTPRouteDestination{{
2010				Destination: &networking.Destination{Host: "foo.baz.south"},
2011				Weight:      25,
2012			}, {
2013				Destination: &networking.Destination{Host: "foo.baz.east"},
2014				Weight:      75,
2015			}},
2016		}, valid: true},
2017		{name: "total weight > 100", route: &networking.HTTPRoute{
2018			Route: []*networking.HTTPRouteDestination{{
2019				Destination: &networking.Destination{Host: "foo.baz.south"},
2020				Weight:      55,
2021			}, {
2022				Destination: &networking.Destination{Host: "foo.baz.east"},
2023				Weight:      50,
2024			}},
2025		}, valid: false},
2026		{name: "total weight < 100", route: &networking.HTTPRoute{
2027			Route: []*networking.HTTPRouteDestination{{
2028				Destination: &networking.Destination{Host: "foo.baz.south"},
2029				Weight:      49,
2030			}, {
2031				Destination: &networking.Destination{Host: "foo.baz.east"},
2032				Weight:      50,
2033			}},
2034		}, valid: false},
2035		{name: "simple redirect", route: &networking.HTTPRoute{
2036			Redirect: &networking.HTTPRedirect{
2037				Uri:       "/lerp",
2038				Authority: "foo.biz",
2039			},
2040		}, valid: true},
2041		{name: "conflicting redirect and route", route: &networking.HTTPRoute{
2042			Route: []*networking.HTTPRouteDestination{{
2043				Destination: &networking.Destination{Host: "foo.baz"},
2044			}},
2045			Redirect: &networking.HTTPRedirect{
2046				Uri:       "/lerp",
2047				Authority: "foo.biz",
2048			},
2049		}, valid: false},
2050		{name: "request response headers", route: &networking.HTTPRoute{
2051			Route: []*networking.HTTPRouteDestination{{
2052				Destination: &networking.Destination{Host: "foo.baz"},
2053			}},
2054		}, valid: true},
2055		{name: "valid headers", route: &networking.HTTPRoute{
2056			Route: []*networking.HTTPRouteDestination{{
2057				Destination: &networking.Destination{Host: "foo.baz"},
2058				Headers: &networking.Headers{
2059					Request: &networking.Headers_HeaderOperations{
2060						Add: map[string]string{
2061							"name": "",
2062						},
2063						Set: map[string]string{
2064							"name": "",
2065						},
2066						Remove: []string{
2067							"name",
2068						},
2069					},
2070					Response: &networking.Headers_HeaderOperations{
2071						Add: map[string]string{
2072							"name": "",
2073						},
2074						Set: map[string]string{
2075							"name": "",
2076						},
2077						Remove: []string{
2078							"name",
2079						},
2080					},
2081				},
2082			}},
2083		}, valid: true},
2084		{name: "empty header name - request add", route: &networking.HTTPRoute{
2085			Route: []*networking.HTTPRouteDestination{{
2086				Destination: &networking.Destination{Host: "foo.baz"},
2087				Headers: &networking.Headers{
2088					Request: &networking.Headers_HeaderOperations{
2089						Add: map[string]string{
2090							"": "value",
2091						},
2092					},
2093				},
2094			}},
2095		}, valid: false},
2096		{name: "empty header name - request set", route: &networking.HTTPRoute{
2097			Route: []*networking.HTTPRouteDestination{{
2098				Destination: &networking.Destination{Host: "foo.baz"},
2099				Headers: &networking.Headers{
2100					Request: &networking.Headers_HeaderOperations{
2101						Set: map[string]string{
2102							"": "value",
2103						},
2104					},
2105				},
2106			}},
2107		}, valid: false},
2108		{name: "empty header name - request remove", route: &networking.HTTPRoute{
2109			Route: []*networking.HTTPRouteDestination{{
2110				Destination: &networking.Destination{Host: "foo.baz"},
2111				Headers: &networking.Headers{
2112					Request: &networking.Headers_HeaderOperations{
2113						Remove: []string{
2114							"",
2115						},
2116					},
2117				},
2118			}},
2119		}, valid: false},
2120		{name: "empty header name - response add", route: &networking.HTTPRoute{
2121			Route: []*networking.HTTPRouteDestination{{
2122				Destination: &networking.Destination{Host: "foo.baz"},
2123				Headers: &networking.Headers{
2124					Response: &networking.Headers_HeaderOperations{
2125						Add: map[string]string{
2126							"": "value",
2127						},
2128					},
2129				},
2130			}},
2131		}, valid: false},
2132		{name: "empty header name - response set", route: &networking.HTTPRoute{
2133			Route: []*networking.HTTPRouteDestination{{
2134				Destination: &networking.Destination{Host: "foo.baz"},
2135				Headers: &networking.Headers{
2136					Response: &networking.Headers_HeaderOperations{
2137						Set: map[string]string{
2138							"": "value",
2139						},
2140					},
2141				},
2142			}},
2143		}, valid: false},
2144		{name: "empty header name - response remove", route: &networking.HTTPRoute{
2145			Route: []*networking.HTTPRouteDestination{{
2146				Destination: &networking.Destination{Host: "foo.baz"},
2147				Headers: &networking.Headers{
2148					Response: &networking.Headers_HeaderOperations{
2149						Remove: []string{
2150							"",
2151						},
2152					},
2153				},
2154			}},
2155		}, valid: false},
2156		{name: "null header match", route: &networking.HTTPRoute{
2157			Route: []*networking.HTTPRouteDestination{{
2158				Destination: &networking.Destination{Host: "foo.bar"},
2159			}},
2160			Match: []*networking.HTTPMatchRequest{{
2161				Headers: map[string]*networking.StringMatch{
2162					"header": nil,
2163				},
2164			}},
2165		}, valid: false},
2166		{name: "nil match", route: &networking.HTTPRoute{
2167			Route: []*networking.HTTPRouteDestination{{
2168				Destination: &networking.Destination{Host: "foo.bar"},
2169			}},
2170			Match: nil,
2171		}, valid: true},
2172		{name: "match with nil element", route: &networking.HTTPRoute{
2173			Route: []*networking.HTTPRouteDestination{{
2174				Destination: &networking.Destination{Host: "foo.bar"},
2175			}},
2176			Match: []*networking.HTTPMatchRequest{nil},
2177		}, valid: true},
2178		{name: "invalid mirror percent", route: &networking.HTTPRoute{
2179			MirrorPercent: &types.UInt32Value{Value: 101},
2180			Route: []*networking.HTTPRouteDestination{{
2181				Destination: &networking.Destination{Host: "foo.bar"},
2182			}},
2183			Match: []*networking.HTTPMatchRequest{nil},
2184		}, valid: false},
2185		{name: "invalid mirror percentage", route: &networking.HTTPRoute{
2186			MirrorPercentage: &networking.Percent{
2187				Value: 101,
2188			},
2189			Route: []*networking.HTTPRouteDestination{{
2190				Destination: &networking.Destination{Host: "foo.bar"},
2191			}},
2192			Match: []*networking.HTTPMatchRequest{nil},
2193		}, valid: false},
2194		{name: "valid mirror percentage", route: &networking.HTTPRoute{
2195			MirrorPercentage: &networking.Percent{
2196				Value: 1,
2197			},
2198			Route: []*networking.HTTPRouteDestination{{
2199				Destination: &networking.Destination{Host: "foo.bar"},
2200			}},
2201			Match: []*networking.HTTPMatchRequest{nil},
2202		}, valid: true},
2203	}
2204
2205	for _, tc := range testCases {
2206		t.Run(tc.name, func(t *testing.T) {
2207			if err := validateHTTPRoute(tc.route, false); (err == nil) != tc.valid {
2208				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
2209			}
2210		})
2211	}
2212}
2213
2214func TestValidateRouteDestination(t *testing.T) {
2215	testCases := []struct {
2216		name   string
2217		routes []*networking.RouteDestination
2218		valid  bool
2219	}{
2220		{name: "simple", routes: []*networking.RouteDestination{{
2221			Destination: &networking.Destination{Host: "foo.baz"},
2222		}}, valid: true},
2223		{name: "wildcard dash", routes: []*networking.RouteDestination{{
2224			Destination: &networking.Destination{Host: "*-foo.baz"},
2225		}}, valid: true},
2226		{name: "wildcard prefix", routes: []*networking.RouteDestination{{
2227			Destination: &networking.Destination{Host: "*foo.baz"},
2228		}}, valid: true},
2229		{name: "wildcard", routes: []*networking.RouteDestination{{
2230			Destination: &networking.Destination{Host: "*"},
2231		}}, valid: false},
2232		{name: "bad wildcard", routes: []*networking.RouteDestination{{
2233			Destination: &networking.Destination{Host: "foo.*"},
2234		}}, valid: false},
2235		{name: "bad fqdn", routes: []*networking.RouteDestination{{
2236			Destination: &networking.Destination{Host: "default/baz"},
2237		}}, valid: false},
2238		{name: "no destination", routes: []*networking.RouteDestination{{
2239			Destination: nil,
2240		}}, valid: false},
2241		{name: "weighted", routes: []*networking.RouteDestination{{
2242			Destination: &networking.Destination{Host: "foo.baz.south"},
2243			Weight:      25,
2244		}, {
2245			Destination: &networking.Destination{Host: "foo.baz.east"},
2246			Weight:      75,
2247		}}, valid: true},
2248		{name: "weight < 0", routes: []*networking.RouteDestination{{
2249			Destination: &networking.Destination{Host: "foo.baz.south"},
2250			Weight:      5,
2251		}, {
2252			Destination: &networking.Destination{Host: "foo.baz.east"},
2253			Weight:      -1,
2254		}}, valid: false},
2255		{name: "total weight > 100", routes: []*networking.RouteDestination{{
2256			Destination: &networking.Destination{Host: "foo.baz.south"},
2257			Weight:      55,
2258		}, {
2259			Destination: &networking.Destination{Host: "foo.baz.east"},
2260			Weight:      50,
2261		}}, valid: false},
2262		{name: "total weight < 100", routes: []*networking.RouteDestination{{
2263			Destination: &networking.Destination{Host: "foo.baz.south"},
2264			Weight:      49,
2265		}, {
2266			Destination: &networking.Destination{Host: "foo.baz.east"},
2267			Weight:      50,
2268		}}, valid: false},
2269		{name: "total weight = 100", routes: []*networking.RouteDestination{{
2270			Destination: &networking.Destination{Host: "foo.baz.south"},
2271			Weight:      100,
2272		}, {
2273			Destination: &networking.Destination{Host: "foo.baz.east"},
2274			Weight:      0,
2275		}}, valid: true},
2276		{name: "weight = 0", routes: []*networking.RouteDestination{{
2277			Destination: &networking.Destination{Host: "foo.baz.south"},
2278			Weight:      0,
2279		}}, valid: true},
2280		{name: "total weight = 0 with multi RouteDestination", routes: []*networking.RouteDestination{{
2281			Destination: &networking.Destination{Host: "foo.baz.south"},
2282			Weight:      0,
2283		}, {
2284			Destination: &networking.Destination{Host: "foo.baz.east"},
2285			Weight:      0,
2286		}}, valid: false},
2287	}
2288
2289	for _, tc := range testCases {
2290		t.Run(tc.name, func(t *testing.T) {
2291			if err := validateRouteDestinations(tc.routes); (err == nil) != tc.valid {
2292				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
2293			}
2294		})
2295	}
2296}
2297
2298// TODO: add TCP test cases once it is implemented
2299func TestValidateVirtualService(t *testing.T) {
2300	testCases := []struct {
2301		name  string
2302		in    proto.Message
2303		valid bool
2304	}{
2305		{name: "simple", in: &networking.VirtualService{
2306			Hosts: []string{"foo.bar"},
2307			Http: []*networking.HTTPRoute{{
2308				Route: []*networking.HTTPRouteDestination{{
2309					Destination: &networking.Destination{Host: "foo.baz"},
2310				}},
2311			}},
2312		}, valid: true},
2313		{name: "duplicate hosts", in: &networking.VirtualService{
2314			Hosts: []string{"*.foo.bar", "*.bar"},
2315			Http: []*networking.HTTPRoute{{
2316				Route: []*networking.HTTPRouteDestination{{
2317					Destination: &networking.Destination{Host: "foo.baz"},
2318				}},
2319			}},
2320		}, valid: false},
2321		{name: "with no destination", in: &networking.VirtualService{
2322			Hosts: []string{"*.foo.bar", "*.bar"},
2323			Http: []*networking.HTTPRoute{{
2324				Route: []*networking.HTTPRouteDestination{{}},
2325			}},
2326		}, valid: false},
2327		{name: "destination with out hosts", in: &networking.VirtualService{
2328			Hosts: []string{"*.foo.bar", "*.bar"},
2329			Http: []*networking.HTTPRoute{{
2330				Route: []*networking.HTTPRouteDestination{{
2331					Destination: &networking.Destination{},
2332				}},
2333			}},
2334		}, valid: false},
2335		{name: "no hosts", in: &networking.VirtualService{
2336			Hosts: nil,
2337			Http: []*networking.HTTPRoute{{
2338				Route: []*networking.HTTPRouteDestination{{
2339					Destination: &networking.Destination{Host: "foo.baz"},
2340				}},
2341			}},
2342		}, valid: false},
2343		{name: "bad host", in: &networking.VirtualService{
2344			Hosts: []string{"foo.ba!r"},
2345			Http: []*networking.HTTPRoute{{
2346				Route: []*networking.HTTPRouteDestination{{
2347					Destination: &networking.Destination{Host: "foo.baz"},
2348				}},
2349			}},
2350		}, valid: false},
2351		{name: "no tcp or http routing", in: &networking.VirtualService{
2352			Hosts: []string{"foo.bar"},
2353		}, valid: false},
2354		{name: "bad gateway", in: &networking.VirtualService{
2355			Hosts:    []string{"foo.bar"},
2356			Gateways: []string{"b@dgateway"},
2357			Http: []*networking.HTTPRoute{{
2358				Route: []*networking.HTTPRouteDestination{{
2359					Destination: &networking.Destination{Host: "foo.baz"},
2360				}},
2361			}},
2362		}, valid: false},
2363		{name: "FQDN for gateway", in: &networking.VirtualService{
2364			Hosts:    []string{"foo.bar"},
2365			Gateways: []string{"gateway.example.com"},
2366			Http: []*networking.HTTPRoute{{
2367				Route: []*networking.HTTPRouteDestination{{
2368					Destination: &networking.Destination{Host: "foo.baz"},
2369				}},
2370			}},
2371		}, valid: true},
2372		{name: "namespace/name for gateway", in: &networking.VirtualService{
2373			Hosts:    []string{"foo.bar"},
2374			Gateways: []string{"ns1/gateway"},
2375			Http: []*networking.HTTPRoute{{
2376				Route: []*networking.HTTPRouteDestination{{
2377					Destination: &networking.Destination{Host: "foo.baz"},
2378				}},
2379			}},
2380		}, valid: true},
2381		{name: "namespace/* for gateway", in: &networking.VirtualService{
2382			Hosts:    []string{"foo.bar"},
2383			Gateways: []string{"ns1/*"},
2384			Http: []*networking.HTTPRoute{{
2385				Route: []*networking.HTTPRouteDestination{{
2386					Destination: &networking.Destination{Host: "foo.baz"},
2387				}},
2388			}},
2389		}, valid: false},
2390		{name: "*/name for gateway", in: &networking.VirtualService{
2391			Hosts:    []string{"foo.bar"},
2392			Gateways: []string{"*/gateway"},
2393			Http: []*networking.HTTPRoute{{
2394				Route: []*networking.HTTPRouteDestination{{
2395					Destination: &networking.Destination{Host: "foo.baz"},
2396				}},
2397			}},
2398		}, valid: false},
2399		{name: "wildcard for mesh gateway", in: &networking.VirtualService{
2400			Hosts: []string{"*"},
2401			Http: []*networking.HTTPRoute{{
2402				Route: []*networking.HTTPRouteDestination{{
2403					Destination: &networking.Destination{Host: "foo.baz"},
2404				}},
2405			}},
2406		}, valid: false},
2407		{name: "wildcard for non-mesh gateway", in: &networking.VirtualService{
2408			Hosts:    []string{"*"},
2409			Gateways: []string{"somegateway"},
2410			Http: []*networking.HTTPRoute{{
2411				Route: []*networking.HTTPRouteDestination{{
2412					Destination: &networking.Destination{Host: "foo.baz"},
2413				}},
2414			}},
2415		}, valid: true},
2416		{name: "missing tcp route", in: &networking.VirtualService{
2417			Hosts: []string{"foo.bar"},
2418			Tcp: []*networking.TCPRoute{{
2419				Match: []*networking.L4MatchAttributes{
2420					{Port: 999},
2421				},
2422			}},
2423		}, valid: false},
2424		{name: "missing tls route", in: &networking.VirtualService{
2425			Hosts: []string{"foo.bar"},
2426			Tls: []*networking.TLSRoute{{
2427				Match: []*networking.TLSMatchAttributes{
2428					{
2429						Port:     999,
2430						SniHosts: []string{"foo.bar"},
2431					},
2432				},
2433			}},
2434		}, valid: false},
2435	}
2436
2437	for _, tc := range testCases {
2438		t.Run(tc.name, func(t *testing.T) {
2439			if err := ValidateVirtualService("", "", tc.in); (err == nil) != tc.valid {
2440				t.Fatalf("got valid=%v but wanted valid=%v: %v", err == nil, tc.valid, err)
2441			}
2442		})
2443	}
2444}
2445
2446func TestValidateDestinationRule(t *testing.T) {
2447	cases := []struct {
2448		name  string
2449		in    proto.Message
2450		valid bool
2451	}{
2452		{name: "simple destination rule", in: &networking.DestinationRule{
2453			Host: "reviews",
2454			Subsets: []*networking.Subset{
2455				{Name: "v1", Labels: map[string]string{"version": "v1"}},
2456				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2457			},
2458		}, valid: true},
2459
2460		{name: "missing destination name", in: &networking.DestinationRule{
2461			Host: "",
2462			Subsets: []*networking.Subset{
2463				{Name: "v1", Labels: map[string]string{"version": "v1"}},
2464				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2465			},
2466		}, valid: false},
2467
2468		{name: "missing subset name", in: &networking.DestinationRule{
2469			Host: "reviews",
2470			Subsets: []*networking.Subset{
2471				{Name: "", Labels: map[string]string{"version": "v1"}},
2472				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2473			},
2474		}, valid: false},
2475
2476		{name: "valid traffic policy, top level", in: &networking.DestinationRule{
2477			Host: "reviews",
2478			TrafficPolicy: &networking.TrafficPolicy{
2479				LoadBalancer: &networking.LoadBalancerSettings{
2480					LbPolicy: &networking.LoadBalancerSettings_Simple{
2481						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2482					},
2483				},
2484				ConnectionPool: &networking.ConnectionPoolSettings{
2485					Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2486					Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2487				},
2488				OutlierDetection: &networking.OutlierDetection{
2489					ConsecutiveErrors: 5,
2490					MinHealthPercent:  20,
2491				},
2492			},
2493			Subsets: []*networking.Subset{
2494				{Name: "v1", Labels: map[string]string{"version": "v1"}},
2495				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2496			},
2497		}, valid: true},
2498
2499		{name: "invalid traffic policy, top level", in: &networking.DestinationRule{
2500			Host: "reviews",
2501			TrafficPolicy: &networking.TrafficPolicy{
2502				LoadBalancer: &networking.LoadBalancerSettings{
2503					LbPolicy: &networking.LoadBalancerSettings_Simple{
2504						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2505					},
2506				},
2507				ConnectionPool: &networking.ConnectionPoolSettings{},
2508				OutlierDetection: &networking.OutlierDetection{
2509					ConsecutiveErrors: 5,
2510					MinHealthPercent:  20,
2511				},
2512			},
2513			Subsets: []*networking.Subset{
2514				{Name: "v1", Labels: map[string]string{"version": "v1"}},
2515				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2516			},
2517		}, valid: false},
2518
2519		{name: "valid traffic policy, subset level", in: &networking.DestinationRule{
2520			Host: "reviews",
2521			Subsets: []*networking.Subset{
2522				{Name: "v1", Labels: map[string]string{"version": "v1"},
2523					TrafficPolicy: &networking.TrafficPolicy{
2524						LoadBalancer: &networking.LoadBalancerSettings{
2525							LbPolicy: &networking.LoadBalancerSettings_Simple{
2526								Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2527							},
2528						},
2529						ConnectionPool: &networking.ConnectionPoolSettings{
2530							Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2531							Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2532						},
2533						OutlierDetection: &networking.OutlierDetection{
2534							ConsecutiveErrors: 5,
2535							MinHealthPercent:  20,
2536						},
2537					},
2538				},
2539				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2540			},
2541		}, valid: true},
2542
2543		{name: "invalid traffic policy, subset level", in: &networking.DestinationRule{
2544			Host: "reviews",
2545			Subsets: []*networking.Subset{
2546				{Name: "v1", Labels: map[string]string{"version": "v1"},
2547					TrafficPolicy: &networking.TrafficPolicy{
2548						LoadBalancer: &networking.LoadBalancerSettings{
2549							LbPolicy: &networking.LoadBalancerSettings_Simple{
2550								Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2551							},
2552						},
2553						ConnectionPool: &networking.ConnectionPoolSettings{},
2554						OutlierDetection: &networking.OutlierDetection{
2555							ConsecutiveErrors: 5,
2556							MinHealthPercent:  20,
2557						},
2558					},
2559				},
2560				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2561			},
2562		}, valid: false},
2563
2564		{name: "valid traffic policy, both levels", in: &networking.DestinationRule{
2565			Host: "reviews",
2566			TrafficPolicy: &networking.TrafficPolicy{
2567				LoadBalancer: &networking.LoadBalancerSettings{
2568					LbPolicy: &networking.LoadBalancerSettings_Simple{
2569						Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2570					},
2571				},
2572				ConnectionPool: &networking.ConnectionPoolSettings{
2573					Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2574					Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2575				},
2576				OutlierDetection: &networking.OutlierDetection{
2577					ConsecutiveErrors: 5,
2578					MinHealthPercent:  20,
2579				},
2580			},
2581			Subsets: []*networking.Subset{
2582				{Name: "v1", Labels: map[string]string{"version": "v1"},
2583					TrafficPolicy: &networking.TrafficPolicy{
2584						LoadBalancer: &networking.LoadBalancerSettings{
2585							LbPolicy: &networking.LoadBalancerSettings_Simple{
2586								Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2587							},
2588						},
2589						ConnectionPool: &networking.ConnectionPoolSettings{
2590							Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2591							Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2592						},
2593						OutlierDetection: &networking.OutlierDetection{
2594							ConsecutiveErrors: 5,
2595							MinHealthPercent:  30,
2596						},
2597					},
2598				},
2599				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2600			},
2601		}, valid: true},
2602
2603		{name: "negative consecutive errors", in: &networking.DestinationRule{
2604			Host: "reviews",
2605			TrafficPolicy: &networking.TrafficPolicy{
2606				OutlierDetection: &networking.OutlierDetection{
2607					ConsecutiveErrors: -1,
2608				},
2609			},
2610			Subsets: []*networking.Subset{
2611				{Name: "v1", Labels: map[string]string{"version": "v1"}},
2612				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2613			},
2614		}, valid: false},
2615
2616		{name: "deprecated consecutive errors set together with consecutive 5xx errors", in: &networking.DestinationRule{
2617			Host: "reviews",
2618			TrafficPolicy: &networking.TrafficPolicy{
2619				OutlierDetection: &networking.OutlierDetection{
2620					ConsecutiveErrors:     3,
2621					Consecutive_5XxErrors: &types.UInt32Value{Value: 3},
2622				},
2623			},
2624			Subsets: []*networking.Subset{
2625				{Name: "v1", Labels: map[string]string{"version": "v1"}},
2626				{Name: "v2", Labels: map[string]string{"version": "v2"}},
2627			},
2628		}, valid: false},
2629	}
2630	for _, c := range cases {
2631		if got := ValidateDestinationRule(someName, someNamespace, c.in); (got == nil) != c.valid {
2632			t.Errorf("ValidateDestinationRule failed on %v: got valid=%v but wanted valid=%v: %v",
2633				c.name, got == nil, c.valid, got)
2634		}
2635	}
2636}
2637
2638func TestValidateTrafficPolicy(t *testing.T) {
2639	cases := []struct {
2640		name  string
2641		in    networking.TrafficPolicy
2642		valid bool
2643	}{
2644		{name: "valid traffic policy", in: networking.TrafficPolicy{
2645			LoadBalancer: &networking.LoadBalancerSettings{
2646				LbPolicy: &networking.LoadBalancerSettings_Simple{
2647					Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2648				},
2649			},
2650			ConnectionPool: &networking.ConnectionPoolSettings{
2651				Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2652				Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2653			},
2654			OutlierDetection: &networking.OutlierDetection{
2655				ConsecutiveErrors: 5,
2656				MinHealthPercent:  20,
2657			},
2658		},
2659			valid: true},
2660		{name: "invalid traffic policy, nil entries", in: networking.TrafficPolicy{},
2661			valid: false},
2662
2663		{name: "invalid traffic policy, missing port in port level settings", in: networking.TrafficPolicy{
2664			PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{
2665				{
2666					LoadBalancer: &networking.LoadBalancerSettings{
2667						LbPolicy: &networking.LoadBalancerSettings_Simple{
2668							Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2669						},
2670					},
2671					ConnectionPool: &networking.ConnectionPoolSettings{
2672						Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2673						Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2674					},
2675					OutlierDetection: &networking.OutlierDetection{
2676						ConsecutiveErrors: 5,
2677						MinHealthPercent:  20,
2678					},
2679				},
2680			},
2681		},
2682			valid: false},
2683		{name: "invalid traffic policy, bad connection pool", in: networking.TrafficPolicy{
2684			LoadBalancer: &networking.LoadBalancerSettings{
2685				LbPolicy: &networking.LoadBalancerSettings_Simple{
2686					Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2687				},
2688			},
2689			ConnectionPool: &networking.ConnectionPoolSettings{},
2690			OutlierDetection: &networking.OutlierDetection{
2691				ConsecutiveErrors: 5,
2692				MinHealthPercent:  20,
2693			},
2694		},
2695			valid: false},
2696		{name: "invalid traffic policy, panic threshold too low", in: networking.TrafficPolicy{
2697			LoadBalancer: &networking.LoadBalancerSettings{
2698				LbPolicy: &networking.LoadBalancerSettings_Simple{
2699					Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2700				},
2701			},
2702			ConnectionPool: &networking.ConnectionPoolSettings{
2703				Tcp:  &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: 7},
2704				Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: 11},
2705			},
2706			OutlierDetection: &networking.OutlierDetection{
2707				ConsecutiveErrors: 5,
2708				MinHealthPercent:  -1,
2709			},
2710		},
2711			valid: false},
2712	}
2713	for _, c := range cases {
2714		if got := validateTrafficPolicy(&c.in); (got == nil) != c.valid {
2715			t.Errorf("ValidateTrafficPolicy failed on %v: got valid=%v but wanted valid=%v: %v",
2716				c.name, got == nil, c.valid, got)
2717		}
2718	}
2719}
2720
2721func TestValidateConnectionPool(t *testing.T) {
2722	cases := []struct {
2723		name  string
2724		in    networking.ConnectionPoolSettings
2725		valid bool
2726	}{
2727		{name: "valid connection pool, tcp and http", in: networking.ConnectionPoolSettings{
2728			Tcp: &networking.ConnectionPoolSettings_TCPSettings{
2729				MaxConnections: 7,
2730				ConnectTimeout: &types.Duration{Seconds: 2},
2731			},
2732			Http: &networking.ConnectionPoolSettings_HTTPSettings{
2733				Http1MaxPendingRequests:  2,
2734				Http2MaxRequests:         11,
2735				MaxRequestsPerConnection: 5,
2736				MaxRetries:               4,
2737				IdleTimeout:              &types.Duration{Seconds: 30},
2738			},
2739		},
2740			valid: true},
2741
2742		{name: "valid connection pool, tcp only", in: networking.ConnectionPoolSettings{
2743			Tcp: &networking.ConnectionPoolSettings_TCPSettings{
2744				MaxConnections: 7,
2745				ConnectTimeout: &types.Duration{Seconds: 2},
2746			},
2747		},
2748			valid: true},
2749
2750		{name: "valid connection pool, http only", in: networking.ConnectionPoolSettings{
2751			Http: &networking.ConnectionPoolSettings_HTTPSettings{
2752				Http1MaxPendingRequests:  2,
2753				Http2MaxRequests:         11,
2754				MaxRequestsPerConnection: 5,
2755				MaxRetries:               4,
2756				IdleTimeout:              &types.Duration{Seconds: 30},
2757			},
2758		},
2759			valid: true},
2760
2761		{name: "valid connection pool, http only with empty idle timeout", in: networking.ConnectionPoolSettings{
2762			Http: &networking.ConnectionPoolSettings_HTTPSettings{
2763				Http1MaxPendingRequests:  2,
2764				Http2MaxRequests:         11,
2765				MaxRequestsPerConnection: 5,
2766				MaxRetries:               4,
2767			},
2768		},
2769			valid: true},
2770
2771		{name: "invalid connection pool, empty", in: networking.ConnectionPoolSettings{}, valid: false},
2772
2773		{name: "invalid connection pool, bad max connections", in: networking.ConnectionPoolSettings{
2774			Tcp: &networking.ConnectionPoolSettings_TCPSettings{MaxConnections: -1}},
2775			valid: false},
2776
2777		{name: "invalid connection pool, bad connect timeout", in: networking.ConnectionPoolSettings{
2778			Tcp: &networking.ConnectionPoolSettings_TCPSettings{
2779				ConnectTimeout: &types.Duration{Seconds: 2, Nanos: 5}}},
2780			valid: false},
2781
2782		{name: "invalid connection pool, bad max pending requests", in: networking.ConnectionPoolSettings{
2783			Http: &networking.ConnectionPoolSettings_HTTPSettings{Http1MaxPendingRequests: -1}},
2784			valid: false},
2785
2786		{name: "invalid connection pool, bad max requests", in: networking.ConnectionPoolSettings{
2787			Http: &networking.ConnectionPoolSettings_HTTPSettings{Http2MaxRequests: -1}},
2788			valid: false},
2789
2790		{name: "invalid connection pool, bad max requests per connection", in: networking.ConnectionPoolSettings{
2791			Http: &networking.ConnectionPoolSettings_HTTPSettings{MaxRequestsPerConnection: -1}},
2792			valid: false},
2793
2794		{name: "invalid connection pool, bad max retries", in: networking.ConnectionPoolSettings{
2795			Http: &networking.ConnectionPoolSettings_HTTPSettings{MaxRetries: -1}},
2796			valid: false},
2797
2798		{name: "invalid connection pool, bad idle timeout", in: networking.ConnectionPoolSettings{
2799			Http: &networking.ConnectionPoolSettings_HTTPSettings{IdleTimeout: &types.Duration{Seconds: 30, Nanos: 5}}},
2800			valid: false},
2801	}
2802
2803	for _, c := range cases {
2804		if got := validateConnectionPool(&c.in); (got == nil) != c.valid {
2805			t.Errorf("ValidateConnectionSettings failed on %v: got valid=%v but wanted valid=%v: %v",
2806				c.name, got == nil, c.valid, got)
2807		}
2808	}
2809}
2810
2811func TestValidateLoadBalancer(t *testing.T) {
2812	duration := types.Duration{Seconds: int64(time.Hour / time.Second)}
2813	cases := []struct {
2814		name  string
2815		in    networking.LoadBalancerSettings
2816		valid bool
2817	}{
2818		{name: "valid load balancer with simple load balancing", in: networking.LoadBalancerSettings{
2819			LbPolicy: &networking.LoadBalancerSettings_Simple{
2820				Simple: networking.LoadBalancerSettings_ROUND_ROBIN,
2821			},
2822		},
2823			valid: true},
2824
2825		{name: "valid load balancer with consistentHash load balancing", in: networking.LoadBalancerSettings{
2826			LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
2827				ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
2828					MinimumRingSize: 1024,
2829					HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
2830						HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
2831							Name: "test",
2832							Ttl:  &duration,
2833						},
2834					},
2835				},
2836			},
2837		},
2838			valid: true},
2839
2840		{name: "invalid load balancer with consistentHash load balancing, missing ttl", in: networking.LoadBalancerSettings{
2841			LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
2842				ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
2843					MinimumRingSize: 1024,
2844					HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
2845						HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
2846							Name: "test",
2847						},
2848					},
2849				},
2850			},
2851		},
2852			valid: false},
2853
2854		{name: "invalid load balancer with consistentHash load balancing, missing name", in: networking.LoadBalancerSettings{
2855			LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
2856				ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
2857					MinimumRingSize: 1024,
2858					HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
2859						HttpCookie: &networking.LoadBalancerSettings_ConsistentHashLB_HTTPCookie{
2860							Ttl: &duration,
2861						},
2862					},
2863				},
2864			},
2865		},
2866			valid: false},
2867	}
2868
2869	for _, c := range cases {
2870		if got := validateLoadBalancer(&c.in); (got == nil) != c.valid {
2871			t.Errorf("validateLoadBalancer failed on %v: got valid=%v but wanted valid=%v: %v",
2872				c.name, got == nil, c.valid, got)
2873		}
2874	}
2875}
2876
2877func TestValidateOutlierDetection(t *testing.T) {
2878	cases := []struct {
2879		name  string
2880		in    networking.OutlierDetection
2881		valid bool
2882	}{
2883		{name: "valid outlier detection", in: networking.OutlierDetection{
2884			ConsecutiveErrors:  5,
2885			Interval:           &types.Duration{Seconds: 2},
2886			BaseEjectionTime:   &types.Duration{Seconds: 2},
2887			MaxEjectionPercent: 50,
2888		}, valid: true},
2889
2890		{name: "invalid outlier detection, bad consecutive errors", in: networking.OutlierDetection{
2891			ConsecutiveErrors: -1},
2892			valid: false},
2893
2894		{name: "invalid outlier detection, bad interval", in: networking.OutlierDetection{
2895			Interval: &types.Duration{Seconds: 2, Nanos: 5}},
2896			valid: false},
2897
2898		{name: "invalid outlier detection, bad base ejection time", in: networking.OutlierDetection{
2899			BaseEjectionTime: &types.Duration{Seconds: 2, Nanos: 5}},
2900			valid: false},
2901
2902		{name: "invalid outlier detection, bad max ejection percent", in: networking.OutlierDetection{
2903			MaxEjectionPercent: 105},
2904			valid: false},
2905		{name: "invalid outlier detection, panic threshold too low", in: networking.OutlierDetection{
2906			MinHealthPercent: -1,
2907		},
2908			valid: false},
2909		{name: "invalid outlier detection, panic threshold too high", in: networking.OutlierDetection{
2910			MinHealthPercent: 101,
2911		},
2912			valid: false},
2913	}
2914
2915	for _, c := range cases {
2916		if got := validateOutlierDetection(&c.in); (got == nil) != c.valid {
2917			t.Errorf("ValidateOutlierDetection failed on %v: got valid=%v but wanted valid=%v: %v",
2918				c.name, got == nil, c.valid, got)
2919		}
2920	}
2921}
2922
2923func TestValidateEnvoyFilter(t *testing.T) {
2924	tests := []struct {
2925		name  string
2926		in    proto.Message
2927		error string
2928	}{
2929		{name: "empty filters", in: &networking.EnvoyFilter{}, error: ""},
2930
2931		{name: "invalid applyTo", in: &networking.EnvoyFilter{
2932			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2933				{
2934					ApplyTo: 0,
2935				},
2936			},
2937		}, error: "Envoy filter: missing applyTo"},
2938		{name: "nil patch", in: &networking.EnvoyFilter{
2939			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2940				{
2941					ApplyTo: networking.EnvoyFilter_LISTENER,
2942					Patch:   nil,
2943				},
2944			},
2945		}, error: "Envoy filter: missing patch"},
2946		{name: "invalid patch operation", in: &networking.EnvoyFilter{
2947			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2948				{
2949					ApplyTo: networking.EnvoyFilter_LISTENER,
2950					Patch:   &networking.EnvoyFilter_Patch{},
2951				},
2952			},
2953		}, error: "Envoy filter: missing patch operation"},
2954		{name: "nil patch value", in: &networking.EnvoyFilter{
2955			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2956				{
2957					ApplyTo: networking.EnvoyFilter_LISTENER,
2958					Patch: &networking.EnvoyFilter_Patch{
2959						Operation: networking.EnvoyFilter_Patch_ADD,
2960					},
2961				},
2962			},
2963		}, error: "Envoy filter: missing patch value for non-remove operation"},
2964		{name: "match with invalid regex", in: &networking.EnvoyFilter{
2965			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2966				{
2967					ApplyTo: networking.EnvoyFilter_LISTENER,
2968					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
2969						Proxy: &networking.EnvoyFilter_ProxyMatch{
2970							ProxyVersion: "%#@~++==`24c234`",
2971						},
2972					},
2973					Patch: &networking.EnvoyFilter_Patch{
2974						Operation: networking.EnvoyFilter_Patch_REMOVE,
2975					},
2976				},
2977			},
2978		}, error: "Envoy filter: invalid regex for proxy version, [error parsing regexp: invalid nested repetition operator: `++`]"},
2979		{name: "match with valid regex", in: &networking.EnvoyFilter{
2980			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2981				{
2982					ApplyTo: networking.EnvoyFilter_LISTENER,
2983					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
2984						Proxy: &networking.EnvoyFilter_ProxyMatch{
2985							ProxyVersion: `release-1\.2-23434`,
2986						},
2987					},
2988					Patch: &networking.EnvoyFilter_Patch{
2989						Operation: networking.EnvoyFilter_Patch_REMOVE,
2990					},
2991				},
2992			},
2993		}, error: ""},
2994		{name: "listener with invalid match", in: &networking.EnvoyFilter{
2995			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
2996				{
2997					ApplyTo: networking.EnvoyFilter_LISTENER,
2998					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
2999						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
3000							Cluster: &networking.EnvoyFilter_ClusterMatch{},
3001						},
3002					},
3003					Patch: &networking.EnvoyFilter_Patch{
3004						Operation: networking.EnvoyFilter_Patch_REMOVE,
3005					},
3006				},
3007			},
3008		}, error: "Envoy filter: applyTo for listener class objects cannot have non listener match"},
3009		{name: "listener with invalid filter match", in: &networking.EnvoyFilter{
3010			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3011				{
3012					ApplyTo: networking.EnvoyFilter_LISTENER,
3013					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3014						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3015							Listener: &networking.EnvoyFilter_ListenerMatch{
3016								FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
3017									Sni:    "124",
3018									Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{},
3019								},
3020							},
3021						},
3022					},
3023					Patch: &networking.EnvoyFilter_Patch{
3024						Operation: networking.EnvoyFilter_Patch_REMOVE,
3025					},
3026				},
3027			},
3028		}, error: "Envoy filter: filter match has no name to match on"},
3029		{name: "listener with sub filter match and invalid applyTo", in: &networking.EnvoyFilter{
3030			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3031				{
3032					ApplyTo: networking.EnvoyFilter_LISTENER,
3033					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3034						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3035							Listener: &networking.EnvoyFilter_ListenerMatch{
3036								FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
3037									Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
3038										Name:      "random",
3039										SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{},
3040									},
3041								},
3042							},
3043						},
3044					},
3045					Patch: &networking.EnvoyFilter_Patch{
3046						Operation: networking.EnvoyFilter_Patch_REMOVE,
3047					},
3048				},
3049			},
3050		}, error: "Envoy filter: subfilter match can be used with applyTo HTTP_FILTER only"},
3051		{name: "listener with sub filter match and invalid filter name", in: &networking.EnvoyFilter{
3052			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3053				{
3054					ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
3055					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3056						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3057							Listener: &networking.EnvoyFilter_ListenerMatch{
3058								FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
3059									Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
3060										Name:      "random",
3061										SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{},
3062									},
3063								},
3064							},
3065						},
3066					},
3067					Patch: &networking.EnvoyFilter_Patch{
3068						Operation: networking.EnvoyFilter_Patch_REMOVE,
3069					},
3070				},
3071			},
3072		}, error: "Envoy filter: subfilter match requires filter match with envoy.http_connection_manager"},
3073		{name: "listener with sub filter match and no sub filter name", in: &networking.EnvoyFilter{
3074			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3075				{
3076					ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
3077					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3078						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3079							Listener: &networking.EnvoyFilter_ListenerMatch{
3080								FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
3081									Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
3082										Name:      "envoy.http_connection_manager",
3083										SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{},
3084									},
3085								},
3086							},
3087						},
3088					},
3089					Patch: &networking.EnvoyFilter_Patch{
3090						Operation: networking.EnvoyFilter_Patch_REMOVE,
3091					},
3092				},
3093			},
3094		}, error: "Envoy filter: subfilter match has no name to match on"},
3095		{name: "route configuration with invalid match", in: &networking.EnvoyFilter{
3096			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3097				{
3098					ApplyTo: networking.EnvoyFilter_VIRTUAL_HOST,
3099					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3100						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
3101							Cluster: &networking.EnvoyFilter_ClusterMatch{},
3102						},
3103					},
3104					Patch: &networking.EnvoyFilter_Patch{
3105						Operation: networking.EnvoyFilter_Patch_REMOVE,
3106					},
3107				},
3108			},
3109		}, error: "Envoy filter: applyTo for http route class objects cannot have non route configuration match"},
3110		{name: "cluster with invalid match", in: &networking.EnvoyFilter{
3111			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3112				{
3113					ApplyTo: networking.EnvoyFilter_CLUSTER,
3114					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3115						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3116							Listener: &networking.EnvoyFilter_ListenerMatch{},
3117						},
3118					},
3119					Patch: &networking.EnvoyFilter_Patch{
3120						Operation: networking.EnvoyFilter_Patch_REMOVE,
3121					},
3122				},
3123			},
3124		}, error: "Envoy filter: applyTo for cluster class objects cannot have non cluster match"},
3125		{name: "invalid patch value", in: &networking.EnvoyFilter{
3126			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3127				{
3128					ApplyTo: networking.EnvoyFilter_CLUSTER,
3129					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3130						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
3131							Cluster: &networking.EnvoyFilter_ClusterMatch{},
3132						},
3133					},
3134					Patch: &networking.EnvoyFilter_Patch{
3135						Operation: networking.EnvoyFilter_Patch_ADD,
3136						Value: &types.Struct{
3137							Fields: map[string]*types.Value{
3138								"foo": {
3139									Kind: &types.Value_BoolValue{BoolValue: false},
3140								},
3141							},
3142						},
3143					},
3144				},
3145			},
3146		}, error: `Envoy filter: unknown field "foo" in envoy_api_v2.Cluster`},
3147		{name: "happy config", in: &networking.EnvoyFilter{
3148			ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
3149				{
3150					ApplyTo: networking.EnvoyFilter_CLUSTER,
3151					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3152						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
3153							Cluster: &networking.EnvoyFilter_ClusterMatch{},
3154						},
3155					},
3156					Patch: &networking.EnvoyFilter_Patch{
3157						Operation: networking.EnvoyFilter_Patch_ADD,
3158						Value: &types.Struct{
3159							Fields: map[string]*types.Value{
3160								"lb_policy": {
3161									Kind: &types.Value_StringValue{StringValue: "RING_HASH"},
3162								},
3163							},
3164						},
3165					},
3166				},
3167				{
3168					ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
3169					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3170						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3171							Listener: &networking.EnvoyFilter_ListenerMatch{
3172								FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
3173									Name: "envoy.tcp_proxy",
3174								},
3175							},
3176						},
3177					},
3178					Patch: &networking.EnvoyFilter_Patch{
3179						Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
3180						Value: &types.Struct{
3181							Fields: map[string]*types.Value{
3182								"typed_config": {
3183									Kind: &types.Value_StructValue{StructValue: &types.Struct{
3184										Fields: map[string]*types.Value{
3185											"@type": {
3186												Kind: &types.Value_StringValue{
3187													StringValue: "type.googleapis.com/envoy.config.filter.network.ext_authz.v2.ExtAuthz",
3188												},
3189											},
3190										},
3191									}},
3192								},
3193							},
3194						},
3195					},
3196				},
3197				{
3198					ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
3199					Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
3200						ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
3201							Listener: &networking.EnvoyFilter_ListenerMatch{
3202								FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
3203									Name: "envoy.tcp_proxy",
3204								},
3205							},
3206						},
3207					},
3208					Patch: &networking.EnvoyFilter_Patch{
3209						Operation: networking.EnvoyFilter_Patch_INSERT_FIRST,
3210						Value: &types.Struct{
3211							Fields: map[string]*types.Value{
3212								"typed_config": {
3213									Kind: &types.Value_StructValue{StructValue: &types.Struct{
3214										Fields: map[string]*types.Value{
3215											"@type": {
3216												Kind: &types.Value_StringValue{
3217													StringValue: "type.googleapis.com/envoy.config.filter.network.ext_authz.v2.ExtAuthz",
3218												},
3219											},
3220										},
3221									}},
3222								},
3223							},
3224						},
3225					},
3226				},
3227			},
3228		}, error: ""},
3229	}
3230	for _, tt := range tests {
3231		t.Run(tt.name, func(t *testing.T) {
3232			err := ValidateEnvoyFilter(someName, someNamespace, tt.in)
3233			if err == nil && tt.error != "" {
3234				t.Fatalf("ValidateEnvoyFilter(%v) = nil, wanted %q", tt.in, tt.error)
3235			} else if err != nil && tt.error == "" {
3236				t.Fatalf("ValidateEnvoyFilter(%v) = %v, wanted nil", tt.in, err)
3237			} else if err != nil && !strings.Contains(err.Error(), tt.error) {
3238				t.Fatalf("ValidateEnvoyFilter(%v) = %v, wanted %q", tt.in, err, tt.error)
3239			}
3240		})
3241	}
3242}
3243
3244func TestValidateServiceEntries(t *testing.T) {
3245	cases := []struct {
3246		name  string
3247		in    networking.ServiceEntry
3248		valid bool
3249	}{
3250		{name: "discovery type DNS", in: networking.ServiceEntry{
3251			Hosts: []string{"*.google.com"},
3252			Ports: []*networking.Port{
3253				{Number: 80, Protocol: "http", Name: "http-valid1"},
3254				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3255			},
3256			Endpoints: []*networking.WorkloadEntry{
3257				{Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}},
3258				{Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}},
3259			},
3260			Resolution: networking.ServiceEntry_DNS,
3261		},
3262			valid: true},
3263
3264		{name: "discovery type DNS, label tlsMode: istio", in: networking.ServiceEntry{
3265			Hosts: []string{"*.google.com"},
3266			Ports: []*networking.Port{
3267				{Number: 80, Protocol: "http", Name: "http-valid1"},
3268				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3269			},
3270			Endpoints: []*networking.WorkloadEntry{
3271				{Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}, Labels: map[string]string{"security.istio.io/tlsMode": "istio"}},
3272				{Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}, Labels: map[string]string{"security.istio.io/tlsMode": "istio"}},
3273			},
3274			Resolution: networking.ServiceEntry_DNS,
3275		},
3276			valid: true},
3277
3278		{name: "discovery type DNS, IP in endpoints", in: networking.ServiceEntry{
3279			Hosts: []string{"*.google.com"},
3280			Ports: []*networking.Port{
3281				{Number: 80, Protocol: "http", Name: "http-valid1"},
3282				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3283			},
3284			Endpoints: []*networking.WorkloadEntry{
3285				{Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}},
3286				{Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}},
3287			},
3288			Resolution: networking.ServiceEntry_DNS,
3289		},
3290			valid: true},
3291
3292		{name: "empty hosts", in: networking.ServiceEntry{
3293			Ports: []*networking.Port{
3294				{Number: 80, Protocol: "http", Name: "http-valid1"},
3295			},
3296			Endpoints: []*networking.WorkloadEntry{
3297				{Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}},
3298			},
3299			Resolution: networking.ServiceEntry_DNS,
3300		},
3301			valid: false},
3302
3303		{name: "bad hosts", in: networking.ServiceEntry{
3304			Hosts: []string{"-"},
3305			Ports: []*networking.Port{
3306				{Number: 80, Protocol: "http", Name: "http-valid1"},
3307			},
3308			Endpoints: []*networking.WorkloadEntry{
3309				{Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}},
3310			},
3311			Resolution: networking.ServiceEntry_DNS,
3312		},
3313			valid: false},
3314		{name: "full wildcard host", in: networking.ServiceEntry{
3315			Hosts: []string{"foo.com", "*"},
3316			Ports: []*networking.Port{
3317				{Number: 80, Protocol: "http", Name: "http-valid1"},
3318			},
3319			Endpoints: []*networking.WorkloadEntry{
3320				{Address: "in.google.com", Ports: map[string]uint32{"http-valid2": 9080}},
3321			},
3322			Resolution: networking.ServiceEntry_DNS,
3323		},
3324			valid: false},
3325		{name: "short name host", in: networking.ServiceEntry{
3326			Hosts: []string{"foo", "bar.com"},
3327			Ports: []*networking.Port{
3328				{Number: 80, Protocol: "http", Name: "http-valid1"},
3329			},
3330			Endpoints: []*networking.WorkloadEntry{
3331				{Address: "in.google.com", Ports: map[string]uint32{"http-valid1": 9080}},
3332			},
3333			Resolution: networking.ServiceEntry_DNS,
3334		},
3335			valid: true},
3336		{name: "undefined endpoint port", in: networking.ServiceEntry{
3337			Hosts: []string{"google.com"},
3338			Ports: []*networking.Port{
3339				{Number: 80, Protocol: "http", Name: "http-valid1"},
3340				{Number: 80, Protocol: "http", Name: "http-valid2"},
3341			},
3342			Endpoints: []*networking.WorkloadEntry{
3343				{Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}},
3344				{Address: "in.google.com", Ports: map[string]uint32{"http-dne": 9080}},
3345			},
3346			Resolution: networking.ServiceEntry_DNS,
3347		},
3348			valid: false},
3349
3350		{name: "discovery type DNS, non-FQDN endpoint", in: networking.ServiceEntry{
3351			Hosts: []string{"*.google.com"},
3352			Ports: []*networking.Port{
3353				{Number: 80, Protocol: "http", Name: "http-valid1"},
3354				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3355			},
3356			Endpoints: []*networking.WorkloadEntry{
3357				{Address: "*.lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}},
3358				{Address: "in.google.com", Ports: map[string]uint32{"http-dne": 9080}},
3359			},
3360			Resolution: networking.ServiceEntry_DNS,
3361		},
3362			valid: false},
3363
3364		{name: "discovery type DNS, non-FQDN host", in: networking.ServiceEntry{
3365			Hosts: []string{"*.google.com"},
3366			Ports: []*networking.Port{
3367				{Number: 80, Protocol: "http", Name: "http-valid1"},
3368				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3369			},
3370
3371			Resolution: networking.ServiceEntry_DNS,
3372		},
3373			valid: false},
3374
3375		{name: "discovery type DNS, no endpoints", in: networking.ServiceEntry{
3376			Hosts: []string{"google.com"},
3377			Ports: []*networking.Port{
3378				{Number: 80, Protocol: "http", Name: "http-valid1"},
3379				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3380			},
3381
3382			Resolution: networking.ServiceEntry_DNS,
3383		},
3384			valid: true},
3385
3386		{name: "discovery type DNS, unix endpoint", in: networking.ServiceEntry{
3387			Hosts: []string{"*.google.com"},
3388			Ports: []*networking.Port{
3389				{Number: 80, Protocol: "http", Name: "http-valid1"},
3390			},
3391			Endpoints: []*networking.WorkloadEntry{
3392				{Address: "unix:///lon/google/com"},
3393			},
3394			Resolution: networking.ServiceEntry_DNS,
3395		},
3396			valid: false},
3397
3398		{name: "discovery type none", in: networking.ServiceEntry{
3399			Hosts: []string{"google.com"},
3400			Ports: []*networking.Port{
3401				{Number: 80, Protocol: "http", Name: "http-valid1"},
3402				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3403			},
3404			Resolution: networking.ServiceEntry_NONE,
3405		},
3406			valid: true},
3407
3408		{name: "discovery type none, endpoints provided", in: networking.ServiceEntry{
3409			Hosts: []string{"google.com"},
3410			Ports: []*networking.Port{
3411				{Number: 80, Protocol: "http", Name: "http-valid1"},
3412				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3413			},
3414			Endpoints: []*networking.WorkloadEntry{
3415				{Address: "lon.google.com", Ports: map[string]uint32{"http-valid1": 8080}},
3416			},
3417			Resolution: networking.ServiceEntry_NONE,
3418		},
3419			valid: false},
3420
3421		{name: "discovery type none, cidr addresses", in: networking.ServiceEntry{
3422			Hosts:     []string{"google.com"},
3423			Addresses: []string{"172.1.2.16/16"},
3424			Ports: []*networking.Port{
3425				{Number: 80, Protocol: "http", Name: "http-valid1"},
3426				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3427			},
3428			Resolution: networking.ServiceEntry_NONE,
3429		},
3430			valid: true},
3431
3432		{name: "discovery type static, cidr addresses with endpoints", in: networking.ServiceEntry{
3433			Hosts:     []string{"google.com"},
3434			Addresses: []string{"172.1.2.16/16"},
3435			Ports: []*networking.Port{
3436				{Number: 80, Protocol: "http", Name: "http-valid1"},
3437				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3438			},
3439			Endpoints: []*networking.WorkloadEntry{
3440				{Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}},
3441				{Address: "2.2.2.2", Ports: map[string]uint32{"http-valid2": 9080}},
3442			},
3443			Resolution: networking.ServiceEntry_STATIC,
3444		},
3445			valid: true},
3446
3447		{name: "discovery type static", in: networking.ServiceEntry{
3448			Hosts:     []string{"google.com"},
3449			Addresses: []string{"172.1.2.16"},
3450			Ports: []*networking.Port{
3451				{Number: 80, Protocol: "http", Name: "http-valid1"},
3452				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3453			},
3454			Endpoints: []*networking.WorkloadEntry{
3455				{Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}},
3456				{Address: "2.2.2.2", Ports: map[string]uint32{"http-valid2": 9080}},
3457			},
3458			Resolution: networking.ServiceEntry_STATIC,
3459		},
3460			valid: true},
3461
3462		{name: "discovery type static, FQDN in endpoints", in: networking.ServiceEntry{
3463			Hosts:     []string{"google.com"},
3464			Addresses: []string{"172.1.2.16"},
3465			Ports: []*networking.Port{
3466				{Number: 80, Protocol: "http", Name: "http-valid1"},
3467				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3468			},
3469			Endpoints: []*networking.WorkloadEntry{
3470				{Address: "google.com", Ports: map[string]uint32{"http-valid1": 8080}},
3471				{Address: "2.2.2.2", Ports: map[string]uint32{"http-valid2": 9080}},
3472			},
3473			Resolution: networking.ServiceEntry_STATIC,
3474		},
3475			valid: false},
3476
3477		{name: "discovery type static, missing endpoints", in: networking.ServiceEntry{
3478			Hosts:     []string{"google.com"},
3479			Addresses: []string{"172.1.2.16"},
3480			Ports: []*networking.Port{
3481				{Number: 80, Protocol: "http", Name: "http-valid1"},
3482				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3483			},
3484			Resolution: networking.ServiceEntry_STATIC,
3485		},
3486			valid: true},
3487
3488		{name: "discovery type static, bad endpoint port name", in: networking.ServiceEntry{
3489			Hosts:     []string{"google.com"},
3490			Addresses: []string{"172.1.2.16"},
3491			Ports: []*networking.Port{
3492				{Number: 80, Protocol: "http", Name: "http-valid1"},
3493				{Number: 8080, Protocol: "http", Name: "http-valid2"},
3494			},
3495			Endpoints: []*networking.WorkloadEntry{
3496				{Address: "1.1.1.1", Ports: map[string]uint32{"http-valid1": 8080}},
3497				{Address: "2.2.2.2", Ports: map[string]uint32{"http-dne": 9080}},
3498			},
3499			Resolution: networking.ServiceEntry_STATIC,
3500		},
3501			valid: false},
3502
3503		{name: "discovery type none, conflicting port names", in: networking.ServiceEntry{
3504			Hosts: []string{"google.com"},
3505			Ports: []*networking.Port{
3506				{Number: 80, Protocol: "http", Name: "http-conflict"},
3507				{Number: 8080, Protocol: "http", Name: "http-conflict"},
3508			},
3509			Resolution: networking.ServiceEntry_NONE,
3510		},
3511			valid: false},
3512
3513		{name: "discovery type none, conflicting port numbers", in: networking.ServiceEntry{
3514			Hosts: []string{"google.com"},
3515			Ports: []*networking.Port{
3516				{Number: 80, Protocol: "http", Name: "http-conflict1"},
3517				{Number: 80, Protocol: "http", Name: "http-conflict2"},
3518			},
3519			Resolution: networking.ServiceEntry_NONE,
3520		},
3521			valid: false},
3522
3523		{name: "unix socket", in: networking.ServiceEntry{
3524			Hosts: []string{"uds.cluster.local"},
3525			Ports: []*networking.Port{
3526				{Number: 6553, Protocol: "grpc", Name: "grpc-service1"},
3527			},
3528			Resolution: networking.ServiceEntry_STATIC,
3529			Endpoints: []*networking.WorkloadEntry{
3530				{Address: "unix:///path/to/socket"},
3531			},
3532		},
3533			valid: true},
3534
3535		{name: "unix socket, relative path", in: networking.ServiceEntry{
3536			Hosts: []string{"uds.cluster.local"},
3537			Ports: []*networking.Port{
3538				{Number: 6553, Protocol: "grpc", Name: "grpc-service1"},
3539			},
3540			Resolution: networking.ServiceEntry_STATIC,
3541			Endpoints: []*networking.WorkloadEntry{
3542				{Address: "unix://./relative/path.sock"},
3543			},
3544		},
3545			valid: false},
3546
3547		{name: "unix socket, endpoint ports", in: networking.ServiceEntry{
3548			Hosts: []string{"uds.cluster.local"},
3549			Ports: []*networking.Port{
3550				{Number: 6553, Protocol: "grpc", Name: "grpc-service1"},
3551			},
3552			Resolution: networking.ServiceEntry_STATIC,
3553			Endpoints: []*networking.WorkloadEntry{
3554				{Address: "unix:///path/to/socket", Ports: map[string]uint32{"grpc-service1": 6553}},
3555			},
3556		},
3557			valid: false},
3558
3559		{name: "unix socket, multiple service ports", in: networking.ServiceEntry{
3560			Hosts: []string{"uds.cluster.local"},
3561			Ports: []*networking.Port{
3562				{Number: 6553, Protocol: "grpc", Name: "grpc-service1"},
3563				{Number: 80, Protocol: "http", Name: "http-service2"},
3564			},
3565			Resolution: networking.ServiceEntry_STATIC,
3566			Endpoints: []*networking.WorkloadEntry{
3567				{Address: "unix:///path/to/socket"},
3568			},
3569		},
3570			valid: false},
3571		{name: "empty protocol", in: networking.ServiceEntry{
3572			Hosts:     []string{"google.com"},
3573			Addresses: []string{"172.1.2.16/16"},
3574			Ports: []*networking.Port{
3575				{Number: 80, Protocol: "http", Name: "http-valid1"},
3576				{Number: 8080, Name: "http-valid2"},
3577			},
3578			Resolution: networking.ServiceEntry_NONE,
3579		},
3580			valid: true},
3581		{name: "selector", in: networking.ServiceEntry{
3582			Hosts:            []string{"google.com"},
3583			WorkloadSelector: &networking.WorkloadSelector{Labels: map[string]string{"foo": "bar"}},
3584			Ports: []*networking.Port{
3585				{Number: 80, Protocol: "http", Name: "http-valid1"},
3586			},
3587		},
3588			valid: true},
3589		{name: "selector and endpoints", in: networking.ServiceEntry{
3590			Hosts:            []string{"google.com"},
3591			WorkloadSelector: &networking.WorkloadSelector{Labels: map[string]string{"foo": "bar"}},
3592			Ports: []*networking.Port{
3593				{Number: 80, Protocol: "http", Name: "http-valid1"},
3594			},
3595			Endpoints: []*networking.WorkloadEntry{
3596				{Address: "1.1.1.1"},
3597			},
3598		},
3599			valid: false},
3600		{name: "bad selector key", in: networking.ServiceEntry{
3601			Hosts:            []string{"google.com"},
3602			WorkloadSelector: &networking.WorkloadSelector{Labels: map[string]string{"": "bar"}},
3603			Ports: []*networking.Port{
3604				{Number: 80, Protocol: "http", Name: "http-valid1"},
3605			},
3606		},
3607			valid: false},
3608	}
3609
3610	for _, c := range cases {
3611		t.Run(c.name, func(t *testing.T) {
3612			if got := ValidateServiceEntry(someName, someNamespace, &c.in); (got == nil) != c.valid {
3613				t.Errorf("ValidateServiceEntry got valid=%v but wanted valid=%v: %v",
3614					got == nil, c.valid, got)
3615			}
3616		})
3617	}
3618}
3619
3620func TestValidateAuthenticationPolicy(t *testing.T) {
3621	cases := []struct {
3622		name       string
3623		configName string
3624		in         proto.Message
3625		valid      bool
3626	}{
3627		{
3628			name:       "empty policy with namespace-wide policy name",
3629			configName: constants.DefaultAuthenticationPolicyName,
3630			in:         &authn.Policy{},
3631			valid:      true,
3632		},
3633		{
3634			name:       "empty policy with non-default name",
3635			configName: someName,
3636			in:         &authn.Policy{},
3637			valid:      false,
3638		},
3639		{
3640			name:       "service-specific policy with namespace-wide name",
3641			configName: constants.DefaultAuthenticationPolicyName,
3642			in: &authn.Policy{
3643				// nolint: staticcheck
3644				Targets: []*authn.TargetSelector{{
3645					Name: "foo",
3646				}},
3647			},
3648			valid: false,
3649		},
3650		{
3651			name:       "Targets only policy",
3652			configName: someName,
3653			in: &authn.Policy{
3654				// nolint: staticcheck
3655				Targets: []*authn.TargetSelector{{
3656					Name: "foo",
3657				}},
3658			},
3659			valid: true,
3660		},
3661		{
3662			name:       "Source mTLS",
3663			configName: constants.DefaultAuthenticationPolicyName,
3664			in: &authn.Policy{
3665				Peers: []*authn.PeerAuthenticationMethod{{
3666					Params: &authn.PeerAuthenticationMethod_Mtls{},
3667				}},
3668			},
3669			valid: true,
3670		},
3671		{
3672			name:       "Source JWT",
3673			configName: constants.DefaultAuthenticationPolicyName,
3674			in: &authn.Policy{
3675				Peers: []*authn.PeerAuthenticationMethod{{
3676					Params: &authn.PeerAuthenticationMethod_Jwt{
3677						// nolint: staticcheck
3678						Jwt: &authn.Jwt{
3679							Issuer:     "istio.io",
3680							JwksUri:    "https://secure.istio.io/oauth/v1/certs",
3681							JwtHeaders: []string{"x-goog-iap-jwt-assertion"},
3682						},
3683					},
3684				}},
3685			},
3686			valid: true,
3687		},
3688		{
3689			name:       "Origin",
3690			configName: constants.DefaultAuthenticationPolicyName,
3691			in: &authn.Policy{
3692				// nolint: staticcheck
3693				Origins: []*authn.OriginAuthenticationMethod{
3694					{
3695						// nolint: staticcheck
3696						Jwt: &authn.Jwt{
3697							Issuer:     "istio.io",
3698							JwksUri:    "https://secure.istio.io/oauth/v1/certs",
3699							JwtHeaders: []string{"x-goog-iap-jwt-assertion"},
3700						},
3701					},
3702				},
3703			},
3704			valid: true,
3705		},
3706		{
3707			name:       "Bad JkwsURI",
3708			configName: constants.DefaultAuthenticationPolicyName,
3709			in: &authn.Policy{
3710				// nolint: staticcheck
3711				Origins: []*authn.OriginAuthenticationMethod{
3712					{
3713						// nolint: staticcheck
3714						Jwt: &authn.Jwt{
3715							Issuer:     "istio.io",
3716							JwksUri:    "secure.istio.io/oauth/v1/certs",
3717							JwtHeaders: []string{"x-goog-iap-jwt-assertion"},
3718						},
3719					},
3720				},
3721			},
3722			valid: false,
3723		},
3724		{
3725			name:       "Bad JkwsURI Port",
3726			configName: constants.DefaultAuthenticationPolicyName,
3727			in: &authn.Policy{
3728				// nolint: staticcheck
3729				Origins: []*authn.OriginAuthenticationMethod{
3730					{
3731						// nolint: staticcheck
3732						Jwt: &authn.Jwt{
3733							Issuer:     "istio.io",
3734							JwksUri:    "https://secure.istio.io:not-a-number/oauth/v1/certs",
3735							JwtHeaders: []string{"x-goog-iap-jwt-assertion"},
3736						},
3737					},
3738				},
3739			},
3740			valid: false,
3741		},
3742		{
3743			name:       "Duplicate Jwt issuers",
3744			configName: constants.DefaultAuthenticationPolicyName,
3745			in: &authn.Policy{
3746				Peers: []*authn.PeerAuthenticationMethod{{
3747					Params: &authn.PeerAuthenticationMethod_Jwt{
3748						// nolint: staticcheck
3749						Jwt: &authn.Jwt{
3750							Issuer:     "istio.io",
3751							JwksUri:    "https://secure.istio.io/oauth/v1/certs",
3752							JwtHeaders: []string{"x-goog-iap-jwt-assertion"},
3753						},
3754					},
3755				}},
3756				// nolint: staticcheck
3757				Origins: []*authn.OriginAuthenticationMethod{
3758					{
3759						Jwt: &authn.Jwt{
3760							Issuer:     "istio.io",
3761							JwksUri:    "https://secure.istio.io/oauth/v1/certs",
3762							JwtHeaders: []string{"x-goog-iap-jwt-assertion"},
3763						},
3764					},
3765				},
3766			},
3767			valid: false,
3768		},
3769		{
3770			name:       "Just binding",
3771			configName: constants.DefaultAuthenticationPolicyName,
3772			in: &authn.Policy{
3773				// nolint: staticcheck
3774				PrincipalBinding: authn.PrincipalBinding_USE_ORIGIN,
3775			},
3776			valid: true,
3777		},
3778		{
3779			name:       "Bad target name",
3780			configName: someName,
3781			in: &authn.Policy{
3782				// nolint: staticcheck
3783				Targets: []*authn.TargetSelector{
3784					{
3785						Name: "foo.bar",
3786					},
3787				},
3788			},
3789			valid: false,
3790		},
3791		{
3792			name:       "Good target name",
3793			configName: someName,
3794			in: &authn.Policy{
3795				// nolint: staticcheck
3796				Targets: []*authn.TargetSelector{
3797					{
3798						Name: "good-service-name",
3799					},
3800				},
3801			},
3802			valid: true,
3803		},
3804	}
3805	for _, c := range cases {
3806		if got := ValidateAuthenticationPolicy(c.configName, someNamespace, c.in); (got == nil) != c.valid {
3807			t.Errorf("ValidateAuthenticationPolicy(%v): got(%v) != want(%v): %v\n", c.name, got == nil, c.valid, got)
3808		}
3809	}
3810}
3811
3812func TestValidateAuthenticationMeshPolicy(t *testing.T) {
3813	cases := []struct {
3814		name       string
3815		configName string
3816		in         proto.Message
3817		valid      bool
3818	}{
3819		{
3820			name:       "good name",
3821			configName: constants.DefaultAuthenticationPolicyName,
3822			in:         &authn.Policy{},
3823			valid:      true,
3824		},
3825		{
3826			name:       "bad-name",
3827			configName: someName,
3828			in:         &authn.Policy{},
3829			valid:      false,
3830		},
3831		{
3832			name:       "has targets",
3833			configName: constants.DefaultAuthenticationPolicyName,
3834			in: &authn.Policy{
3835				Targets: []*authn.TargetSelector{{
3836					Name: "foo",
3837				}},
3838			},
3839			valid: false,
3840		},
3841		{
3842			name:       "good",
3843			configName: constants.DefaultAuthenticationPolicyName,
3844			in: &authn.Policy{
3845				Peers: []*authn.PeerAuthenticationMethod{{
3846					Params: &authn.PeerAuthenticationMethod_Mtls{},
3847				}},
3848			},
3849			valid: true,
3850		},
3851		{
3852			name:       "empty origin",
3853			configName: constants.DefaultAuthenticationPolicyName,
3854			in: &authn.Policy{
3855				Origins: []*authn.OriginAuthenticationMethod{{}},
3856			},
3857			valid: false,
3858		},
3859		{
3860			name:       "nil origin",
3861			configName: constants.DefaultAuthenticationPolicyName,
3862			in: &authn.Policy{
3863				Origins: []*authn.OriginAuthenticationMethod{nil},
3864			},
3865			valid: false,
3866		},
3867	}
3868	for _, c := range cases {
3869		if got := ValidateAuthenticationPolicy(c.configName, "", c.in); (got == nil) != c.valid {
3870			t.Errorf("ValidateAuthenticationPolicy(%v): got(%v) != want(%v): %v\n", c.name, got == nil, c.valid, got)
3871		}
3872	}
3873}
3874
3875func TestValidateAuthorizationPolicy(t *testing.T) {
3876	cases := []struct {
3877		name  string
3878		in    proto.Message
3879		valid bool
3880	}{
3881		{
3882			name: "good",
3883			in: &security_beta.AuthorizationPolicy{
3884				Selector: &api.WorkloadSelector{
3885					MatchLabels: map[string]string{
3886						"app":     "httpbin",
3887						"version": "v1",
3888					},
3889				},
3890				Rules: []*security_beta.Rule{
3891					{
3892						From: []*security_beta.Rule_From{
3893							{
3894								Source: &security_beta.Source{
3895									Principals: []string{"sa1"},
3896								},
3897							},
3898							{
3899								Source: &security_beta.Source{
3900									Principals: []string{"sa2"},
3901								},
3902							},
3903						},
3904						To: []*security_beta.Rule_To{
3905							{
3906								Operation: &security_beta.Operation{
3907									Methods: []string{"GET"},
3908								},
3909							},
3910							{
3911								Operation: &security_beta.Operation{
3912									Methods: []string{"POST"},
3913								},
3914							},
3915						},
3916						When: []*security_beta.Condition{
3917							{
3918								Key:    "source.ip",
3919								Values: []string{"1.2.3.4", "5.6.7.0/24"},
3920							},
3921							{
3922								Key:    "request.headers[:authority]",
3923								Values: []string{"v1", "v2"},
3924							},
3925						},
3926					},
3927				},
3928			},
3929			valid: true,
3930		},
3931		{
3932			name: "allow-rules-nil",
3933			in: &security_beta.AuthorizationPolicy{
3934				Action: security_beta.AuthorizationPolicy_ALLOW,
3935			},
3936			valid: true,
3937		},
3938		{
3939			name: "deny-rules-nil",
3940			in: &security_beta.AuthorizationPolicy{
3941				Action: security_beta.AuthorizationPolicy_DENY,
3942			},
3943			valid: false,
3944		},
3945		{
3946			name: "selector-empty-value",
3947			in: &security_beta.AuthorizationPolicy{
3948				Selector: &api.WorkloadSelector{
3949					MatchLabels: map[string]string{
3950						"app":     "",
3951						"version": "v1",
3952					},
3953				},
3954			},
3955			valid: true,
3956		},
3957		{
3958			name: "selector-empty-key",
3959			in: &security_beta.AuthorizationPolicy{
3960				Selector: &api.WorkloadSelector{
3961					MatchLabels: map[string]string{
3962						"app": "httpbin",
3963						"":    "v1",
3964					},
3965				},
3966			},
3967			valid: false,
3968		},
3969		{
3970			name: "selector-wildcard-value",
3971			in: &security_beta.AuthorizationPolicy{
3972				Selector: &api.WorkloadSelector{
3973					MatchLabels: map[string]string{
3974						"app": "httpbin-*",
3975					},
3976				},
3977			},
3978			valid: false,
3979		},
3980		{
3981			name: "selector-wildcard-key",
3982			in: &security_beta.AuthorizationPolicy{
3983				Selector: &api.WorkloadSelector{
3984					MatchLabels: map[string]string{
3985						"app-*": "httpbin",
3986					},
3987				},
3988			},
3989			valid: false,
3990		},
3991		{
3992			name: "from-empty",
3993			in: &security_beta.AuthorizationPolicy{
3994				Rules: []*security_beta.Rule{
3995					{
3996						From: []*security_beta.Rule_From{},
3997					},
3998				},
3999			},
4000			valid: false,
4001		},
4002		{
4003			name: "source-nil",
4004			in: &security_beta.AuthorizationPolicy{
4005				Rules: []*security_beta.Rule{
4006					{
4007						From: []*security_beta.Rule_From{
4008							{},
4009						},
4010					},
4011				},
4012			},
4013			valid: false,
4014		},
4015		{
4016			name: "source-empty",
4017			in: &security_beta.AuthorizationPolicy{
4018				Rules: []*security_beta.Rule{
4019					{
4020						From: []*security_beta.Rule_From{
4021							{
4022								Source: &security_beta.Source{},
4023							},
4024						},
4025					},
4026				},
4027			},
4028			valid: false,
4029		},
4030		{
4031			name: "to-empty",
4032			in: &security_beta.AuthorizationPolicy{
4033				Rules: []*security_beta.Rule{
4034					{
4035						To: []*security_beta.Rule_To{},
4036					},
4037				},
4038			},
4039			valid: false,
4040		},
4041		{
4042			name: "operation-nil",
4043			in: &security_beta.AuthorizationPolicy{
4044				Rules: []*security_beta.Rule{
4045					{
4046						To: []*security_beta.Rule_To{
4047							{},
4048						},
4049					},
4050				},
4051			},
4052			valid: false,
4053		},
4054		{
4055			name: "operation-empty",
4056			in: &security_beta.AuthorizationPolicy{
4057				Rules: []*security_beta.Rule{
4058					{
4059						To: []*security_beta.Rule_To{
4060							{
4061								Operation: &security_beta.Operation{},
4062							},
4063						},
4064					},
4065				},
4066			},
4067			valid: false,
4068		},
4069		{
4070			name: "Principals-empty",
4071			in: &security_beta.AuthorizationPolicy{
4072				Rules: []*security_beta.Rule{
4073					{
4074						From: []*security_beta.Rule_From{
4075							{
4076								Source: &security_beta.Source{
4077									Principals: []string{"p1", ""},
4078								},
4079							},
4080						},
4081					},
4082				},
4083			},
4084			valid: false,
4085		},
4086		{
4087			name: "NotPrincipals-empty",
4088			in: &security_beta.AuthorizationPolicy{
4089				Rules: []*security_beta.Rule{
4090					{
4091						From: []*security_beta.Rule_From{
4092							{
4093								Source: &security_beta.Source{
4094									NotPrincipals: []string{"p1", ""},
4095								},
4096							},
4097						},
4098					},
4099				},
4100			},
4101			valid: false,
4102		},
4103		{
4104			name: "RequestPrincipals-empty",
4105			in: &security_beta.AuthorizationPolicy{
4106				Rules: []*security_beta.Rule{
4107					{
4108						From: []*security_beta.Rule_From{
4109							{
4110								Source: &security_beta.Source{
4111									RequestPrincipals: []string{"p1", ""},
4112								},
4113							},
4114						},
4115					},
4116				},
4117			},
4118			valid: false,
4119		},
4120		{
4121			name: "NotRequestPrincipals-empty",
4122			in: &security_beta.AuthorizationPolicy{
4123				Rules: []*security_beta.Rule{
4124					{
4125						From: []*security_beta.Rule_From{
4126							{
4127								Source: &security_beta.Source{
4128									NotRequestPrincipals: []string{"p1", ""},
4129								},
4130							},
4131						},
4132					},
4133				},
4134			},
4135			valid: false,
4136		},
4137		{
4138			name: "Namespaces-empty",
4139			in: &security_beta.AuthorizationPolicy{
4140				Rules: []*security_beta.Rule{
4141					{
4142						From: []*security_beta.Rule_From{
4143							{
4144								Source: &security_beta.Source{
4145									Namespaces: []string{"ns", ""},
4146								},
4147							},
4148						},
4149					},
4150				},
4151			},
4152			valid: false,
4153		},
4154		{
4155			name: "NotNamespaces-empty",
4156			in: &security_beta.AuthorizationPolicy{
4157				Rules: []*security_beta.Rule{
4158					{
4159						From: []*security_beta.Rule_From{
4160							{
4161								Source: &security_beta.Source{
4162									NotNamespaces: []string{"ns", ""},
4163								},
4164							},
4165						},
4166					},
4167				},
4168			},
4169			valid: false,
4170		},
4171		{
4172			name: "IpBlocks-empty",
4173			in: &security_beta.AuthorizationPolicy{
4174				Rules: []*security_beta.Rule{
4175					{
4176						From: []*security_beta.Rule_From{
4177							{
4178								Source: &security_beta.Source{
4179									IpBlocks: []string{"1.2.3.4", ""},
4180								},
4181							},
4182						},
4183					},
4184				},
4185			},
4186			valid: false,
4187		},
4188		{
4189			name: "NotIpBlocks-empty",
4190			in: &security_beta.AuthorizationPolicy{
4191				Rules: []*security_beta.Rule{
4192					{
4193						From: []*security_beta.Rule_From{
4194							{
4195								Source: &security_beta.Source{
4196									NotIpBlocks: []string{"1.2.3.4", ""},
4197								},
4198							},
4199						},
4200					},
4201				},
4202			},
4203			valid: false,
4204		},
4205		{
4206			name: "Hosts-empty",
4207			in: &security_beta.AuthorizationPolicy{
4208				Rules: []*security_beta.Rule{
4209					{
4210						To: []*security_beta.Rule_To{
4211							{
4212								Operation: &security_beta.Operation{
4213									Hosts: []string{"host", ""},
4214								},
4215							},
4216						},
4217					},
4218				},
4219			},
4220			valid: false,
4221		},
4222		{
4223			name: "NotHosts-empty",
4224			in: &security_beta.AuthorizationPolicy{
4225				Rules: []*security_beta.Rule{
4226					{
4227						To: []*security_beta.Rule_To{
4228							{
4229								Operation: &security_beta.Operation{
4230									NotHosts: []string{"host", ""},
4231								},
4232							},
4233						},
4234					},
4235				},
4236			},
4237			valid: false,
4238		},
4239		{
4240			name: "Ports-empty",
4241			in: &security_beta.AuthorizationPolicy{
4242				Rules: []*security_beta.Rule{
4243					{
4244						To: []*security_beta.Rule_To{
4245							{
4246								Operation: &security_beta.Operation{
4247									Ports: []string{"80", ""},
4248								},
4249							},
4250						},
4251					},
4252				},
4253			},
4254			valid: false,
4255		},
4256		{
4257			name: "NotPorts-empty",
4258			in: &security_beta.AuthorizationPolicy{
4259				Rules: []*security_beta.Rule{
4260					{
4261						To: []*security_beta.Rule_To{
4262							{
4263								Operation: &security_beta.Operation{
4264									NotPorts: []string{"80", ""},
4265								},
4266							},
4267						},
4268					},
4269				},
4270			},
4271			valid: false,
4272		},
4273		{
4274			name: "Methods-empty",
4275			in: &security_beta.AuthorizationPolicy{
4276				Rules: []*security_beta.Rule{
4277					{
4278						To: []*security_beta.Rule_To{
4279							{
4280								Operation: &security_beta.Operation{
4281									Methods: []string{"GET", ""},
4282								},
4283							},
4284						},
4285					},
4286				},
4287			},
4288			valid: false,
4289		},
4290		{
4291			name: "NotMethods-empty",
4292			in: &security_beta.AuthorizationPolicy{
4293				Rules: []*security_beta.Rule{
4294					{
4295						To: []*security_beta.Rule_To{
4296							{
4297								Operation: &security_beta.Operation{
4298									NotMethods: []string{"GET", ""},
4299								},
4300							},
4301						},
4302					},
4303				},
4304			},
4305			valid: false,
4306		},
4307		{
4308			name: "Paths-empty",
4309			in: &security_beta.AuthorizationPolicy{
4310				Rules: []*security_beta.Rule{
4311					{
4312						To: []*security_beta.Rule_To{
4313							{
4314								Operation: &security_beta.Operation{
4315									Paths: []string{"/path", ""},
4316								},
4317							},
4318						},
4319					},
4320				},
4321			},
4322			valid: false,
4323		},
4324		{
4325			name: "NotPaths-empty",
4326			in: &security_beta.AuthorizationPolicy{
4327				Rules: []*security_beta.Rule{
4328					{
4329						To: []*security_beta.Rule_To{
4330							{
4331								Operation: &security_beta.Operation{
4332									NotPaths: []string{"/path", ""},
4333								},
4334							},
4335						},
4336					},
4337				},
4338			},
4339			valid: false,
4340		},
4341		{
4342			name: "value-empty",
4343			in: &security_beta.AuthorizationPolicy{
4344				Rules: []*security_beta.Rule{
4345					{
4346						When: []*security_beta.Condition{
4347							{
4348								Key:    "request.headers[:authority]",
4349								Values: []string{"v1", ""},
4350							},
4351						},
4352					},
4353				},
4354			},
4355			valid: false,
4356		},
4357		{
4358			name: "invalid ip and port",
4359			in: &security_beta.AuthorizationPolicy{
4360				Rules: []*security_beta.Rule{
4361					{
4362						From: []*security_beta.Rule_From{
4363							{
4364								Source: &security_beta.Source{
4365									IpBlocks:    []string{"1.2.3.4", "ip1"},
4366									NotIpBlocks: []string{"5.6.7.8", "ip2"},
4367								},
4368							},
4369						},
4370						To: []*security_beta.Rule_To{
4371							{
4372								Operation: &security_beta.Operation{
4373									Ports:    []string{"80", "port1"},
4374									NotPorts: []string{"90", "port2"},
4375								},
4376							},
4377						},
4378					},
4379				},
4380			},
4381			valid: false,
4382		},
4383		{
4384			name: "condition-key-missing",
4385			in: &security_beta.AuthorizationPolicy{
4386				Selector: &api.WorkloadSelector{
4387					MatchLabels: map[string]string{
4388						"app": "httpbin",
4389					},
4390				},
4391				Rules: []*security_beta.Rule{
4392					{
4393						When: []*security_beta.Condition{
4394							{
4395								Values: []string{"v1", "v2"},
4396							},
4397						},
4398					},
4399				},
4400			},
4401			valid: false,
4402		},
4403		{
4404			name: "condition-key-empty",
4405			in: &security_beta.AuthorizationPolicy{
4406				Selector: &api.WorkloadSelector{
4407					MatchLabels: map[string]string{
4408						"app": "httpbin",
4409					},
4410				},
4411				Rules: []*security_beta.Rule{
4412					{
4413						When: []*security_beta.Condition{
4414							{
4415								Key:    "",
4416								Values: []string{"v1", "v2"},
4417							},
4418						},
4419					},
4420				},
4421			},
4422			valid: false,
4423		},
4424		{
4425			name: "condition-value-missing",
4426			in: &security_beta.AuthorizationPolicy{
4427				Selector: &api.WorkloadSelector{
4428					MatchLabels: map[string]string{
4429						"app": "httpbin",
4430					},
4431				},
4432				Rules: []*security_beta.Rule{
4433					{
4434						When: []*security_beta.Condition{
4435							{
4436								Key: "source.principal",
4437							},
4438						},
4439					},
4440				},
4441			},
4442			valid: false,
4443		},
4444		{
4445			name: "condition-value-invalid",
4446			in: &security_beta.AuthorizationPolicy{
4447				Rules: []*security_beta.Rule{
4448					{
4449						When: []*security_beta.Condition{
4450							{
4451								Key:    "source.ip",
4452								Values: []string{"a.b.c.d"},
4453							},
4454						},
4455					},
4456				},
4457			},
4458			valid: false,
4459		},
4460		{
4461			name: "condition-notValue-invalid",
4462			in: &security_beta.AuthorizationPolicy{
4463				Rules: []*security_beta.Rule{
4464					{
4465						When: []*security_beta.Condition{
4466							{
4467								Key:       "source.ip",
4468								NotValues: []string{"a.b.c.d"},
4469							},
4470						},
4471					},
4472				},
4473			},
4474			valid: false,
4475		},
4476		{
4477			name: "condition-unknown",
4478			in: &security_beta.AuthorizationPolicy{
4479				Selector: &api.WorkloadSelector{
4480					MatchLabels: map[string]string{
4481						"app": "httpbin",
4482					},
4483				},
4484				Rules: []*security_beta.Rule{
4485					{
4486						When: []*security_beta.Condition{
4487							{
4488								Key:    "key1",
4489								Values: []string{"v1"},
4490							},
4491						},
4492					},
4493				},
4494			},
4495			valid: false,
4496		},
4497	}
4498
4499	for _, c := range cases {
4500		t.Run(c.name, func(t *testing.T) {
4501			if got := ValidateAuthorizationPolicy("", "", c.in); (got == nil) != c.valid {
4502				t.Errorf("got: %v\nwant: %v", got, c.valid)
4503			}
4504		})
4505	}
4506}
4507
4508func TestValidateServiceRole(t *testing.T) {
4509	cases := []struct {
4510		name         string
4511		in           proto.Message
4512		expectErrMsg string
4513	}{
4514		{
4515			name:         "invalid proto",
4516			expectErrMsg: "cannot cast to ServiceRole",
4517		},
4518		{
4519			name:         "empty rules",
4520			in:           &rbac.ServiceRole{},
4521			expectErrMsg: "at least 1 rule must be specified",
4522		},
4523		{
4524			name: "has both methods and not_methods",
4525			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4526				{
4527					Methods:    []string{"GET", "POST"},
4528					NotMethods: []string{"DELETE"},
4529				},
4530			}},
4531			expectErrMsg: "cannot have both regular and *not* attributes for the same kind (i.e. methods and not_methods) for rule 0",
4532		},
4533		{
4534			name: "has both ports and not_ports",
4535			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4536				{
4537					Ports:    []int32{9080},
4538					NotPorts: []int32{443},
4539				},
4540			}},
4541			expectErrMsg: "cannot have both regular and *not* attributes for the same kind (i.e. ports and not_ports) for rule 0",
4542		},
4543		{
4544			name: "has out of range port",
4545			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4546				{
4547					Ports: []int32{9080, -80},
4548				},
4549			}},
4550			expectErrMsg: "at least one port is not in the range of [0, 65535]",
4551		},
4552		{
4553			name: "has both first-class field and constraints",
4554			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4555				{
4556					Ports: []int32{9080},
4557					Constraints: []*rbac.AccessRule_Constraint{
4558						{Key: "destination.port", Values: []string{"80"}},
4559					},
4560				},
4561			}},
4562			expectErrMsg: "cannot define destination.port for rule 0 because a similar first-class field has been defined",
4563		},
4564		{
4565			name: "no key in constraint",
4566			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4567				{
4568					Methods: []string{"GET", "POST"},
4569					Constraints: []*rbac.AccessRule_Constraint{
4570						{Key: "key", Values: []string{"value"}},
4571						{Key: "key", Values: []string{"value"}},
4572					},
4573				},
4574				{
4575					Methods: []string{"GET", "POST"},
4576					Constraints: []*rbac.AccessRule_Constraint{
4577						{Key: "key", Values: []string{"value"}},
4578						{Values: []string{"value"}},
4579					},
4580				},
4581			}},
4582			expectErrMsg: "key cannot be empty for constraint 1 in rule 1",
4583		},
4584		{
4585			name: "no value in constraint",
4586			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4587				{
4588					Methods: []string{"GET", "POST"},
4589					Constraints: []*rbac.AccessRule_Constraint{
4590						{Key: "key", Values: []string{"value"}},
4591						{Key: "key", Values: []string{"value"}},
4592					},
4593				},
4594				{
4595					Methods: []string{"GET", "POST"},
4596					Constraints: []*rbac.AccessRule_Constraint{
4597						{Key: "key", Values: []string{"value"}},
4598						{Key: "key", Values: []string{}},
4599					},
4600				},
4601			}},
4602			expectErrMsg: "at least 1 value must be specified for constraint 1 in rule 1",
4603		},
4604		{
4605			name: "success proto",
4606			in: &rbac.ServiceRole{Rules: []*rbac.AccessRule{
4607				{
4608					Methods:  []string{"GET", "POST"},
4609					NotHosts: []string{"finances.google.com"},
4610					Constraints: []*rbac.AccessRule_Constraint{
4611						{Key: "key", Values: []string{"value"}},
4612						{Key: "key", Values: []string{"value"}},
4613					},
4614				},
4615				{
4616					Methods: []string{"GET", "POST"},
4617					Constraints: []*rbac.AccessRule_Constraint{
4618						{Key: "key", Values: []string{"value"}},
4619						{Key: "key", Values: []string{"value"}},
4620					},
4621				},
4622			}},
4623		},
4624	}
4625	for _, c := range cases {
4626		err := ValidateServiceRole(someName, someNamespace, c.in)
4627		if err == nil {
4628			if len(c.expectErrMsg) != 0 {
4629				t.Errorf("ValidateServiceRole(%v): got nil but want %q\n", c.name, c.expectErrMsg)
4630			}
4631		} else if err.Error() != c.expectErrMsg {
4632			t.Errorf("ValidateServiceRole(%v): got %q but want %q\n", c.name, err.Error(), c.expectErrMsg)
4633		}
4634	}
4635}
4636
4637func TestValidateServiceRoleBinding(t *testing.T) {
4638	cases := []struct {
4639		name         string
4640		in           proto.Message
4641		expectErrMsg string
4642	}{
4643		{
4644			name:         "invalid proto",
4645			expectErrMsg: "cannot cast to ServiceRoleBinding",
4646		},
4647		{
4648			name: "no subject",
4649			in: &rbac.ServiceRoleBinding{
4650				Subjects: []*rbac.Subject{},
4651				RoleRef:  &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"},
4652			},
4653			expectErrMsg: "at least 1 subject must be specified",
4654		},
4655		{
4656			name: "no user, group and properties",
4657			in: &rbac.ServiceRoleBinding{
4658				Subjects: []*rbac.Subject{
4659					{User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}},
4660					{User: "", Group: "", Properties: map[string]string{}},
4661				},
4662				RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"},
4663			},
4664			expectErrMsg: "empty subjects are not allowed. Found an empty subject at index 1",
4665		},
4666		{
4667			name: "no roleRef",
4668			in: &rbac.ServiceRoleBinding{
4669				Subjects: []*rbac.Subject{
4670					{User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}},
4671					{User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}},
4672				},
4673			},
4674			expectErrMsg: "exactly one of `roleRef`, `role`, or `actions` must be specified",
4675		},
4676		{
4677			name: "incorrect kind",
4678			in: &rbac.ServiceRoleBinding{
4679				Subjects: []*rbac.Subject{
4680					{User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}},
4681					{User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}},
4682				},
4683				RoleRef: &rbac.RoleRef{Kind: "ServiceRoleTypo", Name: "ServiceRole001"},
4684			},
4685			expectErrMsg: `kind set to "ServiceRoleTypo", currently the only supported value is "ServiceRole"`,
4686		},
4687		{
4688			name: "no name",
4689			in: &rbac.ServiceRoleBinding{
4690				Subjects: []*rbac.Subject{
4691					{User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}},
4692					{User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}},
4693				},
4694				Role: "/",
4695			},
4696			expectErrMsg: "`role` cannot have an empty ServiceRole name",
4697		},
4698		{
4699			name: "no name",
4700			in: &rbac.ServiceRoleBinding{
4701				Subjects: []*rbac.Subject{
4702					{User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}},
4703					{User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}},
4704				},
4705				RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: ""},
4706			},
4707			expectErrMsg: "`name` in `roleRef` cannot be empty",
4708		},
4709		{
4710			name: "first-class field already exists",
4711			in: &rbac.ServiceRoleBinding{
4712				Subjects: []*rbac.Subject{
4713					{Namespaces: []string{"default"}, Properties: map[string]string{"source.namespace": "istio-system"}},
4714				},
4715				RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"},
4716			},
4717			expectErrMsg: "cannot define source.namespace for binding 0 because a similar first-class field has been defined",
4718		},
4719		{
4720			name: "use * for names",
4721			in: &rbac.ServiceRoleBinding{
4722				Subjects: []*rbac.Subject{
4723					{Names: []string{"*"}},
4724				},
4725				RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"},
4726			},
4727			expectErrMsg: "do not use * for names or not_names (in rule 0)",
4728		},
4729		{
4730			name: "success proto",
4731			in: &rbac.ServiceRoleBinding{
4732				Subjects: []*rbac.Subject{
4733					{User: "User0", Group: "Group0", Properties: map[string]string{"prop0": "value0"}},
4734					{User: "User1", Group: "Group1", Properties: map[string]string{"prop1": "value1"}},
4735				},
4736				RoleRef: &rbac.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"},
4737			},
4738		},
4739	}
4740	for _, c := range cases {
4741		err := ValidateServiceRoleBinding(someName, someNamespace, c.in)
4742		if err == nil {
4743			if len(c.expectErrMsg) != 0 {
4744				t.Errorf("ValidateServiceRoleBinding(%v): got nil but want %q\n", c.name, c.expectErrMsg)
4745			}
4746		} else if err.Error() != c.expectErrMsg {
4747			t.Errorf("ValidateServiceRoleBinding(%v): got %q but want %q\n", c.name, err.Error(), c.expectErrMsg)
4748		}
4749	}
4750}
4751
4752func TestValidateClusterRbacConfig(t *testing.T) {
4753	cases := []struct {
4754		caseName     string
4755		name         string
4756		namespace    string
4757		in           proto.Message
4758		expectErrMsg string
4759	}{
4760		{
4761			caseName:     "invalid proto",
4762			expectErrMsg: "cannot cast to ClusterRbacConfig",
4763		},
4764		{
4765			caseName: "invalid name",
4766			name:     "cluster-rbac-config",
4767			in:       &rbac.RbacConfig{Mode: rbac.RbacConfig_ON_WITH_INCLUSION},
4768			expectErrMsg: fmt.Sprintf("ClusterRbacConfig has invalid name(cluster-rbac-config), name must be %q",
4769				constants.DefaultRbacConfigName),
4770		},
4771		{
4772			caseName: "success proto",
4773			name:     constants.DefaultRbacConfigName,
4774			in:       &rbac.RbacConfig{Mode: rbac.RbacConfig_ON},
4775		},
4776		{
4777			caseName:     "empty exclusion",
4778			name:         constants.DefaultRbacConfigName,
4779			in:           &rbac.RbacConfig{Mode: rbac.RbacConfig_ON_WITH_EXCLUSION},
4780			expectErrMsg: "exclusion cannot be null (use 'exclusion: {}' for none)",
4781		},
4782		{
4783			caseName:     "empty inclusion",
4784			name:         constants.DefaultRbacConfigName,
4785			in:           &rbac.RbacConfig{Mode: rbac.RbacConfig_ON_WITH_INCLUSION},
4786			expectErrMsg: "inclusion cannot be null (use 'inclusion: {}' for none)",
4787		},
4788	}
4789	for _, c := range cases {
4790		err := ValidateClusterRbacConfig(c.name, c.namespace, c.in)
4791		if err == nil {
4792			if len(c.expectErrMsg) != 0 {
4793				t.Errorf("ValidateClusterRbacConfig(%v): got nil but want %q\n", c.caseName, c.expectErrMsg)
4794			}
4795		} else if err.Error() != c.expectErrMsg {
4796			t.Errorf("ValidateClusterRbacConfig(%v): got %q but want %q\n", c.caseName, err.Error(), c.expectErrMsg)
4797		}
4798	}
4799}
4800
4801func TestValidateMixerService(t *testing.T) {
4802	cases := []struct {
4803		name  string
4804		in    *mccpb.IstioService
4805		valid bool
4806	}{
4807		{
4808			name: "no name and service",
4809			in:   &mccpb.IstioService{},
4810		},
4811		{
4812			name: "specify both name and service",
4813			in:   &mccpb.IstioService{Service: "test-service-service", Name: "test-service-name"},
4814		},
4815		{
4816			name: "specify both namespace and service",
4817			in:   &mccpb.IstioService{Service: "test-service-service", Namespace: "test-service-namespace"},
4818		},
4819		{
4820			name: "specify both domain and service",
4821			in:   &mccpb.IstioService{Service: "test-service-service", Domain: "test-service-domain"},
4822		},
4823		{
4824			name: "invalid name label",
4825			in:   &mccpb.IstioService{Name: strings.Repeat("x", 64)},
4826		},
4827		{
4828			name: "invalid namespace label",
4829			in:   &mccpb.IstioService{Name: "test-service-name", Namespace: strings.Repeat("x", 64)},
4830		},
4831		{
4832			name: "invalid domain or labels",
4833			in:   &mccpb.IstioService{Name: "test-service-name", Domain: strings.Repeat("x", 256)},
4834		},
4835		{
4836			name:  "valid",
4837			in:    validService,
4838			valid: true,
4839		},
4840	}
4841
4842	for _, c := range cases {
4843		t.Run(c.name, func(t *testing.T) {
4844			if got := ValidateMixerService(c.in); (got == nil) != c.valid {
4845				t.Errorf("ValidateMixerService(%v): got(%v) != want(%v): %v", c.name, got == nil, c.valid, got)
4846			}
4847		})
4848	}
4849}
4850
4851func TestValidateSidecar(t *testing.T) {
4852	tests := []struct {
4853		name  string
4854		in    *networking.Sidecar
4855		valid bool
4856	}{
4857		{"empty ingress and egress", &networking.Sidecar{}, false},
4858		{"default", &networking.Sidecar{
4859			Egress: []*networking.IstioEgressListener{
4860				{
4861					Hosts: []string{"*/*"},
4862				},
4863			},
4864		}, true},
4865		{"import local namespace with wildcard", &networking.Sidecar{
4866			Egress: []*networking.IstioEgressListener{
4867				{
4868					Hosts: []string{"./*"},
4869				},
4870			},
4871		}, true},
4872		{"import local namespace with fqdn", &networking.Sidecar{
4873			Egress: []*networking.IstioEgressListener{
4874				{
4875					Hosts: []string{"./foo.com"},
4876				},
4877			},
4878		}, true},
4879		{"import nothing", &networking.Sidecar{
4880			Egress: []*networking.IstioEgressListener{
4881				{
4882					Hosts: []string{"~/*"},
4883				},
4884			},
4885		}, true},
4886		{"bad egress host 1", &networking.Sidecar{
4887			Egress: []*networking.IstioEgressListener{
4888				{
4889					Hosts: []string{"*"},
4890				},
4891			},
4892		}, false},
4893		{"bad egress host 2", &networking.Sidecar{
4894			Egress: []*networking.IstioEgressListener{
4895				{
4896					Hosts: []string{"/"},
4897				},
4898			},
4899		}, false},
4900		{"empty egress host", &networking.Sidecar{
4901			Egress: []*networking.IstioEgressListener{
4902				{
4903					Hosts: []string{},
4904				},
4905			},
4906		}, false},
4907		{"multiple wildcard egress", &networking.Sidecar{
4908			Egress: []*networking.IstioEgressListener{
4909				{
4910					Hosts: []string{
4911						"*/foo.com",
4912					},
4913				},
4914				{
4915					Hosts: []string{
4916						"ns1/bar.com",
4917					},
4918				},
4919			},
4920		}, false},
4921		{"wildcard egress not in end", &networking.Sidecar{
4922			Egress: []*networking.IstioEgressListener{
4923				{
4924					Hosts: []string{
4925						"*/foo.com",
4926					},
4927				},
4928				{
4929					Port: &networking.Port{
4930						Protocol: "http",
4931						Number:   8080,
4932						Name:     "h8080",
4933					},
4934					Hosts: []string{
4935						"ns1/bar.com",
4936					},
4937				},
4938			},
4939		}, false},
4940		{"invalid Port", &networking.Sidecar{
4941			Egress: []*networking.IstioEgressListener{
4942				{
4943					Port: &networking.Port{
4944						Protocol: "http1",
4945						Number:   1000000,
4946						Name:     "",
4947					},
4948					Hosts: []string{
4949						"ns1/bar.com",
4950					},
4951				},
4952			},
4953		}, false},
4954		{"Port without name", &networking.Sidecar{
4955			Egress: []*networking.IstioEgressListener{
4956				{
4957					Port: &networking.Port{
4958						Protocol: "http",
4959						Number:   8080,
4960					},
4961					Hosts: []string{
4962						"ns1/bar.com",
4963					},
4964				},
4965			},
4966		}, true},
4967		{"UDS bind in outbound", &networking.Sidecar{
4968			Egress: []*networking.IstioEgressListener{
4969				{
4970					Port: &networking.Port{
4971						Protocol: "http",
4972						Number:   0,
4973						Name:     "uds",
4974					},
4975					Hosts: []string{
4976						"ns1/bar.com",
4977					},
4978					Bind: "unix:///@foo/bar/com",
4979				},
4980			},
4981		}, true},
4982		{"UDS bind in inbound", &networking.Sidecar{
4983			Ingress: []*networking.IstioIngressListener{
4984				{
4985					Port: &networking.Port{
4986						Protocol: "http",
4987						Number:   0,
4988						Name:     "uds",
4989					},
4990					Bind:            "unix:///@foo/bar/com",
4991					DefaultEndpoint: "127.0.0.1:9999",
4992				},
4993			},
4994		}, false},
4995		{"UDS bind in outbound 2", &networking.Sidecar{
4996			Egress: []*networking.IstioEgressListener{
4997				{
4998					Port: &networking.Port{
4999						Protocol: "http",
5000						Number:   0,
5001						Name:     "uds",
5002					},
5003					Hosts: []string{
5004						"ns1/bar.com",
5005					},
5006					Bind: "unix:///foo/bar/com",
5007				},
5008			},
5009		}, true},
5010		{"invalid bind", &networking.Sidecar{
5011			Egress: []*networking.IstioEgressListener{
5012				{
5013					Port: &networking.Port{
5014						Protocol: "http",
5015						Number:   0,
5016						Name:     "uds",
5017					},
5018					Hosts: []string{
5019						"ns1/bar.com",
5020					},
5021					Bind: "foobar:///@foo/bar/com",
5022				},
5023			},
5024		}, false},
5025		{"invalid capture mode with uds bind", &networking.Sidecar{
5026			Egress: []*networking.IstioEgressListener{
5027				{
5028					Port: &networking.Port{
5029						Protocol: "http",
5030						Number:   0,
5031						Name:     "uds",
5032					},
5033					Hosts: []string{
5034						"ns1/bar.com",
5035					},
5036					Bind:        "unix:///@foo/bar/com",
5037					CaptureMode: networking.CaptureMode_IPTABLES,
5038				},
5039			},
5040		}, false},
5041		{"duplicate UDS bind", &networking.Sidecar{
5042			Egress: []*networking.IstioEgressListener{
5043				{
5044					Port: &networking.Port{
5045						Protocol: "http",
5046						Number:   0,
5047						Name:     "uds",
5048					},
5049					Hosts: []string{
5050						"ns1/bar.com",
5051					},
5052					Bind: "unix:///@foo/bar/com",
5053				},
5054				{
5055					Port: &networking.Port{
5056						Protocol: "http",
5057						Number:   0,
5058						Name:     "uds",
5059					},
5060					Hosts: []string{
5061						"ns1/bar.com",
5062					},
5063					Bind: "unix:///@foo/bar/com",
5064				},
5065			},
5066		}, false},
5067		{"duplicate ports", &networking.Sidecar{
5068			Egress: []*networking.IstioEgressListener{
5069				{
5070					Port: &networking.Port{
5071						Protocol: "http",
5072						Number:   90,
5073						Name:     "foo",
5074					},
5075					Hosts: []string{
5076						"ns1/bar.com",
5077					},
5078				},
5079				{
5080					Port: &networking.Port{
5081						Protocol: "tcp",
5082						Number:   90,
5083						Name:     "tcp",
5084					},
5085					Hosts: []string{
5086						"ns2/bar.com",
5087					},
5088				},
5089			},
5090		}, false},
5091		{"ingress without port", &networking.Sidecar{
5092			Ingress: []*networking.IstioIngressListener{
5093				{
5094					DefaultEndpoint: "127.0.0.1:110",
5095				},
5096			},
5097			Egress: []*networking.IstioEgressListener{
5098				{
5099					Hosts: []string{"*/*"},
5100				},
5101			},
5102		}, false},
5103		{"ingress with duplicate ports", &networking.Sidecar{
5104			Ingress: []*networking.IstioIngressListener{
5105				{
5106					Port: &networking.Port{
5107						Protocol: "http",
5108						Number:   90,
5109						Name:     "foo",
5110					},
5111					DefaultEndpoint: "127.0.0.1:110",
5112				},
5113				{
5114					Port: &networking.Port{
5115						Protocol: "tcp",
5116						Number:   90,
5117						Name:     "bar",
5118					},
5119					DefaultEndpoint: "127.0.0.1:110",
5120				},
5121			},
5122			Egress: []*networking.IstioEgressListener{
5123				{
5124					Hosts: []string{"*/*"},
5125				},
5126			},
5127		}, false},
5128		{"ingress without default endpoint", &networking.Sidecar{
5129			Ingress: []*networking.IstioIngressListener{
5130				{
5131					Port: &networking.Port{
5132						Protocol: "http",
5133						Number:   90,
5134						Name:     "foo",
5135					},
5136				},
5137			},
5138			Egress: []*networking.IstioEgressListener{
5139				{
5140					Hosts: []string{"*/*"},
5141				},
5142			},
5143		}, false},
5144		{"ingress with invalid default endpoint IP", &networking.Sidecar{
5145			Ingress: []*networking.IstioIngressListener{
5146				{
5147					Port: &networking.Port{
5148						Protocol: "http",
5149						Number:   90,
5150						Name:     "foo",
5151					},
5152					DefaultEndpoint: "1.1.1.1:90",
5153				},
5154			},
5155		}, false},
5156		{"ingress with invalid default endpoint uds", &networking.Sidecar{
5157			Ingress: []*networking.IstioIngressListener{
5158				{
5159					Port: &networking.Port{
5160						Protocol: "http",
5161						Number:   90,
5162						Name:     "foo",
5163					},
5164					DefaultEndpoint: "unix:///",
5165				},
5166			},
5167			Egress: []*networking.IstioEgressListener{
5168				{
5169					Hosts: []string{"*/*"},
5170				},
5171			},
5172		}, false},
5173		{"ingress with invalid default endpoint port", &networking.Sidecar{
5174			Ingress: []*networking.IstioIngressListener{
5175				{
5176					Port: &networking.Port{
5177						Protocol: "http",
5178						Number:   90,
5179						Name:     "foo",
5180					},
5181					DefaultEndpoint: "127.0.0.1:hi",
5182				},
5183			},
5184			Egress: []*networking.IstioEgressListener{
5185				{
5186					Hosts: []string{"*/*"},
5187				},
5188			},
5189		}, false},
5190		{"valid ingress and egress", &networking.Sidecar{
5191			Ingress: []*networking.IstioIngressListener{
5192				{
5193					Port: &networking.Port{
5194						Protocol: "http",
5195						Number:   90,
5196						Name:     "foo",
5197					},
5198					DefaultEndpoint: "127.0.0.1:9999",
5199				},
5200			},
5201			Egress: []*networking.IstioEgressListener{
5202				{
5203					Hosts: []string{"*/*"},
5204				},
5205			},
5206		}, true},
5207		{"valid ingress and empty egress", &networking.Sidecar{
5208			Ingress: []*networking.IstioIngressListener{
5209				{
5210					Port: &networking.Port{
5211						Protocol: "http",
5212						Number:   90,
5213						Name:     "foo",
5214					},
5215					DefaultEndpoint: "127.0.0.1:9999",
5216				},
5217			},
5218		}, false},
5219		{"empty protocol", &networking.Sidecar{
5220			Ingress: []*networking.IstioIngressListener{
5221				{
5222					Port: &networking.Port{
5223						Number: 90,
5224						Name:   "foo",
5225					},
5226					DefaultEndpoint: "127.0.0.1:9999",
5227				},
5228			},
5229			Egress: []*networking.IstioEgressListener{
5230				{
5231					Hosts: []string{"*/*"},
5232				},
5233			},
5234		}, true},
5235		{"ALLOW_ANY sidecar egress policy with no egress proxy ", &networking.Sidecar{
5236			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
5237				Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
5238			},
5239			Egress: []*networking.IstioEgressListener{
5240				{
5241					Hosts: []string{"*/*"},
5242				},
5243			},
5244		}, true},
5245		{"sidecar egress proxy with RESGISTRY_ONLY(default)", &networking.Sidecar{
5246			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
5247				EgressProxy: &networking.Destination{
5248					Host:   "foo.bar",
5249					Subset: "shiny",
5250					Port: &networking.PortSelector{
5251						Number: 5000,
5252					},
5253				},
5254			},
5255			Egress: []*networking.IstioEgressListener{
5256				{
5257					Hosts: []string{"*/*"},
5258				},
5259			},
5260		}, false},
5261		{"sidecar egress proxy with ALLOW_ANY", &networking.Sidecar{
5262			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
5263				Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
5264				EgressProxy: &networking.Destination{
5265					Host:   "foo.bar",
5266					Subset: "shiny",
5267					Port: &networking.PortSelector{
5268						Number: 5000,
5269					},
5270				},
5271			},
5272			Egress: []*networking.IstioEgressListener{
5273				{
5274					Hosts: []string{"*/*"},
5275				},
5276			},
5277		}, true},
5278		{"sidecar egress proxy with ALLOW_ANY, service hostname invalid fqdn", &networking.Sidecar{
5279			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
5280				Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
5281				EgressProxy: &networking.Destination{
5282					Host:   "foobar*123",
5283					Subset: "shiny",
5284					Port: &networking.PortSelector{
5285						Number: 5000,
5286					},
5287				},
5288			},
5289			Egress: []*networking.IstioEgressListener{
5290				{
5291					Hosts: []string{"*/*"},
5292				},
5293			},
5294		}, false},
5295		{"sidecar egress proxy(without Port) with ALLOW_ANY", &networking.Sidecar{
5296			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
5297				Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
5298				EgressProxy: &networking.Destination{
5299					Host:   "foo.bar",
5300					Subset: "shiny",
5301				},
5302			},
5303			Egress: []*networking.IstioEgressListener{
5304				{
5305					Hosts: []string{"*/*"},
5306				},
5307			},
5308		}, false},
5309	}
5310
5311	for _, tt := range tests {
5312		t.Run(tt.name, func(t *testing.T) {
5313			err := ValidateSidecar("foo", "bar", tt.in)
5314			if err == nil && !tt.valid {
5315				t.Fatalf("ValidateSidecar(%v) = true, wanted false", tt.in)
5316			} else if err != nil && tt.valid {
5317				t.Fatalf("ValidateSidecar(%v) = %v, wanted true", tt.in, err)
5318			}
5319		})
5320	}
5321}
5322
5323func TestValidateLocalityLbSetting(t *testing.T) {
5324	cases := []struct {
5325		name  string
5326		in    *networking.LocalityLoadBalancerSetting
5327		valid bool
5328	}{
5329		{
5330			name:  "valid mesh config without LocalityLoadBalancerSetting",
5331			in:    nil,
5332			valid: true,
5333		},
5334
5335		{
5336			name: "invalid LocalityLoadBalancerSetting_Distribute total weight > 100",
5337			in: &networking.LocalityLoadBalancerSetting{
5338				Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{
5339					{
5340						From: "a/b/c",
5341						To: map[string]uint32{
5342							"a/b/c": 80,
5343							"a/b1":  25,
5344						},
5345					},
5346				},
5347			},
5348			valid: false,
5349		},
5350		{
5351			name: "invalid LocalityLoadBalancerSetting_Distribute total weight < 100",
5352			in: &networking.LocalityLoadBalancerSetting{
5353				Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{
5354					{
5355						From: "a/b/c",
5356						To: map[string]uint32{
5357							"a/b/c": 80,
5358							"a/b1":  15,
5359						},
5360					},
5361				},
5362			},
5363			valid: false,
5364		},
5365		{
5366			name: "invalid LocalityLoadBalancerSetting_Distribute weight = 0",
5367			in: &networking.LocalityLoadBalancerSetting{
5368				Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{
5369					{
5370						From: "a/b/c",
5371						To: map[string]uint32{
5372							"a/b/c": 0,
5373							"a/b1":  100,
5374						},
5375					},
5376				},
5377			},
5378			valid: false,
5379		},
5380		{
5381			name: "invalid LocalityLoadBalancerSetting specify both distribute and failover",
5382			in: &networking.LocalityLoadBalancerSetting{
5383				Distribute: []*networking.LocalityLoadBalancerSetting_Distribute{
5384					{
5385						From: "a/b/c",
5386						To: map[string]uint32{
5387							"a/b/c": 80,
5388							"a/b1":  20,
5389						},
5390					},
5391				},
5392				Failover: []*networking.LocalityLoadBalancerSetting_Failover{
5393					{
5394						From: "region1",
5395						To:   "region2",
5396					},
5397				},
5398			},
5399			valid: false,
5400		},
5401
5402		{
5403			name: "invalid failover src and dst have same region",
5404			in: &networking.LocalityLoadBalancerSetting{
5405				Failover: []*networking.LocalityLoadBalancerSetting_Failover{
5406					{
5407						From: "region1",
5408						To:   "region1",
5409					},
5410				},
5411			},
5412			valid: false,
5413		},
5414	}
5415
5416	for _, c := range cases {
5417		if got := validateLocalityLbSetting(c.in); (got == nil) != c.valid {
5418			t.Errorf("ValidateLocalityLbSetting failed on %v: got valid=%v but wanted valid=%v: %v",
5419				c.name, got == nil, c.valid, got)
5420		}
5421	}
5422}
5423
5424func TestValidateLocalities(t *testing.T) {
5425	cases := []struct {
5426		name       string
5427		localities []string
5428		valid      bool
5429	}{
5430		{
5431			name:       "multi wildcard locality",
5432			localities: []string{"*/zone/*"},
5433			valid:      false,
5434		},
5435		{
5436			name:       "wildcard not in suffix",
5437			localities: []string{"*/zone"},
5438			valid:      false,
5439		},
5440		{
5441			name:       "explicit wildcard region overlap",
5442			localities: []string{"*", "a/b/c"},
5443			valid:      false,
5444		},
5445		{
5446			name:       "implicit wildcard region overlap",
5447			localities: []string{"a", "a/b/c"},
5448			valid:      false,
5449		},
5450		{
5451			name:       "explicit wildcard zone overlap",
5452			localities: []string{"a/*", "a/b/c"},
5453			valid:      false,
5454		},
5455		{
5456			name:       "implicit wildcard zone overlap",
5457			localities: []string{"a/b", "a/b/c"},
5458			valid:      false,
5459		},
5460		{
5461			name:       "explicit wildcard subzone overlap",
5462			localities: []string{"a/b/*", "a/b/c"},
5463			valid:      false,
5464		},
5465		{
5466			name:       "implicit wildcard subzone overlap",
5467			localities: []string{"a/b", "a/b/c"},
5468			valid:      false,
5469		},
5470		{
5471			name:       "valid localities",
5472			localities: []string{"a1/*", "a2/*", "a3/b3/c3", "a4/b4", "a5/b5/*"},
5473			valid:      true,
5474		},
5475	}
5476	for _, c := range cases {
5477		t.Run(c.name, func(t *testing.T) {
5478			err := validateLocalities(c.localities)
5479			if !c.valid && err == nil {
5480				t.Errorf("expect invalid localities")
5481			}
5482
5483			if c.valid && err != nil {
5484				t.Errorf("expect valid localities. but got err %v", err)
5485			}
5486		})
5487	}
5488}
5489
5490func TestValidationIPAddress(t *testing.T) {
5491	tests := []struct {
5492		name string
5493		addr string
5494		ok   bool
5495	}{
5496		{
5497			name: "valid ipv4 address",
5498			addr: "1.1.1.1",
5499			ok:   true,
5500		},
5501		{
5502			name: "invalid ipv4 address",
5503			addr: "1.1.A.1",
5504			ok:   false,
5505		},
5506		{
5507			name: "valid ipv6 subnet",
5508			addr: "2001:1::1",
5509			ok:   true,
5510		},
5511		{
5512			name: "invalid ipv6 address",
5513			addr: "2001:1:G::1",
5514			ok:   false,
5515		},
5516	}
5517	for _, tt := range tests {
5518		if err := ValidateIPAddress(tt.addr); err != nil {
5519			if tt.ok {
5520				t.Errorf("test: \"%s\" expected to succeed but failed with error: %+v", tt.name, err)
5521			}
5522		} else {
5523			if !tt.ok {
5524				t.Errorf("test: \"%s\" expected to fail but succeeded", tt.name)
5525			}
5526		}
5527	}
5528}
5529
5530func TestValidationIPSubnet(t *testing.T) {
5531	tests := []struct {
5532		name   string
5533		subnet string
5534		ok     bool
5535	}{
5536		{
5537			name:   "valid ipv4 subnet",
5538			subnet: "1.1.1.1/24",
5539			ok:     true,
5540		},
5541		{
5542			name:   "invalid ipv4 subnet",
5543			subnet: "1.1.1.1/48",
5544			ok:     false,
5545		},
5546		{
5547			name:   "valid ipv6 subnet",
5548			subnet: "2001:1::1/64",
5549			ok:     true,
5550		},
5551		{
5552			name:   "invalid ipv6 subnet",
5553			subnet: "2001:1::1/132",
5554			ok:     false,
5555		},
5556	}
5557
5558	for _, tt := range tests {
5559		if err := ValidateIPSubnet(tt.subnet); err != nil {
5560			if tt.ok {
5561				t.Errorf("test: \"%s\" expected to succeed but failed with error: %+v", tt.name, err)
5562			}
5563		} else {
5564			if !tt.ok {
5565				t.Errorf("test: \"%s\" expected to fail but succeeded", tt.name)
5566			}
5567		}
5568	}
5569}
5570
5571func TestValidateRequestAuthentication(t *testing.T) {
5572	cases := []struct {
5573		name       string
5574		configName string
5575		in         proto.Message
5576		valid      bool
5577	}{
5578		{
5579			name:       "empty spec",
5580			configName: constants.DefaultAuthenticationPolicyName,
5581			in:         &security_beta.RequestAuthentication{},
5582			valid:      true,
5583		},
5584		{
5585			name:       "another empty spec",
5586			configName: constants.DefaultAuthenticationPolicyName,
5587			in: &security_beta.RequestAuthentication{
5588				Selector: &api.WorkloadSelector{},
5589			},
5590			valid: true,
5591		},
5592		{
5593			name:       "empty spec with non default name",
5594			configName: someName,
5595			in:         &security_beta.RequestAuthentication{},
5596			valid:      true,
5597		},
5598		{
5599			name:       "default name with non empty selector",
5600			configName: constants.DefaultAuthenticationPolicyName,
5601			in: &security_beta.RequestAuthentication{
5602				Selector: &api.WorkloadSelector{
5603					MatchLabels: map[string]string{
5604						"app": "httpbin",
5605					},
5606				},
5607			},
5608			valid: true,
5609		},
5610		{
5611			name:       "empty jwt rule",
5612			configName: "foo",
5613			in: &security_beta.RequestAuthentication{
5614				JwtRules: []*security_beta.JWTRule{
5615					{},
5616				},
5617			},
5618			valid: false,
5619		},
5620		{
5621			name:       "empty issuer",
5622			configName: "foo",
5623			in: &security_beta.RequestAuthentication{
5624				JwtRules: []*security_beta.JWTRule{
5625					{
5626						Issuer: "",
5627					},
5628				},
5629			},
5630			valid: false,
5631		},
5632		{
5633			name:       "bad JwksUri - no protocol",
5634			configName: "foo",
5635			in: &security_beta.RequestAuthentication{
5636				JwtRules: []*security_beta.JWTRule{
5637					{
5638						Issuer:  "foo.com",
5639						JwksUri: "foo.com",
5640					},
5641				},
5642			},
5643			valid: false,
5644		},
5645		{
5646			name:       "bad JwksUri - invalid port",
5647			configName: "foo",
5648			in: &security_beta.RequestAuthentication{
5649				JwtRules: []*security_beta.JWTRule{
5650					{
5651						Issuer:  "foo.com",
5652						JwksUri: "https://foo.com:not-a-number",
5653					},
5654				},
5655			},
5656			valid: false,
5657		},
5658		{
5659			name:       "empy value",
5660			configName: "foo",
5661			in: &security_beta.RequestAuthentication{
5662				Selector: &api.WorkloadSelector{
5663					MatchLabels: map[string]string{
5664						"app":     "httpbin",
5665						"version": "",
5666					},
5667				},
5668				JwtRules: []*security_beta.JWTRule{
5669					{
5670						Issuer:  "foo.com",
5671						JwksUri: "https://foo.com/cert",
5672					},
5673				},
5674			},
5675			valid: true,
5676		},
5677		{
5678			name:       "bad selector - empy key",
5679			configName: "foo",
5680			in: &security_beta.RequestAuthentication{
5681				Selector: &api.WorkloadSelector{
5682					MatchLabels: map[string]string{
5683						"app": "httpbin",
5684						"":    "v1",
5685					},
5686				},
5687				JwtRules: []*security_beta.JWTRule{
5688					{
5689						Issuer:  "foo.com",
5690						JwksUri: "https://foo.com/cert",
5691					},
5692				},
5693			},
5694			valid: false,
5695		},
5696		{
5697			name:       "bad header location",
5698			configName: constants.DefaultAuthenticationPolicyName,
5699			in: &security_beta.RequestAuthentication{
5700				JwtRules: []*security_beta.JWTRule{
5701					{
5702						Issuer:  "foo.com",
5703						JwksUri: "https://foo.com",
5704						FromHeaders: []*security_beta.JWTHeader{
5705							{
5706								Name:   "",
5707								Prefix: "Bearer ",
5708							},
5709						},
5710					},
5711				},
5712			},
5713			valid: false,
5714		},
5715		{
5716			name:       "bad param location",
5717			configName: constants.DefaultAuthenticationPolicyName,
5718			in: &security_beta.RequestAuthentication{
5719				JwtRules: []*security_beta.JWTRule{
5720					{
5721						Issuer:     "foo.com",
5722						JwksUri:    "https://foo.com",
5723						FromParams: []string{""},
5724					},
5725				},
5726			},
5727			valid: false,
5728		},
5729		{
5730			name:       "good",
5731			configName: constants.DefaultAuthenticationPolicyName,
5732			in: &security_beta.RequestAuthentication{
5733				JwtRules: []*security_beta.JWTRule{
5734					{
5735						Issuer:  "foo.com",
5736						JwksUri: "https://foo.com",
5737						FromHeaders: []*security_beta.JWTHeader{
5738							{
5739								Name:   "x-foo",
5740								Prefix: "Bearer ",
5741							},
5742						},
5743					},
5744				},
5745			},
5746			valid: true,
5747		},
5748	}
5749
5750	for _, c := range cases {
5751		t.Run(c.name, func(t *testing.T) {
5752			if got := ValidateRequestAuthentication(c.configName, someNamespace, c.in); (got == nil) != c.valid {
5753				t.Errorf("got(%v) != want(%v)\n", got, c.valid)
5754			}
5755		})
5756	}
5757}
5758
5759func TestValidatePeerAuthentication(t *testing.T) {
5760	cases := []struct {
5761		name       string
5762		configName string
5763		in         proto.Message
5764		valid      bool
5765	}{
5766		{
5767			name:       "empty spec",
5768			configName: constants.DefaultAuthenticationPolicyName,
5769			in:         &security_beta.PeerAuthentication{},
5770			valid:      true,
5771		},
5772		{
5773			name:       "empty mtls",
5774			configName: constants.DefaultAuthenticationPolicyName,
5775			in: &security_beta.PeerAuthentication{
5776				Selector: &api.WorkloadSelector{},
5777			},
5778			valid: true,
5779		},
5780		{
5781			name:       "empty spec with non default name",
5782			configName: someName,
5783			in:         &security_beta.PeerAuthentication{},
5784			valid:      true,
5785		},
5786		{
5787			name:       "default name with non empty selector",
5788			configName: constants.DefaultAuthenticationPolicyName,
5789			in: &security_beta.PeerAuthentication{
5790				Selector: &api.WorkloadSelector{
5791					MatchLabels: map[string]string{
5792						"app": "httpbin",
5793					},
5794				},
5795			},
5796			valid: true,
5797		},
5798		{
5799			name:       "empty port level mtls",
5800			configName: "foo",
5801			in: &security_beta.PeerAuthentication{
5802				Selector: &api.WorkloadSelector{
5803					MatchLabels: map[string]string{
5804						"app": "httpbin",
5805					},
5806				},
5807				PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{},
5808			},
5809			valid: false,
5810		},
5811		{
5812			name:       "empty selector with port level mtls",
5813			configName: constants.DefaultAuthenticationPolicyName,
5814			in: &security_beta.PeerAuthentication{
5815				PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{
5816					8080: {
5817						Mode: security_beta.PeerAuthentication_MutualTLS_UNSET,
5818					},
5819				},
5820			},
5821			valid: false,
5822		},
5823		{
5824			name:       "port 0",
5825			configName: "foo",
5826			in: &security_beta.PeerAuthentication{
5827				PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{
5828					0: {
5829						Mode: security_beta.PeerAuthentication_MutualTLS_UNSET,
5830					},
5831				},
5832			},
5833			valid: false,
5834		},
5835		{
5836			name:       "unset mode",
5837			configName: constants.DefaultAuthenticationPolicyName,
5838			in: &security_beta.PeerAuthentication{
5839				Mtls: &security_beta.PeerAuthentication_MutualTLS{
5840					Mode: security_beta.PeerAuthentication_MutualTLS_UNSET,
5841				},
5842			},
5843			valid: true,
5844		},
5845		{
5846			name:       "port level",
5847			configName: "port-level",
5848			in: &security_beta.PeerAuthentication{
5849				Selector: &api.WorkloadSelector{
5850					MatchLabels: map[string]string{
5851						"app": "httpbin",
5852					},
5853				},
5854				PortLevelMtls: map[uint32]*security_beta.PeerAuthentication_MutualTLS{
5855					8080: {
5856						Mode: security_beta.PeerAuthentication_MutualTLS_UNSET,
5857					},
5858				},
5859			},
5860			valid: true,
5861		},
5862	}
5863
5864	for _, c := range cases {
5865		t.Run(c.name, func(t *testing.T) {
5866			if got := ValidatePeerAuthentication(c.configName, someNamespace, c.in); (got == nil) != c.valid {
5867				t.Errorf("got(%v) != want(%v)\n", got, c.valid)
5868			}
5869		})
5870	}
5871}
5872
5873func TestServiceSettings(t *testing.T) {
5874	cases := []struct {
5875		name  string
5876		hosts []string
5877		valid bool
5878	}{
5879		{
5880			name: "good",
5881			hosts: []string{
5882				"*.foo.bar",
5883				"bar.baz.svc.cluster.local",
5884			},
5885			valid: true,
5886		},
5887		{
5888			name: "bad",
5889			hosts: []string{
5890				"foo.bar.*",
5891			},
5892			valid: false,
5893		},
5894	}
5895
5896	for _, c := range cases {
5897		t.Run(c.name, func(t *testing.T) {
5898			m := meshconfig.MeshConfig{
5899				ServiceSettings: []*meshconfig.MeshConfig_ServiceSettings{
5900					{
5901						Hosts: c.hosts,
5902					},
5903				},
5904			}
5905
5906			if got := validateServiceSettings(&m); (got == nil) != c.valid {
5907				t.Errorf("got(%v) != want(%v)\n", got, c.valid)
5908			}
5909		})
5910	}
5911}
5912
5913func TestValidateMeshNetworks(t *testing.T) {
5914	testcases := []struct {
5915		name  string
5916		mn    *meshconfig.MeshNetworks
5917		valid bool
5918	}{
5919		{
5920			name:  "Empty MeshNetworks",
5921			mn:    &meshconfig.MeshNetworks{},
5922			valid: true,
5923		},
5924		{
5925			name: "Valid MeshNetworks",
5926			mn: &meshconfig.MeshNetworks{
5927				Networks: map[string]*meshconfig.Network{
5928					"n1": {
5929						Endpoints: []*meshconfig.Network_NetworkEndpoints{
5930							{
5931								Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{
5932									FromRegistry: "Kubernetes",
5933								},
5934							},
5935						},
5936						Gateways: []*meshconfig.Network_IstioNetworkGateway{
5937							{
5938								Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{
5939									RegistryServiceName: "istio-ingressgateway.istio-system.svc.cluster.local",
5940								},
5941								Port: 80,
5942							},
5943						},
5944					},
5945					"n2": {
5946						Endpoints: []*meshconfig.Network_NetworkEndpoints{
5947							{
5948								Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{
5949									FromRegistry: "cluster1",
5950								},
5951							},
5952						},
5953						Gateways: []*meshconfig.Network_IstioNetworkGateway{
5954							{
5955								Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{
5956									RegistryServiceName: "istio-ingressgateway.istio-system.svc.cluster.local",
5957								},
5958								Port: 443,
5959							},
5960						},
5961					},
5962				},
5963			},
5964			valid: true,
5965		},
5966		{
5967			name: "Invalid registry name",
5968			mn: &meshconfig.MeshNetworks{
5969				Networks: map[string]*meshconfig.Network{
5970					"n1": {
5971						Endpoints: []*meshconfig.Network_NetworkEndpoints{
5972							{
5973								Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{
5974									FromRegistry: "cluster.local",
5975								},
5976							},
5977						},
5978						Gateways: []*meshconfig.Network_IstioNetworkGateway{
5979							{
5980								Gw: &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{
5981									RegistryServiceName: "istio-ingressgateway.istio-system.svc.cluster.local",
5982								},
5983								Port: 80,
5984							},
5985						},
5986					},
5987				},
5988			},
5989			valid: false,
5990		},
5991	}
5992
5993	for _, tc := range testcases {
5994		t.Run(tc.name, func(t *testing.T) {
5995			err := ValidateMeshNetworks(tc.mn)
5996			if err != nil && tc.valid {
5997				t.Errorf("error not expected on valid meshnetworks: %v", *tc.mn)
5998			}
5999			if err == nil && !tc.valid {
6000				t.Errorf("expected an error on invalid meshnetworks: %v", *tc.mn)
6001			}
6002		})
6003	}
6004}
6005