istio
1// Copyright 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 model16
17import (18"testing"19
20rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"21"github.com/google/go-cmp/cmp"22"google.golang.org/protobuf/proto"23"google.golang.org/protobuf/testing/protocmp"24
25"istio.io/istio/pkg/util/protomarshal"26)
27
28func TestRequestPrincipal(t *testing.T) {29cases := []struct {30in string31want string32}{33{34in: "*",35want: `36and_ids:
37ids:
38- metadata:
39filter: envoy.filters.http.jwt_authn
40path:
41- key: payload
42- key: iss
43value:
44string_match:
45safe_regex: {regex: .+}
46- metadata:
47filter: envoy.filters.http.jwt_authn
48path:
49- key: payload
50- key: sub
51value:
52string_match:
53safe_regex: {regex: .+}
54`,55},56{57in: "foo*",58want: `59and_ids:
60ids:
61- metadata:
62filter: envoy.filters.http.jwt_authn
63path:
64- key: payload
65- key: iss
66value:
67string_match:
68prefix: foo
69- metadata:
70filter: envoy.filters.http.jwt_authn
71path:
72- key: payload
73- key: sub
74value:
75string_match:
76safe_regex: {regex: .+}
77`,78},79{80in: "foo/*",81want: `82and_ids:
83ids:
84- metadata:
85filter: envoy.filters.http.jwt_authn
86path:
87- key: payload
88- key: iss
89value:
90string_match:
91exact: foo
92- metadata:
93filter: envoy.filters.http.jwt_authn
94path:
95- key: payload
96- key: sub
97value:
98string_match:
99safe_regex: {regex: .+}
100`,101},102{103in: "foo/bar*",104want: `105and_ids:
106ids:
107- metadata:
108filter: envoy.filters.http.jwt_authn
109path:
110- key: payload
111- key: iss
112value:
113string_match:
114exact: foo
115- metadata:
116filter: envoy.filters.http.jwt_authn
117path:
118- key: payload
119- key: sub
120value:
121string_match:
122prefix: bar
123`,124},125{126in: "*foo",127want: `128and_ids:
129ids:
130- metadata:
131filter: envoy.filters.http.jwt_authn
132path:
133- key: payload
134- key: iss
135value:
136string_match:
137safe_regex: {regex: .+}
138- metadata:
139filter: envoy.filters.http.jwt_authn
140path:
141- key: payload
142- key: sub
143value:
144string_match:
145suffix: foo
146`,147},148{149in: "*/foo",150want: `151and_ids:
152ids:
153- metadata:
154filter: envoy.filters.http.jwt_authn
155path:
156- key: payload
157- key: iss
158value:
159string_match:
160safe_regex: {regex: .+}
161- metadata:
162filter: envoy.filters.http.jwt_authn
163path:
164- key: payload
165- key: sub
166value:
167string_match:
168exact: foo
169`,170},171{172in: "*bar/foo",173want: `174and_ids:
175ids:
176- metadata:
177filter: envoy.filters.http.jwt_authn
178path:
179- key: payload
180- key: iss
181value:
182string_match:
183suffix: bar
184- metadata:
185filter: envoy.filters.http.jwt_authn
186path:
187- key: payload
188- key: sub
189value:
190string_match:
191exact: foo
192`,193},194{195in: "foo/bar",196want: `197and_ids:
198ids:
199- metadata:
200filter: envoy.filters.http.jwt_authn
201path:
202- key: payload
203- key: iss
204value:
205string_match:
206exact: foo
207- metadata:
208filter: envoy.filters.http.jwt_authn
209path:
210- key: payload
211- key: sub
212value:
213string_match:
214exact: bar
215`,216},217}218rpg := requestPrincipalGenerator{}219for _, tc := range cases {220t.Run(tc.in, func(t *testing.T) {221got, err := rpg.extendedPrincipal("", []string{tc.in}, false)222if err != nil {223t.Fatal(err)224}225principal := yamlPrincipal(t, tc.want)226if diff := cmp.Diff(got, principal, protocmp.Transform()); diff != "" {227t.Errorf("diff detected: %v", diff)228}229})230}231}
232
233func TestGenerator(t *testing.T) {234cases := []struct {235name string236g generator
237key string238value string239forTCP bool240want any
241}{242{243name: "destIPGenerator",244g: destIPGenerator{},245value: "1.2.3.4",246want: yamlPermission(t, `247destinationIp:
248addressPrefix: 1.2.3.4
249prefixLen: 32`),250},251{252name: "destPortGenerator",253g: destPortGenerator{},254value: "80",255want: yamlPermission(t, `256destinationPort: 80`),257},258{259name: "connSNIGenerator",260g: connSNIGenerator{},261value: "exact.com",262want: yamlPermission(t, `263requestedServerName:
264exact: exact.com`),265},266{267name: "envoyFilterGenerator-string",268g: envoyFilterGenerator{},269key: "experimental.a.b.c[d]",270value: "val",271want: yamlPermission(t, `272metadata:
273filter: a.b.c
274path:
275- key: d
276value:
277stringMatch:
278exact: val`),279},280{281name: "envoyFilterGenerator-invalid",282g: envoyFilterGenerator{},283key: "experimental.a.b.c]",284value: "val",285},286{287name: "envoyFilterGenerator-list",288g: envoyFilterGenerator{},289key: "experimental.a.b.c[d]",290value: "[v1, v2]",291want: yamlPermission(t, `292metadata:
293filter: a.b.c
294path:
295- key: d
296value:
297listMatch:
298oneOf:
299stringMatch:
300exact: v1, v2`),301},302{303name: "srcIPGenerator",304g: srcIPGenerator{},305value: "1.2.3.4",306want: yamlPrincipal(t, `307directRemoteIp:
308addressPrefix: 1.2.3.4
309prefixLen: 32`),310},311{312name: "remoteIPGenerator",313g: remoteIPGenerator{},314value: "1.2.3.4",315want: yamlPrincipal(t, `316remoteIp:
317addressPrefix: 1.2.3.4
318prefixLen: 32`),319},320{321name: "srcNamespaceGenerator-http",322g: srcNamespaceGenerator{},323value: "foo",324want: yamlPrincipal(t, `325filter_state:
326key: io.istio.peer_principal
327string_match:
328safeRegex:
329regex: .*/ns/foo/.*`),330},331{332name: "srcNamespaceGenerator-tcp",333g: srcNamespaceGenerator{},334value: "foo",335forTCP: true,336want: yamlPrincipal(t, `337filter_state:
338key: io.istio.peer_principal
339string_match:
340safeRegex:
341regex: .*/ns/foo/.*`),342},343{344name: "srcPrincipalGenerator-http",345g: srcPrincipalGenerator{},346key: "source.principal",347value: "foo",348want: yamlPrincipal(t, `349filter_state:
350key: io.istio.peer_principal
351string_match:
352exact: spiffe://foo`),353},354{355name: "srcPrincipalGenerator-tcp",356g: srcPrincipalGenerator{},357key: "source.principal",358value: "foo",359forTCP: true,360want: yamlPrincipal(t, `361filter_state:
362key: io.istio.peer_principal
363string_match:
364exact: spiffe://foo`),365},366{367name: "requestPrincipalGenerator",368g: requestPrincipalGenerator{},369key: "request.auth.principal",370value: "foo",371want: yamlPrincipal(t, `372metadata:
373filter: istio_authn
374path:
375- key: request.auth.principal
376value:
377stringMatch:
378exact: foo`),379},380{381name: "requestAudiencesGenerator",382g: requestAudiencesGenerator{},383key: "request.auth.audiences",384value: "foo",385want: yamlPrincipal(t, `386metadata:
387filter: istio_authn
388path:
389- key: request.auth.audiences
390value:
391stringMatch:
392exact: foo`),393},394{395name: "requestPresenterGenerator",396g: requestPresenterGenerator{},397key: "request.auth.presenter",398value: "foo",399want: yamlPrincipal(t, `400metadata:
401filter: istio_authn
402path:
403- key: request.auth.presenter
404value:
405stringMatch:
406exact: foo`),407},408{409name: "requestHeaderGenerator",410g: requestHeaderGenerator{},411key: "request.headers[x-foo]",412value: "foo",413want: yamlPrincipal(t, `414header:
415name: x-foo
416stringMatch:
417exact: foo`),418},419{420name: "requestClaimGenerator",421g: requestClaimGenerator{},422key: "request.auth.claims[bar]",423value: "foo",424want: yamlPrincipal(t, `425metadata:
426filter: istio_authn
427path:
428- key: request.auth.claims
429- key: bar
430value:
431listMatch:
432oneOf:
433stringMatch:
434exact: foo`),435},436{437name: "requestNestedClaimsGenerator",438g: requestClaimGenerator{},439key: "request.auth.claims[bar][baz]",440value: "foo",441want: yamlPrincipal(t, `442metadata:
443filter: istio_authn
444path:
445- key: request.auth.claims
446- key: bar
447- key: baz
448value:
449listMatch:
450oneOf:
451stringMatch:
452exact: foo`),453},454{455name: "hostGenerator",456g: hostGenerator{},457value: "foo",458want: yamlPermission(t, `459header:
460stringMatch:
461exact: foo
462ignoreCase: true
463name: :authority`),464},465{466name: "pathGenerator",467g: pathGenerator{},468value: "/abc",469want: yamlPermission(t, `470urlPath:
471path:
472exact: /abc`),473},474{475name: "methodGenerator",476g: methodGenerator{},477value: "GET",478want: yamlPermission(t, `479header:
480name: :method
481stringMatch:
482exact: GET`),483},484}485
486for _, tc := range cases {487t.Run(tc.name, func(t *testing.T) {488var got any489var err error490// nolint: gocritic491if _, ok := tc.want.(*rbacpb.Permission); ok {492got, err = tc.g.permission(tc.key, tc.value, tc.forTCP)493if err != nil {494t.Errorf("both permission and principal returned error")495}496} else if _, ok := tc.want.(*rbacpb.Principal); ok {497got, err = tc.g.principal(tc.key, tc.value, tc.forTCP, false)498if err != nil {499t.Errorf("both permission and principal returned error")500}501} else {502_, err1 := tc.g.principal(tc.key, tc.value, tc.forTCP, false)503_, err2 := tc.g.permission(tc.key, tc.value, tc.forTCP)504if err1 == nil || err2 == nil {505t.Fatalf("wanted error")506}507return508}509if diff := cmp.Diff(got, tc.want, protocmp.Transform()); diff != "" {510var gotYaml string511gotProto, ok := got.(proto.Message)512if !ok {513t.Fatal("failed to extract proto")514}515if gotYaml, err = protomarshal.ToYAML(gotProto); err != nil {516t.Fatalf("%s: failed to parse yaml: %s", tc.name, err)517}518t.Errorf("got:\n %v\n but want:\n %v", gotYaml, tc.want)519}520})521}522}
523
524func yamlPermission(t *testing.T, yaml string) *rbacpb.Permission {525t.Helper()526p := &rbacpb.Permission{}527if err := protomarshal.ApplyYAML(yaml, p); err != nil {528t.Fatalf("failed to parse yaml: %s", err)529}530return p531}
532
533func yamlPrincipal(t *testing.T, yaml string) *rbacpb.Principal {534t.Helper()535p := &rbacpb.Principal{}536if err := protomarshal.ApplyYAML(yaml, p); err != nil {537t.Fatalf("failed to parse yaml: %s", err)538}539return p540}
541