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 <[email protected]> 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 +}
