1// Copyright 2020 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	"reflect"
19	"testing"
20
21	rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v2"
22	"github.com/gogo/protobuf/proto"
23
24	"istio.io/istio/pkg/util/protomarshal"
25)
26
27func TestGenerator(t *testing.T) {
28	cases := []struct {
29		name   string
30		g      generator
31		key    string
32		value  string
33		forTCP bool
34		want   interface{}
35	}{
36		{
37			name:  "destIPGenerator",
38			g:     destIPGenerator{},
39			value: "1.2.3.4",
40			want: yamlPermission(t, `
41         destinationIp:
42          addressPrefix: 1.2.3.4
43          prefixLen: 32`),
44		},
45		{
46			name:  "destPortGenerator",
47			g:     destPortGenerator{},
48			value: "80",
49			want: yamlPermission(t, `
50         destinationPort: 80`),
51		},
52		{
53			name:  "connSNIGenerator",
54			g:     connSNIGenerator{},
55			value: "exact.com",
56			want: yamlPermission(t, `
57         requestedServerName:
58          exact: exact.com`),
59		},
60		{
61			name:  "envoyFilterGenerator-string",
62			g:     envoyFilterGenerator{},
63			key:   "experimental.a.b.c[d]",
64			value: "val",
65			want: yamlPermission(t, `
66         metadata:
67          filter: a.b.c
68          path:
69          - key: d
70          value:
71            stringMatch:
72              exact: val`),
73		},
74		{
75			name:  "envoyFilterGenerator-list",
76			g:     envoyFilterGenerator{},
77			key:   "experimental.a.b.c[d]",
78			value: "[v1, v2]",
79			want: yamlPermission(t, `
80         metadata:
81          filter: a.b.c
82          path:
83          - key: d
84          value:
85            listMatch:
86              oneOf:
87                stringMatch:
88                  exact: v1, v2`),
89		},
90		{
91			name:  "srcIPGenerator",
92			g:     srcIPGenerator{},
93			value: "1.2.3.4",
94			want: yamlPrincipal(t, `
95         sourceIp:
96          addressPrefix: 1.2.3.4
97          prefixLen: 32`),
98		},
99		{
100			name:  "srcNamespaceGenerator-http",
101			g:     srcNamespaceGenerator{},
102			value: "foo",
103			want: yamlPrincipal(t, `
104         metadata:
105          filter: istio_authn
106          path:
107          - key: source.principal
108          value:
109            stringMatch:
110              safeRegex:
111                googleRe2: {}
112                regex: .*/ns/foo/.*`),
113		},
114		{
115			name:   "srcNamespaceGenerator-tcp",
116			g:      srcNamespaceGenerator{},
117			value:  "foo",
118			forTCP: true,
119			want: yamlPrincipal(t, `
120         authenticated:
121          principalName:
122            safeRegex:
123              googleRe2: {}
124              regex: .*/ns/foo/.*`),
125		},
126		{
127			name:  "srcPrincipalGenerator-http",
128			g:     srcPrincipalGenerator{},
129			key:   "source.principal",
130			value: "foo",
131			want: yamlPrincipal(t, `
132         metadata:
133          filter: istio_authn
134          path:
135          - key: source.principal
136          value:
137            stringMatch:
138              exact: foo`),
139		},
140		{
141			name:   "srcPrincipalGenerator-tcp",
142			g:      srcPrincipalGenerator{},
143			key:    "source.principal",
144			value:  "foo",
145			forTCP: true,
146			want: yamlPrincipal(t, `
147         authenticated:
148          principalName:
149            exact: spiffe://foo`),
150		},
151		{
152			name:  "requestPrincipalGenerator",
153			g:     requestPrincipalGenerator{},
154			key:   "request.auth.principal",
155			value: "foo",
156			want: yamlPrincipal(t, `
157         metadata:
158          filter: istio_authn
159          path:
160          - key: request.auth.principal
161          value:
162            stringMatch:
163              exact: foo`),
164		},
165		{
166			name:  "requestAudiencesGenerator",
167			g:     requestAudiencesGenerator{},
168			key:   "request.auth.audiences",
169			value: "foo",
170			want: yamlPrincipal(t, `
171         metadata:
172          filter: istio_authn
173          path:
174          - key: request.auth.audiences
175          value:
176            stringMatch:
177              exact: foo`),
178		},
179		{
180			name:  "requestPresenterGenerator",
181			g:     requestPresenterGenerator{},
182			key:   "request.auth.presenter",
183			value: "foo",
184			want: yamlPrincipal(t, `
185         metadata:
186          filter: istio_authn
187          path:
188          - key: request.auth.presenter
189          value:
190            stringMatch:
191              exact: foo`),
192		},
193		{
194			name:  "requestHeaderGenerator",
195			g:     requestHeaderGenerator{},
196			key:   "request.headers[x-foo]",
197			value: "foo",
198			want: yamlPrincipal(t, `
199         header:
200          exactMatch: foo
201          name: x-foo`),
202		},
203		{
204			name:  "requestClaimGenerator",
205			g:     requestClaimGenerator{},
206			key:   "request.auth.claims[bar]",
207			value: "foo",
208			want: yamlPrincipal(t, `
209         metadata:
210          filter: istio_authn
211          path:
212          - key: request.auth.claims
213          - key: bar
214          value:
215            listMatch:
216              oneOf:
217                stringMatch:
218                  exact: foo`),
219		},
220		{
221			name:  "hostGenerator",
222			g:     hostGenerator{},
223			value: "foo",
224			want: yamlPermission(t, `
225         header:
226          exactMatch: foo
227          name: :authority`),
228		},
229		{
230			name:  "pathGenerator14",
231			g:     pathGenerator{isIstioVersionGE15: false},
232			value: "/abc",
233			want: yamlPermission(t, `
234         header:
235          exactMatch: /abc
236          name: :path`),
237		},
238		{
239			name:  "pathGenerator15",
240			g:     pathGenerator{isIstioVersionGE15: true},
241			value: "/abc",
242			want: yamlPermission(t, `
243         urlPath:
244          path:
245            exact: /abc`),
246		},
247		{
248			name:  "methodGenerator",
249			g:     methodGenerator{},
250			value: "GET",
251			want: yamlPermission(t, `
252         header:
253          exactMatch: GET
254          name: :method`),
255		},
256	}
257
258	for _, tc := range cases {
259		t.Run(tc.name, func(t *testing.T) {
260			var got interface{}
261			var err error
262			if _, ok := tc.want.(*rbacpb.Permission); ok {
263				got, err = tc.g.permission(tc.key, tc.value, tc.forTCP)
264				if err != nil {
265					t.Errorf("both permission and principal returned error")
266				}
267			} else {
268				got, err = tc.g.principal(tc.key, tc.value, tc.forTCP)
269				if err != nil {
270					t.Errorf("both permission and principal returned error")
271				}
272			}
273			if !reflect.DeepEqual(got, tc.want) {
274				var gotYaml string
275				gotProto, ok := got.(proto.Message)
276				if !ok {
277					t.Fatal("failed to extract proto")
278				}
279				if gotYaml, err = protomarshal.ToYAML(gotProto); err != nil {
280					t.Fatalf("%s: failed to parse yaml: %s", tc.name, err)
281				}
282				t.Errorf("got:\n %v\n but want:\n %v", gotYaml, tc.want)
283			}
284		})
285	}
286}
287
288func yamlPermission(t *testing.T, yaml string) *rbacpb.Permission {
289	t.Helper()
290	p := &rbacpb.Permission{}
291	if err := protomarshal.ApplyYAML(yaml, p); err != nil {
292		t.Fatalf("failed to parse yaml: %s", err)
293	}
294	return p
295}
296
297func yamlPrincipal(t *testing.T, yaml string) *rbacpb.Principal {
298	t.Helper()
299	p := &rbacpb.Principal{}
300	if err := protomarshal.ApplyYAML(yaml, p); err != nil {
301		t.Fatalf("failed to parse yaml: %s", err)
302	}
303	return p
304}
305