Skip to content

Commit 2ee1d52

Browse files
committed
add SCC-related roles caching and other fixes
1 parent ff250cb commit 2ee1d52

File tree

2 files changed

+200
-36
lines changed

2 files changed

+200
-36
lines changed

pkg/psalabelsyncer/podsecurity_label_sync_controller.go

+31-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"k8s.io/client-go/tools/cache"
1919
psapi "k8s.io/pod-security-admission/api"
2020

21+
securityv1 "github.com/openshift/api/security/v1"
2122
securityv1informers "github.com/openshift/client-go/security/informers/externalversions/security/v1"
2223
securityv1listers "github.com/openshift/client-go/security/listers/security/v1"
2324

@@ -80,18 +81,41 @@ func NewPodSecurityAdmissionLabelSynchronizationController(
8081
saToSCCsCache: NewSAToSCCCache(rbacInformers, sccInformer),
8182
}
8283

83-
// FIXME: filter out NSes that don't have the SCC UIDs annotation set yet because that makes the conversion panic
84-
// FIXME: also make the conversion not panic but error out instead
8584
return factory.New().
8685
WithSync(c.sync).
87-
WithInformers(
88-
namespaceInformer.Informer(),
89-
rbacInformers.Roles().Informer(),
86+
WithFilteredEventsInformers(
87+
func(obj interface{}) bool {
88+
return c.saToSCCsCache.IsRoleBindingRelevant(obj)
89+
},
9090
rbacInformers.RoleBindings().Informer(),
91-
rbacInformers.ClusterRoles().Informer(),
9291
rbacInformers.ClusterRoleBindings().Informer(),
92+
).
93+
WithFilteredEventsInformers(
94+
func(obj interface{}) bool {
95+
return c.saToSCCsCache.IsRoleInvolvesSCCs(obj, true)
96+
},
97+
rbacInformers.Roles().Informer(),
98+
rbacInformers.ClusterRoles().Informer(),
99+
).
100+
WithFilteredEventsInformers(
101+
func(obj interface{}) bool {
102+
// TODO: also probably don't react on NSes that are being deleted
103+
ns, ok := obj.(*corev1.Namespace)
104+
if !ok {
105+
return false
106+
}
107+
// the SCC mapping requires the annotation
108+
// FIXME: make the mapping not panic but error out instead
109+
if ns.Annotations == nil || len(ns.Annotations[securityv1.UIDRangeAnnotation]) == 0 {
110+
return false
111+
}
112+
return true
113+
},
114+
namespaceInformer.Informer(),
115+
).
116+
WithInformers(
93117
serviceAccountInformer.Informer(),
94-
sccInformer.Informer(),
118+
sccInformer.Informer(), // FIXME: we need to resync the cache on an SCC update (in case one is added or removed)
95119
).
96120
ToController(
97121
controllerName,

pkg/psalabelsyncer/sccrolecache.go

+169-29
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package psalabelsyncer
33
import (
44
"fmt"
55
"strings"
6+
"sync"
67

78
corev1 "k8s.io/api/core/v1"
89
rbacv1 "k8s.io/api/rbac/v1"
9-
"k8s.io/apimachinery/pkg/api/errors"
1010
"k8s.io/apimachinery/pkg/labels"
1111
"k8s.io/apimachinery/pkg/util/sets"
1212
"k8s.io/apiserver/pkg/authentication/serviceaccount"
@@ -15,6 +15,7 @@ import (
1515
rbacv1informers "k8s.io/client-go/informers/rbac/v1"
1616
rbacv1listers "k8s.io/client-go/listers/rbac/v1"
1717
"k8s.io/client-go/tools/cache"
18+
"k8s.io/klog/v2"
1819
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
1920

2021
securityv1 "github.com/openshift/api/security/v1"
@@ -34,6 +35,9 @@ type SAToSCCCache struct {
3435

3536
rolesSynced cache.InformerSynced
3637
roleBindingsSynced cache.InformerSynced
38+
39+
usefulRolesLock sync.RWMutex
40+
usefulRoles map[string][]string
3741
}
3842

3943
// role and clusterrolebinding object for generic handling, assumes one and
@@ -54,7 +58,7 @@ func newRoleBindingObj(obj interface{}) (*roleBindingObj, error) {
5458
}, nil
5559
}
5660

57-
return nil, fmt.Errorf("the object is neither a RoleBinding, nor a ClusterRoleBinding: %v", obj)
61+
return nil, fmt.Errorf("the object is neither a RoleBinding, nor a ClusterRoleBinding: %T", obj)
5862
}
5963

6064
func (r *roleBindingObj) RoleRef() rbacv1.RoleRef {
@@ -78,6 +82,57 @@ func (r *roleBindingObj) AppliesToNS(ns string) bool {
7882
return ns == r.roleBinding.Namespace
7983
}
8084

85+
func (r *roleBindingObj) Namespace() string {
86+
if r.clusterRoleBinding != nil {
87+
return ""
88+
}
89+
return r.roleBinding.Namespace
90+
}
91+
92+
// roleObj helps to handle roles and clusterroles in a generic manner
93+
type roleObj struct {
94+
role *rbacv1.Role
95+
clusterRole *rbacv1.ClusterRole
96+
}
97+
98+
func newRoleObj(obj interface{}) (*roleObj, error) {
99+
switch r := obj.(type) {
100+
case *rbacv1.ClusterRole:
101+
return &roleObj{
102+
clusterRole: r,
103+
}, nil
104+
case *rbacv1.Role:
105+
return &roleObj{
106+
role: r,
107+
}, nil
108+
case *roleObj:
109+
return r, nil
110+
default:
111+
return nil, fmt.Errorf("the object is neither a Role, nor a ClusterRole: %T", obj)
112+
}
113+
}
114+
115+
func (r *roleObj) Rules() []rbacv1.PolicyRule {
116+
if role := r.clusterRole; role != nil {
117+
return role.Rules
118+
}
119+
return r.role.Rules
120+
}
121+
122+
func (r *roleObj) Name() string {
123+
if role := r.clusterRole; role != nil {
124+
return role.Name
125+
}
126+
return r.role.Name
127+
}
128+
129+
func (r *roleObj) Namespace() string {
130+
if role := r.clusterRole; role != nil {
131+
return role.Namespace
132+
}
133+
return r.role.Namespace
134+
}
135+
81136
func BySAIndexKeys(obj interface{}) ([]string, error) {
82137
roleBinding, err := newRoleBindingObj(obj)
83138
if err != nil {
@@ -111,6 +166,8 @@ func NewSAToSCCCache(rbacInformers rbacv1informers.Interface, sccInfomer securit
111166
// TODO: do I need these?
112167
rolesSynced: rbacInformers.Roles().Informer().HasSynced,
113168
roleBindingsSynced: rbacInformers.RoleBindings().Informer().HasSynced,
169+
170+
usefulRoles: make(map[string][]string),
114171
}
115172
}
116173

@@ -163,41 +220,124 @@ func (c *SAToSCCCache) SCCsFor(serviceAccount *corev1.ServiceAccount) (sets.Stri
163220
return nil, err
164221
}
165222

223+
roleCachedKey := fmt.Sprintf("/%s", rb.RoleRef().Name)
224+
if rb.RoleRef().Kind == "Role" {
225+
roleCachedKey = rb.Namespace() + roleCachedKey
226+
}
227+
228+
c.usefulRolesLock.RLock()
229+
// this role does not have SCC-related rules
230+
cachedAllowedSCCs := c.usefulRoles[roleCachedKey]
231+
if len(cachedAllowedSCCs) == 0 {
232+
continue
233+
}
234+
c.usefulRolesLock.RUnlock()
235+
166236
// we particularly care only about Roles in the SA NS
167237
if roleRef := rb.RoleRef(); rb.AppliesToNS(serviceAccount.Namespace) && roleRef.APIGroup == rbacv1.GroupName {
168-
switch roleRef.Kind {
169-
case "Role":
170-
r, err := c.roleLister.Roles(serviceAccount.Namespace).Get(roleRef.Name)
171-
if err != nil {
172-
if errors.IsNotFound(err) {
173-
continue
174-
}
175-
// TODO: maybe just ignore and log?
176-
return nil, err
177-
}
178-
allowedSCCs.Insert(SCCsAllowedByPolicyRules(serviceAccount.Namespace, realSAUserInfo, sccs, r.Rules)...)
179-
180-
case "ClusterRole":
181-
r, err := c.clusterRoleLister.Get(roleRef.Name)
182-
if err != nil {
183-
if errors.IsNotFound(err) {
184-
continue
185-
}
186-
// TODO: maybe just ignore and log?
187-
return nil, err
188-
}
189-
allowedSCCs.Insert(SCCsAllowedByPolicyRules(serviceAccount.Namespace, realSAUserInfo, sccs, r.Rules)...)
190-
191-
default:
192-
// ignore invalid role references
193-
continue
194-
}
238+
allowedSCCs.Insert(cachedAllowedSCCs...)
195239
}
196240
}
197241

198242
return allowedSCCs, nil
199243
}
200244

245+
func (c *SAToSCCCache) GetRoleFromRoleRef(ns string, roleRef rbacv1.RoleRef) (*roleObj, error) {
246+
var role interface{}
247+
var err error
248+
switch kind := roleRef.Kind; kind {
249+
case "Role":
250+
role, err = c.roleLister.Roles(ns).Get(roleRef.Name)
251+
case "ClusterRole":
252+
role, err = c.clusterRoleLister.Get(roleRef.Name)
253+
default:
254+
return nil, fmt.Errorf("unknown kind in roleRef: %s", kind)
255+
}
256+
if err != nil {
257+
return nil, err
258+
}
259+
260+
return newRoleObj(role)
261+
}
262+
263+
func (c *SAToSCCCache) IsRoleBindingRelevant(obj interface{}) bool {
264+
rb, err := newRoleBindingObj(obj)
265+
if err != nil {
266+
klog.Warningf("unexpected error, this may be a bug: %v", err)
267+
return false
268+
}
269+
270+
role, err := c.GetRoleFromRoleRef(rb.Namespace(), rb.RoleRef())
271+
if err != nil {
272+
klog.Infof("failed to retrieve a role for a rolebinding ref: %v", err)
273+
return false
274+
}
275+
276+
// TODO: actually cache the relevant rolebindings and relevant roles
277+
// or maybe only the roles and update cached roles on a role update?
278+
return c.IsRoleInvolvesSCCs(role, false)
279+
}
280+
281+
func (c *SAToSCCCache) IsRoleInvolvesSCCs(obj interface{}, isRoleUpdate bool) bool {
282+
role, err := newRoleObj(obj)
283+
if err != nil {
284+
klog.Warningf("unexpected error, this may be a bug: %v", err)
285+
return false
286+
}
287+
288+
sccs, err := c.sccLister.List(labels.Everything()) // TODO: this should probably requeue, right?
289+
if err != nil {
290+
klog.Warning("failed to list SCCs: %v", err)
291+
return false
292+
}
293+
294+
if isRoleUpdate {
295+
c.SyncRoleCache(role.Namespace(), role.Name(), role.Rules(), sccs)
296+
}
297+
298+
c.usefulRolesLock.RLock()
299+
defer c.usefulRolesLock.RUnlock()
300+
return len(c.usefulRoles[fmt.Sprintf("%s/%s", role.Namespace(), role.Name())]) != 0
301+
}
302+
303+
func (c *SAToSCCCache) InitializeRoleCache() error {
304+
roles, err := c.roleLister.List(labels.Everything())
305+
if err != nil {
306+
return fmt.Errorf("failed to initialize role cache: %w", err)
307+
}
308+
309+
clusterRoles, err := c.clusterRoleLister.List(labels.Everything())
310+
if err != nil {
311+
return fmt.Errorf("failed to initialize role cache: %w", err)
312+
}
313+
314+
sccs, err := c.sccLister.List(labels.Everything())
315+
if err != nil {
316+
return fmt.Errorf("failed to initialize role cache: %w", err)
317+
}
318+
319+
for _, r := range roles {
320+
c.SyncRoleCache(r.Namespace, r.Name, r.Rules, sccs)
321+
}
322+
323+
for _, r := range clusterRoles {
324+
c.SyncRoleCache(r.Namespace, r.Name, r.Rules, sccs)
325+
}
326+
327+
return nil
328+
}
329+
330+
func (c *SAToSCCCache) SyncRoleCache(roleNS, roleName string, rules []rbacv1.PolicyRule, sccs []*securityv1.SecurityContextConstraints) {
331+
dummyUserInfo := &user.DefaultInfo{
332+
Name: "dummyUser",
333+
}
334+
if allowedSCCs := SCCsAllowedByPolicyRules("", dummyUserInfo, sccs, rules); len(allowedSCCs) > 0 {
335+
c.usefulRolesLock.Lock()
336+
c.usefulRoles[fmt.Sprintf("%s/%s", roleNS, roleName)] = allowedSCCs
337+
c.usefulRolesLock.Unlock()
338+
}
339+
}
340+
201341
func SCCsAllowedByPolicyRules(nsName string, saUserInfo user.Info, sccs []*securityv1.SecurityContextConstraints, rules []rbacv1.PolicyRule) []string {
202342
ar := authorizer.AttributesRecord{
203343
User: saUserInfo,

0 commit comments

Comments
 (0)