/* * * Copyright 2021 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package authz import ( "strings" "testing" "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/testing/protocmp" v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" ) func TestTranslatePolicy(t *testing.T) { tests := map[string]struct { authzPolicy string wantErr string wantPolicies []*v3rbacpb.RBAC }{ "valid policy": { authzPolicy: `{ "name": "authz", "deny_rules": [ { "name": "deny_policy_1", "source": { "principals":[ "spiffe://foo.abc", "spiffe://bar*", "*baz", "spiffe://abc.*.com" ] } }], "allow_rules": [ { "name": "allow_policy_1", "source": { "principals":["*"] }, "request": { "paths": ["path-foo*"] } }, { "name": "allow_policy_2", "request": { "paths": [ "path-bar", "*baz" ], "headers": [ { "key": "key-1", "values": ["foo", "*bar"] }, { "key": "key-2", "values": ["baz*"] } ] } }] }`, wantPolicies: []*v3rbacpb.RBAC{ { Action: v3rbacpb.RBAC_DENY, Policies: map[string]*v3rbacpb.Policy{ "authz_deny_policy_1": { Principals: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{ Ids: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "spiffe://foo.abc"}, }}, }}, {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "spiffe://bar"}, }}, }}, {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: "baz"}, }}, }}, {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "spiffe://abc.*.com"}, }}, }}, }, }}}, }, Permissions: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_Any{Any: true}}, }, }, }, }, { Action: v3rbacpb.RBAC_ALLOW, Policies: map[string]*v3rbacpb.Policy{ "authz_allow_policy_1": { Principals: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{ Ids: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: ".+"}}, }}, }}, }, }}}, }, Permissions: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_OrRules{OrRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_UrlPath{ UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: "path-foo"}, }}}, }}, }, }}}, }, }}}, }, }, "authz_allow_policy_2": { Principals: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_Any{Any: true}}, }, Permissions: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_OrRules{OrRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_UrlPath{ UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: "path-bar"}, }}}, }}, {Rule: &v3rbacpb.Permission_UrlPath{ UrlPath: &v3matcherpb.PathMatcher{Rule: &v3matcherpb.PathMatcher_Path{Path: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: "baz"}, }}}, }}, }, }}}, {Rule: &v3rbacpb.Permission_AndRules{AndRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_OrRules{OrRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_Header{ Header: &v3routepb.HeaderMatcher{ Name: "key-1", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: "foo"}, }, }}, {Rule: &v3rbacpb.Permission_Header{ Header: &v3routepb.HeaderMatcher{ Name: "key-1", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SuffixMatch{SuffixMatch: "bar"}, }, }}, }, }}}, {Rule: &v3rbacpb.Permission_OrRules{OrRules: &v3rbacpb.Permission_Set{ Rules: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_Header{ Header: &v3routepb.HeaderMatcher{ Name: "key-2", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "baz"}, }, }}, }, }}}, }, }}}, }, }}}, }, }, }, }, }, }, "allow authenticated": { authzPolicy: `{ "name": "authz", "allow_rules": [ { "name": "allow_authenticated", "source": { "principals":["*", ""] } }] }`, wantPolicies: []*v3rbacpb.RBAC{ { Action: v3rbacpb.RBAC_ALLOW, Policies: map[string]*v3rbacpb.Policy{ "authz_allow_authenticated": { Principals: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_OrIds{OrIds: &v3rbacpb.Principal_Set{ Ids: []*v3rbacpb.Principal{ {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{SafeRegex: &v3matcherpb.RegexMatcher{Regex: ".+"}}, }}, }}, {Identifier: &v3rbacpb.Principal_Authenticated_{ Authenticated: &v3rbacpb.Principal_Authenticated{PrincipalName: &v3matcherpb.StringMatcher{ MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: ""}, }}, }}, }, }}}, }, Permissions: []*v3rbacpb.Permission{ {Rule: &v3rbacpb.Permission_Any{Any: true}}, }, }, }, }, }, }, "unknown field": { authzPolicy: `{"random": 123}`, wantErr: "failed to unmarshal policy", }, "missing name field": { authzPolicy: `{}`, wantErr: `"name" is not present`, }, "invalid field type": { authzPolicy: `{"name": 123}`, wantErr: "failed to unmarshal policy", }, "missing allow rules field": { authzPolicy: `{"name": "authz-foo"}`, wantErr: `"allow_rules" is not present`, }, "missing rule name field": { authzPolicy: `{ "name": "authz-foo", "allow_rules": [{}] }`, wantErr: `"allow_rules" 0: "name" is not present`, }, "missing header key": { authzPolicy: `{ "name": "authz", "allow_rules": [{ "name": "allow_policy_1", "request": {"headers":[{"key":"key-a", "values": ["value-a"]}, {}]} }] }`, wantErr: `"allow_rules" 0: "headers" 1: "key" is not present`, }, "missing header values": { authzPolicy: `{ "name": "authz", "allow_rules": [{ "name": "allow_policy_1", "request": {"headers":[{"key":"key-a"}]} }] }`, wantErr: `"allow_rules" 0: "headers" 0: "values" is not present`, }, "unsupported header": { authzPolicy: `{ "name": "authz", "allow_rules": [{ "name": "allow_policy_1", "request": {"headers":[{"key":":method", "values":["GET"]}]} }] }`, wantErr: `"allow_rules" 0: "headers" 0: unsupported "key" :method`, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { gotPolicies, gotErr := translatePolicy(test.authzPolicy) if gotErr != nil && !strings.HasPrefix(gotErr.Error(), test.wantErr) { t.Fatalf("unexpected error\nwant:%v\ngot:%v", test.wantErr, gotErr) } if diff := cmp.Diff(gotPolicies, test.wantPolicies, protocmp.Transform()); diff != "" { t.Fatalf("unexpected policy\ndiff (-want +got):\n%s", diff) } }) } }