1// Copyright 2018 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 model
16
17import (
18	"fmt"
19	"reflect"
20	"testing"
21
22	"github.com/davecgh/go-spew/spew"
23	auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
24	core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
25
26	"istio.io/istio/pilot/pkg/features"
27	"istio.io/istio/pilot/pkg/model"
28	"istio.io/istio/pilot/pkg/networking/util"
29)
30
31func TestConstructSdsSecretConfig(t *testing.T) {
32	gRPCConfig := &core.GrpcService_GoogleGrpc{
33		TargetUri:  "/tmp/sdsuds.sock",
34		StatPrefix: SDSStatPrefix,
35		ChannelCredentials: &core.GrpcService_GoogleGrpc_ChannelCredentials{
36			CredentialSpecifier: &core.GrpcService_GoogleGrpc_ChannelCredentials_LocalCredentials{
37				LocalCredentials: &core.GrpcService_GoogleGrpc_GoogleLocalCredentials{},
38			},
39		},
40	}
41
42	gRPCConfig.CredentialsFactoryName = FileBasedMetadataPlugName
43	gRPCConfig.CallCredentials = ConstructgRPCCallCredentials(K8sSATrustworthyJwtFileName, K8sSAJwtTokenHeaderKey)
44
45	cases := []struct {
46		serviceAccount string
47		sdsUdsPath     string
48		expected       *auth.SdsSecretConfig
49	}{
50		{
51			serviceAccount: "spiffe://cluster.local/ns/bar/sa/foo",
52			sdsUdsPath:     "/tmp/sdsuds.sock",
53			expected: &auth.SdsSecretConfig{
54				Name: "spiffe://cluster.local/ns/bar/sa/foo",
55				SdsConfig: &core.ConfigSource{
56					InitialFetchTimeout: features.InitialFetchTimeout,
57					ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
58						ApiConfigSource: &core.ApiConfigSource{
59							ApiType: core.ApiConfigSource_GRPC,
60							GrpcServices: []*core.GrpcService{
61								{
62									TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
63										EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName},
64									},
65								},
66							},
67							RefreshDelay: nil,
68						},
69					},
70				},
71			},
72		},
73		{
74			serviceAccount: "spiffe://cluster.local/ns/bar/sa/foo",
75			sdsUdsPath:     "/tmp/sdsuds.sock",
76			expected: &auth.SdsSecretConfig{
77				Name: "spiffe://cluster.local/ns/bar/sa/foo",
78				SdsConfig: &core.ConfigSource{
79					InitialFetchTimeout: features.InitialFetchTimeout,
80					ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
81						ApiConfigSource: &core.ApiConfigSource{
82							ApiType: core.ApiConfigSource_GRPC,
83							GrpcServices: []*core.GrpcService{
84								{
85									TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
86										EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName},
87									},
88								},
89							},
90							RefreshDelay: nil,
91						},
92					},
93				},
94			},
95		},
96		{
97			serviceAccount: "",
98			sdsUdsPath:     "/tmp/sdsuds.sock",
99			expected:       nil,
100		},
101		{
102			serviceAccount: "",
103			sdsUdsPath:     "spiffe://cluster.local/ns/bar/sa/foo",
104			expected:       nil,
105		},
106	}
107
108	for _, c := range cases {
109		if got := ConstructSdsSecretConfig(c.serviceAccount, c.sdsUdsPath); !reflect.DeepEqual(got, c.expected) {
110			t.Errorf("ConstructSdsSecretConfig: got(%#v) != want(%#v)\n", got, c.expected)
111			fmt.Println(got)
112			fmt.Println(c.expected)
113		}
114	}
115}
116
117func TestConstructSdsSecretConfigWithCustomUds(t *testing.T) {
118	cases := []struct {
119		serviceAccount string
120		sdsUdsPath     string
121		expected       *auth.SdsSecretConfig
122	}{
123		{
124			serviceAccount: "spiffe://cluster.local/ns/bar/sa/foo",
125			sdsUdsPath:     "/tmp/sdsuds.sock",
126			expected: &auth.SdsSecretConfig{
127				Name: "spiffe://cluster.local/ns/bar/sa/foo",
128				SdsConfig: &core.ConfigSource{
129					InitialFetchTimeout: features.InitialFetchTimeout,
130					ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
131						ApiConfigSource: &core.ApiConfigSource{
132							ApiType: core.ApiConfigSource_GRPC,
133							GrpcServices: []*core.GrpcService{
134								{
135									TargetSpecifier: &core.GrpcService_GoogleGrpc_{
136										GoogleGrpc: &core.GrpcService_GoogleGrpc{
137											TargetUri:  "/tmp/sdsuds.sock",
138											StatPrefix: SDSStatPrefix,
139										},
140									},
141								},
142							},
143							RefreshDelay: nil,
144						},
145					},
146				},
147			},
148		},
149		{
150			serviceAccount: "",
151			sdsUdsPath:     "/tmp/sdsuds.sock",
152			expected:       nil,
153		},
154		{
155			serviceAccount: "spiffe://cluster.local/ns/bar/sa/foo",
156			sdsUdsPath:     "",
157			expected:       nil,
158		},
159	}
160
161	for _, c := range cases {
162		if got := ConstructSdsSecretConfigWithCustomUds(c.serviceAccount, c.sdsUdsPath); !reflect.DeepEqual(got, c.expected) {
163			t.Errorf("ConstructSdsSecretConfig: got(%#v) != want(%#v)\n", got, c.expected)
164		}
165	}
166}
167
168func TestApplyToCommonTLSContext(t *testing.T) {
169	testCases := []struct {
170		name       string
171		sdsUdsPath string
172		node       *model.Proxy
173		result     *auth.CommonTlsContext
174	}{
175		{
176			name:       "MTLSStrict using SDS",
177			sdsUdsPath: "/tmp/sdsuds.sock",
178			node: &model.Proxy{
179				Metadata: &model.NodeMetadata{
180					SdsEnabled: true,
181				},
182			},
183			result: &auth.CommonTlsContext{
184				TlsCertificateSdsSecretConfigs: []*auth.SdsSecretConfig{
185					{
186						Name: "default",
187						SdsConfig: &core.ConfigSource{
188							InitialFetchTimeout: features.InitialFetchTimeout,
189							ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
190								ApiConfigSource: &core.ApiConfigSource{
191									ApiType: core.ApiConfigSource_GRPC,
192									GrpcServices: []*core.GrpcService{
193										{
194											TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
195												EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName},
196											},
197										},
198									},
199								},
200							},
201						},
202					},
203				},
204				ValidationContextType: &auth.CommonTlsContext_CombinedValidationContext{
205					CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{
206						DefaultValidationContext: &auth.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{})},
207						ValidationContextSdsSecretConfig: &auth.SdsSecretConfig{
208							Name: "ROOTCA",
209							SdsConfig: &core.ConfigSource{
210								InitialFetchTimeout: features.InitialFetchTimeout,
211								ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
212									ApiConfigSource: &core.ApiConfigSource{
213										ApiType: core.ApiConfigSource_GRPC,
214										GrpcServices: []*core.GrpcService{
215											{
216												TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
217													EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName},
218												},
219											},
220										},
221									},
222								},
223							},
224						},
225					},
226				},
227			},
228		},
229		{
230			name:       "MTLS using SDS with custom certs in metadata",
231			sdsUdsPath: "/tmp/sdsuds.sock",
232			node: &model.Proxy{
233				Metadata: &model.NodeMetadata{
234					SdsEnabled:         true,
235					TLSServerCertChain: "serverCertChain",
236					TLSServerKey:       "serverKey",
237					TLSServerRootCert:  "servrRootCert",
238				},
239			},
240			result: &auth.CommonTlsContext{
241				TlsCertificateSdsSecretConfigs: []*auth.SdsSecretConfig{
242					{
243						Name: "file-cert:serverCertChain~serverKey",
244						SdsConfig: &core.ConfigSource{
245							InitialFetchTimeout: features.InitialFetchTimeout,
246							ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
247								ApiConfigSource: &core.ApiConfigSource{
248									ApiType: core.ApiConfigSource_GRPC,
249									GrpcServices: []*core.GrpcService{
250										{
251											TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
252												EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName},
253											},
254										},
255									},
256								},
257							},
258						},
259					},
260				},
261				ValidationContextType: &auth.CommonTlsContext_CombinedValidationContext{
262					CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{
263						DefaultValidationContext: &auth.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{})},
264						ValidationContextSdsSecretConfig: &auth.SdsSecretConfig{
265							Name: "file-root:servrRootCert",
266							SdsConfig: &core.ConfigSource{
267								InitialFetchTimeout: features.InitialFetchTimeout,
268								ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{
269									ApiConfigSource: &core.ApiConfigSource{
270										ApiType: core.ApiConfigSource_GRPC,
271										GrpcServices: []*core.GrpcService{
272											{
273												TargetSpecifier: &core.GrpcService_EnvoyGrpc_{
274													EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName},
275												},
276											},
277										},
278									},
279								},
280							},
281						},
282					},
283				},
284			},
285		},
286		{
287			name:       "ISTIO_MUTUAL SDS without node meta",
288			sdsUdsPath: "/tmp/sdsuds.sock",
289			node: &model.Proxy{
290				Metadata: &model.NodeMetadata{},
291			},
292			result: &auth.CommonTlsContext{
293				TlsCertificates: []*auth.TlsCertificate{
294					{
295						CertificateChain: &core.DataSource{
296							Specifier: &core.DataSource_Filename{
297								Filename: "/etc/certs/cert-chain.pem",
298							},
299						},
300						PrivateKey: &core.DataSource{
301							Specifier: &core.DataSource_Filename{
302								Filename: "/etc/certs/key.pem",
303							},
304						},
305					},
306				},
307				ValidationContextType: &auth.CommonTlsContext_ValidationContext{
308					ValidationContext: &auth.CertificateValidationContext{
309						TrustedCa: &core.DataSource{
310							Specifier: &core.DataSource_Filename{
311								Filename: "/etc/certs/root-cert.pem",
312							},
313						},
314					},
315				},
316			},
317		},
318		{
319			name:       "ISTIO_MUTUAL with custom cert paths from proxy node metadata and SDS disabled",
320			sdsUdsPath: "/tmp/sdsuds.sock",
321			node: &model.Proxy{
322				Metadata: &model.NodeMetadata{
323					TLSServerCertChain: "/custom/path/to/cert-chain.pem",
324					TLSServerKey:       "/custom-key.pem",
325					TLSServerRootCert:  "/custom/path/to/root.pem",
326				},
327			},
328			result: &auth.CommonTlsContext{
329				TlsCertificates: []*auth.TlsCertificate{
330					{
331						CertificateChain: &core.DataSource{
332							Specifier: &core.DataSource_Filename{
333								Filename: "/custom/path/to/cert-chain.pem",
334							},
335						},
336						PrivateKey: &core.DataSource{
337							Specifier: &core.DataSource_Filename{
338								Filename: "/custom-key.pem",
339							},
340						},
341					},
342				},
343				ValidationContextType: &auth.CommonTlsContext_ValidationContext{
344					ValidationContext: &auth.CertificateValidationContext{
345						TrustedCa: &core.DataSource{
346							Specifier: &core.DataSource_Filename{
347								Filename: "/custom/path/to/root.pem",
348							},
349						},
350					},
351				},
352			},
353		},
354	}
355
356	for _, test := range testCases {
357		t.Run(test.name, func(t *testing.T) {
358			tlsContext := &auth.CommonTlsContext{}
359			ApplyToCommonTLSContext(tlsContext, test.node.Metadata, test.sdsUdsPath, []string{})
360
361			if !reflect.DeepEqual(tlsContext, test.result) {
362				t.Errorf("got() = %v, want %v", spew.Sdump(tlsContext), spew.Sdump(test.result))
363			}
364		})
365	}
366}
367