diff --git a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go index 5b9dd66ac4b..aec172bf994 100644 --- a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go +++ b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator_test.go @@ -31,7 +31,9 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" + featuregatetesting "k8s.io/component-base/featuregate/testing" csitrans "k8s.io/csi-translation-lib" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/kubelet/configmap" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" kubepod "k8s.io/kubernetes/pkg/kubelet/pod" @@ -1067,6 +1069,195 @@ func TestCheckVolumeFSResize(t *testing.T) { } } +func TestCheckVolumeSELinux(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ReadWriteOncePod, true)() + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)() + fullOpts := &v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c1,c2", + } + differentFullOpts := &v1.SELinuxOptions{ + User: "system_u", + Role: "object_r", + Type: "container_t", + Level: "s0:c9998,c9999", + } + partialOpts := &v1.SELinuxOptions{ + Level: "s0:c3,c4", + } + + testcases := []struct { + name string + accessModes []v1.PersistentVolumeAccessMode + existingContainerSELinuxOpts *v1.SELinuxOptions + newContainerSELinuxOpts *v1.SELinuxOptions + pluginSupportsSELinux bool + expectError bool + expectedContext string + }{ + { + name: "RWOP with plugin with SELinux with full context in pod", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + newContainerSELinuxOpts: fullOpts, + pluginSupportsSELinux: true, + expectedContext: "system_u:object_r:container_file_t:s0:c1,c2", + }, + { + name: "RWOP with plugin with SELinux with partial context in pod", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + newContainerSELinuxOpts: partialOpts, + pluginSupportsSELinux: true, + expectedContext: "system_u:object_r:container_file_t:s0:c3,c4", + }, + { + name: "RWX with plugin with SELinux with fill context in pod", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}, + newContainerSELinuxOpts: fullOpts, + pluginSupportsSELinux: true, + expectedContext: "", // RWX volumes don't support SELinux + }, + { + name: "RWOP with plugin with no SELinux with fill context in pod", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + newContainerSELinuxOpts: fullOpts, + pluginSupportsSELinux: false, + expectedContext: "", // plugin doesn't support SELinux + }, + { + name: "RWOP with plugin with SELinux with no context in pod", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + newContainerSELinuxOpts: nil, + pluginSupportsSELinux: true, + expectedContext: "", + }, + { + name: "RWOP with plugin with SELinux with full context in pod with existing pod", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + existingContainerSELinuxOpts: fullOpts, + newContainerSELinuxOpts: fullOpts, + pluginSupportsSELinux: true, + expectedContext: "system_u:object_r:container_file_t:s0:c1,c2", + }, + { + name: "mismatched SELinux with RWX - success", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}, + existingContainerSELinuxOpts: fullOpts, + newContainerSELinuxOpts: differentFullOpts, + pluginSupportsSELinux: true, + expectedContext: "", + }, + { + name: "mismatched SELinux with RWOP - failure", + accessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOncePod}, + existingContainerSELinuxOpts: fullOpts, + newContainerSELinuxOpts: differentFullOpts, + pluginSupportsSELinux: true, + expectError: true, + // The original seLinuxOpts are kept in DSW + expectedContext: "system_u:object_r:container_file_t:s0:c1,c2", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dswp-test-volume-name", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{RBD: &v1.RBDPersistentVolumeSource{}}, + Capacity: volumeCapacity(1), + ClaimRef: &v1.ObjectReference{Namespace: "ns", Name: "file-bound"}, + AccessModes: tc.accessModes, + }, + } + pvc := &v1.PersistentVolumeClaim{ + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: pv.Name, + Resources: v1.ResourceRequirements{ + Requests: pv.Spec.Capacity, + }, + AccessModes: tc.accessModes, + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + Capacity: pv.Spec.Capacity, + }, + } + + container := v1.Container{ + SecurityContext: &v1.SecurityContext{ + SELinuxOptions: nil, + }, + VolumeMounts: []v1.VolumeMount{ + { + Name: pv.Name, + MountPath: "/mnt", + }, + }, + } + + fakeVolumePluginMgr, plugin := volumetesting.GetTestKubeletVolumePluginMgr(t) + plugin.SupportsSELinux = tc.pluginSupportsSELinux + dswp, fakePodManager, fakeDSW, _, _ := createDswpWithVolumeWithCustomPluginMgr(t, pv, pvc, fakeVolumePluginMgr) + + var existingPod *v1.Pod + if tc.existingContainerSELinuxOpts != nil { + // Add existing pod + volume + existingContainer := container + existingContainer.SecurityContext.SELinuxOptions = tc.existingContainerSELinuxOpts + existingPod = createPodWithVolume("dswp-old-pod", "dswp-test-volume-name", "file-bound", []v1.Container{existingContainer}) + fakePodManager.AddPod(existingPod) + dswp.findAndAddNewPods() + } + + newContainer := container + newContainer.SecurityContext.SELinuxOptions = tc.newContainerSELinuxOpts + newPod := createPodWithVolume("dswp-test-pod", "dswp-test-volume-name", "file-bound", []v1.Container{newContainer}) + + // Act - add the new Pod + fakePodManager.AddPod(newPod) + dswp.findAndAddNewPods() + + // Assert + + // Check the global volume state + uniquePodName := types.UniquePodName(newPod.UID) + uniqueVolumeName := v1.UniqueVolumeName("fake-plugin/" + newPod.Spec.Volumes[0].Name) + volumeExists := fakeDSW.VolumeExists(uniqueVolumeName, tc.expectedContext) + if !volumeExists { + t.Errorf( + "VolumeExists(%q) failed. Expected: Actual: <%v>", + uniqueVolumeName, + volumeExists) + } + + // Check the Pod local volume state + podExistsInVolume := fakeDSW.PodExistsInVolume(uniquePodName, uniqueVolumeName, tc.expectedContext) + if !podExistsInVolume && !tc.expectError { + t.Errorf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } + if podExistsInVolume && tc.expectError { + t.Errorf( + "DSW PodExistsInVolume returned incorrect value. Expected: Actual: <%v>", + podExistsInVolume) + } + errors := fakeDSW.GetPodsWithErrors() + if tc.expectError && len(errors) == 0 { + t.Errorf("Expected Pod error, got none") + } + if !tc.expectError && len(errors) > 0 { + t.Errorf("Unexpected Pod errors: %v", errors) + } + verifyVolumeExistsInVolumesToMount(t, uniqueVolumeName, false /* expectReportedInUse */, fakeDSW) + }) + } +} + func createResizeRelatedVolumes(volumeMode *v1.PersistentVolumeMode) (pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) { pv = &v1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ @@ -1175,7 +1366,7 @@ func createPodWithVolume(pod, pv, pvc string, containers []v1.Container) *v1.Pod return &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: pod, - UID: "dswp-test-pod-uid", + UID: kubetypes.UID(pod + "-uid"), Namespace: "dswp-test", }, Spec: v1.PodSpec{ diff --git a/pkg/volume/util/util_test.go b/pkg/volume/util/util_test.go index 7df0fc10efd..fe46963afbb 100644 --- a/pkg/volume/util/util_test.go +++ b/pkg/volume/util/util_test.go @@ -28,7 +28,10 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" _ "k8s.io/kubernetes/pkg/apis/core/install" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/util/slice" "k8s.io/kubernetes/pkg/volume" utilptr "k8s.io/utils/pointer"