diff --git a/pkg/kubelet/volumemanager/cache/actual_state_of_world.go b/pkg/kubelet/volumemanager/cache/actual_state_of_world.go index 40c7d943c7e..f20ae8be04b 100644 --- a/pkg/kubelet/volumemanager/cache/actual_state_of_world.go +++ b/pkg/kubelet/volumemanager/cache/actual_state_of_world.go @@ -27,7 +27,9 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/operationexecutor" @@ -73,7 +75,7 @@ type ActualStateOfWorld interface { // global mount point prior to detach. // If a volume with the name volumeName does not exist in the list of // attached volumes, an error is returned. - SetDeviceMountState(volumeName v1.UniqueVolumeName, deviceMountState operationexecutor.DeviceMountState, devicePath, deviceMountPath string) error + SetDeviceMountState(volumeName v1.UniqueVolumeName, deviceMountState operationexecutor.DeviceMountState, devicePath, deviceMountPath, seLinuxMountContext string) error // DeletePodFromVolume removes the given pod from the given volume in the // cache indicating the volume has been successfully unmounted from the pod. @@ -107,7 +109,7 @@ type ActualStateOfWorld interface { // volumes, depend on this to update the contents of the volume. // All volume mounting calls should be idempotent so a second mount call for // volumes that do not need to update contents should not fail. - PodExistsInVolume(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName, desiredVolumeSize resource.Quantity) (bool, string, error) + PodExistsInVolume(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName, desiredVolumeSize resource.Quantity, seLinuxLabel string) (bool, string, error) // PodRemovedFromVolume returns true if the given pod does not exist in the list of // mountedPods for the given volume in the cache, indicating that the pod has @@ -182,6 +184,11 @@ type AttachedVolume struct { // DeviceMountState indicates if device has been globally mounted or is not. DeviceMountState operationexecutor.DeviceMountState + + // SELinuxMountContext is the context with that the volume is globally mounted + // (via -o context=XYZ mount option). If empty, the volume is not mounted with + // "-o context=". + SELinuxMountContext string } // DeviceMayBeMounted returns true if device is mounted in global path or is in @@ -288,6 +295,11 @@ type attachedVolume struct { // persistentVolumeSize records size of the volume when pod was started or // size after successful completion of volume expansion operation. persistentVolumeSize *resource.Quantity + + // seLinuxMountContext is the context with that the volume is mounted to global directory + // (via -o context=XYZ mount option). If nil, the volume is not mounted. If "", the volume is + // mounted without "-o context=". + seLinuxMountContext *string } // The mountedPod object represents a pod for which the kubelet volume manager @@ -333,6 +345,11 @@ type mountedPod struct { // - VolumeMounted: means volume for pod has been successfully mounted // - VolumeMountUncertain: means volume for pod may not be mounted, but it must be unmounted volumeMountStateForPod operationexecutor.VolumeMountState + + // seLinuxMountContext is the context with that the volume is mounted to Pod directory + // (via -o context=XYZ mount option). If nil, the volume is not mounted. If "", the volume is + // mounted without "-o context=". + seLinuxMountContext string } func (asw *actualStateOfWorld) MarkVolumeAsAttached( @@ -465,13 +482,13 @@ func (asw *actualStateOfWorld) MarkVolumeAsUnmounted( } func (asw *actualStateOfWorld) MarkDeviceAsMounted( - volumeName v1.UniqueVolumeName, devicePath, deviceMountPath string) error { - return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceGloballyMounted, devicePath, deviceMountPath) + volumeName v1.UniqueVolumeName, devicePath, deviceMountPath, seLinuxMountContext string) error { + return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceGloballyMounted, devicePath, deviceMountPath, seLinuxMountContext) } func (asw *actualStateOfWorld) MarkDeviceAsUncertain( - volumeName v1.UniqueVolumeName, devicePath, deviceMountPath string) error { - return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceMountUncertain, devicePath, deviceMountPath) + volumeName v1.UniqueVolumeName, devicePath, deviceMountPath, seLinuxMountContext string) error { + return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceMountUncertain, devicePath, deviceMountPath, seLinuxMountContext) } func (asw *actualStateOfWorld) MarkVolumeMountAsUncertain(markVolumeOpts operationexecutor.MarkVolumeOpts) error { @@ -481,7 +498,7 @@ func (asw *actualStateOfWorld) MarkVolumeMountAsUncertain(markVolumeOpts operati func (asw *actualStateOfWorld) MarkDeviceAsUnmounted( volumeName v1.UniqueVolumeName) error { - return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceNotMounted, "", "") + return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceNotMounted, "", "", "") } func (asw *actualStateOfWorld) GetDeviceMountState(volumeName v1.UniqueVolumeName) operationexecutor.DeviceMountState { @@ -629,6 +646,7 @@ func (asw *actualStateOfWorld) AddPodToVolume(markVolumeOpts operationexecutor.M volumeGidValue: volumeGidValue, volumeSpec: volumeSpec, volumeMountStateForPod: markVolumeOpts.VolumeMountState, + seLinuxMountContext: markVolumeOpts.SELinuxMountContext, } } @@ -646,6 +664,15 @@ func (asw *actualStateOfWorld) AddPodToVolume(markVolumeOpts operationexecutor.M podObj.mounter = mounter } asw.attachedVolumes[volumeName].mountedPods[podName] = podObj + if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + // Store the mount context also in the AttachedVolume to have a global volume context + // for a quick comparison in PodExistsInVolume. + if volumeObj.seLinuxMountContext == nil { + volumeObj.seLinuxMountContext = &markVolumeOpts.SELinuxMountContext + asw.attachedVolumes[volumeName] = volumeObj + } + } + return nil } @@ -685,7 +712,7 @@ func (asw *actualStateOfWorld) MarkRemountRequired( } func (asw *actualStateOfWorld) SetDeviceMountState( - volumeName v1.UniqueVolumeName, deviceMountState operationexecutor.DeviceMountState, devicePath, deviceMountPath string) error { + volumeName v1.UniqueVolumeName, deviceMountState operationexecutor.DeviceMountState, devicePath, deviceMountPath, seLinuxMountContext string) error { asw.Lock() defer asw.Unlock() @@ -701,6 +728,11 @@ func (asw *actualStateOfWorld) SetDeviceMountState( if devicePath != "" { volumeObj.devicePath = devicePath } + if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + if seLinuxMountContext != "" { + volumeObj.seLinuxMountContext = &seLinuxMountContext + } + } asw.attachedVolumes[volumeName] = volumeObj return nil } @@ -776,7 +808,7 @@ func (asw *actualStateOfWorld) DeleteVolume(volumeName v1.UniqueVolumeName) erro return nil } -func (asw *actualStateOfWorld) PodExistsInVolume(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName, desiredVolumeSize resource.Quantity) (bool, string, error) { +func (asw *actualStateOfWorld) PodExistsInVolume(podName volumetypes.UniquePodName, volumeName v1.UniqueVolumeName, desiredVolumeSize resource.Quantity, seLinuxLabel string) (bool, string, error) { asw.RLock() defer asw.RUnlock() @@ -785,6 +817,22 @@ func (asw *actualStateOfWorld) PodExistsInVolume(podName volumetypes.UniquePodNa return false, "", newVolumeNotAttachedError(volumeName) } + if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + if volumeObj.seLinuxMountContext != nil { + // The volume is mounted, check its SELinux context mount option + if *volumeObj.seLinuxMountContext != seLinuxLabel { + fullErr := newSELinuxMountMismatchError(volumeName) + if util.IsRWOP(volumeObj.spec) { + return false, volumeObj.devicePath, fullErr + } else { + // This is not an error yet, but it will be when support for RWO and RWX volumes is added + // TODO: bump some metric here + klog.V(4).ErrorS(fullErr, "Please report this error in https://github.com/kubernetes/enhancements/issues/1710, together with full Pod yaml file") + } + } + } + } + podObj, podExists := volumeObj.mountedPods[podName] if podExists { // if volume mount was uncertain we should keep trying to mount the volume @@ -905,7 +953,6 @@ func (asw *actualStateOfWorld) GetAllMountedVolumes() []MountedVolume { mountedVolume, getMountedVolume(&podObj, &volumeObj)) } - } } @@ -1010,15 +1057,22 @@ func (asw *actualStateOfWorld) SyncReconstructedVolume(volumeName v1.UniqueVolum func (asw *actualStateOfWorld) newAttachedVolume( attachedVolume *attachedVolume) AttachedVolume { + seLinuxMountContext := "" + if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + if attachedVolume.seLinuxMountContext != nil { + seLinuxMountContext = *attachedVolume.seLinuxMountContext + } + } return AttachedVolume{ AttachedVolume: operationexecutor.AttachedVolume{ - VolumeName: attachedVolume.volumeName, - VolumeSpec: attachedVolume.spec, - NodeName: asw.nodeName, - PluginIsAttachable: attachedVolume.pluginIsAttachable, - DevicePath: attachedVolume.devicePath, - DeviceMountPath: attachedVolume.deviceMountPath, - PluginName: attachedVolume.pluginName}, + VolumeName: attachedVolume.volumeName, + VolumeSpec: attachedVolume.spec, + NodeName: asw.nodeName, + PluginIsAttachable: attachedVolume.pluginIsAttachable, + DevicePath: attachedVolume.devicePath, + DeviceMountPath: attachedVolume.deviceMountPath, + PluginName: attachedVolume.pluginName, + SELinuxMountContext: seLinuxMountContext}, DeviceMountState: attachedVolume.deviceMountState, } } @@ -1105,6 +1159,10 @@ func IsFSResizeRequiredError(err error) bool { // mountedPod and attachedVolume objects. func getMountedVolume( mountedPod *mountedPod, attachedVolume *attachedVolume) MountedVolume { + seLinuxMountContext := "" + if attachedVolume.seLinuxMountContext != nil { + seLinuxMountContext = *attachedVolume.seLinuxMountContext + } return MountedVolume{ MountedVolume: operationexecutor.MountedVolume{ PodName: mountedPod.podName, @@ -1117,5 +1175,32 @@ func getMountedVolume( BlockVolumeMapper: mountedPod.blockVolumeMapper, VolumeGidValue: mountedPod.volumeGidValue, VolumeSpec: mountedPod.volumeSpec, - DeviceMountPath: attachedVolume.deviceMountPath}} + DeviceMountPath: attachedVolume.deviceMountPath, + SELinuxMountContext: seLinuxMountContext}} + +} + +// seLinuxMountMismatchError is an error returned when PodExistsInVolume() found +// a volume mounted with a different SELinux label than expected. +type seLinuxMountMismatchError struct { + volumeName v1.UniqueVolumeName +} + +func (err seLinuxMountMismatchError) Error() string { + return fmt.Sprintf( + "volumeName %q is already mounted to a different pod with a different SELinux label", + err.volumeName) +} + +func newSELinuxMountMismatchError(volumeName v1.UniqueVolumeName) error { + return seLinuxMountMismatchError{ + volumeName: volumeName, + } +} + +// IsSELinuxMountMismatchError returns true if the specified error is a +// seLinuxMountMismatchError. +func IsSELinuxMountMismatchError(err error) bool { + _, ok := err.(seLinuxMountMismatchError) + return ok } diff --git a/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go b/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go index 019cfabf342..ecbb35fe55b 100644 --- a/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go +++ b/pkg/kubelet/volumemanager/cache/actual_state_of_world_test.go @@ -737,7 +737,7 @@ func Test_MarkDeviceAsMounted_Positive_NewVolume(t *testing.T) { } // Act - err = asw.MarkDeviceAsMounted(generatedVolumeName, devicePath, deviceMountPath) + err = asw.MarkDeviceAsMounted(generatedVolumeName, devicePath, deviceMountPath, "") // Assert if err != nil { @@ -824,7 +824,7 @@ func TestUncertainVolumeMounts(t *testing.T) { t.Fatalf("expected volume %s to be found in aws.GetPossiblyMountedVolumesForPod", volumeSpec1.Name()) } - volExists, _, _ := asw.PodExistsInVolume(podName1, generatedVolumeName1, resource.Quantity{}) + volExists, _, _ := asw.PodExistsInVolume(podName1, generatedVolumeName1, resource.Quantity{}, "") if volExists { t.Fatalf("expected volume %s to not exist in asw", generatedVolumeName1) } @@ -910,7 +910,7 @@ func verifyPodExistsInVolumeAsw( expectedDevicePath string, asw ActualStateOfWorld) { podExistsInVolume, devicePath, err := - asw.PodExistsInVolume(expectedPodName, expectedVolumeName, resource.Quantity{}) + asw.PodExistsInVolume(expectedPodName, expectedVolumeName, resource.Quantity{}, "") if err != nil { t.Fatalf( "ASW PodExistsInVolume failed. Expected: Actual: <%v>", err) @@ -952,7 +952,7 @@ func verifyPodDoesntExistInVolumeAsw( expectVolumeToExist bool, asw ActualStateOfWorld) { podExistsInVolume, devicePath, err := - asw.PodExistsInVolume(podToCheck, volumeToCheck, resource.Quantity{}) + asw.PodExistsInVolume(podToCheck, volumeToCheck, resource.Quantity{}, "") if !expectVolumeToExist && err == nil { t.Fatalf( "ASW PodExistsInVolume did not return error. Expected: Actual: <%v>", err) diff --git a/pkg/kubelet/volumemanager/cache/desired_state_of_world.go b/pkg/kubelet/volumemanager/cache/desired_state_of_world.go index 0a999833a5e..148f3775ca6 100644 --- a/pkg/kubelet/volumemanager/cache/desired_state_of_world.go +++ b/pkg/kubelet/volumemanager/cache/desired_state_of_world.go @@ -28,7 +28,10 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/util/feature" + "k8s.io/klog/v2" apiv1resource "k8s.io/kubernetes/pkg/api/v1/resource" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util/operationexecutor" @@ -55,7 +58,7 @@ type DesiredStateOfWorld interface { // added. // If a pod with the same unique name already exists under the specified // volume, this is a no-op. - AddPodToVolume(podName types.UniquePodName, pod *v1.Pod, volumeSpec *volume.Spec, outerVolumeSpecName string, volumeGidValue string) (v1.UniqueVolumeName, error) + AddPodToVolume(podName types.UniquePodName, pod *v1.Pod, volumeSpec *volume.Spec, outerVolumeSpecName string, volumeGidValue string, seLinuxContainerContexts []*v1.SELinuxOptions) (v1.UniqueVolumeName, error) // MarkVolumesReportedInUse sets the ReportedInUse value to true for the // reportedVolumes. For volumes not in the reportedVolumes list, the @@ -83,7 +86,7 @@ type DesiredStateOfWorld interface { // volumes that should be attached to this node. // If a pod with the same unique name does not exist under the specified // volume, false is returned. - VolumeExists(volumeName v1.UniqueVolumeName) bool + VolumeExists(volumeName v1.UniqueVolumeName, seLinuxMountContext string) bool // PodExistsInVolume returns true if the given pod exists in the list of // podsToMount for the given volume in the cache. @@ -91,7 +94,7 @@ type DesiredStateOfWorld interface { // volume, false is returned. // If a volume with the name volumeName does not exist in the list of // attached volumes, false is returned. - PodExistsInVolume(podName types.UniquePodName, volumeName v1.UniqueVolumeName) bool + PodExistsInVolume(podName types.UniquePodName, volumeName v1.UniqueVolumeName, seLinuxMountContext string) bool // GetVolumesToMount generates and returns a list of volumes that should be // attached to this node and the pods they should be mounted to based on the @@ -195,6 +198,13 @@ type volumeToMount struct { // persistentVolumeSize records desired size of a persistent volume. // Usually this value reflects size recorded in pv.Spec.Capacity persistentVolumeSize *resource.Quantity + + // seLinuxFileLabel is desired SELinux label on files on the volume. If empty, then + // - either the context+label is unknown (assigned randomly by the container runtime) + // - or the volume plugin responsible for this volume does not support mounting with -o context + // - or the OS does not support SELinux + // In all cases, the SELinux context does not matter when mounting the volume. + seLinuxFileLabel string } // The pod object represents a pod that references the underlying volume and @@ -232,7 +242,8 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( pod *v1.Pod, volumeSpec *volume.Spec, outerVolumeSpecName string, - volumeGidValue string) (v1.UniqueVolumeName, error) { + volumeGidValue string, + seLinuxContainerContexts []*v1.SELinuxOptions) (v1.UniqueVolumeName, error) { dsw.Lock() defer dsw.Unlock() @@ -268,7 +279,63 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( volumeName = util.GetUniqueVolumeNameFromSpecWithPod(podName, volumePlugin, volumeSpec) } - if _, volumeExists := dsw.volumesToMount[volumeName]; !volumeExists { + var seLinuxFileLabel string + // Volume plugin supports SELinux context mount for all its volumes. + var pluginSupportsSELinuxContextMount bool + // The volume is ReadWriteOncePod. We don't support other volume types in SELinuxMountReadWriteOncePod feature. + // Don't use mount option to apply the SELinux context, still, track the context and report metrics of things + // that would break if the feature was for all volume access modes. + var isRWOP bool + + if feature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + pluginSupportsSELinuxContextMount, err = dsw.getSELinuxMountSupport(volumeSpec) + if err != nil { + return "", err + } + isRWOP = util.IsRWOP(volumeSpec) + if pluginSupportsSELinuxContextMount { + // Ensure that a volume that can be mounted with "-o context=XYZ" is + // used only by containers with the same SELinux contexts. + for _, containerContext := range seLinuxContainerContexts { + newLabel, err := util.SELinuxOptionsToFileLabel(containerContext) + if err != nil { + fullErr := fmt.Errorf("failed to construct SELinux label from context %q: %s", containerContext, err) + if isRWOP { + // Cannot mount with -o context if the context can't be composed. + return "", fullErr + } else { + // This is not an error yet, but it will be when support for RWO and RWX volumes is added + // TODO: bump some metric here + klog.V(4).ErrorS(err, "Please report this error in https://github.com/kubernetes/enhancements/issues/1710, together with full Pod yaml file") + break + } + } + if seLinuxFileLabel == "" { + seLinuxFileLabel = newLabel + continue + } + if seLinuxFileLabel != newLabel { + fullErr := fmt.Errorf("volume %s is used with two different SELinux contexts in the same pod: %q, %q", volumeSpec.Name(), seLinuxFileLabel, newLabel) + if isRWOP { + return "", fullErr + } else { + // This is not an error yet, but it will be when support for RWO and RWX volumes is added + // TODO: bump some metric here + klog.V(4).ErrorS(err, "Please report this error in https://github.com/kubernetes/enhancements/issues/1710, together with full Pod yaml file") + break + } + } + } + } else { + // Volume plugin does not support SELinux context mount. + // DSW will track this volume with SELinux label "", i.e. no mount with + // -o context. + seLinuxFileLabel = "" + } + } + klog.V(4).InfoS("volume final SELinux label decided", "volume", volumeSpec.Name(), "label", seLinuxFileLabel) + + if vol, volumeExists := dsw.volumesToMount[volumeName]; !volumeExists { var sizeLimit *resource.Quantity if volumeSpec.Volume != nil { if util.IsLocalEphemeralVolume(*volumeSpec.Volume) { @@ -291,6 +358,7 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( volumeGidValue: volumeGidValue, reportedInUse: false, desiredSizeLimit: sizeLimit, + seLinuxFileLabel: seLinuxFileLabel, } // record desired size of the volume if volumeSpec.PersistentVolume != nil { @@ -300,9 +368,24 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( vmt.persistentVolumeSize = &pvCapCopy } } - dsw.volumesToMount[volumeName] = vmt + } else { + // volume exists + if pluginSupportsSELinuxContextMount { + if seLinuxFileLabel != vol.seLinuxFileLabel { + // TODO: update the error message after tests, e.g. add at least the conflicting pod names. + fullErr := fmt.Errorf("conflicting SELinux labels of volume %s: %q and %q", volumeSpec.Name(), vol.seLinuxFileLabel, seLinuxFileLabel) + if isRWOP { + return "", fullErr + } else { + // This is not an error yet, but it will be when support for RWO and RWX volumes is added + // TODO: bump some metric here + klog.V(4).ErrorS(err, "Please report this error in https://github.com/kubernetes/enhancements/issues/1710, together with full Pod yaml file") + } + } + } } + oldPodMount, ok := dsw.volumesToMount[volumeName].podsToMount[podName] mountRequestTime := time.Now() if ok && !volumePlugin.RequiresRemount(volumeSpec) { @@ -380,16 +463,22 @@ func (dsw *desiredStateOfWorld) UpdatePersistentVolumeSize(volumeName v1.UniqueV } func (dsw *desiredStateOfWorld) VolumeExists( - volumeName v1.UniqueVolumeName) bool { + volumeName v1.UniqueVolumeName, seLinuxMountContext string) bool { dsw.RLock() defer dsw.RUnlock() - _, volumeExists := dsw.volumesToMount[volumeName] - return volumeExists + vol, volumeExists := dsw.volumesToMount[volumeName] + if !volumeExists { + return false + } + if feature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + return vol.seLinuxFileLabel == seLinuxMountContext + } + return true } func (dsw *desiredStateOfWorld) PodExistsInVolume( - podName types.UniquePodName, volumeName v1.UniqueVolumeName) bool { + podName types.UniquePodName, volumeName v1.UniqueVolumeName, seLinuxMountOption string) bool { dsw.RLock() defer dsw.RUnlock() @@ -398,6 +487,15 @@ func (dsw *desiredStateOfWorld) PodExistsInVolume( return false } + if feature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + if volumeObj.seLinuxFileLabel != seLinuxMountOption { + // The volume is in DSW, but with a different SELinux mount option. + // Report it as unused, so the volume is unmounted and mounted back + // with the right SELinux option. + return false + } + } + _, podExists := volumeObj.podsToMount[podName] return podExists } @@ -448,6 +546,7 @@ func (dsw *desiredStateOfWorld) GetVolumesToMount() []VolumeToMount { ReportedInUse: volumeObj.reportedInUse, MountRequestTime: podObj.mountRequestTime, DesiredSizeLimit: volumeObj.desiredSizeLimit, + SELinuxLabel: volumeObj.seLinuxFileLabel, }, } if volumeObj.persistentVolumeSize != nil { @@ -504,3 +603,7 @@ func (dsw *desiredStateOfWorld) MarkVolumeAttachability(volumeName v1.UniqueVolu volumeObj.pluginIsAttachable = attachable dsw.volumesToMount[volumeName] = volumeObj } + +func (dsw *desiredStateOfWorld) getSELinuxMountSupport(volumeSpec *volume.Spec) (bool, error) { + return util.SupportsSELinuxContextMount(volumeSpec, dsw.volumePluginMgr) +} 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 4777b32de1f..3d3d6639976 100644 --- a/pkg/kubelet/volumemanager/cache/desired_state_of_world_test.go +++ b/pkg/kubelet/volumemanager/cache/desired_state_of_world_test.go @@ -17,10 +17,11 @@ limitations under the License. package cache import ( - "k8s.io/apimachinery/pkg/api/resource" "testing" - "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/pkg/volume" volumetesting "k8s.io/kubernetes/pkg/volume/testing" @@ -59,7 +60,7 @@ func Test_AddPodToVolume_Positive_NewPodNewVolume(t *testing.T) { // Act generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */) // Assert if err != nil { @@ -104,7 +105,7 @@ func Test_AddPodToVolume_Positive_ExistingPodExistingVolume(t *testing.T) { // Act generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */) // Assert if err != nil { @@ -259,8 +260,8 @@ func Test_AddPodToVolume_Positive_NamesForDifferentPodsAndDifferentVolumes(t *te for name, v := range testcases { volumeSpec1 := &volume.Spec{Volume: &v.pod1.Spec.Volumes[0]} volumeSpec2 := &volume.Spec{Volume: &v.pod2.Spec.Volumes[0]} - generatedVolumeName1, err1 := dsw.AddPodToVolume(util.GetUniquePodName(v.pod1), v.pod1, volumeSpec1, volumeSpec1.Name(), "") - generatedVolumeName2, err2 := dsw.AddPodToVolume(util.GetUniquePodName(v.pod2), v.pod2, volumeSpec2, volumeSpec2.Name(), "") + generatedVolumeName1, err1 := dsw.AddPodToVolume(util.GetUniquePodName(v.pod1), v.pod1, volumeSpec1, volumeSpec1.Name(), "", nil) + generatedVolumeName2, err2 := dsw.AddPodToVolume(util.GetUniquePodName(v.pod2), v.pod2, volumeSpec2, volumeSpec2.Name(), "", nil) if err1 != nil { t.Fatalf("test %q: AddPodToVolume failed. Expected: Actual: <%v>", name, err1) } @@ -309,7 +310,7 @@ func Test_DeletePodFromVolume_Positive_PodExistsVolumeExists(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } @@ -407,19 +408,19 @@ func Test_MarkVolumesReportedInUse_Positive_NewPodNewVolume(t *testing.T) { pod3Name := util.GetUniquePodName(pod3) generatedVolume1Name, err := dsw.AddPodToVolume( - pod1Name, pod1, volume1Spec, volume1Spec.Name(), "" /* volumeGidValue */) + pod1Name, pod1, volume1Spec, volume1Spec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } generatedVolume2Name, err := dsw.AddPodToVolume( - pod2Name, pod2, volume2Spec, volume2Spec.Name(), "" /* volumeGidValue */) + pod2Name, pod2, volume2Spec, volume2Spec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } generatedVolume3Name, err := dsw.AddPodToVolume( - pod3Name, pod3, volume3Spec, volume3Spec.Name(), "" /* volumeGidValue */) + pod3Name, pod3, volume3Spec, volume3Spec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } @@ -580,14 +581,14 @@ func Test_AddPodToVolume_WithEmptyDirSizeLimit(t *testing.T) { } for i := range pod1.Spec.Volumes { volumeSpec := &volume.Spec{Volume: &pod1.Spec.Volumes[i]} - _, err := dsw.AddPodToVolume(pod1Name, pod1, volumeSpec, volumeSpec.Name(), "") + _, err := dsw.AddPodToVolume(pod1Name, pod1, volumeSpec, volumeSpec.Name(), "", nil /* seLinuxContainerContexts */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } } for i := range pod2.Spec.Volumes { volumeSpec := &volume.Spec{Volume: &pod2.Spec.Volumes[i]} - _, err := dsw.AddPodToVolume(pod2Name, pod2, volumeSpec, volumeSpec.Name(), "") + _, err := dsw.AddPodToVolume(pod2Name, pod2, volumeSpec, volumeSpec.Name(), "", nil /* seLinuxContainerContexts */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } diff --git a/pkg/kubelet/volumemanager/metrics/metrics_test.go b/pkg/kubelet/volumemanager/metrics/metrics_test.go index cc3f93b166f..20228115f4f 100644 --- a/pkg/kubelet/volumemanager/metrics/metrics_test.go +++ b/pkg/kubelet/volumemanager/metrics/metrics_test.go @@ -19,7 +19,7 @@ package metrics import ( "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8stypes "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache" @@ -56,7 +56,7 @@ func TestMetricCollection(t *testing.T) { podName := util.GetUniquePodName(pod) // Add one volume to DesiredStateOfWorld - generatedVolumeName, err := dsw.AddPodToVolume(podName, pod, volumeSpec, volumeSpec.Name(), "") + generatedVolumeName, err := dsw.AddPodToVolume(podName, pod, volumeSpec, volumeSpec.Name(), "", "" /* seLinuxLabel */) if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) } diff --git a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go index d2d82f4a591..7b0c7536356 100644 --- a/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go +++ b/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go @@ -283,7 +283,7 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes( } allVolumesAdded := true - mounts, devices := util.GetPodVolumeNames(pod) + mounts, devices, seLinuxContainerContexts := util.GetPodVolumeNames(pod) // Process volume spec for each volume defined in pod for _, podVolume := range pod.Spec.Volumes { @@ -304,7 +304,7 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes( // Add volume to desired state of world uniqueVolumeName, err := dswp.desiredStateOfWorld.AddPodToVolume( - uniquePodName, pod, volumeSpec, podVolume.Name, volumeGidValue) + uniquePodName, pod, volumeSpec, podVolume.Name, volumeGidValue, seLinuxContainerContexts[podVolume.Name]) if err != nil { klog.ErrorS(err, "Failed to add volume to desiredStateOfWorld", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "volumeSpecName", volumeSpec.Name()) dswp.desiredStateOfWorld.AddErrorToPod(uniquePodName, err.Error()) diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler.go b/pkg/kubelet/volumemanager/reconciler/reconciler.go index 4c33fdf636a..9ea0e961f40 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler.go @@ -199,7 +199,7 @@ func (rc *reconciler) reconcile() { func (rc *reconciler) unmountVolumes() { // Ensure volumes that should be unmounted are unmounted. for _, mountedVolume := range rc.actualStateOfWorld.GetAllMountedVolumes() { - if !rc.desiredStateOfWorld.PodExistsInVolume(mountedVolume.PodName, mountedVolume.VolumeName) { + if !rc.desiredStateOfWorld.PodExistsInVolume(mountedVolume.PodName, mountedVolume.VolumeName, mountedVolume.SELinuxMountContext) { // Volume is mounted, unmount it klog.V(5).InfoS(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountVolume", "")) err := rc.operationExecutor.UnmountVolume( @@ -217,9 +217,14 @@ func (rc *reconciler) unmountVolumes() { func (rc *reconciler) mountOrAttachVolumes() { // Ensure volumes that should be attached/mounted are attached/mounted. for _, volumeToMount := range rc.desiredStateOfWorld.GetVolumesToMount() { - volMounted, devicePath, err := rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName, volumeToMount.PersistentVolumeSize) + volMounted, devicePath, err := rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName, volumeToMount.PersistentVolumeSize, volumeToMount.SELinuxLabel) volumeToMount.DevicePath = devicePath - if cache.IsVolumeNotAttachedError(err) { + if cache.IsSELinuxMountMismatchError(err) { + // TODO: check error message + lower frequency, this can be noisy + klog.ErrorS(err, volumeToMount.GenerateErrorDetailed("mount precondition failed", err).Error(), "pod", klog.KObj(volumeToMount.Pod)) + // TODO: report error better, this may be too noisy + rc.desiredStateOfWorld.AddErrorToPod(volumeToMount.PodName, err.Error()) + } else if cache.IsVolumeNotAttachedError(err) { rc.waitForVolumeAttach(volumeToMount) } else if !volMounted || cache.IsRemountRequiredError(err) { rc.mountAttachedVolumes(volumeToMount, err) @@ -373,7 +378,7 @@ func (rc *reconciler) waitForVolumeAttach(volumeToMount cache.VolumeToMount) { func (rc *reconciler) unmountDetachDevices() { for _, attachedVolume := range rc.actualStateOfWorld.GetUnmountedVolumes() { // Check IsOperationPending to avoid marking a volume as detached if it's in the process of mounting. - if !rc.desiredStateOfWorld.VolumeExists(attachedVolume.VolumeName) && + if !rc.desiredStateOfWorld.VolumeExists(attachedVolume.VolumeName, attachedVolume.SELinuxMountContext) && !rc.operationExecutor.IsOperationPending(attachedVolume.VolumeName, nestedpendingoperations.EmptyUniquePodName, nestedpendingoperations.EmptyNodeName) { if attachedVolume.DeviceMayBeMounted() { // Volume is globally mounted to device, unmount it @@ -765,7 +770,8 @@ func (rc *reconciler) updateStates(volumesNeedUpdate map[v1.UniqueVolumeName]*gl klog.ErrorS(err, "Could not find device mount path for volume", "volumeName", gvl.volumeName) continue } - err = rc.actualStateOfWorld.MarkDeviceAsMounted(gvl.volumeName, gvl.devicePath, deviceMountPath) + // TODO(jsafrane): add reconstructed SELinux context + err = rc.actualStateOfWorld.MarkDeviceAsMounted(gvl.volumeName, gvl.devicePath, deviceMountPath, "") if err != nil { klog.ErrorS(err, "Could not mark device is mounted to actual state of world", "volume", gvl.volumeName) continue diff --git a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go index c69004e03cc..9a54b878b53 100644 --- a/pkg/kubelet/volumemanager/reconciler/reconciler_test.go +++ b/pkg/kubelet/volumemanager/reconciler/reconciler_test.go @@ -160,7 +160,7 @@ func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -349,7 +349,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) // Assert @@ -428,7 +428,7 @@ func Test_Run_Negative_VolumeMountControllerAttachEnabled(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -506,7 +506,7 @@ func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -608,7 +608,7 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -715,7 +715,7 @@ func Test_Run_Positive_VolumeAttachAndMap(t *testing.T) { } podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -827,7 +827,7 @@ func Test_Run_Positive_BlockVolumeMapControllerAttachEnabled(t *testing.T) { podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) // Assert @@ -924,7 +924,7 @@ func Test_Run_Positive_BlockVolumeAttachMapUnmapDetach(t *testing.T) { podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -1048,7 +1048,7 @@ func Test_Run_Positive_VolumeUnmapControllerAttachEnabled(t *testing.T) { podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { @@ -1319,7 +1319,7 @@ func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) { volumeSpec := &volume.Spec{PersistentVolume: pv} podName := util.GetUniquePodName(pod) volumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) @@ -1340,7 +1340,7 @@ func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) { // Simulate what DSOWP does pvWithSize.Spec.Capacity[v1.ResourceStorage] = tc.newPVSize volumeSpec = &volume.Spec{PersistentVolume: pvWithSize} - dsw.AddPodToVolume(podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + dsw.AddPodToVolume(podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) t.Logf("Changing size of the volume to %s", tc.newPVSize.String()) newSize := tc.newPVSize.DeepCopy() @@ -1573,7 +1573,7 @@ func Test_UncertainDeviceGlobalMounts(t *testing.T) { volumeSpec := &volume.Spec{PersistentVolume: pv} podName := util.GetUniquePodName(pod) volumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) @@ -1795,7 +1795,7 @@ func Test_UncertainVolumeMountState(t *testing.T) { volumeSpec := &volume.Spec{PersistentVolume: pv} podName := util.GetUniquePodName(pod) volumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) // Assert if err != nil { t.Fatalf("AddPodToVolume failed. Expected: Actual: <%v>", err) @@ -2133,7 +2133,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) { volumeSpecCopy := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) generatedVolumeName, err := dsw.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) if err != nil { @@ -2158,7 +2158,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) { klog.InfoS("UnmountDevice called") var generatedVolumeNameCopy v1.UniqueVolumeName generatedVolumeNameCopy, err = dsw.AddPodToVolume( - podName, pod, volumeSpecCopy, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpecCopy, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeNameCopy}) return nil } @@ -2410,7 +2410,7 @@ func TestSyncStates(t *testing.T) { volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} podName := util.GetUniquePodName(pod) volumeName, err := rcInstance.desiredStateOfWorld.AddPodToVolume( - podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) + podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* SELinuxContext */) if err != nil { t.Fatalf("error adding volume %s to dsow: %v", volumeSpec.Name(), err) } diff --git a/pkg/kubelet/volumemanager/volume_manager.go b/pkg/kubelet/volumemanager/volume_manager.go index 03c2abd66bc..0357ea0f5d1 100644 --- a/pkg/kubelet/volumemanager/volume_manager.go +++ b/pkg/kubelet/volumemanager/volume_manager.go @@ -536,7 +536,7 @@ func filterUnmountedVolumes(mountedVolumes sets.String, expectedVolumes []string // getExpectedVolumes returns a list of volumes that must be mounted in order to // consider the volume setup step for this pod satisfied. func getExpectedVolumes(pod *v1.Pod) []string { - mounts, devices := util.GetPodVolumeNames(pod) + mounts, devices, _ := util.GetPodVolumeNames(pod) return mounts.Union(devices).UnsortedList() } diff --git a/pkg/volume/util/operationexecutor/operation_executor.go b/pkg/volume/util/operationexecutor/operation_executor.go index 13079bf1581..d16e287be68 100644 --- a/pkg/volume/util/operationexecutor/operation_executor.go +++ b/pkg/volume/util/operationexecutor/operation_executor.go @@ -166,7 +166,7 @@ func NewOperationExecutor( } } -// MarkVolumeOpts is an struct to pass arguments to MountVolume functions +// MarkVolumeOpts is a struct to pass arguments to MountVolume functions type MarkVolumeOpts struct { PodName volumetypes.UniquePodName PodUID types.UID @@ -177,6 +177,7 @@ type MarkVolumeOpts struct { VolumeGidVolume string VolumeSpec *volume.Spec VolumeMountState VolumeMountState + SELinuxMountContext string } // ActualStateOfWorldMounterUpdater defines a set of operations updating the actual @@ -192,10 +193,10 @@ type ActualStateOfWorldMounterUpdater interface { MarkVolumeMountAsUncertain(markVolumeOpts MarkVolumeOpts) error // Marks the specified volume as having been globally mounted. - MarkDeviceAsMounted(volumeName v1.UniqueVolumeName, devicePath, deviceMountPath string) error + MarkDeviceAsMounted(volumeName v1.UniqueVolumeName, devicePath, deviceMountPath, seLinuxMountContext string) error // MarkDeviceAsUncertain marks device state in global mount path as uncertain - MarkDeviceAsUncertain(volumeName v1.UniqueVolumeName, devicePath, deviceMountPath string) error + MarkDeviceAsUncertain(volumeName v1.UniqueVolumeName, devicePath, deviceMountPath, seLinuxMountContext string) error // Marks the specified volume as having its global mount unmounted. MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error @@ -446,6 +447,9 @@ type VolumeToMount struct { // PersistentVolumeSize stores desired size of the volume. // usually this is the size if pv.Spec.Capacity PersistentVolumeSize resource.Quantity + + // SELinux label that should be used to mount. + SELinuxLabel string } // DeviceMountState represents device mount state in a global path. @@ -552,6 +556,8 @@ type AttachedVolume struct { // PluginName is the Unescaped Qualified name of the volume plugin used to // attach and mount this volume. PluginName string + + SELinuxMountContext string } // GenerateMsgDetailed returns detailed msgs for attached volumes @@ -728,6 +734,10 @@ type MountedVolume struct { // DeviceMountPath contains the path on the node where the device should // be mounted after it is attached. DeviceMountPath string + + // SELinuxMountContext is value of mount option 'mount -o context=XYZ'. + // If empty, no such mount option was used. + SELinuxMountContext string } // GenerateMsgDetailed returns detailed msgs for mounted volumes diff --git a/pkg/volume/util/operationexecutor/operation_generator.go b/pkg/volume/util/operationexecutor/operation_generator.go index 6237755bbdb..cec5ed9a246 100644 --- a/pkg/volume/util/operationexecutor/operation_generator.go +++ b/pkg/volume/util/operationexecutor/operation_generator.go @@ -645,7 +645,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( volumeToMount.VolumeSpec, devicePath, deviceMountPath, - volume.DeviceMounterArgs{FsGroup: fsGroup}, + volume.DeviceMounterArgs{FsGroup: fsGroup, SELinuxLabel: volumeToMount.SELinuxLabel}, ) if err != nil { og.checkForFailedMount(volumeToMount, err) @@ -659,7 +659,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( // Update actual state of world to reflect volume is globally mounted markDeviceMountedErr := actualStateOfWorld.MarkDeviceAsMounted( - volumeToMount.VolumeName, devicePath, deviceMountPath) + volumeToMount.VolumeName, devicePath, deviceMountPath, volumeToMount.SELinuxLabel) if markDeviceMountedErr != nil { // On failure, return error. Caller will log and retry. eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MarkDeviceAsMounted failed", markDeviceMountedErr) @@ -688,6 +688,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( FsGroup: hostGID, DesiredSize: volumeToMount.DesiredSizeLimit, FSGroupChangePolicy: fsGroupChangePolicy, + SELinuxLabel: volumeToMount.SELinuxLabel, }) // Update actual state of world markOpts := MarkVolumeOpts{ @@ -699,6 +700,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc( VolumeGidVolume: volumeToMount.VolumeGidValue, VolumeSpec: volumeToMount.VolumeSpec, VolumeMountState: VolumeMounted, + SELinuxMountContext: volumeToMount.SELinuxLabel, } if mountErr != nil { og.checkForFailedMount(volumeToMount, mountErr) @@ -787,7 +789,7 @@ func (og *operationGenerator) markDeviceErrorState(volumeToMount VolumeToMount, actualStateOfWorld.GetDeviceMountState(volumeToMount.VolumeName) == DeviceNotMounted { // only devices which are not mounted can be marked as uncertain. We do not want to mark a device // which was previously marked as mounted here as uncertain. - markDeviceUncertainError := actualStateOfWorld.MarkDeviceAsUncertain(volumeToMount.VolumeName, devicePath, deviceMountPath) + markDeviceUncertainError := actualStateOfWorld.MarkDeviceAsUncertain(volumeToMount.VolumeName, devicePath, deviceMountPath, volumeToMount.SELinuxLabel) if markDeviceUncertainError != nil { klog.Errorf(volumeToMount.GenerateErrorDetailed("MountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainError).Error()) } @@ -955,7 +957,7 @@ func (og *operationGenerator) GenerateUnmountDeviceFunc( unmountDeviceErr := volumeDeviceUnmounter.UnmountDevice(deviceMountPath) if unmountDeviceErr != nil { // Mark the device as uncertain, so MountDevice is called for new pods. UnmountDevice may be already in progress. - markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain(deviceToDetach.VolumeName, deviceToDetach.DevicePath, deviceMountPath) + markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain(deviceToDetach.VolumeName, deviceToDetach.DevicePath, deviceMountPath, deviceToDetach.SELinuxMountContext) if markDeviceUncertainErr != nil { // There is nothing else we can do. Hope that UnmountDevice will be re-tried shortly. klog.Errorf(deviceToDetach.GenerateErrorDetailed("UnmountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr).Error()) @@ -976,7 +978,7 @@ func (og *operationGenerator) GenerateUnmountDeviceFunc( // The device is still in use elsewhere. Caller will log and retry. if deviceOpened { // Mark the device as uncertain, so MountDevice is called for new pods. - markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain(deviceToDetach.VolumeName, deviceToDetach.DevicePath, deviceMountPath) + markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain(deviceToDetach.VolumeName, deviceToDetach.DevicePath, deviceMountPath, deviceToDetach.SELinuxMountContext) if markDeviceUncertainErr != nil { // There is nothing else we can do. Hope that UnmountDevice will be re-tried shortly. klog.Errorf(deviceToDetach.GenerateErrorDetailed("UnmountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr).Error()) @@ -1114,7 +1116,7 @@ func (og *operationGenerator) GenerateMapVolumeFunc( // Update actual state of world to reflect volume is globally mounted markedDevicePath := devicePath markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted( - volumeToMount.VolumeName, markedDevicePath, globalMapPath) + volumeToMount.VolumeName, markedDevicePath, globalMapPath, "") if markDeviceMappedErr != nil { // On failure, return error. Caller will log and retry. eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.MarkDeviceAsMounted failed", markDeviceMappedErr) @@ -1183,7 +1185,7 @@ func (og *operationGenerator) GenerateMapVolumeFunc( // TODO: This can be improved after #82492 is merged and ASW has state. if markedDevicePath != devicePath { markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted( - volumeToMount.VolumeName, devicePath, globalMapPath) + volumeToMount.VolumeName, devicePath, globalMapPath, "") if markDeviceMappedErr != nil { // On failure, return error. Caller will log and retry. eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.MarkDeviceAsMounted failed", markDeviceMappedErr) @@ -1411,7 +1413,7 @@ func (og *operationGenerator) GenerateUnmapDeviceFunc( // cases below. The volume is marked as fully un-mapped at the end of this function, when everything // succeeds. markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain( - deviceToDetach.VolumeName, deviceToDetach.DevicePath, globalMapPath) + deviceToDetach.VolumeName, deviceToDetach.DevicePath, globalMapPath, "" /* seLinuxMountContext */) if markDeviceUncertainErr != nil { // On failure, return error. Caller will log and retry. eventErr, detailedErr := deviceToDetach.GenerateError("UnmapDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr) diff --git a/pkg/volume/util/selinux.go b/pkg/volume/util/selinux.go new file mode 100644 index 00000000000..f05c30121f4 --- /dev/null +++ b/pkg/volume/util/selinux.go @@ -0,0 +1,117 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + + "github.com/opencontainers/selinux/go-selinux" + "github.com/opencontainers/selinux/go-selinux/label" + v1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" + "k8s.io/kubernetes/pkg/features" + "k8s.io/kubernetes/pkg/volume" +) + +// SELinuxOptionsToFileLabel returns SELinux file label for given options. +func SELinuxOptionsToFileLabel(opts *v1.SELinuxOptions) (string, error) { + if opts == nil { + return "", nil + } + + args := contextOptions(opts) + if len(args) == 0 { + return "", nil + } + + // TODO: use interface for InitLabels for unit tests. + processLabel, fileLabel, err := label.InitLabels(args) + if err != nil { + // In theory, this should be unreachable. InitLabels can fail only when args contain an unknown option, + // and all options returned by contextOptions are known. + return "", err + } + // InitLabels() may allocate a new unique SELinux label in kubelet memory. The label is *not* allocated + // in the container runtime. Clear it to avoid memory problems. + // ReleaseLabel on non-allocated label is NOOP. + selinux.ReleaseLabel(processLabel) + + return fileLabel, nil +} + +// Convert SELinuxOptions to []string accepted by label.InitLabels +func contextOptions(opts *v1.SELinuxOptions) []string { + if opts == nil { + return nil + } + args := make([]string, 0, 3) + if opts.User != "" { + args = append(args, "user:"+opts.User) + } + if opts.Role != "" { + args = append(args, "role:"+opts.Role) + } + if opts.Type != "" { + args = append(args, "type:"+opts.Type) + } + if opts.Level != "" { + args = append(args, "level:"+opts.Level) + } + return args +} + +// SupportsSELinuxContextMount checks if the given volumeSpec supports with mount -o context +func SupportsSELinuxContextMount(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) (bool, error) { + // This is cheap + if !selinux.GetEnabled() { + return false, nil + } + + plugin, _ := volumePluginMgr.FindPluginBySpec(volumeSpec) + if plugin != nil { + return plugin.SupportsSELinuxContextMount(volumeSpec) + } + + return false, nil +} + +func IsRWOP(volumeSpec *volume.Spec) bool { + if !utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod) { + return false + } + if volumeSpec.PersistentVolume == nil { + return false + } + if len(volumeSpec.PersistentVolume.Spec.AccessModes) != 1 { + return false + } + if !v1helper.ContainsAccessMode(volumeSpec.PersistentVolume.Spec.AccessModes, v1.ReadWriteOncePod) { + return false + } + return true +} + +// AddSELinuxMountOption adds -o context="XYZ" mount option to a given list +func AddSELinuxMountOption(options []string, seLinuxContext string) []string { + if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + return options + } + // Use double quotes to support a comma "," in the SELinux context string. + // For example: dirsync,context="system_u:object_r:container_file_t:s0:c15,c25",noatime + return append(options, fmt.Sprintf("context=%q", seLinuxContext)) +} diff --git a/pkg/volume/util/util.go b/pkg/volume/util/util.go index dae2ec59c37..e7df9538f32 100644 --- a/pkg/volume/util/util.go +++ b/pkg/volume/util/util.go @@ -275,16 +275,6 @@ func JoinMountOptions(userOptions []string, systemOptions []string) []string { return allMountOptions.List() } -// AddSELinuxMountOption adds -o context="XYZ mount option to a given list -func AddSELinuxMountOption(options []string, seLinuxContext string) []string { - if !utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { - return options - } - // Use double quotes to support a comma "," in the SELinux context string. - // For example: dirsync,context="system_u:object_r:container_file_t:s0:c15,c25",noatime - return append(options, "context=%q", seLinuxContext) -} - // ContainsAccessMode returns whether the requested mode is contained by modes func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { for _, m := range modes { @@ -584,15 +574,29 @@ func IsLocalEphemeralVolume(volume v1.Volume) bool { } // GetPodVolumeNames returns names of volumes that are used in a pod, -// either as filesystem mount or raw block device. -func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String) { +// either as filesystem mount or raw block device, together with list +// of all SELinux contexts of all containers that use the volumes. +func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String, seLinuxContainerContexts map[string][]*v1.SELinuxOptions) { mounts = sets.NewString() devices = sets.NewString() + seLinuxContainerContexts = make(map[string][]*v1.SELinuxOptions) podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(container *v1.Container, containerType podutil.ContainerType) bool { + var seLinuxOptions *v1.SELinuxOptions + if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { + effectiveContainerSecurity := securitycontext.DetermineEffectiveSecurityContext(pod, container) + if effectiveContainerSecurity != nil { + // No DeepCopy, SELinuxOptions is already a copy of Pod's or container's SELinuxOptions + seLinuxOptions = effectiveContainerSecurity.SELinuxOptions + } + } + if container.VolumeMounts != nil { for _, mount := range container.VolumeMounts { mounts.Insert(mount.Name) + if seLinuxOptions != nil { + seLinuxContainerContexts[mount.Name] = append(seLinuxContainerContexts[mount.Name], seLinuxOptions.DeepCopy()) + } } } if container.VolumeDevices != nil {