diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 99a6df8296a..51920ebca0f 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -31,6 +31,10 @@ const ( // OCISeccompBPFHookAnnotation is the annotation used by the OCI seccomp BPF hook for tracing container syscalls OCISeccompBPFHookAnnotation = "io.containers.trace-syscall" + + // TrySkipVolumeSELinuxLabelAnnotation is the annotation used for optionally skipping relabeling a volume + // with the specified SELinux label. The relabeling will be skipped if the top layer is already labeled correctly. + TrySkipVolumeSELinuxLabelAnnotation = "io.kubernetes.cri-o.TrySkipVolumeSELinuxLabel" ) var AllAllowedAnnotations = []string{ @@ -43,4 +47,5 @@ var AllAllowedAnnotations = []string{ IRQLoadBalancingAnnotation, OCISeccompBPFHookAnnotation, rdt.RdtContainerAnnotation, + TrySkipVolumeSELinuxLabelAnnotation, } diff --git a/server/container_create.go b/server/container_create.go index 5a9d7c8dcec..86e63c4e6b1 100644 --- a/server/container_create.go +++ b/server/container_create.go @@ -131,7 +131,7 @@ func addImageVolumes(ctx context.Context, rootfs string, s *Server, containerInf return nil, err1 } if mountLabel != "" { - if err1 := securityLabel(fp, mountLabel, true); err1 != nil { + if err1 := securityLabel(fp, mountLabel, true, false); err1 != nil { return nil, err1 } } @@ -143,7 +143,7 @@ func addImageVolumes(ctx context.Context, rootfs string, s *Server, containerInf } // Label the source with the sandbox selinux mount label if mountLabel != "" { - if err1 := securityLabel(src, mountLabel, true); err1 != nil { + if err1 := securityLabel(src, mountLabel, true, false); err1 != nil { return nil, err1 } } @@ -235,7 +235,7 @@ func setupContainerUser(ctx context.Context, specgen *generate.Generator, rootfs return err } if passwdPath != "" { - if err := securityLabel(passwdPath, mountLabel, false); err != nil { + if err := securityLabel(passwdPath, mountLabel, false, false); err != nil { return err } diff --git a/server/container_create_linux.go b/server/container_create_linux.go index 0b59fabeb9f..b3c0ab0ce86 100644 --- a/server/container_create_linux.go +++ b/server/container_create_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package server @@ -135,6 +136,14 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrIface.Contai // eventually, we'd like to access all of these variables through the interface themselves, and do most // of the translation between CRI config -> oci/storage container in the container package + + // TODO: eventually, this should be in the container package, but it's going through a lot of churn + // and SpecAddAnnotations is already being passed too many arguments + // Filter early so any use of the annotations don't use the wrong values + if err := s.Runtime().FilterDisallowedAnnotations(sb.RuntimeHandler(), ctr.Config().Annotations); err != nil { + return nil, err + } + containerID := ctr.ID() containerName := ctr.Name() containerConfig := ctr.Config() @@ -268,7 +277,12 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrIface.Contai processLabel = "" } - containerVolumes, ociMounts, err := addOCIBindMounts(ctx, mountLabel, containerConfig, specgen, s.config.RuntimeConfig.BindMountPrefix, s.config.AbsentMountSourcesToReject) + maybeRelabel := false + if val, present := sb.Annotations()[crioann.TrySkipVolumeSELinuxLabelAnnotation]; present && val == "true" { + maybeRelabel = true + } + + containerVolumes, ociMounts, err := addOCIBindMounts(ctx, ctr, mountLabel, s.config.RuntimeConfig.BindMountPrefix, s.config.AbsentMountSourcesToReject, maybeRelabel) if err != nil { return nil, err } @@ -525,7 +539,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrIface.Contai options = []string{"ro"} } if sb.ResolvPath() != "" { - if err := securityLabel(sb.ResolvPath(), mountLabel, false); err != nil { + if err := securityLabel(sb.ResolvPath(), mountLabel, false, false); err != nil { return nil, err } ctr.SpecAddMount(rspec.Mount{ @@ -537,7 +551,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrIface.Contai } if sb.HostnamePath() != "" { - if err := securityLabel(sb.HostnamePath(), mountLabel, false); err != nil { + if err := securityLabel(sb.HostnamePath(), mountLabel, false, false); err != nil { return nil, err } ctr.SpecAddMount(rspec.Mount{ @@ -592,12 +606,6 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrIface.Contai } }() - // TODO: eventually, this should be in the container package, but it's going through a lot of churn - // and SpecAddAnnotations is already passed too many arguments - if err := s.Runtime().FilterDisallowedAnnotations(sb.RuntimeHandler(), ctr.Config().Annotations); err != nil { - return nil, err - } - // Get RDT class rdtClass, err := s.Config().Rdt().ContainerClassFromAnnotations(metadata.Name, containerConfig.Annotations, sb.Annotations()) if err != nil { @@ -796,7 +804,7 @@ func setupWorkingDirectory(rootfs, mountLabel, containerCwd string) error { return err } if mountLabel != "" { - if err1 := securityLabel(fp, mountLabel, false); err1 != nil { + if err1 := securityLabel(fp, mountLabel, false, false); err1 != nil { return err1 } } @@ -826,9 +834,11 @@ func clearReadOnly(m *rspec.Mount) { m.Options = append(m.Options, "rw") } -func addOCIBindMounts(ctx context.Context, mountLabel string, containerConfig *types.ContainerConfig, specgen *generate.Generator, bindMountPrefix string, absentMountSourcesToReject []string) ([]oci.ContainerVolume, []rspec.Mount, error) { +func addOCIBindMounts(ctx context.Context, ctr ctrIface.Container, mountLabel, bindMountPrefix string, absentMountSourcesToReject []string, maybeRelabel bool) ([]oci.ContainerVolume, []rspec.Mount, error) { volumes := []oci.ContainerVolume{} ociMounts := []rspec.Mount{} + containerConfig := ctr.Config() + specgen := ctr.Spec() mounts := containerConfig.Mounts // Sort mounts in number of parts. This ensures that high level mounts don't @@ -934,7 +944,7 @@ func addOCIBindMounts(ctx context.Context, mountLabel string, containerConfig *t } if m.SelinuxRelabel { - if err := securityLabel(src, mountLabel, false); err != nil { + if err := securityLabel(src, mountLabel, false, maybeRelabel); err != nil { return nil, nil, err } } diff --git a/server/container_create_linux_test.go b/server/container_create_linux_test.go index 27c0d2375fb..c99ba792371 100644 --- a/server/container_create_linux_test.go +++ b/server/container_create_linux_test.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux package server @@ -6,28 +7,38 @@ import ( "context" "testing" + "github.com/cri-o/cri-o/pkg/container" "github.com/cri-o/cri-o/server/cri/types" - "github.com/opencontainers/runtime-tools/generate" ) func TestAddOCIBindsForDev(t *testing.T) { - specgen, err := generate.New("linux") + ctr, err := container.New() if err != nil { t.Error(err) } - config := &types.ContainerConfig{ + if err := ctr.SetConfig(&types.ContainerConfig{ Mounts: []*types.Mount{ { ContainerPath: "/dev", HostPath: "/dev", }, }, + Metadata: &types.ContainerMetadata{ + Name: "testctr", + }, + }, &types.PodSandboxConfig{ + Metadata: &types.PodSandboxMetadata{ + Name: "testpod", + }, + }); err != nil { + t.Error(err) } - _, binds, err := addOCIBindMounts(context.Background(), "", config, &specgen, "", nil) + + _, binds, err := addOCIBindMounts(context.Background(), ctr, "", "", nil, false) if err != nil { t.Error(err) } - for _, m := range specgen.Mounts() { + for _, m := range ctr.Spec().Mounts() { if m.Destination == "/dev" { t.Error("/dev shouldn't be in the spec if it's bind mounted from kube") } @@ -45,19 +56,29 @@ func TestAddOCIBindsForDev(t *testing.T) { } func TestAddOCIBindsForSys(t *testing.T) { - specgen, err := generate.New("linux") + ctr, err := container.New() if err != nil { t.Error(err) } - config := &types.ContainerConfig{ + if err := ctr.SetConfig(&types.ContainerConfig{ Mounts: []*types.Mount{ { ContainerPath: "/sys", HostPath: "/sys", }, }, + Metadata: &types.ContainerMetadata{ + Name: "testctr", + }, + }, &types.PodSandboxConfig{ + Metadata: &types.PodSandboxMetadata{ + Name: "testpod", + }, + }); err != nil { + t.Error(err) } - _, binds, err := addOCIBindMounts(context.Background(), "", config, &specgen, "", nil) + + _, binds, err := addOCIBindMounts(context.Background(), ctr, "", "", nil, false) if err != nil { t.Error(err) } diff --git a/server/label_linux.go b/server/label_linux.go index 0d687800296..b359985152d 100644 --- a/server/label_linux.go +++ b/server/label_linux.go @@ -5,10 +5,20 @@ import ( "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) -func securityLabel(path, secLabel string, shared bool) error { +func securityLabel(path, secLabel string, shared, maybeRelabel bool) error { + if maybeRelabel { + currentLabel, err := label.FileLabel(path) + if err == nil && currentLabel == secLabel { + logrus.Debugf( + "Skipping relabel for %s, as TrySkipVolumeSELinuxRelabel is true and the label of the top level of the volume is already correct", + path) + return nil + } + } if err := label.Relabel(path, secLabel, shared); err != nil && !errors.Is(err, unix.ENOTSUP) { return fmt.Errorf("relabel failed %s: %v", path, err) } diff --git a/server/label_unsupported.go b/server/label_unsupported.go index 39ebd94a5f4..a3eb836df4f 100644 --- a/server/label_unsupported.go +++ b/server/label_unsupported.go @@ -1,7 +1,8 @@ +//go:build !linux // +build !linux package server -func securityLabel(path string, seclabel string, shared bool) error { +func securityLabel(path string, seclabel string, shared, maybeRelabel bool) error { return nil } diff --git a/test/devices.bats b/test/devices.bats index 369947e75ab..24dad7fee81 100644 --- a/test/devices.bats +++ b/test/devices.bats @@ -15,18 +15,6 @@ function teardown() { cleanup_test } -function create_device_runtime() { - cat << EOF > "$CRIO_CONFIG_DIR/01-device.conf" -[crio.runtime] -default_runtime = "device" -[crio.runtime.runtimes.device] -runtime_path = "$RUNTIME_BINARY_PATH" -runtime_root = "$RUNTIME_ROOT" -runtime_type = "$RUNTIME_TYPE" -allowed_annotations = ["io.kubernetes.cri-o.Devices"] -EOF -} - @test "additional devices support" { OVERRIDE_OPTIONS="--additional-devices /dev/null:/dev/qifoo:rwm" start_crio pod_id=$(crictl runp "$TESTDATA"/sandbox_config.json) @@ -80,7 +68,7 @@ EOF } @test "annotation devices support" { - create_device_runtime + create_runtime_with_allowed_annotation "device" "io.kubernetes.cri-o.Devices" start_crio jq ' .annotations."io.kubernetes.cri-o.Devices" = "/dev/null:/dev/qifoo:rwm"' \ @@ -110,7 +98,7 @@ EOF } @test "annotation should override configured additional_devices" { - create_device_runtime + create_runtime_with_allowed_annotation "device" "io.kubernetes.cri-o.Devices" OVERRIDE_OPTIONS="--additional-devices /dev/urandom:/dev/qifoo:rwm" start_crio @@ -128,7 +116,7 @@ EOF } @test "annotation should configure multiple devices" { - create_device_runtime + create_runtime_with_allowed_annotation "device" "io.kubernetes.cri-o.Devices" start_crio jq ' .annotations."io.kubernetes.cri-o.Devices" = "/dev/null:/dev/qifoo:rwm,/dev/urandom:/dev/peterfoo:rwm"' \ @@ -147,7 +135,7 @@ EOF } @test "annotation should fail if one device is invalid" { - create_device_runtime + create_runtime_with_allowed_annotation "device" "io.kubernetes.cri-o.Devices" start_crio jq ' .annotations."io.kubernetes.cri-o.Devices" = "/dev/null:/dev/qifoo:rwm,/dove/null"' \ diff --git a/test/helpers.bash b/test/helpers.bash index 07a7549698e..752b666e2e8 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -548,3 +548,17 @@ function fail() { function is_cgroup_v2() { test "$(stat -f -c%T /sys/fs/cgroup)" = "cgroup2fs" } + +function create_runtime_with_allowed_annotation() { + local NAME="$1" + local ANNOTATION="$2" + cat <"$CRIO_CONFIG_DIR/01-$NAME.conf" +[crio.runtime] +default_runtime = "$NAME" +[crio.runtime.runtimes.$NAME] +runtime_path = "$RUNTIME_BINARY_PATH" +runtime_root = "$RUNTIME_ROOT" +runtime_type = "$RUNTIME_TYPE" +allowed_annotations = ["$ANNOTATION"] +EOF +} diff --git a/test/selinux.bats b/test/selinux.bats index 78b7303167d..5d66306cdca 100644 --- a/test/selinux.bats +++ b/test/selinux.bats @@ -20,3 +20,48 @@ function teardown() { ctr_id=$(crictl create "$pod_id" "$TESTDATA"/container_redis.json "$TESTDIR"/sandbox.json) crictl start "$ctr_id" } + +@test "selinux skips relabeling if TrySkipVolumeSELinuxLabel annotation is present" { + if [[ $(getenforce) != "Enforcing" ]]; then + skip "not enforcing" + fi + VOLUME="$TESTDIR"/dir + mkdir "$VOLUME" + touch "$VOLUME"/file + + create_runtime_with_allowed_annotation "selinux" "io.kubernetes.cri-o.TrySkipVolumeSELinuxLabel" + start_crio + + jq ' .linux.security_context.selinux_options = {"level": "s0:c100,c200"} + | .annotations["io.kubernetes.cri-o.TrySkipVolumeSELinuxLabel"] = "true"' \ + "$TESTDATA"/sandbox_config.json > "$TESTDIR"/sandbox.json + + jq --arg path "$VOLUME" \ + ' .mounts = [ { + host_path: $path, + container_path: "/tmp/path", + selinux_relabel: true + } ]' \ + "$TESTDATA"/container_redis.json > "$TESTDIR"/container.json + + pod_id=$(crictl runp "$TESTDIR"/sandbox.json) + ctr_id=$(crictl create "$pod_id" "$TESTDIR"/container.json "$TESTDIR"/sandbox.json) + + crictl rm "$ctr_id" + + # shellcheck disable=SC2012 + oldlabel=$(ls -Z "$VOLUME" | awk '{ printf $1 }') + + # Label file, but not top dir. This will show us the directory was not relabeled (as expected) + chcon --reference "$TESTDIR"/container.json "$VOLUME"/file # || \ + + # shellcheck disable=SC2012 + label=$(ls -Z "$VOLUME" | awk '{ printf $1 }') + [[ "$oldlabel" != "$label" ]] + + # Recreate. Since top level is already labeled right, there won't be a relabel. + ctr_id=$(crictl create "$pod_id" "$TESTDIR"/container.json "$TESTDIR"/sandbox.json) + # shellcheck disable=SC2012 + newlabel=$(ls -Z "$VOLUME" | awk '{ printf $1 }') + [[ "$label" == "$newlabel" ]] +} diff --git a/test/shm_size.bats b/test/shm_size.bats index b6dc7c75438..41ca968e49f 100644 --- a/test/shm_size.bats +++ b/test/shm_size.bats @@ -23,7 +23,7 @@ EOF } @test "check /dev/shm is changed" { - create_shmsize_runtime + create_runtime_with_allowed_annotation "shmsize" "io.kubernetes.cri-o.ShmSize" start_crio # Run base container to ensure it creates at all pod_id=$(crictl runp <(jq '.annotations."io.kubernetes.cri-o.ShmSize" = "16Mi"' "$TESTDATA"/sandbox_config.json)) @@ -40,7 +40,7 @@ EOF } @test "check /dev/shm fails with incorrect values" { - create_shmsize_runtime + create_runtime_with_allowed_annotation "shmsize" "io.kubernetes.cri-o.ShmSize" start_crio # Ensure pod fails if /dev/shm size is negative ! crictl runp <(jq '.annotations."io.kubernetes.cri-o.ShmSize" = "-1"' "$TESTDATA"/sandbox_config.json)