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
117 changes: 82 additions & 35 deletions openshift/tests-extension/test/olmv1-preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ 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"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
"sigs.k8s.io/controller-runtime/pkg/client"

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 +39,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)

// Use an in-cluster catalog and bundle so tests do not depend on external indexes.
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("catalog %q and package %q are ready", catalogName, packageName))

By(fmt.Sprintf("creating namespace %s", namespace))
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -58,39 +87,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) {
// runNegativePreflightTest creates a ClusterRole that is missing one required permission,
// a ClusterExtension that uses it (via the in-cluster catalog), then waits for the 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,28 +150,35 @@ 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 for that SA using the in-cluster catalog.
// Set watchNamespace in config so the controller can run preflight; otherwise it fails on config validation first.
ce := helpers.NewClusterExtensionObject(packageName, preflightBundleVersion, ceName, saName, namespace, helpers.WithCatalogNameSelector(catalogName))
ce.Spec.Config = &olmv1.ClusterExtensionConfig{
ConfigType: "Inline",
Inline: &apiextensionsv1.JSON{
Raw: []byte(fmt.Sprintf(`{"watchNamespace": "%s"}`, namespace)),
},
}
Expect(k8sClient.Create(ctx, ce)).To(Succeed(), "failed to create ClusterExtension")
DeferCleanup(func(ctx SpecContext) {
_ = k8sClient.Delete(ctx, ce)
})

// Step 5: Wait for failure
// Step 5: Wait for the controller to report preflight failure.
// The error is in the Progressing condition. We only check the message, not True/False, so the test stays stable.
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"))
c := meta.FindStatusCondition(latest.Status.Conditions, olmv1.TypeProgressing)
g.Expect(c).NotTo(BeNil(), "Progressing condition should be set")
g.Expect(c.Message).To(ContainSubstring("pre-authorization failed"), "message should report pre-authorization failure")
}).WithTimeout(helpers.DefaultTimeout).WithPolling(helpers.DefaultPolling).Should(Succeed())
}

// createDeficientClusterRole returns a modified ClusterRole according to the test scenario.
// createDeficientClusterRole returns a ClusterRole that is missing one permission needed by the test scenario.
func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName string) *rbacv1.ClusterRole {
var baseRules []rbacv1.PolicyRule
if helpers.IsFeatureGateEnabled(features.FeatureGateNewOLMBoxCutterRuntime) {
Expand Down Expand Up @@ -189,7 +225,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName

switch scenario {
case scenarioMissingServicePerms:
// Remove 'services' and 'services/finalizers'
// Remove services and services/finalizers so preflight fails.
for i, r := range rules {
if r.APIGroups[0] == "" {
filtered := []string{}
Expand All @@ -202,7 +238,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName
}
}
case scenarioMissingCreateVerb:
// Remove 'create' verb
// Remove the create verb so preflight fails.
for i, r := range rules {
if r.APIGroups[0] == "" {
filtered := []string{}
Expand All @@ -215,7 +251,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName
}
}
case scenarioMissingClusterRoleBindingsPerms:
// Remove 'clusterrolebindings'
// Remove clusterrolebindings so preflight fails.
for i, r := range rules {
if r.APIGroups[0] == "rbac.authorization.k8s.io" {
filtered := []string{}
Expand All @@ -228,26 +264,37 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName
}
}
case scenarioMissingNamedConfigMapPerms:
// Restrict configmaps to named subset (resourceNames)
for i, r := range rules {
if r.APIGroups[0] == "" {
// Allow only one ClusterRole by name so the SA cannot manage the rest; preflight then fails.
// The singleown bundle uses ClusterRoles like webhook-operator-metrics-reader.
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:
// Remove olm.operatorframework.io permission for finalizers
// Remove permission for clusterextensions/finalizers so preflight fails.
filtered := []rbacv1.PolicyRule{}
for _, r := range rules {
if len(r.APIGroups) != 1 || r.APIGroups[0] != "olm.operatorframework.io" {
filtered = append(filtered, r)
}
}
rules = filtered
case scenarioMissingClusterExtensionRevisionsFinalizerPerms:
// Remove permission for clusterextensionrevisions/finalizers so preflight fails.
filtered := []rbacv1.PolicyRule{}
for _, r := range rules {
if len(r.APIGroups) != 1 || r.APIGroups[0] != "olm.operatorframework.io" {
Expand All @@ -256,7 +303,7 @@ func createDeficientClusterRole(scenario preflightAuthTestScenario, name, ceName
}
rules = filtered
case scenarioMissingEscalateAndBindPerms:
// Remove 'bind' and 'escalate' verbs
// Remove bind and escalate verbs so preflight fails.
for i, r := range rules {
if r.APIGroups[0] == "rbac.authorization.k8s.io" {
filtered := []string{}
Expand Down