From 99fe9fcc43286fede7fcd2db8c6d2522a181f54d Mon Sep 17 00:00:00 2001 From: Kui Wang Date: Tue, 24 Mar 2026 13:23:33 +0800 Subject: [PATCH] add create verb to boxcutter preflight --- cmd/operator-controller/main.go | 5 +- test/e2e/features/install.feature | 35 +++++++++ test/e2e/steps/steps.go | 24 +++++++ ...-sa-boxcutter-no-create-rbac-template.yaml | 72 +++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 test/e2e/steps/testdata/olm-sa-boxcutter-no-create-rbac-template.yaml diff --git a/cmd/operator-controller/main.go b/cmd/operator-controller/main.go index f905d105d..5f83e1fb1 100644 --- a/cmd/operator-controller/main.go +++ b/cmd/operator-controller/main.go @@ -617,7 +617,10 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl // 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(), + authorization.WithNamespacedCollectionVerbs("create"), + ) } // TODO: better scheme handling - which types do we want to support? diff --git a/test/e2e/features/install.feature b/test/e2e/features/install.feature index ce3fb3430..0ee3b70ef 100644 --- a/test/e2e/features/install.feature +++ b/test/e2e/features/install.feature @@ -535,3 +535,38 @@ Feature: Install ClusterExtension nodeSelector: kubernetes.io/os: linux """ + + @BoxcutterRuntime + @PreflightPermissions + Scenario: Boxcutter preflight check detects missing CREATE permissions + Given ServiceAccount "olm-sa" without create permissions is available in ${TEST_NAMESPACE} + And ClusterExtension is applied + """ + apiVersion: olm.operatorframework.io/v1 + kind: ClusterExtension + metadata: + name: ${NAME} + spec: + namespace: ${TEST_NAMESPACE} + serviceAccount: + name: olm-sa + source: + sourceType: Catalog + catalog: + packageName: test + selector: + matchLabels: + "olm.operatorframework.io/metadata.name": test-catalog + """ + And ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + """ + pre-authorization failed: service account requires the following permissions to manage cluster extension + """ + And ClusterExtension reports Progressing as True with Reason Retrying and Message includes: + """ + Verbs:[create] + """ + When ServiceAccount "olm-sa" with needed permissions is available in ${TEST_NAMESPACE} + Then ClusterExtension is available + And ClusterExtension reports Progressing as True with Reason Succeeded + And ClusterExtension reports Installed as True diff --git a/test/e2e/steps/steps.go b/test/e2e/steps/steps.go index 57cfc864e..0c2d3aa50 100644 --- a/test/e2e/steps/steps.go +++ b/test/e2e/steps/steps.go @@ -34,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" k8sresource "k8s.io/cli-runtime/pkg/resource" + "k8s.io/component-base/featuregate" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -120,6 +121,7 @@ func RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^(?i)ServiceAccount "([^"]*)" with permissions to install extensions is available in "([^"]*)" namespace$`, ServiceAccountWithNeededPermissionsIsAvailableInGivenNamespace) sc.Step(`^(?i)ServiceAccount "([^"]*)" with needed permissions is available in test namespace$`, ServiceAccountWithNeededPermissionsIsAvailableInTestNamespace) sc.Step(`^(?i)ServiceAccount "([^"]*)" with needed permissions is available in \${TEST_NAMESPACE}$`, ServiceAccountWithNeededPermissionsIsAvailableInTestNamespace) + sc.Step(`^(?i)ServiceAccount "([^"]*)" without create permissions is available in \${TEST_NAMESPACE}$`, ServiceAccountWithoutCreatePermissionsIsAvailableInTestNamespace) sc.Step(`^(?i)ServiceAccount "([^"]*)" is available in \${TEST_NAMESPACE}$`, ServiceAccountIsAvailableInNamespace) sc.Step(`^(?i)ServiceAccount "([^"]*)" in test namespace is cluster admin$`, ServiceAccountWithClusterAdminPermissionsIsAvailableInNamespace) sc.Step(`^(?i)ServiceAccount "([^"]+)" in test namespace has permissions to fetch "([^"]+)" metrics$`, ServiceAccountWithFetchMetricsPermissions) @@ -418,6 +420,11 @@ type msgMatchFn func(string) bool func alwaysMatch(_ string) bool { return true } +func isFeatureGateEnabled(feature featuregate.Feature) bool { + enabled, found := featureGates[feature] + return enabled && found +} + func messageComparison(ctx context.Context, msg *godog.DocString) msgMatchFn { msgCmp := alwaysMatch if msg != nil { @@ -877,6 +884,23 @@ func ServiceAccountWithNeededPermissionsIsAvailableInTestNamespace(ctx context.C return applyPermissionsToServiceAccount(ctx, serviceAccount, rbacTemplate) } +// ServiceAccountWithoutCreatePermissionsIsAvailableInTestNamespace creates a ServiceAccount with permissions that +// intentionally exclude the "create" verb to test preflight permission validation for Boxcutter applier. +// This is used to verify that the preflight check properly detects missing CREATE permissions. +// Note: This function requires both @BoxcutterRuntime and @PreflightPermissions tags. +func ServiceAccountWithoutCreatePermissionsIsAvailableInTestNamespace(ctx context.Context, serviceAccount string) error { + // This test is only valid with Boxcutter runtime enabled + if !isFeatureGateEnabled(features.BoxcutterRuntime) { + return fmt.Errorf("this step requires BoxcutterRuntime feature gate to be enabled") + } + // It also requires preflight permissions checks to be enabled + if !isFeatureGateEnabled(features.PreflightPermissions) { + return fmt.Errorf("this step requires PreflightPermissions feature gate to be enabled") + } + rbacTemplate := fmt.Sprintf("%s-boxcutter-no-create-rbac-template.yaml", serviceAccount) + return applyPermissionsToServiceAccount(ctx, serviceAccount, rbacTemplate) +} + // ServiceAccountWithNeededPermissionsIsAvailableInGivenNamespace creates a ServiceAccount and enables creation of any cluster extension on behalf of this account. func ServiceAccountWithNeededPermissionsIsAvailableInGivenNamespace(ctx context.Context, serviceAccount string, ns string) error { sc := scenarioCtx(ctx) diff --git a/test/e2e/steps/testdata/olm-sa-boxcutter-no-create-rbac-template.yaml b/test/e2e/steps/testdata/olm-sa-boxcutter-no-create-rbac-template.yaml new file mode 100644 index 000000000..8581d69cd --- /dev/null +++ b/test/e2e/steps/testdata/olm-sa-boxcutter-no-create-rbac-template.yaml @@ -0,0 +1,72 @@ +--- +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 + # NOTE: Intentionally MISSING "create" verb to test preflight check + - apiGroups: [apiextensions.k8s.io] + resources: [customresourcedefinitions] + verbs: [update, get, delete, patch] + - apiGroups: [""] + resources: + - configmaps + - serviceaccounts + verbs: [update, list, watch, get, delete, patch] + - apiGroups: [ "" ] + resources: + - events + verbs: [ patch ] + - apiGroups: ["apps"] + resources: + - deployments + verbs: [ update, get, delete, patch ] + - apiGroups: ["networking.k8s.io"] + resources: + - networkpolicies + verbs: [update, list, get, delete, patch] + - apiGroups: ["rbac.authorization.k8s.io"] + resources: + - clusterroles + - roles + - clusterrolebindings + - rolebindings + verbs: [ update, get, delete, patch ] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: [update, 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}