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 kube
16
17import (
18	"fmt"
19	"reflect"
20	"strings"
21	"testing"
22	"time"
23
24	"istio.io/api/annotation"
25	"istio.io/istio/pilot/pkg/model"
26	"istio.io/istio/pkg/config/kube"
27	"istio.io/istio/pkg/config/protocol"
28	"istio.io/istio/pkg/spiffe"
29
30	coreV1 "k8s.io/api/core/v1"
31	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32	"k8s.io/apimachinery/pkg/util/intstr"
33)
34
35var (
36	domainSuffix = "company.com"
37	clusterID    = "test-cluster"
38)
39
40func TestConvertProtocol(t *testing.T) {
41	http := "http"
42	type protocolCase struct {
43		port        int32
44		name        string
45		appProtocol *string
46		proto       coreV1.Protocol
47		out         protocol.Instance
48	}
49	protocols := []protocolCase{
50		{8888, "", nil, coreV1.ProtocolTCP, protocol.Unsupported},
51		{25, "", nil, coreV1.ProtocolTCP, protocol.TCP},
52		{53, "", nil, coreV1.ProtocolTCP, protocol.TCP},
53		{3306, "", nil, coreV1.ProtocolTCP, protocol.TCP},
54		{27017, "", nil, coreV1.ProtocolTCP, protocol.TCP},
55		{8888, "http", nil, coreV1.ProtocolTCP, protocol.HTTP},
56		{8888, "http-test", nil, coreV1.ProtocolTCP, protocol.HTTP},
57		{8888, "http", nil, coreV1.ProtocolUDP, protocol.UDP},
58		{8888, "httptest", nil, coreV1.ProtocolTCP, protocol.Unsupported},
59		{25, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP},
60		{53, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP},
61		{3306, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP},
62		{27017, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP},
63		{8888, "https", nil, coreV1.ProtocolTCP, protocol.HTTPS},
64		{8888, "https-test", nil, coreV1.ProtocolTCP, protocol.HTTPS},
65		{8888, "http2", nil, coreV1.ProtocolTCP, protocol.HTTP2},
66		{8888, "http2-test", nil, coreV1.ProtocolTCP, protocol.HTTP2},
67		{8888, "grpc", nil, coreV1.ProtocolTCP, protocol.GRPC},
68		{8888, "grpc-test", nil, coreV1.ProtocolTCP, protocol.GRPC},
69		{8888, "grpc-web", nil, coreV1.ProtocolTCP, protocol.GRPCWeb},
70		{8888, "grpc-web-test", nil, coreV1.ProtocolTCP, protocol.GRPCWeb},
71		{8888, "mongo", nil, coreV1.ProtocolTCP, protocol.Mongo},
72		{8888, "mongo-test", nil, coreV1.ProtocolTCP, protocol.Mongo},
73		{8888, "redis", nil, coreV1.ProtocolTCP, protocol.Redis},
74		{8888, "redis-test", nil, coreV1.ProtocolTCP, protocol.Redis},
75		{8888, "mysql", nil, coreV1.ProtocolTCP, protocol.MySQL},
76		{8888, "mysql-test", nil, coreV1.ProtocolTCP, protocol.MySQL},
77		{8888, "tcp", &http, coreV1.ProtocolTCP, protocol.HTTP},
78	}
79
80	// Create the list of cases for all of the names in both upper and lowercase.
81	cases := make([]protocolCase, 0, len(protocols)*2)
82	for _, p := range protocols {
83		name := p.name
84
85		p.name = strings.ToLower(name)
86		cases = append(cases, p)
87
88		// Don't bother adding uppercase version for empty string.
89		if name != "" {
90			p.name = strings.ToUpper(name)
91			cases = append(cases, p)
92		}
93	}
94
95	for _, c := range cases {
96		testName := strings.Replace(fmt.Sprintf("%s_%s_%d", c.name, c.proto, c.port), "-", "_", -1)
97		t.Run(testName, func(t *testing.T) {
98			out := kube.ConvertProtocol(c.port, c.name, c.proto, c.appProtocol)
99			if out != c.out {
100				t.Fatalf("convertProtocol(%d, %q, %q) => %q, want %q", c.port, c.name, c.proto, out, c.out)
101			}
102		})
103	}
104}
105
106func BenchmarkConvertProtocol(b *testing.B) {
107	cases := []struct {
108		name  string
109		proto coreV1.Protocol
110		out   protocol.Instance
111	}{
112		{"grpc-web-lowercase", coreV1.ProtocolTCP, protocol.GRPCWeb},
113		{"GRPC-WEB-mixedcase", coreV1.ProtocolTCP, protocol.GRPCWeb},
114		{"https-lowercase", coreV1.ProtocolTCP, protocol.HTTPS},
115		{"HTTPS-mixedcase", coreV1.ProtocolTCP, protocol.HTTPS},
116	}
117
118	for _, c := range cases {
119		testName := strings.Replace(c.name, "-", "_", -1)
120		b.Run(testName, func(b *testing.B) {
121			for i := 0; i < b.N; i++ {
122				out := kube.ConvertProtocol(8888, c.name, c.proto, nil)
123				if out != c.out {
124					b.Fatalf("convertProtocol(%q, %q) => %q, want %q", c.name, c.proto, out, c.out)
125				}
126			}
127		})
128	}
129}
130
131func TestServiceConversion(t *testing.T) {
132	serviceName := "service1"
133	namespace := "default"
134	saA := "serviceaccountA"
135	saB := "serviceaccountB"
136	saC := "spiffe://accounts.google.com/serviceaccountC@cloudservices.gserviceaccount.com"
137	saD := "spiffe://accounts.google.com/serviceaccountD@developer.gserviceaccount.com"
138
139	oldTrustDomain := spiffe.GetTrustDomain()
140	spiffe.SetTrustDomain(domainSuffix)
141	defer spiffe.SetTrustDomain(oldTrustDomain)
142
143	ip := "10.0.0.1"
144
145	tnow := time.Now()
146	localSvc := coreV1.Service{
147		ObjectMeta: metaV1.ObjectMeta{
148			Name:      serviceName,
149			Namespace: namespace,
150			Annotations: map[string]string{
151				annotation.AlphaKubernetesServiceAccounts.Name: saA + "," + saB,
152				annotation.AlphaCanonicalServiceAccounts.Name:  saC + "," + saD,
153				"other/annotation": "test",
154			},
155			CreationTimestamp: metaV1.Time{Time: tnow},
156		},
157		Spec: coreV1.ServiceSpec{
158			ClusterIP: ip,
159			Ports: []coreV1.ServicePort{
160				{
161					Name:     "http",
162					Port:     8080,
163					Protocol: coreV1.ProtocolTCP,
164				},
165				{
166					Name:     "https",
167					Protocol: coreV1.ProtocolTCP,
168					Port:     443,
169				},
170			},
171		},
172	}
173
174	service := ConvertService(localSvc, domainSuffix, clusterID)
175	if service == nil {
176		t.Fatalf("could not convert service")
177	}
178
179	if service.CreationTime != tnow {
180		t.Fatalf("incorrect creation time => %v, want %v", service.CreationTime, tnow)
181	}
182
183	if len(service.Ports) != len(localSvc.Spec.Ports) {
184		t.Fatalf("incorrect number of ports => %v, want %v",
185			len(service.Ports), len(localSvc.Spec.Ports))
186	}
187
188	if service.External() {
189		t.Fatal("service should not be external")
190	}
191
192	if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) {
193		t.Fatalf("service hostname incorrect => %q, want %q",
194			service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix))
195	}
196
197	if service.Address != ip {
198		t.Fatalf("service IP incorrect => %q, want %q", service.Address, ip)
199	}
200
201	sa := service.ServiceAccounts
202	if sa == nil || len(sa) != 4 {
203		t.Fatalf("number of service accounts is incorrect")
204	}
205	expected := []string{
206		saC, saD,
207		"spiffe://company.com/ns/default/sa/" + saA,
208		"spiffe://company.com/ns/default/sa/" + saB,
209	}
210	if !reflect.DeepEqual(sa, expected) {
211		t.Fatalf("Unexpected service accounts %v (expecting %v)", sa, expected)
212	}
213}
214
215func TestServiceConversionWithEmptyServiceAccountsAnnotation(t *testing.T) {
216	serviceName := "service1"
217	namespace := "default"
218
219	ip := "10.0.0.1"
220
221	localSvc := coreV1.Service{
222		ObjectMeta: metaV1.ObjectMeta{
223			Name:        serviceName,
224			Namespace:   namespace,
225			Annotations: map[string]string{},
226		},
227		Spec: coreV1.ServiceSpec{
228			ClusterIP: ip,
229			Ports: []coreV1.ServicePort{
230				{
231					Name:     "http",
232					Port:     8080,
233					Protocol: coreV1.ProtocolTCP,
234				},
235				{
236					Name:     "https",
237					Protocol: coreV1.ProtocolTCP,
238					Port:     443,
239				},
240			},
241		},
242	}
243
244	service := ConvertService(localSvc, domainSuffix, clusterID)
245	if service == nil {
246		t.Fatalf("could not convert service")
247	}
248
249	sa := service.ServiceAccounts
250	if len(sa) != 0 {
251		t.Fatalf("number of service accounts is incorrect: %d, expected 0", len(sa))
252	}
253}
254
255func TestExternalServiceConversion(t *testing.T) {
256	serviceName := "service1"
257	namespace := "default"
258
259	extSvc := coreV1.Service{
260		ObjectMeta: metaV1.ObjectMeta{
261			Name:      serviceName,
262			Namespace: namespace,
263		},
264		Spec: coreV1.ServiceSpec{
265			Ports: []coreV1.ServicePort{
266				{
267					Name:     "http",
268					Port:     80,
269					Protocol: coreV1.ProtocolTCP,
270				},
271			},
272			Type:         coreV1.ServiceTypeExternalName,
273			ExternalName: "google.com",
274		},
275	}
276
277	service := ConvertService(extSvc, domainSuffix, clusterID)
278	if service == nil {
279		t.Fatalf("could not convert external service")
280	}
281
282	if len(service.Ports) != len(extSvc.Spec.Ports) {
283		t.Fatalf("incorrect number of ports => %v, want %v",
284			len(service.Ports), len(extSvc.Spec.Ports))
285	}
286
287	if !service.External() {
288		t.Fatal("service should be external")
289	}
290
291	if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) {
292		t.Fatalf("service hostname incorrect => %q, want %q",
293			service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix))
294	}
295}
296
297func TestExternalClusterLocalServiceConversion(t *testing.T) {
298	serviceName := "service1"
299	namespace := "default"
300
301	extSvc := coreV1.Service{
302		ObjectMeta: metaV1.ObjectMeta{
303			Name:      serviceName,
304			Namespace: namespace,
305		},
306		Spec: coreV1.ServiceSpec{
307			Ports: []coreV1.ServicePort{
308				{
309					Name:     "http",
310					Port:     80,
311					Protocol: coreV1.ProtocolTCP,
312				},
313			},
314			Type:         coreV1.ServiceTypeExternalName,
315			ExternalName: "some.test.svc.cluster.local",
316		},
317	}
318
319	domainSuffix := "cluster.local"
320
321	service := ConvertService(extSvc, domainSuffix, clusterID)
322	if service == nil {
323		t.Fatalf("could not convert external service")
324	}
325
326	if len(service.Ports) != len(extSvc.Spec.Ports) {
327		t.Fatalf("incorrect number of ports => %v, want %v",
328			len(service.Ports), len(extSvc.Spec.Ports))
329	}
330
331	if !service.External() {
332		t.Fatal("ExternalName service (even if .cluster.local) should be external")
333	}
334
335	if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) {
336		t.Fatalf("service hostname incorrect => %q, want %q",
337			service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix))
338	}
339}
340
341func TestLBServiceConversion(t *testing.T) {
342	serviceName := "service1"
343	namespace := "default"
344
345	addresses := []coreV1.LoadBalancerIngress{
346		{
347			IP: "127.68.32.112",
348		},
349		{
350			IP: "127.68.32.113",
351		},
352		{
353			Hostname: "127.68.32.114",
354		},
355		{
356			Hostname: "127.68.32.115",
357		},
358	}
359
360	extSvc := coreV1.Service{
361		ObjectMeta: metaV1.ObjectMeta{
362			Name:      serviceName,
363			Namespace: namespace,
364		},
365		Spec: coreV1.ServiceSpec{
366			Ports: []coreV1.ServicePort{
367				{
368					Name:     "http",
369					Port:     80,
370					Protocol: coreV1.ProtocolTCP,
371				},
372			},
373			Type: coreV1.ServiceTypeLoadBalancer,
374		},
375		Status: coreV1.ServiceStatus{
376			LoadBalancer: coreV1.LoadBalancerStatus{
377				Ingress: addresses,
378			},
379		},
380	}
381
382	service := ConvertService(extSvc, domainSuffix, clusterID)
383	if service == nil {
384		t.Fatalf("could not convert external service")
385	}
386
387	if len(service.Attributes.ClusterExternalAddresses[clusterID]) == 0 {
388		t.Fatalf("no load balancer addresses found")
389	}
390
391	for i, addr := range addresses {
392		var want string
393		if len(addr.IP) > 0 {
394			want = addr.IP
395		} else {
396			want = addr.Hostname
397		}
398		got := service.Attributes.ClusterExternalAddresses[clusterID][i]
399		if got != want {
400			t.Fatalf("Expected address %s but got %s", want, got)
401		}
402	}
403}
404
405func TestProbesToPortsConversion(t *testing.T) {
406
407	expected := model.PortList{
408		{
409			Name:     "mgmt-3306",
410			Port:     3306,
411			Protocol: protocol.TCP,
412		},
413		{
414			Name:     "mgmt-9080",
415			Port:     9080,
416			Protocol: protocol.HTTP,
417		},
418	}
419
420	handlers := []coreV1.Handler{
421		{
422			TCPSocket: &coreV1.TCPSocketAction{
423				Port: intstr.IntOrString{StrVal: "mysql", Type: intstr.String},
424			},
425		},
426		{
427			TCPSocket: &coreV1.TCPSocketAction{
428				Port: intstr.IntOrString{IntVal: 3306, Type: intstr.Int},
429			},
430		},
431		{
432			HTTPGet: &coreV1.HTTPGetAction{
433				Path: "/foo",
434				Port: intstr.IntOrString{StrVal: "http-two", Type: intstr.String},
435			},
436		},
437		{
438			HTTPGet: &coreV1.HTTPGetAction{
439				Path: "/foo",
440				Port: intstr.IntOrString{IntVal: 9080, Type: intstr.Int},
441			},
442		},
443	}
444
445	podSpec := &coreV1.PodSpec{
446		Containers: []coreV1.Container{
447			{
448				Name: "scooby",
449				Ports: []coreV1.ContainerPort{
450					{
451						Name:          "mysql",
452						ContainerPort: 3306,
453					},
454					{
455						Name:          "http-two",
456						ContainerPort: 9080,
457					},
458					{
459						Name:          "http",
460						ContainerPort: 80,
461					},
462				},
463				LivenessProbe:  &coreV1.Probe{},
464				ReadinessProbe: &coreV1.Probe{},
465			},
466		},
467	}
468
469	for _, handler1 := range handlers {
470		for _, handler2 := range handlers {
471			if (handler1.TCPSocket != nil && handler2.TCPSocket != nil) ||
472				(handler1.HTTPGet != nil && handler2.HTTPGet != nil) {
473				continue
474			}
475
476			podSpec.Containers[0].LivenessProbe.Handler = handler1
477			podSpec.Containers[0].ReadinessProbe.Handler = handler2
478
479			mgmtPorts, err := ConvertProbesToPorts(podSpec)
480			if err != nil {
481				t.Fatalf("Failed to convert Probes to Ports: %v", err)
482			}
483
484			if !reflect.DeepEqual(mgmtPorts, expected) {
485				t.Fatalf("incorrect number of management ports => %v, want %v",
486					len(mgmtPorts), len(expected))
487			}
488		}
489	}
490}
491
492func TestSecureNamingSANCustomIdentity(t *testing.T) {
493
494	pod := &coreV1.Pod{}
495
496	identity := "foo"
497
498	pod.Annotations = make(map[string]string)
499	pod.Annotations[annotation.AlphaIdentity.Name] = identity
500
501	san := SecureNamingSAN(pod)
502
503	expectedSAN := fmt.Sprintf("spiffe://%v/%v", spiffe.GetTrustDomain(), identity)
504
505	if san != expectedSAN {
506		t.Fatalf("SAN match failed, SAN:%v  expectedSAN:%v", san, expectedSAN)
507	}
508
509}
510
511func TestSecureNamingSAN(t *testing.T) {
512
513	pod := &coreV1.Pod{}
514
515	pod.Annotations = make(map[string]string)
516
517	ns := "anything"
518	sa := "foo"
519	pod.Namespace = ns
520	pod.Spec.ServiceAccountName = sa
521
522	san := SecureNamingSAN(pod)
523
524	expectedSAN := fmt.Sprintf("spiffe://%v/ns/%v/sa/%v", spiffe.GetTrustDomain(), ns, sa)
525
526	if san != expectedSAN {
527		t.Fatalf("SAN match failed, SAN:%v  expectedSAN:%v", san, expectedSAN)
528	}
529}
530