7
7
8
8
corev1 "k8s.io/api/core/v1"
9
9
rbacv1 "k8s.io/api/rbac/v1"
10
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
10
11
"k8s.io/apimachinery/pkg/labels"
11
12
"k8s.io/apimachinery/pkg/util/sets"
12
13
"k8s.io/apiserver/pkg/authentication/serviceaccount"
@@ -15,6 +16,7 @@ import (
15
16
rbacv1informers "k8s.io/client-go/informers/rbac/v1"
16
17
rbacv1listers "k8s.io/client-go/listers/rbac/v1"
17
18
"k8s.io/client-go/tools/cache"
19
+ "k8s.io/client-go/util/retry"
18
20
"k8s.io/klog/v2"
19
21
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
20
22
@@ -23,8 +25,12 @@ import (
23
25
securityv1listers "github.com/openshift/client-go/security/listers/security/v1"
24
26
)
25
27
28
+ // The index name to be used along with the BySAIndexKeys indexing function
26
29
const BySAIndexName = "ByServiceAccount"
27
30
31
+ // SAToSCCCache is a construct that caches roles and rolebindings
32
+ // (and their cluster variants) and based on that and on SCCs present in the cluster
33
+ // it allows retrieving a set of SCCs for a given ServiceAccount
28
34
type SAToSCCCache struct {
29
35
roleLister rbacv1listers.RoleLister
30
36
clusterRoleLister rbacv1listers.ClusterRoleLister
@@ -75,18 +81,21 @@ func (r *roleBindingObj) Subjects() []rbacv1.Subject {
75
81
return r .roleBinding .Subjects
76
82
}
77
83
78
- func (r * roleBindingObj ) AppliesToNS ( ns string ) bool {
84
+ func (r * roleBindingObj ) Namespace () string {
79
85
if r .clusterRoleBinding != nil {
80
- return true
86
+ return ""
81
87
}
82
- return ns == r .roleBinding .Namespace
88
+ return r .roleBinding .Namespace
83
89
}
84
90
85
- func (r * roleBindingObj ) Namespace () string {
91
+ // AppliesToNS returns true if:
92
+ // - the wrapped object is a cluster role binding
93
+ // - the namespace of a wrapped rolebinding matches the namespace supplied in `ns`
94
+ func (r * roleBindingObj ) AppliesToNS (ns string ) bool {
86
95
if r .clusterRoleBinding != nil {
87
- return ""
96
+ return true
88
97
}
89
- return r .roleBinding .Namespace
98
+ return ns == r .roleBinding .Namespace
90
99
}
91
100
92
101
// roleObj helps to handle roles and clusterroles in a generic manner
@@ -133,6 +142,11 @@ func (r *roleObj) Namespace() string {
133
142
return r .role .Namespace
134
143
}
135
144
145
+ // BySAIndexKeys is a cache.IndexFunc indexing function that shall be used on
146
+ // rolebinding and clusterrolebinding informer caches.
147
+ // It retrieves the subjects of the incoming object and if there are SA, SA groups
148
+ // or the system:authenticated group subjects, these will all be returned as a slice
149
+ // of strings to create an index for the SA or SA group.
136
150
func BySAIndexKeys (obj interface {}) ([]string , error ) {
137
151
roleBinding , err := newRoleBindingObj (obj )
138
152
if err != nil {
@@ -163,17 +177,13 @@ func NewSAToSCCCache(rbacInformers rbacv1informers.Interface, sccInfomer securit
163
177
164
178
sccLister : sccInfomer .Lister (),
165
179
166
- // TODO: do I need these?
167
- rolesSynced : rbacInformers .Roles ().Informer ().HasSynced ,
168
- roleBindingsSynced : rbacInformers .RoleBindings ().Informer ().HasSynced ,
169
-
170
180
usefulRoles : make (map [string ][]string ),
171
181
}
172
182
}
173
183
174
184
// SCCsFor returns a slice of all the SCCs that a given service account can use
175
- // to run pods in its namespace
176
- // It expects the serviceAccount name in the system:serviceaccount:<ns>:<name> form
185
+ // to run pods in its namespace.
186
+ // It expects the serviceAccount name in the system:serviceaccount:<ns>:<name> form.
177
187
func (c * SAToSCCCache ) SCCsFor (serviceAccount * corev1.ServiceAccount ) (sets.String , error ) {
178
188
saUserInfo := serviceaccount .UserInfo (
179
189
serviceAccount .Namespace ,
@@ -212,7 +222,6 @@ func (c *SAToSCCCache) SCCsFor(serviceAccount *corev1.ServiceAccount) (sets.Stri
212
222
}
213
223
}
214
224
215
- // TODO: (idea): determine, ahead of time, which SCCs are allowed for all authenticated or for all SAs?
216
225
for _ , o := range objs {
217
226
rb , err := newRoleBindingObj (o )
218
227
if err != nil {
@@ -242,44 +251,63 @@ func (c *SAToSCCCache) SCCsFor(serviceAccount *corev1.ServiceAccount) (sets.Stri
242
251
return allowedSCCs , nil
243
252
}
244
253
245
- func (c * SAToSCCCache ) GetRoleFromRoleRef (ns string , roleRef rbacv1.RoleRef ) (* roleObj , error ) {
254
+ // getRoleFromRoleRef tries to retrieve the role or clusterrole from roleRef.
255
+ func (c * SAToSCCCache ) getRoleFromRoleRef (ns string , roleRef rbacv1.RoleRef ) (* roleObj , error ) {
246
256
var role interface {}
247
- var err error
257
+ var retryErr error
248
258
switch kind := roleRef .Kind ; kind {
249
- case "Role" : // FIXME: add retries (retry.WithBackoff) on NotFound
250
- role , err = c .roleLister .Roles (ns ).Get (roleRef .Name )
259
+ case "Role" :
260
+ retryErr = retry .OnError (retry .DefaultBackoff , apierrors .IsNotFound , func () error {
261
+ var err error
262
+ role , err = c .roleLister .Roles (ns ).Get (roleRef .Name )
263
+ return err
264
+ })
265
+
251
266
case "ClusterRole" :
252
- role , err = c .clusterRoleLister .Get (roleRef .Name )
267
+ retryErr = retry .OnError (retry .DefaultBackoff , apierrors .IsNotFound , func () error {
268
+ var err error
269
+ role , err = c .clusterRoleLister .Get (roleRef .Name )
270
+ return err
271
+ })
272
+
253
273
default :
254
274
return nil , fmt .Errorf ("unknown kind in roleRef: %s" , kind )
255
275
}
256
- if err != nil {
257
- return nil , err
276
+
277
+ if retryErr != nil {
278
+ return nil , retryErr
258
279
}
259
280
260
281
return newRoleObj (role )
261
282
}
262
283
284
+ // IsRoleBindingRelevant returns true if the cluster/rolebinding supplied binds
285
+ // to a Role that provides access to at least one of the SCCs available in the
286
+ // cluster.
263
287
func (c * SAToSCCCache ) IsRoleBindingRelevant (obj interface {}) bool {
264
288
rb , err := newRoleBindingObj (obj )
265
289
if err != nil {
266
290
klog .Warningf ("unexpected error, this may be a bug: %v" , err )
267
291
return false
268
292
}
269
293
270
- role , err := c .GetRoleFromRoleRef (rb .Namespace (), rb .RoleRef ())
294
+ role , err := c .getRoleFromRoleRef (rb .Namespace (), rb .RoleRef ())
271
295
if err != nil {
272
296
klog .Infof ("failed to retrieve a role for a rolebinding ref: %v" , err )
273
297
return false
274
298
}
275
299
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
300
return c .IsRoleInvolvesSCCs (role , false )
279
301
}
280
302
303
+ // IsRoleInvolvesSCCs returns true if the role supplied in obj provides access
304
+ // to at least one of the SCCs present in the cluster.
305
+ // Set isRoleUpdate to true if instead of using the cached role the supplied object
306
+ // should be used to update the role in the cache as well.
281
307
func (c * SAToSCCCache ) IsRoleInvolvesSCCs (obj interface {}, isRoleUpdate bool ) bool {
282
- c .usefulRolesLock .Lock () // TODO: comment this stuff
308
+ // lock the roles cache to make sure the state of the role that's later retrieved here
309
+ // is written properly
310
+ c .usefulRolesLock .Lock ()
283
311
defer c .usefulRolesLock .Unlock ()
284
312
285
313
role , err := newRoleObj (obj )
@@ -288,23 +316,31 @@ func (c *SAToSCCCache) IsRoleInvolvesSCCs(obj interface{}, isRoleUpdate bool) bo
288
316
return false
289
317
}
290
318
291
- sccs , err := c .sccLister .List (labels .Everything ()) // TODO: this should probably requeue, right?
292
- if err != nil {
293
- klog .Warning ("failed to list SCCs: %v" , err )
294
- return false
295
- }
296
-
297
319
if isRoleUpdate {
320
+ sccs , err := c .sccLister .List (labels .Everything ())
321
+ if err != nil {
322
+ klog .Warning ("failed to list SCCs: %v" , err )
323
+ return false
324
+ }
325
+
298
326
c .syncRoleCache (role .Namespace (), role .Name (), role .Rules (), sccs )
299
327
}
300
328
301
329
return len (c .usefulRoles [fmt .Sprintf ("%s/%s" , role .Namespace (), role .Name ())]) != 0
302
330
}
303
331
332
+ // ReinitializeRoleCache clears the current cache of roles that provide access
333
+ // to SCCs and reinitializes it by pulling all cluster-/roles and SCCs and
334
+ // reevaluating them.
335
+ // This should be used rather rarely as there are many computations involved
336
+ // when assessing all the present cluster-/role rules.
304
337
func (c * SAToSCCCache ) ReinitializeRoleCache () error {
305
- c .usefulRolesLock .Lock () // TODO: comment this stuff
338
+ // rewriting the whole cache, lock is needed
339
+ c .usefulRolesLock .Lock ()
306
340
defer c .usefulRolesLock .Unlock ()
307
341
342
+ c .usefulRoles = map [string ][]string {}
343
+
308
344
roles , err := c .roleLister .List (labels .Everything ())
309
345
if err != nil {
310
346
return fmt .Errorf ("failed to initialize role cache: %w" , err )
@@ -331,6 +367,9 @@ func (c *SAToSCCCache) ReinitializeRoleCache() error {
331
367
return nil
332
368
}
333
369
370
+ // syncRoleCache will write the current mapping of "role->SCCs it allows" to the cache.
371
+ // It expects the c.usefulRolesLock to be already locked as even the wrapping context
372
+ // handling roles and SCCs likely requires synchronization.
334
373
func (c * SAToSCCCache ) syncRoleCache (roleNS , roleName string , rules []rbacv1.PolicyRule , sccs []* securityv1.SecurityContextConstraints ) {
335
374
if c .usefulRolesLock .TryLock () {
336
375
c .usefulRolesLock .Unlock ()
@@ -340,12 +379,12 @@ func (c *SAToSCCCache) syncRoleCache(roleNS, roleName string, rules []rbacv1.Pol
340
379
dummyUserInfo := & user.DefaultInfo {
341
380
Name : "dummyUser" ,
342
381
}
343
- if allowedSCCs := SCCsAllowedByPolicyRules ("" , dummyUserInfo , sccs , rules ); len (allowedSCCs ) > 0 {
382
+ if allowedSCCs := sccsAllowedByPolicyRules ("" , dummyUserInfo , sccs , rules ); len (allowedSCCs ) > 0 {
344
383
c .usefulRoles [fmt .Sprintf ("%s/%s" , roleNS , roleName )] = allowedSCCs
345
384
}
346
385
}
347
386
348
- func SCCsAllowedByPolicyRules (nsName string , saUserInfo user.Info , sccs []* securityv1.SecurityContextConstraints , rules []rbacv1.PolicyRule ) []string {
387
+ func sccsAllowedByPolicyRules (nsName string , saUserInfo user.Info , sccs []* securityv1.SecurityContextConstraints , rules []rbacv1.PolicyRule ) []string {
349
388
ar := authorizer.AttributesRecord {
350
389
User : saUserInfo ,
351
390
APIGroup : securityv1 .GroupName ,
0 commit comments