From d9f792633d64f8dfe90d689ea7ab297750b0292e Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Thu, 28 Jul 2022 16:01:05 +0200 Subject: [PATCH] Add AddPodToVolume unit tests with SELinux --- .../cache/desired_state_of_world.go | 4 + .../cache/desired_state_of_world_test.go | 371 +++++++++++++++++- pkg/volume/testing/testing.go | 3 +- 3 files changed, 375 insertions(+), 3 deletions(-) diff --git a/pkg/kubelet/volumemanager/cache/desired_state_of_world.go b/pkg/kubelet/volumemanager/cache/desired_state_of_world.go index 28e9a79727b..d5e76ff3c54 100644 --- a/pkg/kubelet/volumemanager/cache/desired_state_of_world.go +++ b/pkg/kubelet/volumemanager/cache/desired_state_of_world.go @@ -304,6 +304,10 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( } } } + if !util.IsRWOP(volumeSpec) { + // Clear SELinux label for the volume with unsupported access modes. + seLinuxFileLabel = "" + } if seLinuxFileLabel != "" { seLinuxVolumesAdmitted.Add(1.0) } diff --git a/pkg/kubelet/volumemanager/cache/desired_state_of_world_test.go b/pkg/kubelet/volumemanager/cache/desired_state_of_world_test.go index 593228ef42c..c79a8d071bd 100644 --- a/pkg/kubelet/volumemanager/cache/desired_state_of_world_test.go +++ b/pkg/kubelet/volumemanager/cache/desired_state_of_world_test.go @@ -19,10 +19,12 @@ package cache import ( "testing" - "k8s.io/apimachinery/pkg/api/resource" - v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" volumetesting "k8s.io/kubernetes/pkg/volume/testing" "k8s.io/kubernetes/pkg/volume/util" @@ -597,6 +599,371 @@ func Test_AddPodToVolume_WithEmptyDirSizeLimit(t *testing.T) { verifyDesiredSizeLimitInVolumeDsw(t, pod2Name, pod2DesiredSizeLimitMap, dsw) } +// Calls AddPodToVolume() with a volume that support SELinux, but is ReadWriteMany. +// Verifies newly added pod/volume exists via PodExistsInVolume() without SELinux context +// VolumeExists() and GetVolumesToMount() and no errors. +func Test_AddPodToVolume_Positive_SELinuxNoRWOP(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() + // Arrange + plugins := []volume.VolumePlugin{ + &volumetesting.FakeBasicVolumePlugin{ + Plugin: volumetesting.FakeVolumePlugin{ + PluginName: "basic", + SupportsSELinux: true, + }, + }, + } + volumePluginMgr := volume.VolumePluginMgr{} + fakeVolumeHost := volumetesting.NewFakeVolumeHost(t, + "", /* rootDir */ + nil, /* kubeClient */ + nil, /* plugins */ + ) + volumePluginMgr.InitPlugins(plugins, nil /* prober */, fakeVolumeHost) + dsw := NewDesiredStateOfWorld(&volumePluginMgr) + seLinux := v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c1,c2", + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{ + SecurityContext: &v1.PodSecurityContext{ + SELinuxOptions: &seLinux, + }, + Volumes: []v1.Volume{ + { + Name: "volume-name", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "myClaim", + }, + }, + }, + }, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basicPV", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}, + }, + }, + } + podName := util.GetUniquePodName(pod) + seLinuxContainerContexts := []*v1.SELinuxOptions{&seLinux} + + // Act + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, seLinuxContainerContexts) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + verifyVolumeExistsDsw(t, generatedVolumeName, "" /* SELinux */, dsw) + verifyVolumeExistsInVolumesToMount( + t, generatedVolumeName, false /* expectReportedInUse */, dsw) + verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, "" /* SELinux */, dsw) + verifyVolumeExistsWithSpecNameInVolumeDsw(t, podName, volumeSpec.Name(), dsw) +} + +// Calls AddPodToVolume() with a volume that does not support SELinux. +// Verifies newly added pod/volume exists via PodExistsInVolume() without SELinux context +// VolumeExists() and GetVolumesToMount() and no errors. +func Test_AddPodToVolume_Positive_NoSELinuxPlugin(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() + // Arrange + plugins := []volume.VolumePlugin{ + &volumetesting.FakeBasicVolumePlugin{ + Plugin: volumetesting.FakeVolumePlugin{ + PluginName: "basic", + SupportsSELinux: false, + }, + }, + } + volumePluginMgr := volume.VolumePluginMgr{} + fakeVolumeHost := volumetesting.NewFakeVolumeHost(t, + "", /* rootDir */ + nil, /* kubeClient */ + nil, /* plugins */ + ) + volumePluginMgr.InitPlugins(plugins, nil /* prober */, fakeVolumeHost) + dsw := NewDesiredStateOfWorld(&volumePluginMgr) + seLinux := v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c1,c2", + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{ + SecurityContext: &v1.PodSecurityContext{ + SELinuxOptions: &seLinux, + }, + Volumes: []v1.Volume{ + { + Name: "volume-name", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "myClaim", + }, + }, + }, + }, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basicPV", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + }, + }, + } + podName := util.GetUniquePodName(pod) + seLinuxContainerContexts := []*v1.SELinuxOptions{&seLinux} + + // Act + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, seLinuxContainerContexts) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + verifyVolumeExistsDsw(t, generatedVolumeName, "" /* SELinux */, dsw) + verifyVolumeExistsInVolumesToMount( + t, generatedVolumeName, false /* expectReportedInUse */, dsw) + verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, "" /* SELinux */, dsw) + verifyVolumeExistsWithSpecNameInVolumeDsw(t, podName, volumeSpec.Name(), dsw) +} + +// Calls AddPodToVolume() twice to add two pods with the same SELinuxContext +// to the same ReadWriteOncePod PV. +// Verifies newly added pod/volume exists via PodExistsInVolume() +// VolumeExists() and GetVolumesToMount() and no errors. +func Test_AddPodToVolume_Positive_ExistingPodSameSELinuxRWOP(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() + // Arrange + plugins := []volume.VolumePlugin{ + &volumetesting.FakeBasicVolumePlugin{ + Plugin: volumetesting.FakeVolumePlugin{ + PluginName: "basic", + SupportsSELinux: true, + }, + }, + } + volumePluginMgr := volume.VolumePluginMgr{} + fakeVolumeHost := volumetesting.NewFakeVolumeHost(t, + "", /* rootDir */ + nil, /* kubeClient */ + nil, /* plugins */ + ) + volumePluginMgr.InitPlugins(plugins, nil /* prober */, fakeVolumeHost) + dsw := NewDesiredStateOfWorld(&volumePluginMgr) + seLinux := v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c1,c2", + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{ + SecurityContext: &v1.PodSecurityContext{ + SELinuxOptions: &seLinux, + }, + Volumes: []v1.Volume{ + { + Name: "volume-name", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "myClaim", + }, + }, + }, + }, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basicPV", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + }, + }, + } + podName := util.GetUniquePodName(pod) + seLinuxContainerContexts := []*v1.SELinuxOptions{&seLinux} + + // Act + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, seLinuxContainerContexts) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + verifyVolumeExistsDsw(t, generatedVolumeName, "system_u:object_r:container_file_t:s0:c1,c2", dsw) + verifyVolumeExistsInVolumesToMount( + t, generatedVolumeName, false /* expectReportedInUse */, dsw) + verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, "system_u:object_r:container_file_t:s0:c1,c2", dsw) + verifyVolumeExistsWithSpecNameInVolumeDsw(t, podName, volumeSpec.Name(), dsw) + + // Arrange: prepare a different pod with the same context + pod2 := pod.DeepCopy() + pod2.Name = "pod2" + pod2.UID = "pod2uid" + pod2Name := util.GetUniquePodName(pod) + + // Act + generatedVolumeName2, err := dsw.AddPodToVolume( + pod2Name, pod2, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, seLinuxContainerContexts) + // Assert + if err != nil { + t.Fatalf("Second AddPodToVolume failed. Expected: Actual: <%v>", err) + } + if generatedVolumeName2 != generatedVolumeName { + t.Errorf("Expected second generatedVolumeName %s, got %s", generatedVolumeName, generatedVolumeName2) + } + + verifyPodExistsInVolumeDsw(t, pod2Name, generatedVolumeName, "system_u:object_r:container_file_t:s0:c1,c2", dsw) +} + +// Calls AddPodToVolume() twice to add two pods with different SELinuxContext +// to the same ReadWriteOncePod PV. +// Verifies newly added pod/volume exists via PodExistsInVolume() +// VolumeExists() and GetVolumesToMount() and no errors. +func Test_AddPodToVolume_Negative_ExistingPodDifferentSELinuxRWOP(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() + // Arrange + plugins := []volume.VolumePlugin{ + &volumetesting.FakeBasicVolumePlugin{ + Plugin: volumetesting.FakeVolumePlugin{ + PluginName: "basic", + SupportsSELinux: true, + }, + }, + } + volumePluginMgr := volume.VolumePluginMgr{} + fakeVolumeHost := volumetesting.NewFakeVolumeHost(t, + "", /* rootDir */ + nil, /* kubeClient */ + nil, /* plugins */ + ) + volumePluginMgr.InitPlugins(plugins, nil /* prober */, fakeVolumeHost) + dsw := NewDesiredStateOfWorld(&volumePluginMgr) + seLinux1 := v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c1,c2", + } + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + UID: "pod1uid", + }, + Spec: v1.PodSpec{ + SecurityContext: &v1.PodSecurityContext{ + SELinuxOptions: &seLinux1, + }, + Volumes: []v1.Volume{ + { + Name: "volume-name", + VolumeSource: v1.VolumeSource{ + PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ + ClaimName: "myClaim", + }, + }, + }, + }, + }, + } + + volumeSpec := &volume.Spec{ + PersistentVolume: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "basicPV", + }, + Spec: v1.PersistentVolumeSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + }, + }, + } + podName := util.GetUniquePodName(pod) + seLinuxContainerContexts := []*v1.SELinuxOptions{&seLinux1} + + // Act + generatedVolumeName, err := dsw.AddPodToVolume( + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, seLinuxContainerContexts) + + // Assert + if err != nil { + t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) + } + + verifyVolumeExistsDsw(t, generatedVolumeName, "system_u:object_r:container_file_t:s0:c1,c2", dsw) + verifyVolumeExistsInVolumesToMount( + t, generatedVolumeName, false /* expectReportedInUse */, dsw) + verifyPodExistsInVolumeDsw(t, podName, generatedVolumeName, "system_u:object_r:container_file_t:s0:c1,c2", dsw) + verifyVolumeExistsWithSpecNameInVolumeDsw(t, podName, volumeSpec.Name(), dsw) + + // Arrange: prepare a different pod with the same context + seLinux2 := v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c3,c4", + } + seLinuxContainerContexts2 := []*v1.SELinuxOptions{&seLinux2} + pod2 := pod.DeepCopy() + pod2.Name = "pod2" + pod2.UID = "pod2uid" + pod2.Spec.SecurityContext.SELinuxOptions = &seLinux2 + pod2Name := util.GetUniquePodName(pod) + + // Act + _, err = dsw.AddPodToVolume( + pod2Name, pod2, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, seLinuxContainerContexts2) + // Assert + if err == nil { + t.Fatalf("Second AddPodToVolume succeeded, expected a failure") + } + // Verify the original SELinux context is still in DSW + verifyPodExistsInVolumeDsw(t, pod2Name, generatedVolumeName, "system_u:object_r:container_file_t:s0:c1,c2", dsw) +} + func verifyVolumeExistsDsw( t *testing.T, expectedVolumeName v1.UniqueVolumeName, expectedSELinuxContext string, dsw DesiredStateOfWorld) { volumeExists := dsw.VolumeExists(expectedVolumeName, expectedSELinuxContext) diff --git a/pkg/volume/testing/testing.go b/pkg/volume/testing/testing.go index 65949ad148e..7e2f9cac554 100644 --- a/pkg/volume/testing/testing.go +++ b/pkg/volume/testing/testing.go @@ -181,6 +181,7 @@ type FakeVolumePlugin struct { LimitKey string ProvisionDelaySeconds int SupportsRemount bool + SupportsSELinux bool DisableNodeExpansion bool // default to false which means it is attachable by default @@ -285,7 +286,7 @@ func (plugin *FakeVolumePlugin) SupportsBulkVolumeVerification() bool { } func (plugin *FakeVolumePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { - return false, nil + return plugin.SupportsSELinux, nil } func (plugin *FakeVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {