| /* |
| * |
| * 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) |
| } |
| }) |
| } |
| } |