Skip to content

Commit e830ad0

Browse files
committed
Initial clusterRoleBindings feature
Signed-off-by: fxiang1 <fxiang@redhat.com>
1 parent 1678382 commit e830ad0

9 files changed

Lines changed: 289 additions & 20 deletions

api/v1alpha1/clusterpermission_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ type ClusterPermissionSpec struct {
3737
// +kubebuilder:validation:XValidation:rule="has(self.subject) || has(self.subjects)",message="Either subject or subjects has to exist in clusterRoleBinding"
3838
ClusterRoleBinding *ClusterRoleBinding `json:"clusterRoleBinding,omitempty"`
3939

40+
// ClusterRoleBindings represents multiple ClusterRoleBindings that are being created on the managed cluster
41+
// +optional
42+
// +kubebuilder:validation:XValidation:rule="self.all(i, has(i.subject) || has(i.subjects))",message="Either subject or subjects has to exist in every clusterRoleBinding"
43+
ClusterRoleBindings *[]ClusterRoleBinding `json:"clusterRoleBindings,omitempty"`
44+
4045
// Roles represents roles that are being created on the managed cluster
4146
// +optional
4247
Roles *[]Role `json:"roles,omitempty"`

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crds/rbac.open-cluster-management.io_clusterpermissions.yaml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,107 @@ spec:
194194
x-kubernetes-validations:
195195
- message: Either subject or subjects has to exist in clusterRoleBinding
196196
rule: has(self.subject) || has(self.subjects)
197+
clusterRoleBindings:
198+
description: ClusterRoleBindings represents multiple ClusterRoleBindings
199+
that are being created on the managed cluster
200+
items:
201+
description: ClusterRoleBinding represents the ClusterRoleBinding
202+
that is being created on the managed cluster
203+
properties:
204+
name:
205+
description: Name of the ClusterRoleBinding if a name different
206+
than the ClusterPermission name is used
207+
type: string
208+
roleRef:
209+
description: RoleRef contains information that points to the
210+
ClusterRole being used
211+
properties:
212+
apiGroup:
213+
description: APIGroup is the group for the resource being
214+
referenced
215+
type: string
216+
kind:
217+
description: Kind is the type of resource being referenced
218+
type: string
219+
name:
220+
description: Name is the name of resource being referenced
221+
type: string
222+
required:
223+
- apiGroup
224+
- kind
225+
- name
226+
type: object
227+
x-kubernetes-map-type: atomic
228+
subject:
229+
description: |-
230+
Subject contains a reference to the object or user identities a ClusterPermission binding applies to.
231+
Besides the typical subject for a binding, a ManagedServiceAccount can be used as a subject as well.
232+
If both subject and subjects exist then only subjects will be used.
233+
properties:
234+
apiGroup:
235+
description: |-
236+
APIGroup holds the API group of the referenced subject.
237+
Defaults to "" for ServiceAccount subjects.
238+
Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
239+
type: string
240+
kind:
241+
description: |-
242+
Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount".
243+
If the Authorizer does not recognized the kind value, the Authorizer should report an error.
244+
type: string
245+
name:
246+
description: Name of the object being referenced.
247+
type: string
248+
namespace:
249+
description: |-
250+
Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty
251+
the Authorizer should report an error.
252+
type: string
253+
required:
254+
- kind
255+
- name
256+
type: object
257+
x-kubernetes-map-type: atomic
258+
subjects:
259+
description: |-
260+
Subjects contains an array of references to objects or user identities a ClusterPermission binding applies to.
261+
Besides the typical subject for a binding, a ManagedServiceAccount can be used as a subject as well.
262+
If both subject and subjects exist then only subjects will be used.
263+
items:
264+
description: |-
265+
Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference,
266+
or a value for non-objects such as user and group names.
267+
properties:
268+
apiGroup:
269+
description: |-
270+
APIGroup holds the API group of the referenced subject.
271+
Defaults to "" for ServiceAccount subjects.
272+
Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
273+
type: string
274+
kind:
275+
description: |-
276+
Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount".
277+
If the Authorizer does not recognized the kind value, the Authorizer should report an error.
278+
type: string
279+
name:
280+
description: Name of the object being referenced.
281+
type: string
282+
namespace:
283+
description: |-
284+
Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty
285+
the Authorizer should report an error.
286+
type: string
287+
required:
288+
- kind
289+
- name
290+
type: object
291+
x-kubernetes-map-type: atomic
292+
type: array
293+
type: object
294+
type: array
295+
x-kubernetes-validations:
296+
- message: Either subject or subjects has to exist in every clusterRoleBinding
297+
rule: self.all(i, has(i.subject) || has(i.subjects))
197298
roleBindings:
198299
description: RoleBindings represents RoleBindings that are being created
199300
on the managed cluster
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
apiVersion: rbac.open-cluster-management.io/v1alpha1
2+
kind: ClusterPermission
3+
metadata:
4+
name: clusterpermission-multiple-clusterrolebindings
5+
spec:
6+
clusterRoleBindings:
7+
- name: multi-crb-binding1
8+
roleRef:
9+
apiGroup: rbac.authorization.k8s.io
10+
kind: ClusterRole
11+
name: argocd-application-controller-1
12+
subject:
13+
kind: User
14+
name: user1
15+
- name: multi-crb-binding2
16+
roleRef:
17+
apiGroup: rbac.authorization.k8s.io
18+
kind: ClusterRole
19+
name: argocd-application-controller-3
20+
subjects:
21+
- kind: User
22+
name: user2
23+
- kind: Group
24+
name: group1

controllers/clusterpermission_controller.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ func (r *ClusterPermissionReconciler) Reconcile(ctx context.Context, req ctrl.Re
8282
log.Info("validating ClusterPermission")
8383

8484
/* Validations */
85-
if clusterPermission.Spec.ClusterRoleBinding == nil && clusterPermission.Spec.RoleBindings == nil {
85+
if clusterPermission.Spec.ClusterRoleBinding == nil &&
86+
(clusterPermission.Spec.ClusterRoleBindings == nil || len(*clusterPermission.Spec.ClusterRoleBindings) == 0) &&
87+
(clusterPermission.Spec.RoleBindings == nil || len(*clusterPermission.Spec.RoleBindings) == 0) {
8688
log.Info("no bindings defined for ClusterPermission")
8789

8890
err := r.updateStatus(ctx, &clusterPermission, &metav1.Condition{
@@ -117,7 +119,7 @@ func (r *ClusterPermissionReconciler) Reconcile(ctx context.Context, req ctrl.Re
117119

118120
log.Info("preparing ManifestWork payload")
119121

120-
clusterRole, clusterRoleBinding, roles, roleBindings, err := r.generateManifestWorkPayload(
122+
clusterRole, clusterRoleBindings, roles, roleBindings, err := r.generateManifestWorkPayload(
121123
ctx, &clusterPermission)
122124
if err != nil {
123125
log.Error(err, "failed to generate payload")
@@ -134,7 +136,7 @@ func (r *ClusterPermissionReconciler) Reconcile(ctx context.Context, req ctrl.Re
134136

135137
mwName := generateManifestWorkName(clusterPermission)
136138
manifestWork := buildManifestWork(clusterPermission, mwName,
137-
clusterRole, clusterRoleBinding, roles, roleBindings)
139+
clusterRole, clusterRoleBindings, roles, roleBindings)
138140

139141
var mw workv1.ManifestWork
140142
err = r.Get(ctx, types.NamespacedName{Name: mwName, Namespace: clusterPermission.Namespace}, &mw)
@@ -255,9 +257,9 @@ func (r *ClusterPermissionReconciler) generateSubjects(ctx context.Context,
255257

256258
// generateManifestWorkPayload creates the payload for the ManifestWork based on the ClusterPermission spec
257259
func (r *ClusterPermissionReconciler) generateManifestWorkPayload(ctx context.Context, clusterPermission *cpv1alpha1.ClusterPermission) (
258-
*rbacv1.ClusterRole, *rbacv1.ClusterRoleBinding, []rbacv1.Role, []rbacv1.RoleBinding, error) {
260+
*rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding, []rbacv1.Role, []rbacv1.RoleBinding, error) {
259261
var clusterRole *rbacv1.ClusterRole
260-
var clusterRoleBinding *rbacv1.ClusterRoleBinding
262+
var clusterRoleBindings []rbacv1.ClusterRoleBinding
261263
var roles []rbacv1.Role
262264
var roleBindings []rbacv1.RoleBinding
263265

@@ -304,16 +306,63 @@ func (r *ClusterPermissionReconciler) generateManifestWorkPayload(ctx context.Co
304306
clusterRoleBindingRoleRef = *clusterPermission.Spec.ClusterRoleBinding.RoleRef
305307
}
306308

307-
clusterRoleBinding = &rbacv1.ClusterRoleBinding{
308-
TypeMeta: metav1.TypeMeta{
309-
APIVersion: rbacv1.SchemeGroupVersion.String(),
310-
Kind: "ClusterRoleBinding",
311-
},
312-
ObjectMeta: metav1.ObjectMeta{
313-
Name: clusterRoleBindingName,
314-
},
315-
RoleRef: clusterRoleBindingRoleRef,
316-
Subjects: subjects,
309+
// We make the behaviour consistent with how subject and subjects are handled in ClusterRoleBinding
310+
// subjects will just override subject if both are provided
311+
if clusterPermission.Spec.ClusterRoleBindings == nil || len(*clusterPermission.Spec.ClusterRoleBindings) == 0 {
312+
clusterRoleBindings = append(clusterRoleBindings, rbacv1.ClusterRoleBinding{
313+
TypeMeta: metav1.TypeMeta{
314+
APIVersion: rbacv1.SchemeGroupVersion.String(),
315+
Kind: "ClusterRoleBinding",
316+
},
317+
ObjectMeta: metav1.ObjectMeta{
318+
Name: clusterRoleBindingName,
319+
},
320+
RoleRef: clusterRoleBindingRoleRef,
321+
Subjects: subjects,
322+
})
323+
}
324+
}
325+
326+
// ClusterRoleBindings payload (plural)
327+
if clusterPermission.Spec.ClusterRoleBindings != nil && len(*clusterPermission.Spec.ClusterRoleBindings) > 0 {
328+
for _, clusterRoleBinding := range *clusterPermission.Spec.ClusterRoleBindings {
329+
crbSubjects := getSubjects(clusterRoleBinding.Subject, clusterRoleBinding.Subjects)
330+
if err := r.validateSubject(ctx, crbSubjects, clusterPermission.Namespace); err != nil {
331+
return nil, nil, nil, nil, err
332+
}
333+
334+
subjects, err := r.generateSubjects(ctx, crbSubjects, clusterPermission.Namespace)
335+
if err != nil {
336+
return nil, nil, nil, nil, err
337+
}
338+
339+
// default to ClusterPermission name unless using custom name
340+
clusterRoleBindingName := clusterPermission.Name
341+
if clusterRoleBinding.Name != "" {
342+
clusterRoleBindingName = clusterRoleBinding.Name
343+
}
344+
345+
// default to creating a ClusterRole unless using existing ClusterRole
346+
clusterRoleBindingRoleRef := rbacv1.RoleRef{
347+
APIGroup: rbacv1.GroupName,
348+
Kind: "ClusterRole",
349+
Name: clusterPermission.Name,
350+
}
351+
if clusterRoleBinding.RoleRef != nil {
352+
clusterRoleBindingRoleRef = *clusterRoleBinding.RoleRef
353+
}
354+
355+
clusterRoleBindings = append(clusterRoleBindings, rbacv1.ClusterRoleBinding{
356+
TypeMeta: metav1.TypeMeta{
357+
APIVersion: rbacv1.SchemeGroupVersion.String(),
358+
Kind: "ClusterRoleBinding",
359+
},
360+
ObjectMeta: metav1.ObjectMeta{
361+
Name: clusterRoleBindingName,
362+
},
363+
RoleRef: clusterRoleBindingRoleRef,
364+
Subjects: subjects,
365+
})
317366
}
318367
}
319368

@@ -479,5 +528,5 @@ func (r *ClusterPermissionReconciler) generateManifestWorkPayload(ctx context.Co
479528
}
480529
}
481530

482-
return clusterRole, clusterRoleBinding, roles, roleBindings, nil
531+
return clusterRole, clusterRoleBindings, roles, roleBindings, nil
483532
}

controllers/clusterpermission_controller_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,58 @@ var _ = Describe("ClusterPermission controller", func() {
371371
}
372372
return true
373373
}).Should(BeTrue())
374+
375+
By("Creating a ClusterPermission that has multiple ClusterRoleBindings")
376+
clusterPermissionMultipleCRBs := cpv1alpha1.ClusterPermission{
377+
ObjectMeta: metav1.ObjectMeta{
378+
Name: "clusterpermission-multiple-crbs",
379+
Namespace: clusterName,
380+
},
381+
Spec: cpv1alpha1.ClusterPermissionSpec{
382+
ClusterRoleBindings: &[]cpv1alpha1.ClusterRoleBinding{
383+
{
384+
Name: "crb-1",
385+
RoleRef: &rbacv1.RoleRef{
386+
APIGroup: "rbac.authorization.k8s.io",
387+
Kind: "ClusterRole",
388+
Name: "argocd-application-controller-1",
389+
},
390+
Subject: rbacv1.Subject{
391+
Kind: "User",
392+
Name: "user1",
393+
},
394+
},
395+
{
396+
Name: "crb-2",
397+
RoleRef: &rbacv1.RoleRef{
398+
APIGroup: "rbac.authorization.k8s.io",
399+
Kind: "ClusterRole",
400+
Name: "argocd-application-controller-2",
401+
},
402+
Subjects: []rbacv1.Subject{
403+
{
404+
Kind: "User",
405+
Name: "user2",
406+
},
407+
{
408+
Kind: "Group",
409+
Name: "group1",
410+
},
411+
},
412+
},
413+
},
414+
},
415+
}
416+
417+
Expect(k8sClient.Create(ctx, &clusterPermissionMultipleCRBs)).Should(Succeed())
418+
mwKeyMultipleCRBs := types.NamespacedName{Name: generateManifestWorkName(clusterPermissionMultipleCRBs), Namespace: clusterName}
419+
mwMultipleCRBs := workv1.ManifestWork{}
420+
Eventually(func() bool {
421+
if err := k8sClient.Get(ctx, mwKeyMultipleCRBs, &mwMultipleCRBs); err != nil {
422+
return false
423+
}
424+
return true
425+
}).Should(BeTrue())
374426
})
375427
})
376428

controllers/helper.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func generateManifestWorkName(clusterPermission cpv1alpha1.ClusterPermission) st
3333
// buildManifestWork wraps the payloads in a ManifestWork
3434
func buildManifestWork(clusterPermission cpv1alpha1.ClusterPermission, manifestWorkName string,
3535
clusterRole *rbacv1.ClusterRole,
36-
clusterRoleBinding *rbacv1.ClusterRoleBinding,
36+
clusterRoleBindings []rbacv1.ClusterRoleBinding,
3737
roles []rbacv1.Role,
3838
roleBindings []rbacv1.RoleBinding) *workv1.ManifestWork {
3939
var manifests []workv1.Manifest
@@ -42,8 +42,10 @@ func buildManifestWork(clusterPermission cpv1alpha1.ClusterPermission, manifestW
4242
manifests = append(manifests, workv1.Manifest{RawExtension: runtime.RawExtension{Object: clusterRole}})
4343
}
4444

45-
if clusterRoleBinding != nil {
46-
manifests = append(manifests, workv1.Manifest{RawExtension: runtime.RawExtension{Object: clusterRoleBinding}})
45+
if len(clusterRoleBindings) > 0 {
46+
for i := range clusterRoleBindings {
47+
manifests = append(manifests, workv1.Manifest{RawExtension: runtime.RawExtension{Object: &clusterRoleBindings[i]}})
48+
}
4749
}
4850

4951
if len(roles) > 0 {

controllers/helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func Test_buildManifestWork(t *testing.T) {
128128
}
129129
for _, tt := range tests {
130130
t.Run(tt.name, func(t *testing.T) {
131-
got := buildManifestWork(tt.args.clusterPermission, tt.args.manifestWorkName, tt.args.clusterRole, tt.args.clusterRoleBinding,
131+
got := buildManifestWork(tt.args.clusterPermission, tt.args.manifestWorkName, tt.args.clusterRole, []rbacv1.ClusterRoleBinding{*tt.args.clusterRoleBinding},
132132
tt.args.roles, tt.args.roleBindings)
133133
// check work name
134134
if got.Name != tt.wants.manifestWorkName {

0 commit comments

Comments
 (0)