You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2023/07/03 09:53:31 UTC

[camel-k] 01/02: fix(core): Permissions on operator and builder pods (S2I compatibility)

This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 11e989fd6c8a2244955c217221132f3524c2982e
Author: Gaelle Fournier <ga...@gmail.com>
AuthorDate: Wed Jun 28 09:51:49 2023 +0200

    fix(core): Permissions on operator and builder pods (S2I compatibility)
    
    * Change default Dockerfile user from 1001 to 1001: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
---
 build/Dockerfile                     | 12 +++--
 config/rbac/operator-role.yaml       |  6 +++
 pkg/controller/build/build_pod.go    | 40 +++++++++++----
 pkg/controller/build/monitor_pod.go  |  2 +-
 pkg/controller/catalog/initialize.go | 24 +++++++--
 pkg/install/operator.go              |  4 --
 pkg/util/openshift/openshift.go      | 99 ++++++++++++++++++++++++++++++++++++
 7 files changed, 163 insertions(+), 24 deletions(-)

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/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
+}