Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 60 additions & 26 deletions openshift/tests-extension/test/olmv1-preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
. "github.com/onsi/gomega"

"github.com/openshift/api/features"
"github.com/openshift/origin/test/extended/util/image"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
Expand All @@ -19,6 +20,8 @@ import (

olmv1 "github.com/operator-framework/operator-controller/api/v1"

singleownbundle "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/bindata/singleown/bundle"
singleownindex "github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/bindata/singleown/index"
"github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/env"
"github.com/openshift/operator-framework-operator-controller/openshift/tests-extension/pkg/helpers"
)
Expand All @@ -35,16 +38,41 @@ const (
scenarioMissingClusterExtensionRevisionsFinalizerPerms preflightAuthTestScenario = 6
)

const preflightBundleVersion = "0.0.5"

var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMPreflightPermissionChecks][Skipped:Disconnected] OLMv1 operator preflight checks", func() {
var (
namespace string
k8sClient client.Client
namespace string
k8sClient client.Client
catalogName string
packageName string
)
BeforeEach(func() {
BeforeEach(func(ctx SpecContext) {
helpers.RequireOLMv1CapabilityOnOpenshift()
helpers.RequireImageRegistry(ctx)
k8sClient = env.Get().K8sClient
namespace = "preflight-test-ns-" + rand.String(4)

// Build in-cluster bundle and catalog (same approach as single-namespace tests)
crdSuffix := rand.String(4)
packageName = fmt.Sprintf("preflight-operator-%s", crdSuffix)
crdName := fmt.Sprintf("webhooktests-%s.webhook.operators.coreos.io", crdSuffix)
helpers.EnsureCleanupClusterExtension(context.Background(), packageName, crdName)

singleownImage := image.LocationFor("quay.io/olmtest/webhook-operator:v0.0.5")
replacements := map[string]string{
"{{ TEST-BUNDLE }}": "",
"{{ NAMESPACE }}": "",
"{{ TEST-CONTROLLER }}": singleownImage,
"{{ CRD-SUFFIX }}": crdSuffix,
"{{ PACKAGE-NAME }}": packageName,
}
_, _, catalogName, _ = helpers.NewCatalogAndClusterBundles(ctx, replacements,
singleownindex.AssetNames, singleownindex.Asset,
singleownbundle.AssetNames, singleownbundle.Asset,
)
By(fmt.Sprintf("preflight catalog %q and package %q built successfully", catalogName, packageName))

By(fmt.Sprintf("creating namespace %s", namespace))
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -58,39 +86,39 @@ var _ = Describe("[sig-olmv1][OCPFeatureGate:NewOLMPreflightPermissionChecks][Sk
})

It("should report error when {services} are not specified", func(ctx SpecContext) {
runNegativePreflightTest(ctx, scenarioMissingServicePerms, namespace)
runNegativePreflightTest(ctx, scenarioMissingServicePerms, namespace, packageName, catalogName)
})

It("should report error when {create} verb is not specified", func(ctx SpecContext) {
runNegativePreflightTest(ctx, scenarioMissingCreateVerb, namespace)
runNegativePreflightTest(ctx, scenarioMissingCreateVerb, namespace, packageName, catalogName)
})

It("should report error when {ClusterRoleBindings} are not specified", func(ctx SpecContext) {
runNegativePreflightTest(ctx, scenarioMissingClusterRoleBindingsPerms, namespace)
runNegativePreflightTest(ctx, scenarioMissingClusterRoleBindingsPerms, namespace, packageName, catalogName)
})

It("should report error when {ConfigMap:resourceNames} are not all specified", func(ctx SpecContext) {
runNegativePreflightTest(ctx, scenarioMissingNamedConfigMapPerms, namespace)
runNegativePreflightTest(ctx, scenarioMissingNamedConfigMapPerms, namespace, packageName, catalogName)
})

It("should report error when {clusterextension/finalizer} is not specified", func(ctx SpecContext) {
helpers.RequireFeatureGateDisabled(features.FeatureGateNewOLMBoxCutterRuntime)
runNegativePreflightTest(ctx, scenarioMissingClusterExtensionsFinalizerPerms, namespace)
runNegativePreflightTest(ctx, scenarioMissingClusterExtensionsFinalizerPerms, namespace, packageName, catalogName)
})

It("should report error when {clusterextensionrevisions/finalizer} is not specified", func(ctx SpecContext) {
helpers.RequireFeatureGateEnabled(features.FeatureGateNewOLMBoxCutterRuntime)
runNegativePreflightTest(ctx, scenarioMissingClusterExtensionRevisionsFinalizerPerms, namespace)
runNegativePreflightTest(ctx, scenarioMissingClusterExtensionRevisionsFinalizerPerms, namespace, packageName, catalogName)
})

It("should report error when {escalate, bind} is not specified", func(ctx SpecContext) {
runNegativePreflightTest(ctx, scenarioMissingEscalateAndBindPerms, namespace)
runNegativePreflightTest(ctx, scenarioMissingEscalateAndBindPerms, namespace, packageName, catalogName)
})
})

// runNegativePreflightTest creates a deficient ClusterRole and a ClusterExtension that
// relies on it, then waits for the expected preflight failure.
func runNegativePreflightTest(ctx context.Context, scenario preflightAuthTestScenario, namespace string) {
// relies on it (using the in-cluster built catalog), then waits for the expected preflight failure.
func runNegativePreflightTest(ctx context.Context, scenario preflightAuthTestScenario, namespace, packageName, catalogName string) {
k8sClient := env.Get().K8sClient
unique := rand.String(8)

Expand Down Expand Up @@ -121,24 +149,27 @@ func runNegativePreflightTest(ctx context.Context, scenario preflightAuthTestSce
_ = k8sClient.Delete(ctx, crb)
})

// Step 4: Create ClusterExtension referencing that SA
ce := helpers.NewClusterExtensionObject("openshift-pipelines-operator-rh", "1.15.0", ceName, saName, namespace)
// Step 4: Create ClusterExtension referencing that SA, using in-cluster catalog
ce := helpers.NewClusterExtensionObject(packageName, preflightBundleVersion, ceName, saName, namespace, helpers.WithCatalogNameSelector(catalogName))
Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension")
DeferCleanup(func(ctx SpecContext) {
_ = k8sClient.Delete(ctx, ce)
})

// Step 5: Wait for failure
// Pre-authorization failure is reported in the Progressing condition. The controller may set
// Progressing=True (Retrying) or Progressing=False (Blocked); we only assert that the
// message indicates pre-authorization failed (do not assert Status to avoid brittle failures).
By("waiting for ClusterExtension to report preflight failure")
Eventually(func(g Gomega) {
latest := &olmv1.ClusterExtension{}
err := k8sClient.Get(ctx, client.ObjectKey{Name: ce.Name}, latest)
g.Expect(err).NotTo(HaveOccurred())

c := meta.FindStatusCondition(latest.Status.Conditions, "Progressing")
g.Expect(c).NotTo(BeNil())
g.Expect(c.Status).To(Equal(metav1.ConditionTrue))
g.Expect(c.Message).To(ContainSubstring("pre-authorization failed"))
g.Expect(c).NotTo(BeNil(), "Progressing condition should be set")
// Assert only on message: controller may set Status True (Retrying) or False (Blocked).
g.Expect(c.Message).To(ContainSubstring("pre-authorization failed"), "Progressing message should report pre-authorization failure")
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
}

Expand Down Expand Up @@ -228,22 +259,25 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName
}
}
case scenarioMissingNamedConfigMapPerms:
// Restrict configmaps to named subset (resourceNames)
for i, r := range rules {
if r.APIGroups[0] == "" {
// Restrict ClusterRoles to a named subset so the SA cannot manage all roles required by the bundle.
// The webhook/singleown bundle has no ConfigMaps but has ClusterRoles (webhook-operator-*-role, webhook-operator-metrics-reader).
// By allowing only one, pre-authorization fails for the others.
for i := range rules {
if rules[i].APIGroups[0] == "rbac.authorization.k8s.io" {
filtered := []string{}
for _, res := range r.Resources {
if res != "configmaps" && res != "configmaps/finalizers" {
for _, res := range rules[i].Resources {
if res != "clusterroles" && res != "clusterroles/finalizers" {
filtered = append(filtered, res)
}
}
rules[i].Resources = filtered
rules = append(rules, rbacv1.PolicyRule{
APIGroups: []string{""},
Resources: []string{"configmaps"},
Verbs: r.Verbs,
ResourceNames: []string{"config-logging", "tekton-config-defaults", "tekton-config-observability"},
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"clusterroles", "clusterroles/finalizers"},
Verbs: []string{"delete", "deletecollection", "create", "patch", "get", "list", "update", "watch"},
ResourceNames: []string{"webhook-operator-metrics-reader"},
})
break
}
}
case scenarioMissingClusterExtensionsFinalizerPerms:
Expand Down