Skip to content

Commit

Permalink
Add to policy-rules information about the origin of a rule. (#43)
Browse files Browse the repository at this point in the history
Co-authored-by: Gadi Naor <gadi_naor@rapid7.com>
  • Loading branch information
gadinaor and gadinaor-r7 committed Oct 25, 2021
1 parent fb1e18c commit bd71ae5
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 9 deletions.
40 changes: 36 additions & 4 deletions cmd/policyrules_cmd.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"os"
Expand All @@ -13,6 +14,8 @@ import (
"github.com/alcideio/rbac-tool/pkg/rbac"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
v1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/util/sets"
)

func NewCommandPolicyRules() *cobra.Command {
Expand Down Expand Up @@ -117,21 +120,26 @@ rbac-tool policy-rules -o json | jp "[? @.allowedTo[? (verb=='get' || verb=='*'
allowedTo.Resource,
strings.Join(allowedTo.ResourceNames, ","),
strings.Join(allowedTo.NonResourceURLs, ","),
renderOriginatedFromColumn(allowedTo.Namespace, allowedTo.OriginatedFrom),
}
rows = append(rows, row)
}
}

sort.Slice(rows, func(i, j int) bool {
if strings.Compare(rows[i][0], rows[j][0]) == 0 {
return (strings.Compare(rows[i][1], rows[j][1]) < 0)

for c := range [6]int{} {
if strings.Compare(rows[i][c], rows[j][c]) == 0 {
continue
}
return (strings.Compare(rows[i][c], rows[j][c]) < 0)
}

return (strings.Compare(rows[i][0], rows[j][0]) < 0)
return true
})

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"TYPE", "SUBJECT", "VERBS", "NAMESPACE", "API GROUP", "KIND", "NAMES", "NonResourceURI"})
table.SetHeader([]string{"TYPE", "SUBJECT", "VERBS", "NAMESPACE", "API GROUP", "KIND", "NAMES", "NonResourceURI", "ORIGINATED FROM"})
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetBorder(false)
table.SetAlignment(tablewriter.ALIGN_LEFT)
Expand Down Expand Up @@ -175,3 +183,27 @@ rbac-tool policy-rules -o json | jp "[? @.allowedTo[? (verb=='get' || verb=='*'
flags.BoolVarP(&inverse, "not", "n", false, "Inverse the regex matching. Use to search for users that do not match '^system:.*'")
return cmd
}

func renderOriginatedFromColumn(ns string, list []v1.RoleRef) string {
roles := sets.NewString()
clusterRoles := sets.NewString()
s := bytes.NewBufferString("")

for _, ref := range list {
if ref.Kind == "ClusterRole" {
clusterRoles.Insert(ref.Name)
} else {
roles.Insert(fmt.Sprintf("%v/%v ", ns, ref.Name))
}
}

if clusterRoles.Len() > 0 {
s.WriteString(fmt.Sprintf("ClusterRoles>>%v", strings.Join(clusterRoles.List(), ",")))
}

if roles.Len() > 0 {
s.WriteString(fmt.Sprintf("Roles>>%v", strings.Join(roles.List(), ",")))
}

return s.String()
}
28 changes: 23 additions & 5 deletions pkg/rbac/subject_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ import (
"k8s.io/klog"
)

type PolicyRule struct {
v1.PolicyRule

//Specify the Roles or ClusterRoles this rule originated from
OriginatedFrom []v1.RoleRef
}

type SubjectPermissions struct {
Subject v1.Subject

//Rules Per Namespace ... "" means cluster-wide
Rules map[string][]v1.PolicyRule
Rules map[string][]PolicyRule
}

