Skip to content

Commit

Permalink
authz: create interceptors for gRPC security policy API (#4664)
Browse files Browse the repository at this point in the history
* Static Authorization Interceptor
  • Loading branch information
ashithasantosh committed Sep 2, 2021
1 parent d6a5f5f commit b189f5e
Show file tree
Hide file tree
Showing 6 changed files with 595 additions and 103 deletions.
43 changes: 24 additions & 19 deletions authz/rbac_translator.go
Expand Up @@ -23,6 +23,7 @@
package authz

import (
"bytes"
"encoding/json"
"fmt"
"strings"
Expand Down Expand Up @@ -93,7 +94,7 @@ func getStringMatcher(value string) *v3matcherpb.StringMatcher {
switch {
case value == "*":
return &v3matcherpb.StringMatcher{
MatchPattern: &v3matcherpb.StringMatcher_Prefix{},
MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{},
}
case strings.HasSuffix(value, "*"):
prefix := strings.TrimSuffix(value, "*")
Expand All @@ -117,7 +118,7 @@ func getHeaderMatcher(key, value string) *v3routepb.HeaderMatcher {
case value == "*":
return &v3routepb.HeaderMatcher{
Name: key,
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{},
HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{},
}
case strings.HasSuffix(value, "*"):
prefix := strings.TrimSuffix(value, "*")
Expand Down Expand Up @@ -268,34 +269,38 @@ func parseRules(rules []rule, prefixName string) (map[string]*v3rbacpb.Policy, e
}

// translatePolicy translates SDK authorization policy in JSON format to two
// Envoy RBAC polices (deny and allow policy). If the policy cannot be parsed
// or is invalid, an error will be returned.
func translatePolicy(policyStr string) (*v3rbacpb.RBAC, *v3rbacpb.RBAC, error) {
var policy authorizationPolicy
if err := json.Unmarshal([]byte(policyStr), &policy); err != nil {
return nil, nil, fmt.Errorf("failed to unmarshal policy: %v", err)
// Envoy RBAC polices (deny followed by allow policy) or only one Envoy RBAC
// allow policy. If the input policy cannot be parsed or is invalid, an error
// will be returned.
func translatePolicy(policyStr string) ([]*v3rbacpb.RBAC, error) {
policy := &authorizationPolicy{}
d := json.NewDecoder(bytes.NewReader([]byte(policyStr)))
d.DisallowUnknownFields()
if err := d.Decode(policy); err != nil {
return nil, fmt.Errorf("failed to unmarshal policy: %v", err)
}
if policy.Name == "" {
return nil, nil, fmt.Errorf(`"name" is not present`)
return nil, fmt.Errorf(`"name" is not present`)
}
if len(policy.AllowRules) == 0 {
return nil, nil, fmt.Errorf(`"allow_rules" is not present`)
return nil, fmt.Errorf(`"allow_rules" is not present`)
}
allowPolicies, err := parseRules(policy.AllowRules, policy.Name)
if err != nil {
return nil, nil, fmt.Errorf(`"allow_rules" %v`, err)
}
allowRBAC := &v3rbacpb.RBAC{Action: v3rbacpb.RBAC_ALLOW, Policies: allowPolicies}
var denyRBAC *v3rbacpb.RBAC
rbacs := make([]*v3rbacpb.RBAC, 0, 2)
if len(policy.DenyRules) > 0 {
denyPolicies, err := parseRules(policy.DenyRules, policy.Name)
if err != nil {
return nil, nil, fmt.Errorf(`"deny_rules" %v`, err)
return nil, fmt.Errorf(`"deny_rules" %v`, err)
}
denyRBAC = &v3rbacpb.RBAC{
denyRBAC := &v3rbacpb.RBAC{
Action: v3rbacpb.RBAC_DENY,
Policies: denyPolicies,
}
rbacs = append(rbacs, denyRBAC)
}
return denyRBAC, allowRBAC, nil
allowPolicies, err := parseRules(policy.AllowRules, policy.Name)
if err != nil {
return nil, fmt.Errorf(`"allow_rules" %v`, err)
}
allowRBAC := &v3rbacpb.RBAC{Action: v3rbacpb.RBAC_ALLOW, Policies: allowPolicies}
return append(rbacs, allowRBAC), nil
}
209 changes: 127 additions & 82 deletions authz/rbac_translator_test.go
Expand Up @@ -32,10 +32,9 @@ import (

func TestTranslatePolicy(t *testing.T) {
tests := map[string]struct {
authzPolicy string
wantErr string
wantDenyPolicy *v3rbacpb.RBAC
wantAllowPolicy *v3rbacpb.RBAC
authzPolicy string
wantErr string
wantPolicies []*v3rbacpb.RBAC
}{
"valid policy": {
authzPolicy: `{
Expand Down Expand Up @@ -82,81 +81,133 @@ func TestTranslatePolicy(t *testing.T) {
}
}]
}`,
wantDenyPolicy: &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}}},
},
}},
wantAllowPolicy: &v3rbacpb.RBAC{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_Prefix{Prefix: ""}}}}},
}}}}},
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"}}}}}},
}}}}}}}}},
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}},
},
},
},
},
"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"}}}}}},
}}}},
{
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{},
}},
}},
},
}}},
},
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_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_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_Header{
Header: &v3routepb.HeaderMatcher{
Name: "key-2", HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: "baz"}}}},
}}}}}}}}}}}}},
{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"},
},
}},
},
}}},
},
}}},
},
}}},
},
},
},
},
}},
},
},
"unknown field": {
authzPolicy: `{"random": 123}`,
wantErr: "failed to unmarshal policy",
},
"missing name field": {
authzPolicy: `{}`,
Expand All @@ -167,10 +218,8 @@ func TestTranslatePolicy(t *testing.T) {
wantErr: "failed to unmarshal policy",
},
"missing allow rules field": {
authzPolicy: `{"name": "authz-foo"}`,
wantErr: `"allow_rules" is not present`,
wantDenyPolicy: nil,
wantAllowPolicy: nil,
authzPolicy: `{"name": "authz-foo"}`,
wantErr: `"allow_rules" is not present`,
},
"missing rule name field": {
authzPolicy: `{
Expand Down Expand Up @@ -210,18 +259,14 @@ func TestTranslatePolicy(t *testing.T) {
wantErr: `"allow_rules" 0: "headers" 0: unsupported "key" :method`,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
gotDenyPolicy, gotAllowPolicy, gotErr := translatePolicy(test.authzPolicy)
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(gotDenyPolicy, test.wantDenyPolicy, protocmp.Transform()); diff != "" {
t.Fatalf("unexpected deny policy\ndiff (-want +got):\n%s", diff)
}
if diff := cmp.Diff(gotAllowPolicy, test.wantAllowPolicy, protocmp.Transform()); diff != "" {
t.Fatalf("unexpected allow policy\ndiff (-want +got):\n%s", diff)
if diff := cmp.Diff(gotPolicies, test.wantPolicies, protocmp.Transform()); diff != "" {
t.Fatalf("unexpected policy\ndiff (-want +got):\n%s", diff)
}
})
}
Expand Down

0 comments on commit b189f5e

Please sign in to comment.