From fa78dd2ea9cbd00786a931d355e1370d72c4dcbc Mon Sep 17 00:00:00 2001 From: Alejandro Ruiz <4057165+aruiz14@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:46:42 +0200 Subject: [PATCH] Calculate AccessSets once per request instead of per resource (#647) (#660) --- pkg/accesscontrol/access_control.go | 26 ++++++++++++++++++++++++-- pkg/accesscontrol/policy_rule_index.go | 18 ++++++++++-------- pkg/resources/common/formatter.go | 7 +++++-- pkg/schema/factory.go | 4 +--- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/pkg/accesscontrol/access_control.go b/pkg/accesscontrol/access_control.go index 083978e3..6368bd4b 100644 --- a/pkg/accesscontrol/access_control.go +++ b/pkg/accesscontrol/access_control.go @@ -8,6 +8,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) +const accessSetAttribute = "accessSet" + type AccessControl struct { apiserver.SchemaBasedAccess } @@ -25,8 +27,8 @@ func (a *AccessControl) CanDo(apiOp *types.APIRequest, resource, verb, namespace } } group, resource := kv.Split(resource, "/") - accessSet := apiOp.Schemas.Attributes["accessSet"].(*AccessSet) - if accessSet.Grants(verb, schema.GroupResource{ + accessSet := AccessSetFromAPIRequest(apiOp) + if accessSet != nil && accessSet.Grants(verb, schema.GroupResource{ Group: group, Resource: resource, }, namespace, name) { @@ -44,3 +46,23 @@ func (a *AccessControl) CanWatch(apiOp *types.APIRequest, schema *types.APISchem } return a.SchemaBasedAccess.CanWatch(apiOp, schema) } + +// SetAccessSetAttribute stores the provided accessSet using a predefined attribute +func SetAccessSetAttribute(schemas *types.APISchemas, accessSet *AccessSet) { + if schemas.Attributes == nil { + schemas.Attributes = map[string]interface{}{} + } + schemas.Attributes[accessSetAttribute] = accessSet +} + +// AccessSetFromAPIRequest retrieves an AccessSet from the APIRequest Schemas attributes, if defined. +// This attribute must have been previously set by using SetAccessSetAttribute +func AccessSetFromAPIRequest(req *types.APIRequest) *AccessSet { + if req == nil || req.Schemas == nil { + return nil + } + if v, ok := req.Schemas.Attributes[accessSetAttribute]; ok { + return v.(*AccessSet) + } + return nil +} diff --git a/pkg/accesscontrol/policy_rule_index.go b/pkg/accesscontrol/policy_rule_index.go index eed0d48e..a1e9d615 100644 --- a/pkg/accesscontrol/policy_rule_index.go +++ b/pkg/accesscontrol/policy_rule_index.go @@ -153,25 +153,27 @@ func (p *policyRuleIndex) getRoleBindings(subjectName string) []*rbacv1.RoleBind // getRoleRefs gathers rules from roles granted to a given subject through RoleBindings and ClusterRoleBindings func (p *policyRuleIndex) getRoleRefs(subjectName string) subjectGrants { - var clusterRoleBindings []roleRef - for _, crb := range p.getClusterRoleBindings(subjectName) { + crbs := p.getClusterRoleBindings(subjectName) + clusterRoleBindings := make([]roleRef, len(crbs)) + for x, crb := range crbs { rules, resourceVersion := p.getRules(All, crb.RoleRef) - clusterRoleBindings = append(clusterRoleBindings, roleRef{ + clusterRoleBindings[x] = roleRef{ roleName: crb.RoleRef.Name, resourceVersion: resourceVersion, rules: rules, - }) + } } - var roleBindings []roleRef - for _, rb := range p.getRoleBindings(subjectName) { + rbs := p.getRoleBindings(subjectName) + roleBindings := make([]roleRef, len(rbs)) + for x, rb := range rbs { rules, resourceVersion := p.getRules(rb.Namespace, rb.RoleRef) - roleBindings = append(roleBindings, roleRef{ + roleBindings[x] = roleRef{ roleName: rb.RoleRef.Name, namespace: rb.Namespace, resourceVersion: resourceVersion, rules: rules, - }) + } } return subjectGrants{ diff --git a/pkg/resources/common/formatter.go b/pkg/resources/common/formatter.go index 7cc6da09..8749ff20 100644 --- a/pkg/resources/common/formatter.go +++ b/pkg/resources/common/formatter.go @@ -92,9 +92,12 @@ func formatter(summarycache *summarycache.SummaryCache, asl accesscontrol.Access if !ok { return } - accessSet := asl.AccessFor(userInfo) + accessSet := accesscontrol.AccessSetFromAPIRequest(request) if accessSet == nil { - return + accessSet = asl.AccessFor(userInfo) + if accessSet == nil { + return + } } hasUpdate := accessSet.Grants("update", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name()) hasDelete := accessSet.Grants("delete", gvr.GroupResource(), resource.APIObject.Namespace(), resource.APIObject.Name()) diff --git a/pkg/schema/factory.go b/pkg/schema/factory.go index 53c35173..fb55d5df 100644 --- a/pkg/schema/factory.go +++ b/pkg/schema/factory.go @@ -170,9 +170,7 @@ func (c *Collection) schemasForSubject(access *accesscontrol.AccessSet) (*types. } } - result.Attributes = map[string]interface{}{ - "accessSet": access, - } + accesscontrol.SetAccessSetAttribute(result, access) return result, nil }