diff --git a/pkg/build/controller/build/build_controller.go b/pkg/build/controller/build/build_controller.go index 4be35fa22..8dff8ac36 100644 --- a/pkg/build/controller/build/build_controller.go +++ b/pkg/build/controller/build/build_controller.go @@ -1321,7 +1321,7 @@ func (bc *BuildController) createBuildPod(build *buildv1.Build) (*buildUpdate, e return update, fmt.Errorf("could not find registry config for build: %v", err) } if !hasRegistryConf { - // Create the registry config ConfigMap to mount the regsitry config to the existing build pod + // Create the registry config ConfigMap to mount the registry config to the existing build pod update, err = bc.createBuildSystemConfConfigMap(build, existingPod, update) if err != nil { return update, err diff --git a/pkg/build/controller/strategy/custom.go b/pkg/build/controller/strategy/custom.go index ca0159917..1ea162358 100644 --- a/pkg/build/controller/strategy/custom.go +++ b/pkg/build/controller/strategy/custom.go @@ -88,7 +88,7 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA serviceAccount = buildutil.BuilderServiceAccountName } - privileged := true + securityContext := securityContextForBuild(strategy.Env) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: buildutil.GetBuildPodName(build), @@ -99,13 +99,10 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA ServiceAccountName: serviceAccount, Containers: []corev1.Container{ { - Name: CustomBuild, - Image: strategy.From.Name, - Env: containerEnv, - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 - SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - }, + Name: CustomBuild, + Image: strategy.From.Name, + Env: containerEnv, + SecurityContext: securityContext, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, }, }, @@ -138,6 +135,10 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA setupAdditionalSecrets(pod, &pod.Spec.Containers[0], build.Spec.Strategy.CustomStrategy.Secrets) setupContainersConfigs(build, pod) setupBuildCAs(build, pod, additionalCAs, internalRegistryHost) - setupContainersStorage(pod, &pod.Spec.Containers[0]) // for unprivileged builds + setupContainersStorage(pod, &pod.Spec.Containers[0]) + if securityContext == nil || securityContext.Privileged == nil || !*securityContext.Privileged { + setupBuilderAutonsUser(build, strategy.Env, pod) + setupBuilderDeviceFUSE(pod) + } return pod, nil } diff --git a/pkg/build/controller/strategy/custom_test.go b/pkg/build/controller/strategy/custom_test.go index c5f6b0d7e..b5a5c336c 100644 --- a/pkg/build/controller/strategy/custom_test.go +++ b/pkg/build/controller/strategy/custom_test.go @@ -65,9 +65,10 @@ func TestCustomCreateBuildPod(t *testing.T) { // build-system-configmap // certificate authorities // container storage + // container run // global CA injection configmap - if len(container.VolumeMounts) != 8 { - t.Fatalf("Expected 8 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.VolumeMounts) != 9 { + t.Fatalf("Expected 9 volumes in container, got %d", len(container.VolumeMounts)) } expectedMounts := []string{"/var/run/docker.sock", DockerPushSecretMountPath, @@ -76,7 +77,8 @@ func TestCustomCreateBuildPod(t *testing.T) { ConfigMapBuildSystemConfigsMountPath, ConfigMapCertsMountPath, ConfigMapBuildGlobalCAMountPath, - "/var/lib/containers/storage", + "/var/lib/containers", + "/var/run/containers", } for i, expected := range expectedMounts { if container.VolumeMounts[i].MountPath != expected { @@ -94,8 +96,8 @@ func TestCustomCreateBuildPod(t *testing.T) { if !kapihelper.Semantic.DeepEqual(container.Resources, build.Spec.Resources) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, build.Spec.Resources) } - if len(actual.Spec.Volumes) != 8 { - t.Fatalf("Expected 8 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 9 { + t.Fatalf("Expected 9 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } buildJSON, _ := runtime.Encode(customBuildEncodingCodecFactory.LegacyCodec(buildv1.GroupVersion), build) errorCases := map[int][]string{ @@ -267,3 +269,15 @@ func mockCustomBuild(forcePull, emptySource bool) *buildv1.Build { }, } } + +func TestCustomCreateBuildPodAutonsUser(t *testing.T) { + strategy := CustomBuildStrategy{} + + build := mockCustomBuild(false, false) + + testCreateBuildPodAutonsUser(t, build, &strategy, + func(build *buildv1.Build, env corev1.EnvVar) { + build.Spec.Strategy.CustomStrategy.Env = append(build.Spec.Strategy.CustomStrategy.Env, env) + }, + ) +} diff --git a/pkg/build/controller/strategy/docker.go b/pkg/build/controller/strategy/docker.go index 98e0308dd..45b60de40 100644 --- a/pkg/build/controller/strategy/docker.go +++ b/pkg/build/controller/strategy/docker.go @@ -38,8 +38,8 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA return nil, fmt.Errorf("failed to encode the build: %v", err) } - privileged := true strategy := build.Spec.Strategy.DockerStrategy + securityContext := securityContextForBuild(strategy.Env) hostPathFile := v1.HostPathFile containerEnv := []v1.EnvVar{ @@ -69,14 +69,11 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA ServiceAccountName: serviceAccount, Containers: []v1.Container{ { - Name: DockerBuild, - Image: bs.Image, - Args: []string{"openshift-docker-build"}, - Env: copyEnvVarSlice(containerEnv), - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 - SecurityContext: &v1.SecurityContext{ - Privileged: &privileged, - }, + Name: DockerBuild, + Image: bs.Image, + Args: []string{"openshift-docker-build"}, + Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []v1.VolumeMount{ { @@ -133,6 +130,7 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA Image: bs.Image, Args: []string{"openshift-git-clone"}, Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []v1.VolumeMount{ { @@ -152,14 +150,11 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA } if len(build.Spec.Source.Images) > 0 { extractImageContentContainer := v1.Container{ - Name: ExtractImageContentContainer, - Image: bs.Image, - Args: []string{"openshift-extract-image-content"}, - Env: copyEnvVarSlice(containerEnv), - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 - SecurityContext: &v1.SecurityContext{ - Privileged: &privileged, - }, + Name: ExtractImageContentContainer, + Image: bs.Image, + Args: []string{"openshift-extract-image-content"}, + Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []v1.VolumeMount{ { @@ -188,6 +183,7 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA Image: bs.Image, Args: []string{"openshift-manage-dockerfile"}, Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []v1.VolumeMount{ { @@ -212,8 +208,11 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA setupInputConfigMaps(pod, &pod.Spec.Containers[0], build.Spec.Source.ConfigMaps) setupContainersConfigs(build, pod) setupBuildCAs(build, pod, additionalCAs, internalRegistryHost) - setupContainersStorage(pod, &pod.Spec.Containers[0]) // for unprivileged builds - // setupContainersNodeStorage(pod, &pod.Spec.Containers[0]) // for privileged builds + setupContainersStorage(pod, &pod.Spec.Containers[0]) + if securityContext == nil || securityContext.Privileged == nil || !*securityContext.Privileged { + setupBuilderAutonsUser(build, strategy.Env, pod) + setupBuilderDeviceFUSE(pod) + } setupBlobCache(pod) if err := setupBuildVolumes(pod, build.Spec.Strategy.DockerStrategy.Volumes, bs.BuildCSIVolumesEnabled); err != nil { return pod, err diff --git a/pkg/build/controller/strategy/docker_test.go b/pkg/build/controller/strategy/docker_test.go index 4608fd989..bc46423a4 100644 --- a/pkg/build/controller/strategy/docker_test.go +++ b/pkg/build/controller/strategy/docker_test.go @@ -65,7 +65,6 @@ func TestDockerCreateBuildPod(t *testing.T) { "BUILD_REGISTRIES_DIR_PATH": "", "BUILD_SIGNATURE_POLICY_PATH": "", "BUILD_STORAGE_CONF_PATH": "", - "BUILD_STORAGE_DRIVER": "", "BUILD_BLOBCACHE_DIR": "", "BUILD_MOUNT_ETC_PKI_CATRUST": "", } @@ -87,11 +86,12 @@ func TestDockerCreateBuildPod(t *testing.T) { // build-system-config // certificate authorities // container storage + // container run // blobs content cache // global CA injection configmap // node pull secrets - if len(container.VolumeMounts) != 12 { - t.Fatalf("Expected 12 volumes in container, got %d", len(container.VolumeMounts)) + if len(container.VolumeMounts) != 13 { + t.Fatalf("Expected 13 volumes in container, got %d", len(container.VolumeMounts)) } if *actual.Spec.ActiveDeadlineSeconds != 60 { t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds) @@ -106,7 +106,8 @@ func TestDockerCreateBuildPod(t *testing.T) { ConfigMapBuildSystemConfigsMountPath, ConfigMapCertsMountPath, ConfigMapBuildGlobalCAMountPath, - "/var/lib/containers/storage", + "/var/lib/containers", + "/var/run/containers", buildutil.BuildBlobsContentCache, } for i, expected := range expectedMounts { @@ -115,8 +116,8 @@ func TestDockerCreateBuildPod(t *testing.T) { } } // build pod has an extra volume: the git clone source secret - if len(actual.Spec.Volumes) != 13 { - t.Fatalf("Expected 13 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 14 { + t.Fatalf("Expected 14 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } if !kapihelper.Semantic.DeepEqual(container.Resources, build.Spec.Resources) { t.Fatalf("Expected actual=expected, %v != %v", container.Resources, build.Spec.Resources) @@ -238,3 +239,17 @@ func mockDockerBuild() *buildv1.Build { }, } } + +func TestDockerCreateBuildPodAutonsUser(t *testing.T) { + strategy := DockerBuildStrategy{ + Image: "docker-test-image", + } + + build := mockDockerBuild() + + testCreateBuildPodAutonsUser(t, build, &strategy, + func(build *buildv1.Build, env corev1.EnvVar) { + build.Spec.Strategy.DockerStrategy.Env = append(build.Spec.Strategy.DockerStrategy.Env, env) + }, + ) +} diff --git a/pkg/build/controller/strategy/sti.go b/pkg/build/controller/strategy/sti.go index 2fe09ce18..efcff1d21 100644 --- a/pkg/build/controller/strategy/sti.go +++ b/pkg/build/controller/strategy/sti.go @@ -68,7 +68,7 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA } hostPathFile := corev1.HostPathFile - privileged := true + securityContext := securityContextForBuild(strategy.Env) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: buildutil.GetBuildPodName(build), @@ -79,14 +79,11 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA ServiceAccountName: serviceAccount, Containers: []corev1.Container{ { - Name: StiBuild, - Image: bs.Image, - Args: []string{"openshift-sti-build"}, - Env: copyEnvVarSlice(containerEnv), - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 - SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - }, + Name: StiBuild, + Image: bs.Image, + Args: []string{"openshift-sti-build"}, + Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []corev1.VolumeMount{ { @@ -140,6 +137,7 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA Image: bs.Image, Args: []string{"openshift-git-clone"}, Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []corev1.VolumeMount{ { @@ -159,14 +157,11 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA } if len(build.Spec.Source.Images) > 0 { extractImageContentContainer := corev1.Container{ - Name: ExtractImageContentContainer, - Image: bs.Image, - Args: []string{"openshift-extract-image-content"}, - Env: copyEnvVarSlice(containerEnv), - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 - SecurityContext: &corev1.SecurityContext{ - Privileged: &privileged, - }, + Name: ExtractImageContentContainer, + Image: bs.Image, + Args: []string{"openshift-extract-image-content"}, + Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []corev1.VolumeMount{ { @@ -195,6 +190,7 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA Image: bs.Image, Args: []string{"openshift-manage-dockerfile"}, Env: copyEnvVarSlice(containerEnv), + SecurityContext: securityContext, TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, VolumeMounts: []corev1.VolumeMount{ { @@ -219,8 +215,11 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildv1.Build, additionalCA setupInputConfigMaps(pod, &pod.Spec.Containers[0], build.Spec.Source.ConfigMaps) setupContainersConfigs(build, pod) setupBuildCAs(build, pod, additionalCAs, internalRegistryHost) - setupContainersStorage(pod, &pod.Spec.Containers[0]) // for unprivileged builds - // setupContainersNodeStorage(pod, &pod.Spec.Containers[0]) // for privileged builds + setupContainersStorage(pod, &pod.Spec.Containers[0]) + if securityContext == nil || securityContext.Privileged == nil || !*securityContext.Privileged { + setupBuilderAutonsUser(build, strategy.Env, pod) + setupBuilderDeviceFUSE(pod) + } setupBlobCache(pod) if err := setupBuildVolumes(pod, build.Spec.Strategy.SourceStrategy.Volumes, bs.BuildCSIVolumeseEnabled); err != nil { return pod, err diff --git a/pkg/build/controller/strategy/sti_test.go b/pkg/build/controller/strategy/sti_test.go index 749e0499c..7d389c999 100644 --- a/pkg/build/controller/strategy/sti_test.go +++ b/pkg/build/controller/strategy/sti_test.go @@ -101,7 +101,6 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) { "BUILD_REGISTRIES_DIR_PATH": "", "BUILD_SIGNATURE_POLICY_PATH": "", "BUILD_STORAGE_CONF_PATH": "", - "BUILD_STORAGE_DRIVER": "", "BUILD_BLOBCACHE_DIR": "", "BUILD_MOUNT_ETC_PKI_CATRUST": "", } @@ -128,10 +127,11 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) { // build-system-configmap // certificate authorities // container storage + // container run // blobs content cache // global CA injection configmap - if len(container.VolumeMounts) != 12 { - t.Fatalf("Expected 12 volumes in container, got %d %v", len(container.VolumeMounts), container.VolumeMounts) + if len(container.VolumeMounts) != 13 { + t.Fatalf("Expected 13 volumes in container, got %d %v", len(container.VolumeMounts), container.VolumeMounts) } expectedMounts := []string{buildutil.NodePullSecretsPath, buildutil.BuildWorkDirMount, @@ -143,7 +143,8 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) { ConfigMapBuildSystemConfigsMountPath, ConfigMapCertsMountPath, ConfigMapBuildGlobalCAMountPath, - "/var/lib/containers/storage", + "/var/lib/containers", + "/var/run/containers", buildutil.BuildBlobsContentCache, } for i, expected := range expectedMounts { @@ -152,8 +153,8 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) { } } // build pod has an extra volume: the git clone source secret - if len(actual.Spec.Volumes) != 13 { - t.Fatalf("Expected 13 volumes in Build pod, got %d", len(actual.Spec.Volumes)) + if len(actual.Spec.Volumes) != 14 { + t.Fatalf("Expected 14 volumes in Build pod, got %d", len(actual.Spec.Volumes)) } if *actual.Spec.ActiveDeadlineSeconds != 60 { t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds) @@ -303,3 +304,18 @@ func mockSTIBuild() *buildv1.Build { }, } } + +func TestS2ICreateBuildPodAutonsUser(t *testing.T) { + strategy := SourceBuildStrategy{ + Image: "sti-test-image", + SecurityClient: newFakeSecurityClient(true), + } + + build := mockSTIBuild() + + testCreateBuildPodAutonsUser(t, build, &strategy, + func(build *buildv1.Build, env corev1.EnvVar) { + build.Spec.Strategy.SourceStrategy.Env = append(build.Spec.Strategy.SourceStrategy.Env, env) + }, + ) +} diff --git a/pkg/build/controller/strategy/util.go b/pkg/build/controller/strategy/util.go index 5be5b65e1..abb9bfed1 100644 --- a/pkg/build/controller/strategy/util.go +++ b/pkg/build/controller/strategy/util.go @@ -148,7 +148,10 @@ func mountVolume(pod *corev1.Pod, container *corev1.Container, objName, mountPat break } } - mode := int32(0600) + mode := int32(0o600) + if container.SecurityContext == nil || container.SecurityContext.Privileged == nil || !*container.SecurityContext.Privileged { + mode = int32(0o644) // make sure unprivileged builders can read them + } if !volumeExists { volume := makeVolume(volumeName, objName, mode, fsType, volumeSource) pod.Spec.Volumes = append(pod.Spec.Volumes, volume) @@ -465,17 +468,19 @@ func updateConfigsForContainer(c corev1.Container, volumeName string, configDir return c } -// setupContainersStorage creates a volume that we'll use for holding images and working +// setupContainersStorage creates volumes that we'll use for holding images and working // root filesystems for building images. func setupContainersStorage(pod *corev1.Pod, container *corev1.Container) { - exists := false + rootExists, runExists := false, false for _, v := range pod.Spec.Volumes { if v.Name == "container-storage-root" { - exists = true - break + rootExists = true + } + if v.Name == "container-storage-run" { + runExists = true } } - if !exists { + if !rootExists { pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ Name: "container-storage-root", @@ -485,46 +490,26 @@ func setupContainersStorage(pod *corev1.Pod, container *corev1.Container) { }, ) } - container.VolumeMounts = append(container.VolumeMounts, - corev1.VolumeMount{ - Name: "container-storage-root", - MountPath: "/var/lib/containers/storage", - }, - ) - container.Env = append(container.Env, corev1.EnvVar{Name: "BUILD_STORAGE_DRIVER", Value: "overlay"}) -} - -// setupContainersNodeStorage borrows the appropriate storage directories from the node so -// that we can share layers that we're using with the node -func setupContainersNodeStorage(pod *corev1.Pod, container *corev1.Container) { - exists := false - for _, v := range pod.Spec.Volumes { - if v.Name == "node-storage-root" { - exists = true - break - } - } - if !exists { + if !runExists { pod.Spec.Volumes = append(pod.Spec.Volumes, - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 corev1.Volume{ - Name: "node-storage-root", + Name: "container-storage-run", VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/var/lib/containers/storage", - }, + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }, ) } container.VolumeMounts = append(container.VolumeMounts, - // TODO: run unprivileged https://github.com/openshift/origin/issues/662 corev1.VolumeMount{ - Name: "node-storage-root", - MountPath: "/var/lib/containers/storage", + Name: "container-storage-root", + MountPath: "/var/lib/containers", + }, + corev1.VolumeMount{ + Name: "container-storage-run", + MountPath: "/var/run/containers", }, ) - container.Env = append(container.Env, corev1.EnvVar{Name: "BUILD_STORAGE_DRIVER", Value: "overlay"}) } func addVolumeMountToContainers(conts []corev1.Container, mount corev1.VolumeMount) []corev1.Container { @@ -536,6 +521,60 @@ func addVolumeMountToContainers(conts []corev1.Container, mount corev1.VolumeMou return containers } +// securityContextForBuild returns a security context that limits the requested +// privileges because the build spec wanted an unprivileged build, or that sets +// the pod to be privileged otherwise. +func securityContextForBuild(vars []corev1.EnvVar) *corev1.SecurityContext { + privileged := true + for _, env := range vars { + if env.Name == "BUILD_PRIVILEGED" { + if b, err := strconv.ParseBool(env.Value); err == nil { + privileged = b + } else if i, err := strconv.Atoi(env.Value); err == nil { + privileged = i != 0 + } + } + } + securityContext := &corev1.SecurityContext{ + Privileged: &privileged, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeUnconfined, // default profile might ban unshare() + }, + } + if privileged { + uid, gid := int64(0), int64(0) + securityContext.RunAsUser = &uid + securityContext.RunAsGroup = &gid + } else { + securityContext.Capabilities = &corev1.Capabilities{ + Add: []corev1.Capability{ + "CAP_SETFCAP", + }, + Drop: []corev1.Capability{ + "CAP_KILL", + "CAP_MKNOD", + }, + } + } + return securityContext +} + +// Add annotations that should tell CRI-O to provide /dev/fuse in the pod's +// containers' device control group and in the /dev that the runtime will set +// up for them. +// Reference: https://github.com/openshift/machine-config-operator/pull/2805 +func setupBuilderDeviceFUSE(pod *corev1.Pod) { + metav1.SetMetaDataAnnotation(&pod.ObjectMeta, "io.openshift.builder", "") + metav1.SetMetaDataAnnotation(&pod.ObjectMeta, "io.kubernetes.cri-o.Devices", "/dev/fuse:rwm") +} + +// Request that the builder be run in a user namespace, mapped from host ID +// ranges chosen by the node. +func setupBuilderAutonsUser(build *buildv1.Build, vars []corev1.EnvVar, pod *corev1.Pod) { + metav1.SetMetaDataAnnotation(&pod.ObjectMeta, "io.openshift.builder", "") + metav1.SetMetaDataAnnotation(&pod.ObjectMeta, "io.kubernetes.cri-o.userns-mode", "auto:size=65536") +} + // setupBuildCAs mounts certificate authorities for the build from a predetermined ConfigMap. func setupBuildCAs(build *buildv1.Build, pod *corev1.Pod, additionalCAs map[string]string, internalRegistryHost string) { casExist := false @@ -721,7 +760,6 @@ func setupBuildVolumes(pod *corev1.Pod, buildVolumes []buildv1.BuildVolume, csiV } volumeSource.CSI = buildVolume.Source.CSI mountCSIVolume(pod, &pod.Spec.Containers[0], strings.ToLower(buildVolume.Name), PathForBuildVolume(buildVolume.Name), buildVolumeSuffix, &volumeSource) - default: return fmt.Errorf("encountered unsupported build volume source type %q", buildVolume.Source.Type) } diff --git a/pkg/build/controller/strategy/util_test.go b/pkg/build/controller/strategy/util_test.go index 222be33bc..68e94993a 100644 --- a/pkg/build/controller/strategy/util_test.go +++ b/pkg/build/controller/strategy/util_test.go @@ -4,10 +4,12 @@ import ( "fmt" "path/filepath" "reflect" + "strings" "testing" "unsafe" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" buildv1 "github.com/openshift/api/build/v1" ) @@ -813,3 +815,106 @@ func TestSetupBuildVolumes(t *testing.T) { }) } } + +type buildPodCreator interface { + CreateBuildPod(build *buildv1.Build, additionalCAs map[string]string, internalRegistryHost string) (*corev1.Pod, error) +} + +func testCreateBuildPodAutonsUser(t *testing.T, build *buildv1.Build, strategy buildPodCreator, addEnv func(build *buildv1.Build, env corev1.EnvVar)) { + for _, testCase := range []struct { + env string + expectError bool + privileged bool + annotations map[string]string + noAnnotations []string + }{ + { + env: "", + privileged: true, + noAnnotations: []string{ + "io.openshift.builder", + "io.kubernetes.cri-o.Devices", + "io.kubernetes.cri-o.userns-mode", + }, + }, + { + env: "BUILD_PRIVILEGED=0", + privileged: false, + annotations: map[string]string{ + "io.openshift.builder": "", + "io.kubernetes.cri-o.Devices": "/dev/fuse:rwm", + "io.kubernetes.cri-o.userns-mode": "auto:size=65536", + }, + }, + { + env: "BUILD_PRIVILEGED=42", + privileged: true, + noAnnotations: []string{ + "io.openshift.builder", + "io.kubernetes.cri-o.Devices", + "io.kubernetes.cri-o.userns-mode", + }, + }, + { + env: "BUILD_PRIVILEGED=false", + privileged: false, + annotations: map[string]string{ + "io.openshift.builder": "", + "io.kubernetes.cri-o.Devices": "/dev/fuse:rwm", + "io.kubernetes.cri-o.userns-mode": "auto:size=65536", + }, + }, + { + env: "BUILD_PRIVILEGED=true", + privileged: true, + noAnnotations: []string{ + "io.openshift.builder", + "io.kubernetes.cri-o.Devices", + "io.kubernetes.cri-o.userns-mode", + }, + }, + } { + t.Run(testCase.env, func(t *testing.T) { + build := build.DeepCopy() + for _, envVar := range strings.Split(testCase.env, ":") { + if env := strings.SplitN(envVar, "=", 2); len(env) > 1 { + addEnv(build, corev1.EnvVar{Name: env[0], Value: env[1]}) + } + } + actual, err := strategy.CreateBuildPod(build, nil, testInternalRegistryHost) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + for ctrIndex, ctr := range append(actual.Spec.Containers, actual.Spec.InitContainers...) { + sc := ctr.SecurityContext + if sc == nil { + t.Errorf("Container %d in pod spec has no SecurityContext", ctrIndex) + continue + } + if sc.Privileged == nil { + t.Errorf("Container %d in pod spec has no privileged field", ctrIndex) + continue + } + if *sc.Privileged != testCase.privileged { + t.Errorf("Expected privileged: %q to produce privileged=%v, got %v", testCase.env, testCase.privileged, *sc.Privileged) + } + } + for annotation, value := range testCase.annotations { + if !metav1.HasAnnotation(actual.ObjectMeta, annotation) { + t.Errorf("Expected a %q annotation, but don't see one", annotation) + continue + } + annotations := actual.ObjectMeta.GetAnnotations() + if val := annotations[annotation]; val != value { + t.Errorf("Annotation %q was expected to be %q, but was actually %q", annotation, value, val) + } + } + for _, annotation := range testCase.noAnnotations { + if metav1.HasAnnotation(actual.ObjectMeta, annotation) { + t.Errorf("Expected no %q annotation, but got one", annotation) + } + } + }) + } +}