Skip to content

Commit 8141b30

Browse files
committed
Add support for ClusterRole AggregationRule during bundle generation
ClusterRoles that use an AggregationRule often do not have any rules defined directly. Instead, their rules are aggregated from other ClusterRoles that match the AggregationRule’s label selector. The existing generator logic only included rules from ClusterRoles that were explicitly bound via ClusterRoleBindings to the ServiceAccounts used by Deployments. Since ClusterRoles with an AggregationRule typically lack direct rule definitions, the resulting permission bundle ended up being empty. This update adds support for handling ClusterRoles that use an AggregationRule.
1 parent bbee393 commit 8141b30

File tree

2 files changed

+130
-12
lines changed

2 files changed

+130
-12
lines changed

internal/generate/clusterserviceversion/clusterserviceversion_updaters.go

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"encoding/json"
1919
"errors"
2020
"fmt"
21+
"slices"
2122
"sort"
2223
"strings"
2324
"time"
@@ -31,6 +32,9 @@ import (
3132
corev1 "k8s.io/api/core/v1"
3233
rbacv1 "k8s.io/api/rbac/v1"
3334
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
35+
"k8s.io/apimachinery/pkg/api/equality"
36+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37+
"k8s.io/apimachinery/pkg/labels"
3438
"sigs.k8s.io/controller-runtime/pkg/client"
3539

3640
"github.com/operator-framework/operator-sdk/internal/generate/collector"
@@ -57,8 +61,14 @@ func apply(c *collector.Manifests, csv *operatorsv1alpha1.ClusterServiceVersion,
5761
switch strategy.StrategyName {
5862
case operatorsv1alpha1.InstallStrategyNameDeployment:
5963
inPerms, inCPerms, _ := c.SplitCSVPermissionsObjects(extraSAs)
60-
applyRoles(c, inPerms, &strategy.StrategySpec, extraSAs)
61-
applyClusterRoles(c, inCPerms, &strategy.StrategySpec, extraSAs)
64+
err := applyRoles(c, inPerms, &strategy.StrategySpec, extraSAs)
65+
if err != nil {
66+
return fmt.Errorf("can't apply roles: %w", err)
67+
}
68+
err = applyClusterRoles(c, inCPerms, &strategy.StrategySpec, extraSAs)
69+
if err != nil {
70+
return fmt.Errorf("can't apply cluster roles: %w", err)
71+
}
6272
applyDeployments(c, &strategy.StrategySpec)
6373
}
6474
csv.Spec.InstallStrategy = strategy
@@ -88,7 +98,7 @@ const defaultServiceAccountName = "default"
8898

8999
// applyRoles applies Roles to strategy's permissions field by combining Roles bound to ServiceAccounts
90100
// into one set of permissions.
91-
func applyRoles(c *collector.Manifests, objs []client.Object, strategy *operatorsv1alpha1.StrategyDetailsDeployment, extraSAs []string) { //nolint:dupl
101+
func applyRoles(c *collector.Manifests, objs []client.Object, strategy *operatorsv1alpha1.StrategyDetailsDeployment, extraSAs []string) error { //nolint:dupl
92102
roleSet := make(map[string]rbacv1.Role)
93103
cRoleSet := make(map[string]rbacv1.ClusterRole)
94104
for i := range objs {
@@ -120,8 +130,16 @@ func applyRoles(c *collector.Manifests, objs []client.Object, strategy *operator
120130
hasRole = has
121131
case "ClusterRole":
122132
role, has := cRoleSet[binding.RoleRef.Name]
123-
rules = role.Rules
133+
124134
hasRole = has
135+
if has {
136+
var err error
137+
rules, err = getClusterRoleRules(role, c.ClusterRoles)
138+
if err != nil {
139+
return fmt.Errorf("can't get ClusterRole rules: %w", err)
140+
}
141+
}
142+
125143
default:
126144
continue
127145
}
@@ -143,11 +161,13 @@ func applyRoles(c *collector.Manifests, objs []client.Object, strategy *operator
143161
return perms[i].ServiceAccountName < perms[j].ServiceAccountName
144162
})
145163
strategy.Permissions = perms
164+
165+
return nil
146166
}
147167

148168
// applyClusterRoles applies ClusterRoles to strategy's clusterPermissions field by combining ClusterRoles
149169
// bound to ServiceAccounts into one set of clusterPermissions.
150-
func applyClusterRoles(c *collector.Manifests, objs []client.Object, strategy *operatorsv1alpha1.StrategyDetailsDeployment, extraSAs []string) { //nolint:dupl
170+
func applyClusterRoles(c *collector.Manifests, objs []client.Object, strategy *operatorsv1alpha1.StrategyDetailsDeployment, extraSAs []string) error { //nolint:dupl
151171
roleSet := make(map[string]rbacv1.ClusterRole)
152172
for i := range objs {
153173
switch t := objs[i].(type) {
@@ -166,7 +186,12 @@ func applyClusterRoles(c *collector.Manifests, objs []client.Object, strategy *o
166186
continue
167187
}
168188
if role, hasRole := roleSet[binding.RoleRef.Name]; hasRole {
169-
perm.Rules = append(perm.Rules, role.Rules...)
189+
rules, err := getClusterRoleRules(role, c.ClusterRoles)
190+
if err != nil {
191+
return fmt.Errorf("can't get ClusterRole rules: %w", err)
192+
}
193+
194+
perm.Rules = append(perm.Rules, rules...)
170195
saToPermissions[subject.Name] = perm
171196
}
172197
}
@@ -183,6 +208,49 @@ func applyClusterRoles(c *collector.Manifests, objs []client.Object, strategy *o
183208
return perms[i].ServiceAccountName < perms[j].ServiceAccountName
184209
})
185210
strategy.ClusterPermissions = perms
211+
212+
return nil
213+
}
214+
215+
// getClusterRoleRules returns all PolicyRules for a given ClusterRole, including rules from aggregated ClusterRoles
216+
// as specified by the AggregationRule. It recursively collects rules from other ClusterRoles that match the label selectors
217+
// in the AggregationRule, ensuring no duplicate rules are added.
218+
func getClusterRoleRules(clusterRole rbacv1.ClusterRole, clusterRoles []rbacv1.ClusterRole) ([]rbacv1.PolicyRule, error) {
219+
rules := make([]rbacv1.PolicyRule, 0, len(clusterRole.Rules))
220+
rules = append(rules, clusterRole.Rules...)
221+
222+
if clusterRole.AggregationRule == nil {
223+
return rules, nil
224+
}
225+
226+
for _, crSelector := range clusterRole.AggregationRule.ClusterRoleSelectors {
227+
labelSelector, err := metav1.LabelSelectorAsSelector(&crSelector)
228+
if err != nil {
229+
return nil, fmt.Errorf("can't create label selector from ClusterRole %q AggregationRule: %w", clusterRole.Name, err)
230+
}
231+
232+
for _, cr := range clusterRoles {
233+
if cr.Name == clusterRole.Name {
234+
continue
235+
}
236+
237+
if !labelSelector.Matches(labels.Set(cr.Labels)) {
238+
continue
239+
}
240+
241+
for _, rule := range cr.Rules {
242+
ruleExists := slices.ContainsFunc(rules, func(r rbacv1.PolicyRule) bool {
243+
return equality.Semantic.DeepEqual(rule, r)
244+
})
245+
246+
if !ruleExists {
247+
rules = append(rules, rule)
248+
}
249+
}
250+
}
251+
}
252+
253+
return rules, nil
186254
}
187255

188256
// initPermissionSet initializes a map of ServiceAccount name to permissions, which are empty.

internal/generate/clusterserviceversion/clusterserviceversion_updaters_test.go

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
rbacv1 "k8s.io/api/rbac/v1"
2525
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2626
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/labels"
2829
"sigs.k8s.io/controller-runtime/pkg/client"
2930

@@ -65,6 +66,8 @@ var _ = Describe("apply functions", func() {
6566
saName1 = "service-account-1"
6667
roleName1 = "role-1"
6768
cRoleName1 = "cluster-role-1"
69+
cRoleName2 = "cluster-role-2"
70+
cRoleName3 = "cluster-role-3"
6871
)
6972

7073
BeforeEach(func() {
@@ -79,7 +82,8 @@ var _ = Describe("apply functions", func() {
7982
rules := []rbacv1.PolicyRule{{Verbs: []string{"create"}}}
8083
perms := []client.Object{newRole(roleName1, rules...)}
8184
c.RoleBindings = []rbacv1.RoleBinding{newRoleBinding("role-binding", newRoleRef(roleName1), newServiceAccountSubject(saName1))}
82-
applyRoles(c, perms, strategy, nil)
85+
err := applyRoles(c, perms, strategy, nil)
86+
Expect(err).NotTo(HaveOccurred())
8387
Expect(strategy.Permissions).To(Equal([]operatorsv1alpha1.StrategyDeploymentPermissions{
8488
{ServiceAccountName: saName1, Rules: rules},
8589
}))
@@ -90,7 +94,49 @@ var _ = Describe("apply functions", func() {
9094
rules := []rbacv1.PolicyRule{{Verbs: []string{"create"}}}
9195
perms := []client.Object{newClusterRole(cRoleName1, rules...)}
9296
c.ClusterRoleBindings = []rbacv1.ClusterRoleBinding{newClusterRoleBinding("cluster-role-binding", newClusterRoleRef(cRoleName1), newServiceAccountSubject(saName1))}
93-
applyClusterRoles(c, perms, strategy, nil)
97+
err := applyClusterRoles(c, perms, strategy, nil)
98+
Expect(err).NotTo(HaveOccurred())
99+
Expect(strategy.ClusterPermissions).To(Equal([]operatorsv1alpha1.StrategyDeploymentPermissions{
100+
{ServiceAccountName: saName1, Rules: rules},
101+
}))
102+
})
103+
It("adds rules from aggregated ClusterRoles eliminating duplicates to the CSV deployment strategy", func() {
104+
c.Deployments = []appsv1.Deployment{newDeploymentWithServiceAccount(depName1, saName1)}
105+
c.ServiceAccounts = []corev1.ServiceAccount{newServiceAccount(saName1)}
106+
rules := []rbacv1.PolicyRule{{Verbs: []string{"create"}}}
107+
var emptyRules []rbacv1.PolicyRule
108+
perms := []client.Object{
109+
func() *rbacv1.ClusterRole {
110+
cr := newClusterRole(cRoleName1, emptyRules...)
111+
cr.AggregationRule = &rbacv1.AggregationRule{
112+
ClusterRoleSelectors: []metav1.LabelSelector{
113+
{
114+
MatchLabels: map[string]string{
115+
"aggregate-to-cluster-role-1": "true",
116+
},
117+
},
118+
},
119+
}
120+
return cr
121+
}(),
122+
func() *rbacv1.ClusterRole {
123+
cr := newClusterRole(cRoleName2, rules...)
124+
cr.Labels = map[string]string{
125+
"aggregate-to-cluster-role-1": "true",
126+
}
127+
return cr
128+
}(),
129+
func() *rbacv1.ClusterRole {
130+
cr := newClusterRole(cRoleName3, rules...)
131+
cr.Labels = map[string]string{
132+
"aggregate-to-cluster-role-1": "true",
133+
}
134+
return cr
135+
}(),
136+
}
137+
c.ClusterRoleBindings = []rbacv1.ClusterRoleBinding{newClusterRoleBinding("cluster-role-binding", newClusterRoleRef(cRoleName1), newServiceAccountSubject(saName1))}
138+
err := applyClusterRoles(c, perms, strategy, nil)
139+
Expect(err).NotTo(HaveOccurred())
94140
Expect(strategy.ClusterPermissions).To(Equal([]operatorsv1alpha1.StrategyDeploymentPermissions{
95141
{ServiceAccountName: saName1, Rules: rules},
96142
}))
@@ -128,8 +174,10 @@ var _ = Describe("apply functions", func() {
128174
newClusterRoleBinding("cluster-role-binding-2", newClusterRoleRef(cRoleName2), newServiceAccountSubject(extraSAName)),
129175
newClusterRoleBinding("cluster-role-binding-3", newClusterRoleRef(cRoleName3), newServiceAccountSubject(extraSAName)),
130176
}
131-
applyRoles(c, perms, strategy, []string{extraSAName})
132-
applyClusterRoles(c, cperms, strategy, []string{extraSAName})
177+
err := applyRoles(c, perms, strategy, []string{extraSAName})
178+
Expect(err).NotTo(HaveOccurred())
179+
err = applyClusterRoles(c, cperms, strategy, []string{extraSAName})
180+
Expect(err).NotTo(HaveOccurred())
133181
Expect(strategy.Permissions).To(Equal([]operatorsv1alpha1.StrategyDeploymentPermissions{
134182
{ServiceAccountName: saName1, Rules: rules},
135183
{ServiceAccountName: extraSAName, Rules: rules},
@@ -146,14 +194,16 @@ var _ = Describe("apply functions", func() {
146194
c.Deployments = []appsv1.Deployment{newDeploymentWithServiceAccount(depName1, saName1)}
147195
c.ServiceAccounts = []corev1.ServiceAccount{newServiceAccount(saName1)}
148196
c.RoleBindings = []rbacv1.RoleBinding{newRoleBinding("role-binding", newRoleRef(roleName1), newServiceAccountSubject(saName1))}
149-
applyRoles(c, nil, strategy, nil)
197+
err := applyRoles(c, nil, strategy, nil)
198+
Expect(err).NotTo(HaveOccurred())
150199
Expect(strategy.Permissions).To(Equal([]operatorsv1alpha1.StrategyDeploymentPermissions{}))
151200
})
152201
It("adds no ClusterPermissions to the CSV deployment strategy", func() {
153202
c.Deployments = []appsv1.Deployment{newDeploymentWithServiceAccount(depName1, saName1)}
154203
c.ServiceAccounts = []corev1.ServiceAccount{newServiceAccount(saName1)}
155204
c.ClusterRoleBindings = []rbacv1.ClusterRoleBinding{newClusterRoleBinding("cluster-role-binding", newClusterRoleRef(cRoleName1), newServiceAccountSubject(saName1))}
156-
applyClusterRoles(c, nil, strategy, nil)
205+
err := applyClusterRoles(c, nil, strategy, nil)
206+
Expect(err).NotTo(HaveOccurred())
157207
Expect(strategy.ClusterPermissions).To(Equal([]operatorsv1alpha1.StrategyDeploymentPermissions{}))
158208
})
159209
})

0 commit comments

Comments
 (0)