Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ rbac: deduplicate having the same groups, resourceNames, URLs and Verbs #937

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 66 additions & 9 deletions pkg/rbac/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ func (r *Rule) key() ruleKey {
}
}

func (r *Rule) keyWithGroupResourceNamesURLsVerbs() string {
key := r.key()
verbs := strings.Join(r.Verbs, "&")
return fmt.Sprintf("%s + %s + %s + %s", key.Groups, key.ResourceNames, key.URLs, verbs)
}

func (r *Rule) keyWithResourcesResourceNamesURLsVerbs() string {
key := r.key()
verbs := strings.Join(r.Verbs, "&")
return fmt.Sprintf("%s + %s + %s + %s", key.Resources, key.ResourceNames, key.URLs, verbs)
}

// addVerbs adds new verbs into a Rule.
// The duplicates in `r.Verbs` will be removed, and then `r.Verbs` will be sorted.
func (r *Rule) addVerbs(verbs []string) {
Expand Down Expand Up @@ -168,22 +180,28 @@ func (Generator) RegisterMarkers(into *markers.Registry) error {
// GenerateRoles generate a slice of objs representing either a ClusterRole or a Role object
// The order of the objs in the returned slice is stable and determined by their namespaces.
func GenerateRoles(ctx *genall.GenerationContext, roleName string) ([]interface{}, error) {
rulesByNS := make(map[string][]*Rule)
rulesByNSResource := make(map[string][]*Rule)
for _, root := range ctx.Roots {
markerSet, err := markers.PackageMarkers(ctx.Collector, root)
if err != nil {
root.AddError(err)
}

// group RBAC markers by namespace
// group RBAC markers by namespace and separate by resource
for _, markerValue := range markerSet[RuleDefinition.Name] {
rule := markerValue.(Rule)
namespace := rule.Namespace
if _, ok := rulesByNS[namespace]; !ok {
rules := make([]*Rule, 0)
rulesByNS[namespace] = rules
for _, resource := range rule.Resources {
r := Rule{
Groups: rule.Groups,
Resources: []string{resource},
ResourceNames: rule.ResourceNames,
URLs: rule.URLs,
Namespace: rule.Namespace,
Verbs: rule.Verbs,
}
namespace := r.Namespace
rulesByNSResource[namespace] = append(rulesByNSResource[namespace], &r)
}
rulesByNS[namespace] = append(rulesByNS[namespace], &rule)
}
}

Expand All @@ -200,6 +218,45 @@ func GenerateRoles(ctx *genall.GenerationContext, roleName string) ([]interface{
ruleMap[key].addVerbs(rule.Verbs)
}

// deduplicate resources
// 1. create map based on key without resources
ruleMapWithoutResources := make(map[string][]*Rule)
for _, rule := range ruleMap {
// get key without Resources
key := rule.keyWithGroupResourceNamesURLsVerbs()
ruleMapWithoutResources[key] = append(ruleMapWithoutResources[key], rule)
}
// 2. merge to ruleMap
ruleMap = make(map[ruleKey]*Rule)
for _, rules := range ruleMapWithoutResources {
rule := rules[0]
for _, mergeRule := range rules[1:] {
rule.Resources = append(rule.Resources, mergeRule.Resources...)
sbueringer marked this conversation as resolved.
Show resolved Hide resolved
}

key := rule.key()
ruleMap[key] = rule
}

// deduplicate groups
// 1. create map based on key without group
ruleMapWithoutGroup := make(map[string][]*Rule)
for _, rule := range ruleMap {
// get key without Group
key := rule.keyWithResourcesResourceNamesURLsVerbs()
ruleMapWithoutGroup[key] = append(ruleMapWithoutGroup[key], rule)
}
// 2. merge to ruleMap
ruleMap = make(map[ruleKey]*Rule)
for _, rules := range ruleMapWithoutGroup {
rule := rules[0]
for _, mergeRule := range rules[1:] {
rule.Groups = append(rule.Groups, mergeRule.Groups...)
}
key := rule.key()
ruleMap[key] = rule
}

// sort the Rules in rules according to their ruleKeys
keys := make([]ruleKey, 0, len(ruleMap))
for key := range ruleMap {
Expand All @@ -216,15 +273,15 @@ func GenerateRoles(ctx *genall.GenerationContext, roleName string) ([]interface{

// collect all the namespaces and sort them
var namespaces []string
for ns := range rulesByNS {
for ns := range rulesByNSResource {
namespaces = append(namespaces, ns)
}
sort.Strings(namespaces)

// process the items in rulesByNS by the order specified in `namespaces` to make sure that the Role order is stable
var objs []interface{}
for _, ns := range namespaces {
rules := rulesByNS[ns]
rules := rulesByNSResource[ns]
policyRules := NormalizeRules(rules)
if len(policyRules) == 0 {
continue
Expand Down
2 changes: 1 addition & 1 deletion pkg/rbac/parser_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ var _ = Describe("ClusterRole generated by the RBAC Generator", func() {
Expect(err).NotTo(HaveOccurred())

By("parsing the desired YAML")
for i, expectedRoleBytes := range bytes.Split(expectedFile, []byte("\n---\n"))[1:] {
for i, expectedRoleBytes := range bytes.Split(expectedFile, []byte("\n---\n")) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this change allows to use the directly generated output as golden file. The [1:] is not necessary anymore because the document starts with ---\n

By(fmt.Sprintf("comparing the generated Role and expected Role (Pair %d)", i))
obj := objs[i]
switch obj := obj.(type) {
Expand Down
17 changes: 17 additions & 0 deletions pkg/rbac/testdata/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,20 @@ package controller
// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=watch;watch
// +kubebuilder:rbac:groups=art,resources=jobs,verbs=get,namespace=park
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs,resourceNames=foo;bar;baz,verbs=get;watch
// +kubebuilder:rbac:groups=deduplicate-verbs,resources=some,verbs=get;list
// +kubebuilder:rbac:groups=deduplicate-verbs,resources=some,verbs=get
// +kubebuilder:rbac:groups=deduplicate-verbs,resources=some,verbs=list
// +kubebuilder:rbac:groups=deduplicate-resources,resources=one,verbs=create
// +kubebuilder:rbac:groups=deduplicate-resources,resources=two,verbs=create
// +kubebuilder:rbac:groups=deduplicate-resources,resources=three,verbs=create
// +kubebuilder:rbac:groups=deduplicate-groups1,resources=foo,verbs=patch
// +kubebuilder:rbac:groups=deduplicate-groups2,resources=foo,verbs=patch
// +kubebuilder:rbac:groups=deduplicate-groups3,resources=foo,verbs=patch
// +kubebuilder:rbac:groups=deduplicate-all,resources=foo;bar,verbs=get;list
// +kubebuilder:rbac:groups=deduplicate-all,resources=foo,verbs=get
// +kubebuilder:rbac:groups=deduplicate-all,resources=bar,verbs=list
// +kubebuilder:rbac:groups=deduplicate-all-group,resources=foo;bar,verbs=get;list
// +kubebuilder:rbac:groups=not-deduplicate-resources,resources=some,verbs=get
// +kubebuilder:rbac:groups=not-deduplicate-resources,resources=another,verbs=list
// +kubebuilder:rbac:groups=not-deduplicate-groups1,resources=some,verbs=get
// +kubebuilder:rbac:groups=not-deduplicate-groups2,resources=some,verbs=list
59 changes: 51 additions & 8 deletions pkg/rbac/testdata/role.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
Expand Down Expand Up @@ -52,7 +51,57 @@ rules:
- get
- patch
- update

- apiGroups:
- deduplicate-all
- deduplicate-all-group
resources:
- bar
- foo
verbs:
- get
- list
- apiGroups:
- deduplicate-groups1
- deduplicate-groups2
- deduplicate-groups3
resources:
- foo
verbs:
- patch
- apiGroups:
- deduplicate-resources
resources:
- one
- three
- two
verbs:
- create
- apiGroups:
- deduplicate-verbs
resources:
- some
verbs:
- get
- list
- apiGroups:
- not-deduplicate-groups1
- not-deduplicate-resources
resources:
- some
verbs:
- get
- apiGroups:
- not-deduplicate-groups2
resources:
- some
verbs:
- list
- apiGroups:
- not-deduplicate-resources
resources:
- another
verbs:
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
Expand All @@ -66,7 +115,6 @@ rules:
- jobs
verbs:
- get

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
Expand All @@ -76,11 +124,6 @@ metadata:
rules:
- apiGroups:
- art
resources:
- jobs
verbs:
- get
- apiGroups:
- wave
resources:
- jobs
Expand Down