Skip to content

Commit

Permalink
ClusterRole: add aggregationRule support
Browse files Browse the repository at this point in the history
  • Loading branch information
jordiprats authored and derailed committed Sep 14, 2024
1 parent 0917ef7 commit 3a15288
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 3 deletions.
38 changes: 38 additions & 0 deletions internal/cache/cr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Popeye

package cache

import (
"sync"

"github.com/derailed/popeye/internal"
"github.com/derailed/popeye/internal/db"
rbacv1 "k8s.io/api/rbac/v1"
)

// ClusterRole represents ClusterRole cache.
type ClusterRole struct {
db *db.DB
}

// NewClusterRole returns a new ClusterRole cache.
func NewClusterRole(db *db.DB) *ClusterRole {
return &ClusterRole{db: db}
}

// RoleRefs computes all role external references.
func (r *ClusterRole) AggregationMatchers(refs *sync.Map) {
txn, it := r.db.MustITFor(internal.Glossary[internal.CR])
defer txn.Abort()
for o := it.Next(); o != nil; o = it.Next() {
cr := o.(*rbacv1.ClusterRole)
if cr.AggregationRule != nil {
for _, lbs := range cr.AggregationRule.ClusterRoleSelectors {
for k, v := range lbs.MatchLabels {
refs.Store(k, v)
}
}
}
}
}
33 changes: 33 additions & 0 deletions internal/cache/cr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Popeye

package cache_test

import (
"sync"
"testing"

"github.com/derailed/popeye/internal"
"github.com/derailed/popeye/internal/cache"
"github.com/derailed/popeye/internal/db"
"github.com/derailed/popeye/internal/test"
"github.com/stretchr/testify/assert"
rbacv1 "k8s.io/api/rbac/v1"
)

func TestClusterRoleAggregation(t *testing.T) {
dba, err := test.NewTestDB()
assert.NoError(t, err)
l := db.NewLoader(dba)

ctx := test.MakeCtx(t)
assert.NoError(t, test.LoadDB[*rbacv1.ClusterRole](ctx, l.DB, "auth/cr/1.yaml", internal.Glossary[internal.CR]))

cr := cache.NewClusterRole(dba)
var aRefs sync.Map
cr.AggregationMatchers(&aRefs)

value, ok := aRefs.Load("rbac.authorization.k8s.io/aggregate-to-cr4")
assert.True(t, ok)
assert.Equal(t, "true", value)
}
27 changes: 27 additions & 0 deletions internal/cache/testdata/auth/cr/1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
apiVersion: v1
kind: List
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
name: cr4
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.authorization.k8s.io/aggregate-to-cr4: "true"
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
rbac.authorization.k8s.io/aggregate-to-cr4: "true"
name: cr5
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- list
18 changes: 15 additions & 3 deletions internal/lint/cr.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,19 @@ func NewClusterRole(c *issues.Collector, db *db.DB) *ClusterRole {

// Lint sanitizes the resource.
func (s *ClusterRole) Lint(ctx context.Context) error {
var crRefs sync.Map
var crRefs, agRefs sync.Map
crb := cache.NewClusterRoleBinding(s.db)
crb.ClusterRoleRefs(&crRefs)
rb := cache.NewRoleBinding(s.db)
rb.RoleRefs(&crRefs)
s.checkStale(ctx, &crRefs)
cr := cache.NewClusterRole(s.db)
cr.AggregationMatchers(&agRefs)
s.checkStale(ctx, &crRefs, &agRefs)

return nil
}

func (s *ClusterRole) checkStale(ctx context.Context, refs *sync.Map) {
func (s *ClusterRole) checkStale(ctx context.Context, refs *sync.Map, agRefs *sync.Map) {
txn, it := s.db.MustITFor(internal.Glossary[internal.CR])
defer txn.Abort()
for o := it.Next(); o != nil; o = it.Next() {
Expand All @@ -76,6 +78,16 @@ func (s *ClusterRole) checkStale(ctx context.Context, refs *sync.Map) {
if s.system.skip(fqn) {
continue
}
partialRole := false
for key, value := range cr.Labels {
expectedValue, ok := agRefs.Load(key)
if ok && value == expectedValue {
partialRole = true
}
}
if partialRole {
continue
}
if _, ok := refs.Load(cache.ResFqn(cache.ClusterRoleKey, fqn)); !ok {
s.AddCode(ctx, 400)
}
Expand Down
19 changes: 19 additions & 0 deletions internal/lint/cr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,22 @@ func TestCRLint(t *testing.T) {
assert.Equal(t, `[POP-400] Used? Unable to locate resource reference`, ii[0].Message)
assert.Equal(t, rules.InfoLevel, ii[0].Level)
}

func TestCRLintAggregations(t *testing.T) {
dba, err := test.NewTestDB()
assert.NoError(t, err)
l := db.NewLoader(dba)

ctx := test.MakeCtx(t)
assert.NoError(t, test.LoadDB[*rbacv1.ClusterRole](ctx, l.DB, "auth/cr/2.yaml", internal.Glossary[internal.CR]))

cr := NewClusterRole(test.MakeCollector(t), dba)
assert.Nil(t, cr.Lint(test.MakeContext("rbac.authorization.k8s.io/v1/clusterroles", "clusterroles")))
assert.Equal(t, 2, len(cr.Outcome()))

ii := cr.Outcome()["cr4"]
assert.Equal(t, 1, len(ii))

ii = cr.Outcome()["cr5"]
assert.Equal(t, 0, len(ii))
}
27 changes: 27 additions & 0 deletions internal/lint/testdata/auth/cr/2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
apiVersion: v1
kind: List
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
name: cr4
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.authorization.k8s.io/aggregate-to-cr4: "true"
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
rbac.authorization.k8s.io/aggregate-to-cr4: "true"
name: cr5
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- list

0 comments on commit 3a15288

Please sign in to comment.