From 6b23a34eee931d7d89c1cd562cf6de4a4eb122ec Mon Sep 17 00:00:00 2001 From: "Per G. da Silva" Date: Thu, 5 Mar 2026 14:27:13 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Make=20RBACPreAuthorizer=20collecti?= =?UTF-8?q?on=20verbs=20configurable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make both clusterCollectionVerbs and namespacedCollectionVerbs on RBACPreAuthorizer configurable via functional options, decoupling them from the hardcoded verbs that were tightly coupled to the contentmanager's requirements. Both the helm and boxcutter appliers explicitly configure namespacedCollectionVerbs with "create". The helm applier additionally configures clusterCollectionVerbs with "list" and "watch" (needed by contentmanager), while the boxcutter applier uses no cluster collection verbs. Also updates e2e tests to select the appropriate RBAC template based on the BoxcutterRuntime feature gate, using a narrower template without list/watch when BoxcutterRuntime is enabled. Closes: https://github.com/operator-framework/operator-controller/issues/1911 Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Per G. da Silva --- cmd/operator-controller/main.go | 7 +- .../operator-controller/authorization/rbac.go | 48 ++-- .../authorization/rbac_test.go | 241 +++++++++++++++++- .../registryv1/generators/generators.go | 8 + test/e2e/steps/steps.go | 11 +- .../testdata/boxcutter-rbac-template.yaml | 71 ++++++ test/e2e/steps/testdata/rbac-template.yaml | 4 - 7 files changed, 363 insertions(+), 27 deletions(-) create mode 100644 test/e2e/steps/testdata/boxcutter-rbac-template.yaml diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index f026c0222..248e07baa 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -724,7 +724,12 @@ func (c *helmReconcilerConfigurator) Configure(ceReconciler *controllers.Cluster // determine if PreAuthorizer should be enabled based on feature gate var preAuth authorization.PreAuthorizer if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) { - preAuth = authorization.NewRBACPreAuthorizer(c.mgr.GetClient()) + preAuth = authorization.NewRBACPreAuthorizer( + c.mgr.GetClient(), + // Additional verbs / bundle manifest that are expected by the content manager to watch those resources + authorization.WithClusterCollectionVerbs("list", "watch"), + authorization.WithNamespacedCollectionVerbs("create"), + ) } cm := contentmanager.NewManager(clientRestConfigMapper, c.mgr.GetConfig(), c.mgr.GetRESTMapper()) diff --git a/internal/operator-controller/authorization/rbac.go b/internal/operator-controller/authorization/rbac.go index d85e532f8..e2b87cb20 100644 --- a/internal/operator-controller/authorization/rbac.go +++ b/internal/operator-controller/authorization/rbac.go @@ -29,7 +29,7 @@ import ( rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1" rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" "k8s.io/kubernetes/pkg/registry/rbac/validation" - rbac "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" + "k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -59,26 +59,42 @@ type ScopedPolicyRules struct { var objectVerbs = []string{"get", "patch", "update", "delete"} -// Here we are splitting collection verbs based on required scope -// NB: this split is tightly coupled to the requirements of the contentmanager, specifically -// its need for cluster-scoped list/watch permissions. -// TODO: We are accepting this coupling for now, but plan to decouple -// TODO: link for above https://github.com/operator-framework/operator-controller/issues/1911 -var namespacedCollectionVerbs = []string{"create"} -var clusterCollectionVerbs = []string{"list", "watch"} +type RBACPreAuthorizerOption func(*rbacPreAuthorizer) + +// WithClusterCollectionVerbs configures cluster-scoped collection verbs (e.g. list, watch) +// that are checked in addition to object and namespaced collection verbs. +func WithClusterCollectionVerbs(verbs ...string) RBACPreAuthorizerOption { + return func(a *rbacPreAuthorizer) { + a.clusterCollectionVerbs = slices.Clone(verbs) + } +} + +// WithNamespacedCollectionVerbs configures namespaced collection verbs (e.g. create) +// that are checked for each unique namespace across all objects in a GVR. +func WithNamespacedCollectionVerbs(verbs ...string) RBACPreAuthorizerOption { + return func(a *rbacPreAuthorizer) { + a.namespacedCollectionVerbs = slices.Clone(verbs) + } +} type rbacPreAuthorizer struct { - authorizer authorizer.Authorizer - ruleResolver validation.AuthorizationRuleResolver - restMapper meta.RESTMapper + authorizer authorizer.Authorizer + ruleResolver validation.AuthorizationRuleResolver + restMapper meta.RESTMapper + clusterCollectionVerbs []string + namespacedCollectionVerbs []string } -func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer { - return &rbacPreAuthorizer{ +func NewRBACPreAuthorizer(cl client.Client, opts ...RBACPreAuthorizerOption) PreAuthorizer { + a := &rbacPreAuthorizer{ authorizer: newRBACAuthorizer(cl), ruleResolver: newRBACRulesResolver(cl), restMapper: cl.RESTMapper(), } + for _, opt := range opts { + opt(a) + } + return a } func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, user user.Info, manifestReader io.Reader, additionalRequiredPerms ...UserAuthorizerAttributesFactory) ([]ScopedPolicyRules, error) { @@ -88,7 +104,7 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, user user.Info, ma } // derive manifest related attributes records - attributesRecords := dm.asAuthorizationAttributesRecordsForUser(user) + attributesRecords := dm.asAuthorizationAttributesRecordsForUser(user, a.clusterCollectionVerbs, a.namespacedCollectionVerbs) // append additional required perms for _, fn := range additionalRequiredPerms { @@ -324,7 +340,7 @@ func (dm *decodedManifest) rbacObjects() []client.Object { return objects } -func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info) []authorizer.AttributesRecord { +func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info, clusterCollectionVerbs, namespacedCollectionVerbs []string) []authorizer.AttributesRecord { // Calculate initial capacity as an upper-bound estimate: // - For each key: len(objectVerbs) records (4) // - For unique namespaces: len(namespacedCollectionVerbs) records (1 per unique namespace across all keys in a GVR) @@ -369,7 +385,7 @@ func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManag }) } } - // generate records for cluster-scoped collection verbs (list, watch) required by contentmanager + // generate records for cluster-scoped collection verbs (e.g. list, watch) for _, v := range clusterCollectionVerbs { attributeRecords = append(attributeRecords, authorizer.AttributesRecord{ User: manifestManager, diff --git a/internal/operator-controller/authorization/rbac_test.go b/internal/operator-controller/authorization/rbac_test.go index 9d80cc52b..16d5449e4 100644 --- a/internal/operator-controller/authorization/rbac_test.go +++ b/internal/operator-controller/authorization/rbac_test.go @@ -396,7 +396,7 @@ func setupFakeClient(role client.Object) client.Client { func TestPreAuthorize_Success(t *testing.T) { t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { fakeClient := setupFakeClient(privilegedClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch"), WithNamespacedCollectionVerbs("create")) missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) @@ -406,7 +406,7 @@ func TestPreAuthorize_Success(t *testing.T) { func TestPreAuthorize_MissingRBAC(t *testing.T) { t.Run("preauthorize fails and finds missing rbac rules", func(t *testing.T) { fakeClient := setupFakeClient(limitedClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch"), WithNamespacedCollectionVerbs("create")) missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, expectedSingleNamespaceMissingRules, missingRules) @@ -416,7 +416,7 @@ func TestPreAuthorize_MissingRBAC(t *testing.T) { func TestPreAuthorizeMultiNamespace_MissingRBAC(t *testing.T) { t.Run("preauthorize fails and finds missing rbac rules in multiple namespaces", func(t *testing.T) { fakeClient := setupFakeClient(limitedClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch"), WithNamespacedCollectionVerbs("create")) missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifestMultiNamespace)) require.NoError(t, err) require.Equal(t, expectedMultiNamespaceMissingRules, missingRules) @@ -426,7 +426,7 @@ func TestPreAuthorizeMultiNamespace_MissingRBAC(t *testing.T) { func TestPreAuthorize_CheckEscalation(t *testing.T) { t.Run("preauthorize succeeds with no missing rbac rules", func(t *testing.T) { fakeClient := setupFakeClient(escalatingClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch"), WithNamespacedCollectionVerbs("create")) missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) require.NoError(t, err) require.Equal(t, []ScopedPolicyRules{}, missingRules) @@ -436,7 +436,7 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) { func TestPreAuthorize_AdditionalRequiredPerms_MissingRBAC(t *testing.T) { t.Run("preauthorize fails and finds missing rbac rules coming from the additional required permissions", func(t *testing.T) { fakeClient := setupFakeClient(escalatingClusterRole) - preAuth := NewRBACPreAuthorizer(fakeClient) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch"), WithNamespacedCollectionVerbs("create")) missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest), func(user user.Info) []authorizer.AttributesRecord { return []authorizer.AttributesRecord{ { @@ -465,6 +465,237 @@ func TestPreAuthorize_AdditionalRequiredPerms_MissingRBAC(t *testing.T) { }) } +func TestPreAuthorize_WithClusterCollectionVerbs(t *testing.T) { + // expectedNamespacedMissingRules are the missing rules expected in the "test-namespace" + // namespace regardless of cluster collection verb configuration. These come from object + // verbs (get, patch, update, delete), namespaced collection verbs (create), and the + // escalation check for the role/rolebinding in the manifest. + expectedNamespacedMissingRules := ScopedPolicyRules{ + Namespace: "test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{""}, + Resources: []string{"services"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string{"test-service"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string{"test-extension-binding"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string{"test-extension-role"}}, + { + Verbs: []string{"watch"}, + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts"}, + }, + }, + } + + t.Run("no cluster collection verbs option omits cluster-scoped collection rules", func(t *testing.T) { + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient, WithNamespacedCollectionVerbs("create")) + missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) + require.NoError(t, err) + // With no cluster collection verbs, there should be no cluster-scoped (namespace="") missing rules + require.Equal(t, []ScopedPolicyRules{expectedNamespacedMissingRules}, missingRules) + }) + + t.Run("cluster verbs option only checks those verbs at cluster scope", func(t *testing.T) { + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("get", "patch", "update"), WithNamespacedCollectionVerbs("create")) + missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, []ScopedPolicyRules{ + { + Namespace: "", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil), + }, + }, + }, + expectedNamespacedMissingRules, + }, missingRules) + }) + + t.Run("privileged user with no cluster collection verbs succeeds", func(t *testing.T) { + fakeClient := setupFakeClient(privilegedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient, WithNamespacedCollectionVerbs("create")) + missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, []ScopedPolicyRules{}, missingRules) + }) +} + +func TestPreAuthorize_WithNamespacedCollectionVerbs(t *testing.T) { + // expectedClusterMissingRules are the missing rules expected at cluster scope + // when cluster collection verbs are configured as "list", "watch". + expectedClusterMissingRules := ScopedPolicyRules{ + Namespace: "", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil)}, + { + Verbs: []string{"list", "watch"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string(nil), + NonResourceURLs: []string(nil), + }, + }, + } + + t.Run("no namespaced collection verbs option omits namespaced collection rules", func(t *testing.T) { + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch")) + missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) + require.NoError(t, err) + // Without namespaced collection verbs, no "create" rules from collection verbs should appear, + // but object verbs (get, patch, update, delete) and escalation checks still apply + require.Equal(t, []ScopedPolicyRules{ + expectedClusterMissingRules, + { + Namespace: "test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string{"test-service"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string{"test-extension-binding"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string{"test-extension-role"}}, + { + Verbs: []string{"watch"}, + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts"}, + }, + }, + }, + }, missingRules) + }) + + t.Run("namespaced collection verbs option checks those verbs per namespace", func(t *testing.T) { + fakeClient := setupFakeClient(limitedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient, WithClusterCollectionVerbs("list", "watch"), WithNamespacedCollectionVerbs("create", "deletecollection")) + missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) + require.NoError(t, err) + // Should have cluster-scoped missing rules plus namespaced rules with both create and deletecollection. + // Note: "certificates" with apiGroup "*" comes from the escalation check on the Role, not + // from namespaced collection verbs, so it only has "create". + require.Equal(t, []ScopedPolicyRules{ + expectedClusterMissingRules, + { + Namespace: "test-namespace", + MissingRules: []rbacv1.PolicyRule{ + { + Verbs: []string{"create", "deletecollection"}, + APIGroups: []string{""}, + Resources: []string{"services"}}, + { + Verbs: []string{"create", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}}, + { + Verbs: []string{"create", "deletecollection"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}}, + { + Verbs: []string{"create"}, + APIGroups: []string{"*"}, + Resources: []string{"certificates"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{""}, + Resources: []string{"services"}, + ResourceNames: []string{"test-service"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"rolebindings"}, + ResourceNames: []string{"test-extension-binding"}}, + { + Verbs: []string{"delete", "get", "patch", "update"}, + APIGroups: []string{"rbac.authorization.k8s.io"}, + Resources: []string{"roles"}, + ResourceNames: []string{"test-extension-role"}}, + { + Verbs: []string{"watch"}, + APIGroups: []string{"*"}, + Resources: []string{"serviceaccounts"}, + }, + }, + }, + }, missingRules) + }) + + t.Run("privileged user with custom namespaced collection verbs succeeds", func(t *testing.T) { + fakeClient := setupFakeClient(privilegedClusterRole) + preAuth := NewRBACPreAuthorizer(fakeClient, WithNamespacedCollectionVerbs("create", "deletecollection")) + missingRules, err := preAuth.PreAuthorize(context.TODO(), testUser, strings.NewReader(testManifest)) + require.NoError(t, err) + require.Equal(t, []ScopedPolicyRules{}, missingRules) + }) +} + // TestParseEscalationErrorForMissingRules Are tests with respect to https://github.com/kubernetes/api/blob/e8d4d542f6a9a16a694bfc8e3b8cd1557eecfc9d/rbac/v1/types.go#L49-L74 // Goal is: prove the regex works as planned AND that if the error messages ever change we'll learn about it with these tests func TestParseEscalationErrorForMissingRules_ParsingLogic(t *testing.T) { diff --git a/internal/operator-controller/rukpak/render/registryv1/generators/generators.go b/internal/operator-controller/rukpak/render/registryv1/generators/generators.go index 8f45bb762..9815c4dba 100644 --- a/internal/operator-controller/rukpak/render/registryv1/generators/generators.go +++ b/internal/operator-controller/rukpak/render/registryv1/generators/generators.go @@ -148,6 +148,14 @@ func BundleCSVPermissionsGenerator(rv1 *bundle.RegistryV1, opts render.Options) // (opts.TargetNamespaces = [”]), the CSV's permission spec will be promoted to ClusterRole and ClusterRoleBinding // resources. To keep parity with OLMv0, these will also include an extra rule to get, list, watch namespaces // (see https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/operators/olm/operatorgroup.go#L539) +// The reasoning for this added rule is: +// - An operator author designing for both SingleNamespace and AllNamespaces install modes should +// only declare the minimum permissions needed — i.e., no cluster-scoped permissions in its CSV. +// - When OLM places that operator into a global OperatorGroup, it lifts the Roles to ClusterRoles. +// But some operators may need to discover namespaces to function globally, which they didn't need +// (and shouldn't have requested) in single-namespace mode. +// - So OLM automatically appends get/list/watch on namespaces during the lift, bridging the gap +// without requiring the operator author to over-request permissions upfront. func BundleCSVClusterPermissionsGenerator(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) { if rv1 == nil { return nil, fmt.Errorf("bundle cannot be nil") diff --git a/test/e2e/steps/steps.go b/test/e2e/steps/steps.go index abea3fcb2..4697adbf9 100644 --- a/test/e2e/steps/steps.go +++ b/test/e2e/steps/steps.go @@ -44,6 +44,9 @@ const ( olmDeploymentName = "operator-controller-controller-manager" timeout = 5 * time.Minute tick = 1 * time.Second + + helmRBACTemplate = "rbac-template.yaml" + boxcutterRBACTemplate = "boxcutter-rbac-template.yaml" ) var ( @@ -815,8 +818,14 @@ func ServiceAccountIsAvailableInNamespace(ctx context.Context, serviceAccount st } // ServiceAccountWithNeededPermissionsIsAvailableInNamespace creates a ServiceAccount and applies standard RBAC permissions. +// The RBAC template is selected based on the BoxcutterRuntime feature gate: the boxcutter applier does not require +// cluster-scoped list/watch permissions, so a narrower template is used when BoxcutterRuntime is enabled. func ServiceAccountWithNeededPermissionsIsAvailableInNamespace(ctx context.Context, serviceAccount string) error { - return applyPermissionsToServiceAccount(ctx, serviceAccount, "rbac-template.yaml") + rbacTemplate := helmRBACTemplate + if enabled, found := featureGates[features.BoxcutterRuntime]; found && enabled { + rbacTemplate = boxcutterRBACTemplate + } + return applyPermissionsToServiceAccount(ctx, serviceAccount, rbacTemplate) } // ServiceAccountWithClusterAdminPermissionsIsAvailableInNamespace creates a ServiceAccount and applies cluster-admin RBAC. diff --git a/test/e2e/steps/testdata/boxcutter-rbac-template.yaml b/test/e2e/steps/testdata/boxcutter-rbac-template.yaml new file mode 100644 index 000000000..7c35f997e --- /dev/null +++ b/test/e2e/steps/testdata/boxcutter-rbac-template.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ${TEST_NAMESPACE} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ${TEST_NAMESPACE}-${SERVICEACCOUNT_NAME}-olm-admin-clusterrole +rules: + # Allow management of ClusterExtensionRevision finalizers (e.g. by the Boxcutter applier) + - apiGroups: [olm.operatorframework.io] + resources: [clusterextensionrevisions/finalizers] + verbs: [update, patch] + # OLMv0 compatibility requirement for AllNamespaces install + # https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/operators/olm/operatorgroup.go#L530 + - apiGroups: [ "" ] + resources: + - namespaces + verbs: [ get, list, watch ] + # Bundle resource management RBAC derived from bundle resource and permissions described in the ClusterServiceVersion + - apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [update, create, get, delete, patch] + - apiGroups: [""] + resources: + - configmaps + - serviceaccounts + verbs: [update, create, list, watch, get, delete, patch] + - apiGroups: [ "" ] + resources: + - events + verbs: [ create, patch ] + - apiGroups: ["apps"] + resources: + - deployments + verbs: [ update, create, get, delete, patch ] + - apiGroups: ["networking.k8s.io"] + resources: + - networkpolicies + verbs: [update, create, list, get, delete, patch] + - apiGroups: ["rbac.authorization.k8s.io"] + resources: + - clusterroles + - roles + - clusterrolebindings + - rolebindings + verbs: [ update, create, get, delete, patch ] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: [update, create, list, watch, get, delete, patch] + - apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: [create] + - apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: [create] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ${TEST_NAMESPACE}-${SERVICEACCOUNT_NAME}-install-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ${TEST_NAMESPACE}-${SERVICEACCOUNT_NAME}-olm-admin-clusterrole +subjects: + - kind: ServiceAccount + name: ${SERVICEACCOUNT_NAME} + namespace: ${TEST_NAMESPACE} diff --git a/test/e2e/steps/testdata/rbac-template.yaml b/test/e2e/steps/testdata/rbac-template.yaml index 90138b9c6..6eb9be42a 100644 --- a/test/e2e/steps/testdata/rbac-template.yaml +++ b/test/e2e/steps/testdata/rbac-template.yaml @@ -13,10 +13,6 @@ rules: resources: [clusterextensions, clusterextensions/finalizers] resourceNames: ["${CLUSTEREXTENSION_NAME}"] verbs: [update] - # Allow ClusterExtensionRevisions to set blockOwnerDeletion ownerReferences - - apiGroups: [olm.operatorframework.io] - resources: [clusterextensionrevisions, clusterextensionrevisions/finalizers] - verbs: [update, create, list, watch, get, delete, patch] - apiGroups: [apiextensions.k8s.io] resources: [customresourcedefinitions] verbs: [update, create, list, watch, get, delete, patch]