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