From a108bd4301bd81bab014cf5eda010cc63a360963 Mon Sep 17 00:00:00 2001 From: Tyler Pate Date: Thu, 25 Jun 2026 15:49:47 -0700 Subject: [PATCH 1/2] fix: pin CatalogSource pods to restricted-v2 SCC on OpenShift On HCP clusters, SCC admission evaluates against the creating user (the hosted control plane) rather than the pod's ServiceAccount. This broader access set allows customer-installed SCCs with MustRunAs to score as more restrictive than restricted-v2's MustRunAsRange, winning admission and mutating the pod to an unexpected UID. The UID mismatch causes permission denied errors on pre-baked cache directories (e.g. /tmp/cache/pogreb.v1/db/lock), crashing catalog pods. Add the openshift.io/required-scc annotation (GA since OCP 4.14, openshift/enhancements#1391) to pin SCC selection to restricted-v2 when securityContextConfig is Restricted. The annotation is a no-op on non-OpenShift clusters and respects user-provided overrides. Fixes: https://github.com/openshift/operator-framework-olm/issues/1331 --- .../registry/reconciler/reconciler.go | 6 ++ .../registry/reconciler/reconciler_test.go | 58 ++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go b/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go index a4370df5f4..d30104a9c0 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go @@ -356,6 +356,12 @@ func Pod(source *operatorsv1alpha1.CatalogSource, name, opmImg, utilImage, img s if securityContextConfig == operatorsv1alpha1.Restricted { // Apply 'restricted' security settings addSecurityContext(pod, runAsUser) + + // Pin SCC selection to prevent custom SCCs from preempting catalog pods on + // HCP, where the creating user has broader SCC access than the local SA. + if _, alreadySet := podAnnotations["openshift.io/required-scc"]; !alreadySet { + podAnnotations["openshift.io/required-scc"] = "restricted-v2" + } } // Set priorityclass if its annotation exists diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler_test.go index cfcf065dbe..738bb04487 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler_test.go @@ -523,7 +523,7 @@ func TestPodExtractContent(t *testing.T) { GenerateName: "test-", Namespace: "testns", Labels: map[string]string{"olm.pod-spec-hash": "8qB6OcFt60v8HdhXnPkB1cjF39t7RkFx9K0JxW", "olm.managed": "true"}, - Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, + Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true", "openshift.io/required-scc": "restricted-v2"}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ @@ -606,7 +606,7 @@ func TestPodExtractContent(t *testing.T) { GenerateName: "test-", Namespace: "testns", Labels: map[string]string{"olm.pod-spec-hash": "3xuLPXGJ2pzekw21PFU68XUKOYc7PTuW45M521", "olm.managed": "true"}, - Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, + Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true", "openshift.io/required-scc": "restricted-v2"}, }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -738,7 +738,7 @@ func TestPodExtractContent(t *testing.T) { GenerateName: "test-", Namespace: "testns", Labels: map[string]string{"olm.pod-spec-hash": "7noQSgGmkI4BD1MPKe0pEFFfOE3jJtN2DUyZuD", "olm.managed": "true"}, - Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, + Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true", "openshift.io/required-scc": "restricted-v2"}, }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ @@ -1159,6 +1159,58 @@ func TestPodContainerSecurityContext(t *testing.T) { } } +func TestPodRequiredSCCAnnotation(t *testing.T) { + testcases := []struct { + title string + securityConfig v1alpha1.SecurityConfig + inputAnnotations map[string]string + expectAnnotation bool + expectedSCCValue string + }{ + { + title: "Restricted config adds required-scc annotation", + securityConfig: v1alpha1.Restricted, + inputAnnotations: map[string]string{}, + expectAnnotation: true, + expectedSCCValue: "restricted-v2", + }, + { + title: "Legacy config does not add required-scc annotation", + securityConfig: v1alpha1.Legacy, + inputAnnotations: map[string]string{}, + expectAnnotation: false, + }, + { + title: "User-provided required-scc annotation is preserved", + securityConfig: v1alpha1.Restricted, + inputAnnotations: map[string]string{"openshift.io/required-scc": "nonroot-v2"}, + expectAnnotation: true, + expectedSCCValue: "nonroot-v2", + }, + } + + for _, tc := range testcases { + t.Run(tc.title, func(t *testing.T) { + catsrc := &v1alpha1.CatalogSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: testNamespace, + }, + } + pod, err := Pod(catsrc, "catalog", "opmImage", "utilImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, tc.inputAnnotations, int32(0), int32(0), workloadUserID, tc.securityConfig) + require.NoError(t, err) + + val, exists := pod.Annotations["openshift.io/required-scc"] + if tc.expectAnnotation { + require.True(t, exists, "expected openshift.io/required-scc annotation to be present") + require.Equal(t, tc.expectedSCCValue, val) + } else { + require.False(t, exists, "expected openshift.io/required-scc annotation to be absent") + } + }) + } +} + // TestPodAvoidsConcurrentWrite is a regression test for // https://bugzilla.redhat.com/show_bug.cgi?id=2101357 // we were mutating the input annotations and labels parameters causing From 134b429c432003892a98531b53654c27818e2e18 Mon Sep 17 00:00:00 2001 From: Tyler Pate Date: Fri, 26 Jun 2026 01:07:41 -0700 Subject: [PATCH 2/2] vendor: sync reconciler.go after staging change Run `go mod tidy && go mod vendor` to update the vendored copy of reconciler.go to match the staging change from the previous commit. --- .../pkg/controller/registry/reconciler/reconciler.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go index a4370df5f4..d30104a9c0 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler/reconciler.go @@ -356,6 +356,12 @@ func Pod(source *operatorsv1alpha1.CatalogSource, name, opmImg, utilImage, img s if securityContextConfig == operatorsv1alpha1.Restricted { // Apply 'restricted' security settings addSecurityContext(pod, runAsUser) + + // Pin SCC selection to prevent custom SCCs from preempting catalog pods on + // HCP, where the creating user has broader SCC access than the local SA. + if _, alreadySet := podAnnotations["openshift.io/required-scc"]; !alreadySet { + podAnnotations["openshift.io/required-scc"] = "restricted-v2" + } } // Set priorityclass if its annotation exists