Skip to content

Commit 24b9633

Browse files
author
Per Goncalves da Silva
committed
Add installer rbac generator
Signed-off-by: Per Goncalves da Silva <[email protected]> Signed-off-by: Per G. da Silva <[email protected]>
1 parent 00251d6 commit 24b9633

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package convert
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
"strings"
7+
8+
rbacv1 "k8s.io/api/rbac/v1"
9+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"k8s.io/apimachinery/pkg/runtime/schema"
11+
"k8s.io/utils/ptr"
12+
"sigs.k8s.io/controller-runtime/pkg/client"
13+
14+
"github.com/operator-framework/operator-controller/internal/shared/util/filter"
15+
)
16+
17+
var (
18+
unnamedResourceVerbs = []string{"create", "list", "watch"}
19+
namedResourceVerbs = []string{"get", "update", "patch", "delete"}
20+
21+
// clusterScopedResources is a slice of registry+v1 bundle supported cluster scoped resource kinds
22+
clusterScopedResources = []string{
23+
"ClusterRole",
24+
"ClusterRoleBinding",
25+
"PriorityClass",
26+
"ConsoleYAMLSample",
27+
"ConsoleQuickStart",
28+
"ConsoleCLIDownload",
29+
"ConsoleLink",
30+
"CustomResourceDefinition",
31+
}
32+
33+
// clusterScopedResources is a slice of registry+v1 bundle supported namespace scoped resource kinds
34+
namespaceScopedResources = []string{
35+
"Secret",
36+
"ConfigMap",
37+
"ServiceAccount",
38+
"Service",
39+
"Role",
40+
"RoleBinding",
41+
"PrometheusRule",
42+
"ServiceMonitor",
43+
"PodDisruptionBudget",
44+
"VerticalPodAutoscaler",
45+
"Deployment",
46+
}
47+
)
48+
49+
func GenerateInstallerRBAC(objs []client.Object, extensionName string, installNamespace string, watchNamespace string) []client.Object {
50+
generatedObjs := getGeneratedObjs(objs)
51+
52+
var (
53+
serviceAccountName = extensionName + "-installer"
54+
clusterRoleName = extensionName + "-installer-clusterrole"
55+
clusterRoleBindingName = extensionName + "-installer-clusterrole-binding"
56+
roleName = extensionName + "-installer-role"
57+
roleBindingName = extensionName + "-installer-role-binding"
58+
)
59+
60+
rbacManifests := []client.Object{
61+
// installer service account
62+
ptr.To(newServiceAccount(
63+
installNamespace,
64+
serviceAccountName,
65+
)),
66+
67+
// cluster scoped resources
68+
ptr.To(newClusterRole(
69+
clusterRoleName,
70+
slices.Concat(
71+
// finalizer rule
72+
[]rbacv1.PolicyRule{newClusterExtensionFinalizerPolicyRule(extensionName)},
73+
// cluster scoped resource creation and management rules
74+
generatePolicyRules(filter.Filter(objs, isClusterScopedResource)),
75+
// controller rbac scope
76+
collectRBACResourcePolicyRules(filter.Filter(generatedObjs, isClusterRole)),
77+
),
78+
)),
79+
ptr.To(newClusterRoleBinding(
80+
clusterRoleBindingName,
81+
clusterRoleName,
82+
installNamespace,
83+
serviceAccountName,
84+
)),
85+
86+
// namespace scoped install namespace resources
87+
ptr.To(newRole(
88+
installNamespace,
89+
roleName,
90+
slices.Concat(
91+
// namespace scoped resource creation and management rules
92+
generatePolicyRules(filter.Filter(objs, filter.And(isNamespacedResource, inNamespace(installNamespace)))),
93+
// controller rbac scope
94+
collectRBACResourcePolicyRules(filter.Filter(generatedObjs, filter.And(isRole, inNamespace(installNamespace)))),
95+
),
96+
)),
97+
ptr.To(newRoleBinding(
98+
installNamespace,
99+
roleBindingName,
100+
roleName,
101+
installNamespace,
102+
serviceAccountName,
103+
)),
104+
105+
// namespace scoped watch namespace resources
106+
ptr.To(newRole(
107+
watchNamespace,
108+
roleName,
109+
slices.Concat(
110+
// namespace scoped resource creation and management rules
111+
generatePolicyRules(filter.Filter(objs, filter.And(isNamespacedResource, inNamespace(watchNamespace)))),
112+
// controller rbac scope
113+
collectRBACResourcePolicyRules(filter.Filter(generatedObjs, filter.And(isRole, inNamespace(watchNamespace)))),
114+
),
115+
)),
116+
ptr.To(newRoleBinding(
117+
watchNamespace,
118+
roleBindingName,
119+
roleName,
120+
installNamespace,
121+
serviceAccountName,
122+
)),
123+
}
124+
125+
// remove any cluster/role(s) without any defined rules and pair cluster/role add manifests
126+
return slices.DeleteFunc(rbacManifests, isNoRules)
127+
}
128+
129+
func isNoRules(object client.Object) bool {
130+
switch obj := object.(type) {
131+
case *rbacv1.ClusterRole:
132+
return len(obj.Rules) == 0
133+
case *rbacv1.Role:
134+
return len(obj.Rules) == 0
135+
}
136+
return false
137+
}
138+
139+
func getGeneratedObjs(plainObjs []client.Object) []client.Object {
140+
return filter.Filter(plainObjs, func(obj client.Object) bool {
141+
// this is a hack that abuses an internal implementation detail
142+
// we should probably annotate the generated resources coming out of convert.Convert
143+
_, ok := obj.(*unstructured.Unstructured)
144+
return ok
145+
})
146+
}
147+
148+
var isNamespacedResource filter.Predicate[client.Object] = func(o client.Object) bool {
149+
return slices.Contains(namespaceScopedResources, o.GetObjectKind().GroupVersionKind().Kind)
150+
}
151+
152+
var isClusterScopedResource filter.Predicate[client.Object] = func(o client.Object) bool {
153+
return slices.Contains(clusterScopedResources, o.GetObjectKind().GroupVersionKind().Kind)
154+
}
155+
156+
var isClusterRole = isOfKind("ClusterRole")
157+
var isRole = isOfKind("Role")
158+
159+
func isOfKind(kind string) filter.Predicate[client.Object] {
160+
return func(o client.Object) bool {
161+
return o.GetObjectKind().GroupVersionKind().Kind == kind
162+
}
163+
}
164+
165+
func inNamespace(namespace string) filter.Predicate[client.Object] {
166+
return func(o client.Object) bool {
167+
return o.GetNamespace() == namespace
168+
}
169+
}
170+
171+
func generatePolicyRules(objs []client.Object) []rbacv1.PolicyRule {
172+
resourceNameMap := groupResourceNamesByGroupKind(objs)
173+
policyRules := make([]rbacv1.PolicyRule, 0, 2*len(resourceNameMap))
174+
for groupKind, resourceNames := range resourceNameMap {
175+
policyRules = append(policyRules, []rbacv1.PolicyRule{
176+
newPolicyRule(groupKind, unnamedResourceVerbs),
177+
newPolicyRule(groupKind, namedResourceVerbs, resourceNames...),
178+
}...)
179+
}
180+
return policyRules
181+
}
182+
183+
func collectRBACResourcePolicyRules(objs []client.Object) []rbacv1.PolicyRule {
184+
var policyRules []rbacv1.PolicyRule
185+
for _, obj := range objs {
186+
if cr, ok := obj.(*rbacv1.ClusterRole); ok {
187+
policyRules = append(policyRules, cr.Rules...)
188+
} else if r, ok := obj.(*rbacv1.Role); ok {
189+
policyRules = append(policyRules, r.Rules...)
190+
} else {
191+
panic(fmt.Sprintf("unexpected type %T", obj))
192+
}
193+
}
194+
return policyRules
195+
}
196+
197+
func newClusterExtensionFinalizerPolicyRule(clusterExtensionName string) rbacv1.PolicyRule {
198+
return rbacv1.PolicyRule{
199+
APIGroups: []string{"olm.operatorframework.io"},
200+
Resources: []string{"clusterextensions/finalizers"},
201+
Verbs: []string{"update"},
202+
ResourceNames: []string{clusterExtensionName},
203+
}
204+
}
205+
206+
func groupResourceNamesByGroupKind(objs []client.Object) map[schema.GroupKind][]string {
207+
resourceNames := map[schema.GroupKind][]string{}
208+
for _, obj := range objs {
209+
key := obj.GetObjectKind().GroupVersionKind().GroupKind()
210+
if _, ok := resourceNames[key]; !ok {
211+
resourceNames[key] = []string{}
212+
}
213+
resourceNames[key] = append(resourceNames[key], obj.GetName())
214+
}
215+
return resourceNames
216+
}
217+
218+
func newPolicyRule(groupKind schema.GroupKind, verbs []string, resourceNames ...string) rbacv1.PolicyRule {
219+
return rbacv1.PolicyRule{
220+
APIGroups: []string{groupKind.Group},
221+
Resources: []string{fmt.Sprintf("%ss", strings.ToLower(groupKind.Kind))},
222+
Verbs: verbs,
223+
ResourceNames: resourceNames,
224+
}
225+
}

0 commit comments

Comments
 (0)