func NewSubjectPermissions(perms *Permissions) []SubjectPermissions {
Expand Down Expand Up @@ -47,18 +54,24 @@ func NewSubjectPermissions(perms *Permissions) []SubjectPermissions {
if !exist {
subPerms = &SubjectPermissions{
Subject: subject,
Rules: map[string][]v1.PolicyRule{},
Rules: map[string][]PolicyRule{},
}
}

rules, exist := subPerms.Rules[binding.Namespace]
if !exist {
rules = []v1.PolicyRule{}
rules = []PolicyRule{}
}

//klog.V(6).Infof("%+v --add-- %v %v", subject, len(rules), len(role.Rules))
roleRules := make([]PolicyRule, len(role.Rules))
for i, _ := range role.Rules {
roleRules[i].PolicyRule = role.Rules[i]
roleRules[i].OriginatedFrom = []v1.RoleRef{binding.RoleRef}
}

klog.V(6).Infof("%+v --add-- %v %v %+v", subject, len(rules), len(role.Rules), roleRules)

rules = append(rules, role.Rules...)
rules = append(rules, roleRules...)
subPerms.Rules[binding.Namespace] = rules
subjects[sub] = subPerms
}
Expand Down Expand Up @@ -107,6 +120,9 @@ type NamespacedPolicyRule struct {
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path
// Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding.
NonResourceURLs []string `json:"nonResourceURLs,omitempty"`

//The Role/ClusterRole rule references
OriginatedFrom []v1.RoleRef `json:"originatedFrom,omitempty"`
}

type SubjectPolicyList struct {
Expand Down Expand Up @@ -152,6 +168,7 @@ func NewSubjectPermissionsList(policies []SubjectPermissions) []SubjectPolicyLis
Resource: resource,
ResourceNames: rule.ResourceNames,
NonResourceURLs: rule.NonResourceURLs,
OriginatedFrom: rule.OriginatedFrom,
}

nsrules = append(nsrules, subjectPolicy)
Expand All @@ -164,6 +181,7 @@ func NewSubjectPermissionsList(policies []SubjectPermissions) []SubjectPolicyLis
Namespace: namespace,
Verb: verb,
NonResourceURLs: rule.NonResourceURLs,
OriginatedFrom: rule.OriginatedFrom,
}

nsrules = append(nsrules, subjectPolicy)
Expand Down
79 changes: 79 additions & 0 deletions testdata/policyrules/multiple-role-bindings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#
# Install:
# kubectl apply -f testdata/policyrules/multiple-role-bindings.yaml
#
# Run:
# bin/rbac-tool policy-rules -e the-test-user | grep the-test-user
#
# Expect:
#
# ServiceAccount | the-test-user | get | policyrules | core | * | | | Roles>>policyrules/some-rules
# ServiceAccount | the-test-user | get | policyrules | core | * | | | Roles>>policyrules/more-rules
# ServiceAccount | the-test-user | get | policyrules | core | secrets | some-secret | | Roles>>policyrules/some-rules
# ServiceAccount | the-test-user | get | policyrules | core | secrets | | | Roles>>policyrules/more-rules
# ServiceAccount | the-test-user | list | policyrules | core | secrets | some-secret | | Roles>>policyrules/some-rules
# ServiceAccount | the-test-user | watch | policyrules | core | secrets | some-secret | | Roles>>policyrules/some-rules
#
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: policyrules
name: some-rules
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["secrets"]
resourceNames: ["some-secret"]
verbs: ["get", "watch", "list"]
- apiGroups: [""] # "" indicates the core API group
resources: ["*"]
verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: policyrules
name: more-rules
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["secrets"]
verbs: ["get"]
- apiGroups: [""] # "" indicates the core API group
resources: ["*"]
verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
metadata:
name: some-rules-binding
namespace: policyrules
subjects:
- kind: ServiceAccount
name: the-test-user # "name" is case sensitive
namespace: policyrules
roleRef:
# "roleRef" specifies the binding to a Role / ClusterRole
kind: Role #this must be Role or ClusterRole
name: some-rules # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io

---
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
metadata:
name: more-rules-binding
namespace: policyrules
subjects:
- kind: ServiceAccount
name: the-test-user # "name" is case sensitive
namespace: policyrules
roleRef:
# "roleRef" specifies the binding to a Role / ClusterRole
kind: Role #this must be Role or ClusterRole
name: more-rules # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io

0 comments on commit bd71ae5

Please sign in to comment.