Repository: apache/camel-k
Pull request #4487 (2023-06-14 08:39:55)

fix(core): Permissions on operator and builder pods (S2I compatibility)
Ref #4476

For compatibility with S2I publish  strategy:
* Change default Dockerfile user from 1001 to 1001:0 with 775 permissions to be able the write with group 0
* Add builder pod security context compatible with OCP SecurityContextConstraint restricted-v2 (https://docs.openshift.com/container-platform/4.12/authentication/managing-security-context-constraints.html)
* Change Dockerfile S2I to user compatible with SecurityContextConstraint
* Add permission to get a namespace to operator to ensure SecurityContextConstraint labels in namespace are accessible
* Remove root FsGroup on operator as it is no longer needed

This has been tested on:
* OCP (CRC) with default project on routine/pod strategy
* OCP (CRC) with user created project on routine/pod strategy
* spectrum on routine/pod strategy

Note: there is a different user on builder pod dockerfile (`--chown`) because the BuildConfig binary strategy replaces 775 permissions with 755, so we need to fall back to owner user.

**Release Note**
```release-note
fix(core): Permissions on operator and builder pods (S2I compatibility)
```


gansheer: @oscerd @christophd @claudio4j could on of you please launch the tests ?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1c3449b2..8a128fdf2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
 - Horizontal scaling ability for operators [\#4173](https://github.com/apache/camel-k/issues/4173)
 - Observability documentation - Update to micrometer extension [\#4157](https://github.com/apache/camel-k/issues/4157)
 - Multi architecture support - Operator and builder pods [\#4148](https://github.com/apache/camel-k/issues/4148)
+- Add message during Operator Installation that checks that Internal Image Registry is configured [\#4130](https://github.com/apache/camel-k/issues/4130)
 - Helm Node Selector [\#4096](https://github.com/apache/camel-k/issues/4096)
 - Update the version of the kind-action [\#4063](https://github.com/apache/camel-k/issues/4063)
 - Onboard Camel K Tekton tasks [\#3795](https://github.com/apache/camel-k/issues/3795)
diff --git a/build/Dockerfile b/build/Dockerfile
index 92efe413b..5f5251637 100644
--- a/build/Dockerfile
+++ b/build/Dockerfile
@@ -45,14 +45,16 @@ ENV MAVEN_OPTS="${MAVEN_OPTS} -Dlogback.configurationFile=${MAVEN_HOME}/conf/log
 ADD build/_maven_output ${MVN_REPO}
 ADD build/_kamelets /kamelets
 
-RUN chgrp -R 1001 ${MVN_REPO} \
-    && chown -R 1001 ${MVN_REPO} \
+RUN chgrp -R 0 ${MVN_REPO} \
+    && chown -R 1001:0 ${MVN_REPO} \
+    && chmod -R 775 ${MVN_REPO} \
     && chgrp -R 0 /kamelets \
     && chmod -R g=u /kamelets \
-    && chgrp -R 1001 ${MAVEN_HOME} \
-    && chown -R 1001 ${MAVEN_HOME}
+    && chgrp -R 0 ${MAVEN_HOME} \
+    && chown -R 1001:0 ${MAVEN_HOME} \
+    && chmod -R 775 ${MAVEN_HOME}
 
-USER 1001
+USER 1001:0
 
 ADD build/_output/bin/kamel /usr/local/bin/kamel
 
diff --git a/camel-k-sbom/camel-k-sbom.json b/camel-k-sbom/camel-k-sbom.json
index efe1326c3..80f6ad952 100644
--- a/camel-k-sbom/camel-k-sbom.json
+++ b/camel-k-sbom/camel-k-sbom.json
@@ -2,10 +2,10 @@
   "$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
   "bomFormat": "CycloneDX",
   "specVersion": "1.4",
-  "serialNumber": "urn:uuid:acd2b399-b612-4439-986a-9c5f998ec150",
+  "serialNumber": "urn:uuid:9a32bfa5-c168-4550-aed1-42d4cf7a539f",
   "version": 1,
   "metadata": {
-    "timestamp": "2023-06-28T23:48:21Z",
+    "timestamp": "2023-07-02T23:47:41Z",
     "tools": [
       {
         "vendor": "CycloneDX",
@@ -46,11 +46,11 @@
       }
     ],
     "component": {
-      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2@v0.0.0-20230701234727-01e81e95488f?type=module",
       "type": "application",
       "name": "github.com/apache/camel-k/v2",
-      "version": "v0.0.0-20230628234740-9eb4f7fd6792",
-      "purl": "pkg:golang/github.com/apache/camel-k/v2@v0.0.0-20230628234740-9eb4f7fd6792?type=module\u0026goos=linux\u0026goarch=amd64",
+      "version": "v0.0.0-20230701234727-01e81e95488f",
+      "purl": "pkg:golang/github.com/apache/camel-k/v2@v0.0.0-20230701234727-01e81e95488f?type=module\u0026goos=linux\u0026goarch=amd64",
       "externalReferences": [
         {
           "url": "https://github.com/apache/camel-k",
@@ -423,18 +423,18 @@
       }
     },
     {
-      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
       "type": "library",
       "name": "github.com/apache/camel-k/v2/pkg/apis/camel",
-      "version": "v0.0.0-20230628234740-9eb4f7fd6792",
+      "version": "v0.0.0-20230701234727-01e81e95488f",
       "scope": "required",
       "hashes": [
         {
           "alg": "SHA-256",
-          "content": "5ed979cb77b31bf201326a41c89e1938cdf49ff33b94c9101245b16790efa7ac"
+          "content": "6de161ead0c93fb3970b8d5d017c00cf5eae914acd739a499a214bf507ae37ea"
         }
       ],
-      "purl": "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module\u0026goos=linux\u0026goarch=amd64",
+      "purl": "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230701234727-01e81e95488f?type=module\u0026goos=linux\u0026goarch=amd64",
       "externalReferences": [
         {
           "url": "https://github.com/apache/camel-k/v2/pkg/apis/camel",
@@ -443,18 +443,18 @@
       ]
     },
     {
-      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
       "type": "library",
       "name": "github.com/apache/camel-k/v2/pkg/client/camel",
-      "version": "v0.0.0-20230628234740-9eb4f7fd6792",
+      "version": "v0.0.0-20230701234727-01e81e95488f",
       "scope": "required",
       "hashes": [
         {
           "alg": "SHA-256",
-          "content": "5a789694bfa7f2a32d94355fec37a8b09c4e3d76ef3011c9a021cee923cbdca8"
+          "content": "c896056d89cda7c726766421e1c48bce0a68bf42b9fbd704b828c2c17594a9eb"
         }
       ],
-      "purl": "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module\u0026goos=linux\u0026goarch=amd64",
+      "purl": "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230701234727-01e81e95488f?type=module\u0026goos=linux\u0026goarch=amd64",
       "externalReferences": [
         {
           "url": "https://github.com/apache/camel-k/v2/pkg/client/camel",
@@ -463,18 +463,18 @@
       ]
     },
     {
-      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "bom-ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230701234727-01e81e95488f?type=module",
       "type": "library",
       "name": "github.com/apache/camel-k/v2/pkg/kamelet/repository",
-      "version": "v0.0.0-20230628234740-9eb4f7fd6792",
+      "version": "v0.0.0-20230701234727-01e81e95488f",
       "scope": "required",
       "hashes": [
         {
           "alg": "SHA-256",
-          "content": "9f323bf24cc5567ceecb5d327e7925360832f8b64f97d5e3607b03c035c47d15"
+          "content": "86533de3d328f601569c64369d02bb142842273b7cb35e63ce34907245759621"
         }
       ],
-      "purl": "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230628234740-9eb4f7fd6792?type=module\u0026goos=linux\u0026goarch=amd64",
+      "purl": "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230701234727-01e81e95488f?type=module\u0026goos=linux\u0026goarch=amd64",
       "externalReferences": [
         {
           "url": "https://github.com/apache/camel-k/v2/pkg/kamelet/repository",
@@ -4035,12 +4035,12 @@
   ],
   "dependencies": [
     {
-      "ref": "pkg:golang/github.com/apache/camel-k/v2@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "ref": "pkg:golang/github.com/apache/camel-k/v2@v0.0.0-20230701234727-01e81e95488f?type=module",
       "dependsOn": [
         "pkg:golang/github.com/Masterminds/semver@v1.5.0?type=module",
-        "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
-        "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
-        "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+        "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
+        "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
+        "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230701234727-01e81e95488f?type=module",
         "pkg:golang/github.com/container-tools/spectrum@v0.6.25?type=module",
         "pkg:golang/github.com/evanphx/json-patch@v5.6.0+incompatible?type=module",
         "pkg:golang/github.com/fsnotify/fsnotify@v1.6.0?type=module",
@@ -4195,7 +4195,7 @@
       "ref": "pkg:golang/github.com/antlr/antlr4/runtime/Go/antlr@v1.4.10?type=module"
     },
     {
-      "ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
       "dependsOn": [
         "pkg:golang/github.com/imdario/mergo@v0.3.13?type=module",
         "pkg:golang/github.com/stretchr/testify@v1.8.4?type=module",
@@ -4221,9 +4221,9 @@
       ]
     },
     {
-      "ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
       "dependsOn": [
-        "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+        "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
         "pkg:golang/k8s.io/api@v0.25.6?type=module",
         "pkg:golang/k8s.io/apimachinery@v0.25.6?type=module",
         "pkg:golang/k8s.io/client-go@v0.25.6?type=module",
@@ -4267,10 +4267,10 @@
       ]
     },
     {
-      "ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+      "ref": "pkg:golang/github.com/apache/camel-k/v2/pkg/kamelet/repository@v0.0.0-20230701234727-01e81e95488f?type=module",
       "dependsOn": [
-        "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
-        "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230628234740-9eb4f7fd6792?type=module",
+        "pkg:golang/github.com/apache/camel-k/v2/pkg/apis/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
+        "pkg:golang/github.com/apache/camel-k/v2/pkg/client/camel@v0.0.0-20230701234727-01e81e95488f?type=module",
         "pkg:golang/github.com/stretchr/testify@v1.8.4?type=module",
         "pkg:golang/golang.org/x/oauth2@v0.9.0?type=module",
         "pkg:golang/k8s.io/apimachinery@v0.25.6?type=module",
diff --git a/config/rbac/operator-role.yaml b/config/rbac/operator-role.yaml
index 975028317..5c01d853d 100644
--- a/config/rbac/operator-role.yaml
+++ b/config/rbac/operator-role.yaml
@@ -179,3 +179,9 @@ rules:
   verbs:
   - get
   - list
+- apiGroups:
+  - ""
+  resources:
+  - namespaces
+  verbs:
+  - get
\ No newline at end of file
diff --git a/pkg/controller/build/build_pod.go b/pkg/controller/build/build_pod.go
index 4627e8806..bb5fe31a7 100644
--- a/pkg/controller/build/build_pod.go
+++ b/pkg/controller/build/build_pod.go
@@ -33,9 +33,11 @@ import (
 
 	v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/v2/pkg/builder"
+	"github.com/apache/camel-k/v2/pkg/client"
 	"github.com/apache/camel-k/v2/pkg/platform"
 	"github.com/apache/camel-k/v2/pkg/util/defaults"
 	"github.com/apache/camel-k/v2/pkg/util/kubernetes"
+	"github.com/apache/camel-k/v2/pkg/util/openshift"
 )
 
 const (
@@ -112,8 +114,22 @@ var (
 	}
 )
 
-func newBuildPod(ctx context.Context, c ctrl.Reader, build *v1.Build) (*corev1.Pod, error) {
+func newBuildPod(ctx context.Context, c ctrl.Reader, client client.Client, build *v1.Build) (*corev1.Pod, error) {
 	var ugfid int64 = 1001
+	podSecurityContext := &corev1.PodSecurityContext{
+		RunAsUser:  &ugfid,
+		RunAsGroup: &ugfid,
+		FSGroup:    &ugfid,
+	}
+	for _, task := range build.Spec.Tasks {
+		// get pod security context from security context constraint configuration in namespace
+		if task.S2i != nil {
+			podSecurityContextConstrained, _ := openshift.GetOpenshiftPodSecurityContextRestricted(ctx, client, build.BuilderPodNamespace())
+			if podSecurityContextConstrained != nil {
+				podSecurityContext = podSecurityContextConstrained
+			}
+		}
+	}
 	pod := &corev1.Pod{
 		TypeMeta: metav1.TypeMeta{
 			APIVersion: corev1.SchemeGroupVersion.String(),
@@ -130,11 +146,7 @@ func newBuildPod(ctx context.Context, c ctrl.Reader, build *v1.Build) (*corev1.P
 		Spec: corev1.PodSpec{
 			ServiceAccountName: platform.BuilderServiceAccount,
 			RestartPolicy:      corev1.RestartPolicyNever,
-			SecurityContext: &corev1.PodSecurityContext{
-				RunAsUser:  &ugfid,
-				RunAsGroup: &ugfid,
-				FSGroup:    &ugfid,
-			},
+			SecurityContext:    podSecurityContext,
 		},
 	}
 
@@ -143,7 +155,7 @@ func newBuildPod(ctx context.Context, c ctrl.Reader, build *v1.Build) (*corev1.P
 	for _, task := range build.Spec.Tasks {
 		switch {
 		case task.Builder != nil:
-			addBuildTaskToPod(build, task.Builder.Name, pod)
+			addBuildTaskToPod(ctx, client, build, task.Builder.Name, pod)
 		case task.Buildah != nil:
 			err := addBuildahTaskToPod(ctx, c, build, task.Buildah, pod)
 			if err != nil {
@@ -155,9 +167,9 @@ func newBuildPod(ctx context.Context, c ctrl.Reader, build *v1.Build) (*corev1.P
 				return nil, err
 			}
 		case task.S2i != nil:
-			addBuildTaskToPod(build, task.S2i.Name, pod)
+			addBuildTaskToPod(ctx, client, build, task.S2i.Name, pod)
 		case task.Spectrum != nil:
-			addBuildTaskToPod(build, task.Spectrum.Name, pod)
+			addBuildTaskToPod(ctx, client, build, task.Spectrum.Name, pod)
 		case task.Custom != nil:
 			addCustomTaskToPod(build, task.Custom, pod)
 		}
@@ -244,7 +256,7 @@ func buildPodName(build *v1.Build) string {
 	return "camel-k-" + build.Name + "-builder"
 }
 
-func addBuildTaskToPod(build *v1.Build, taskName string, pod *corev1.Pod) {
+func addBuildTaskToPod(ctx context.Context, client client.Client, build *v1.Build, taskName string, pod *corev1.Pod) {
 	if !hasVolume(pod, builderVolume) {
 		pod.Spec.Volumes = append(pod.Spec.Volumes,
 			// EmptyDir volume used to share the build state across tasks
@@ -283,6 +295,14 @@ func addBuildTaskToPod(build *v1.Build, taskName string, pod *corev1.Pod) {
 		Env:        envVars,
 	}
 
+	// get security context from security context constraint configuration in namespace
+	if taskName == "s2i" {
+		securityContextConstrained, _ := openshift.GetOpenshiftSecurityContextRestricted(ctx, client, build.BuilderPodNamespace())
+		if securityContextConstrained != nil {
+			container.SecurityContext = securityContextConstrained
+		}
+	}
+
 	configureResources(build, &container)
 	addContainerToPod(build, container, pod)
 }
diff --git a/pkg/controller/build/monitor_pod.go b/pkg/controller/build/monitor_pod.go
index d16928e00..89255b639 100644
--- a/pkg/controller/build/monitor_pod.go
+++ b/pkg/controller/build/monitor_pod.go
@@ -72,7 +72,7 @@ func (action *monitorPodAction) Handle(ctx context.Context, build *v1.Build) (*v
 		switch build.Status.Phase {
 
 		case v1.BuildPhasePending:
-			if pod, err = newBuildPod(ctx, action.reader, build); err != nil {
+			if pod, err = newBuildPod(ctx, action.reader, action.client, build); err != nil {
 				return nil, err
 			}
 
diff --git a/pkg/controller/catalog/initialize.go b/pkg/controller/catalog/initialize.go
index 79935bcf8..0ab547243 100644
--- a/pkg/controller/catalog/initialize.go
+++ b/pkg/controller/catalog/initialize.go
@@ -39,6 +39,7 @@ import (
 	"github.com/apache/camel-k/v2/pkg/util"
 	"github.com/apache/camel-k/v2/pkg/util/defaults"
 	"github.com/apache/camel-k/v2/pkg/util/kubernetes"
+	"github.com/apache/camel-k/v2/pkg/util/openshift"
 	"github.com/apache/camel-k/v2/pkg/util/s2i"
 
 	spectrum "github.com/container-tools/spectrum/pkg/builder"
@@ -172,13 +173,15 @@ func initializeS2i(ctx context.Context, c client.Client, ip *v1.IntegrationPlatf
 	)
 	imageTag := strings.ToLower(catalog.Spec.Runtime.Version)
 
+	uidStr := getS2iUserID(ctx, c, ip, catalog)
+
 	// Dockfile
 	dockerfile := string([]byte(`
 		FROM ` + catalog.Spec.GetQuarkusToolingImage() + `
-		USER 1001
-		ADD /usr/local/bin/kamel /usr/local/bin/kamel
-		ADD /usr/share/maven/mvnw/ /usr/share/maven/mvnw/
-		ADD ` + defaults.LocalRepository + ` ` + defaults.LocalRepository + `
+		USER ` + uidStr + `:0
+		ADD --chown=` + uidStr + `:0 /usr/local/bin/kamel /usr/local/bin/kamel
+		ADD --chown=` + uidStr + `:0 /usr/share/maven/mvnw/ /usr/share/maven/mvnw/
+		ADD --chown=` + uidStr + `:0 ` + defaults.LocalRepository + ` ` + defaults.LocalRepository + `
 	`))
 
 	owner := catalogReference(catalog)
@@ -557,3 +560,16 @@ func catalogReference(catalog *v1.CamelCatalog) *unstructured.Unstructured {
 	owner.SetKind(catalog.Kind)
 	return owner
 }
+
+// get user id from security context constraint configuration in namespace if present.
+func getS2iUserID(ctx context.Context, c client.Client, ip *v1.IntegrationPlatform, catalog *v1.CamelCatalog) string {
+	ugfidStr := "1001"
+	if ip.Status.Cluster == v1.IntegrationPlatformClusterOpenShift {
+		uidStr, err := openshift.GetOpenshiftUser(ctx, c, catalog.GetNamespace())
+		if err != nil {
+			Log.Error(err, "Unable to retieve an Openshift user and group Ids.")
+		}
+		return uidStr
+	}
+	return ugfidStr
+}
diff --git a/pkg/install/operator.go b/pkg/install/operator.go
index 4f3d1081f..5c5a2e134 100644
--- a/pkg/install/operator.go
+++ b/pkg/install/operator.go
@@ -174,10 +174,6 @@ func OperatorOrCollect(ctx context.Context, cmd *cobra.Command, c client.Client,
 					fmt.Sprintf("--health-port=%d", cfg.Health.Port))
 				d.Spec.Template.Spec.Containers[0].LivenessProbe.HTTPGet.Port = intstr.FromInt(int(cfg.Health.Port))
 			}
-			var ugfid int64 = 0
-			d.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{
-				FSGroup: &ugfid,
-			}
 		}
 		if cfg.Debugging.Enabled {
 			if d, ok := o.(*appsv1.Deployment); ok {
diff --git a/pkg/util/openshift/openshift.go b/pkg/util/openshift/openshift.go
index da3f78f6f..21b932102 100644
--- a/pkg/util/openshift/openshift.go
+++ b/pkg/util/openshift/openshift.go
@@ -18,7 +18,15 @@ limitations under the License.
 package openshift
 
 import (
+	"context"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+
+	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 )
 
@@ -33,3 +41,94 @@ func IsOpenShift(client kubernetes.Interface) (bool, error) {
 
 	return true, nil
 }
+
+// GetOpenshiftPodSecurityContextRestricted return the PodSecurityContext (https://docs.openshift.com/container-platform/4.12/authentication/managing-security-context-constraints.html):
+// FsGroup set to the minimum value in the "openshift.io/sa.scc.supplemental-groups" annotation if exists, else falls back to minimum value "openshift.io/sa.scc.uid-range" annotation.
+func GetOpenshiftPodSecurityContextRestricted(ctx context.Context, client kubernetes.Interface, namespace string) (*corev1.PodSecurityContext, error) {
+
+	ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get namespace %q: %w", namespace, err)
+	}
+
+	uidRange, ok := ns.ObjectMeta.Annotations["openshift.io/sa.scc.uid-range"]
+	if !ok {
+		return nil, errors.New("annotation 'openshift.io/sa.scc.uid-range' not found")
+	}
+
+	supplementalGroups, ok := ns.ObjectMeta.Annotations["openshift.io/sa.scc.supplemental-groups"]
+	if !ok {
+		supplementalGroups = uidRange
+	}
+
+	supplementalGroups = strings.Split(supplementalGroups, ",")[0]
+	fsGroupStr := strings.Split(supplementalGroups, "/")[0]
+	fsGroup, err := strconv.ParseInt(fsGroupStr, 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert fsgroup to integer %q: %w", fsGroupStr, err)
+	}
+
+	psc := corev1.PodSecurityContext{
+		FSGroup: &fsGroup,
+		SeccompProfile: &corev1.SeccompProfile{
+			Type: corev1.SeccompProfileTypeRuntimeDefault,
+		},
+	}
+
+	return &psc, nil
+
+}
+
+// GetOpenshiftSecurityContextRestricted return the PodSecurityContext (https://docs.openshift.com/container-platform/4.12/authentication/managing-security-context-constraints.html):
+// User set to the minimum value in the "openshift.io/sa.scc.uid-range" annotation.
+func GetOpenshiftSecurityContextRestricted(ctx context.Context, client kubernetes.Interface, namespace string) (*corev1.SecurityContext, error) {
+
+	ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get namespace %q: %w", namespace, err)
+	}
+
+	uidRange, ok := ns.ObjectMeta.Annotations["openshift.io/sa.scc.uid-range"]
+	if !ok {
+		return nil, errors.New("annotation 'openshift.io/sa.scc.uid-range' not found")
+	}
+
+	uidStr := strings.Split(uidRange, "/")[0]
+	uid, err := strconv.ParseInt(uidStr, 10, 64)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert uid to integer %q: %w", uidStr, err)
+	}
+
+	runAsNonRoot := true
+	allowPrivilegeEscalation := false
+	sc := corev1.SecurityContext{
+		RunAsUser:    &uid,
+		RunAsNonRoot: &runAsNonRoot,
+		SeccompProfile: &corev1.SeccompProfile{
+			Type: corev1.SeccompProfileTypeRuntimeDefault,
+		},
+		AllowPrivilegeEscalation: &allowPrivilegeEscalation,
+		Capabilities:             &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}},
+	}
+
+	return &sc, nil
+
+}
+
+// GetOpenshiftUser return the UserId (https://docs.openshift.com/container-platform/4.12/authentication/managing-security-context-constraints.html):
+// User set to the minimum value in the "openshift.io/sa.scc.uid-range" annotation.
+func GetOpenshiftUser(ctx context.Context, client kubernetes.Interface, namespace string) (string, error) {
+
+	ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{})
+	if err != nil {
+		return "", fmt.Errorf("failed to get namespace %q: %w", namespace, err)
+	}
+
+	uidRange, ok := ns.ObjectMeta.Annotations["openshift.io/sa.scc.uid-range"]
+	if !ok {
+		return "", errors.New("annotation 'openshift.io/sa.scc.uid-range' not found")
+	}
+
+	uidStr := strings.Split(uidRange, "/")[0]
+	return uidStr, nil
+}


Test patch:
diff --git a/pkg/util/openshift/openshift_test.go b/pkg/util/openshift/openshift_test.go
new file mode 100644
index 000000000..83c561572
--- /dev/null
+++ b/pkg/util/openshift/openshift_test.go
@@ -0,0 +1,121 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package openshift
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	fakeclientset "k8s.io/client-go/kubernetes/fake"
+)
+
+var noSccAnnotationNamespace *corev1.Namespace = &corev1.Namespace{
+	ObjectMeta: metav1.ObjectMeta{
+		Name: "no-scc-annotations-namespace",
+	},
+}
+
+var constrainedNamespace *corev1.Namespace = &corev1.Namespace{
+	ObjectMeta: metav1.ObjectMeta{
+		Name: "myuser",
+		Annotations: map[string]string{
+			"openshift.io/sa.scc.mcs":                 "s0:c26,c5",
+			"openshift.io/sa.scc.supplemental-groups": "1000860000/10000",
+			"openshift.io/sa.scc.uid-range":           "1000860000/10000",
+		},
+		Labels: map[string]string{
+			"kubernetes.io/metadata.name":              "myuser",
+			"pod-security.kubernetes.io/audit":         "restricted",
+			"pod-security.kubernetes.io/audit-version": "v1.24",
+			"pod-security.kubernetes.io/warn":          "restricted",
+			"pod-security.kubernetes.io/warn-version":  "v1.24",
+		},
+	},
+}
+
+func TestGetUserIdNamespaceWithoutLabels(t *testing.T) {
+	kclient := initClientWithNamespace(t, noSccAnnotationNamespace)
+
+	_, errUID := GetOpenshiftUser(context.Background(), kclient, "no-scc-annotations-namespace")
+
+	assert.NotNil(t, errUID)
+	assert.Contains(t, errUID.Error(), "annotation 'openshift.io/sa.scc.uid-range' not found")
+}
+
+func TestGetUserIdNamespaceConstrained(t *testing.T) {
+	kclient := initClientWithNamespace(t, constrainedNamespace)
+
+	uid, errUID := GetOpenshiftUser(context.Background(), kclient, "myuser")
+
+	assert.Nil(t, errUID)
+	assert.Equal(t, "1000860000", uid)
+}
+
+func TestGetPodSecurityContextNamespaceWithoutLabels(t *testing.T) {
+	kclient := initClientWithNamespace(t, noSccAnnotationNamespace)
+
+	_, errPsc := GetOpenshiftPodSecurityContextRestricted(context.Background(), kclient, "no-scc-annotations-namespace")
+
+	assert.NotNil(t, errPsc)
+	assert.Contains(t, errPsc.Error(), "annotation 'openshift.io/sa.scc.uid-range' not found")
+}
+
+func TestGetPodSecurityContextNamespaceConstrained(t *testing.T) {
+	kclient := initClientWithNamespace(t, constrainedNamespace)
+
+	psc, errPsc := GetOpenshiftPodSecurityContextRestricted(context.Background(), kclient, "myuser")
+
+	expectedFsGroup := int64(1000860000)
+	assert.Nil(t, errPsc)
+	assert.NotNil(t, psc)
+	assert.Equal(t, expectedFsGroup, *psc.FSGroup)
+}
+
+func TestGetSecurityContextNamespaceWithoutLabels(t *testing.T) {
+	kclient := initClientWithNamespace(t, noSccAnnotationNamespace)
+
+	_, errSc := GetOpenshiftSecurityContextRestricted(context.Background(), kclient, "no-scc-annotations-namespace")
+
+	assert.NotNil(t, errSc)
+	assert.Contains(t, errSc.Error(), "annotation 'openshift.io/sa.scc.uid-range' not found")
+}
+
+func TestGetSecurityContextNamespaceConstrained(t *testing.T) {
+	kclient := initClientWithNamespace(t, constrainedNamespace)
+
+	sc, errSc := GetOpenshiftSecurityContextRestricted(context.Background(), kclient, "myuser")
+
+	expectedUserID := int64(1000860000)
+	assert.Nil(t, errSc)
+	assert.NotNil(t, sc)
+	assert.Equal(t, expectedUserID, *sc.RunAsUser)
+}
+
+func initClientWithNamespace(t *testing.T, ns *corev1.Namespace) *fakeclientset.Clientset {
+	t.Helper()
+	kclient := fakeclientset.NewSimpleClientset()
+	_, err := kclient.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{})
+	if err != nil {
+		t.Error(err)
+		t.Fail()
+	}
+	return kclient
+}