Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): Permissions on operator and builder pods (S2I compatibility) #4487

Merged
merged 2 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
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
12 changes: 7 additions & 5 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions config/rbac/operator-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,9 @@ rules:
verbs:
- get
- list
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
40 changes: 30 additions & 10 deletions pkg/controller/build/build_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
},
}

Expand All @@ -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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/build/monitor_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
24 changes: 20 additions & 4 deletions pkg/controller/catalog/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
4 changes: 0 additions & 4 deletions pkg/install/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
99 changes: 99 additions & 0 deletions pkg/util/openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
}
Loading