Skip to content

Commit 38843e5

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 38843e5

File tree

1 file changed

+223
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)