Add SELinux context tracking to volume manager

Both ActualStateOfWorld and DesiredStateOfWorld must track SELinux context
of volume mounts.
This commit is contained in:
Jan Safranek 2022-02-11 10:41:16 +01:00
parent 4cfb277e8b
commit 48b0751269
13 changed files with 421 additions and 93 deletions

View File

@ -27,7 +27,9 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor" "k8s.io/kubernetes/pkg/volume/util/operationexecutor"
@ -73,7 +75,7 @@ type ActualStateOfWorld interface {
// global mount point prior to detach. // global mount point prior to detach.
// If a volume with the name volumeName does not exist in the list of // If a volume with the name volumeName does not exist in the list of
// attached volumes, an error is returned. // 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 // DeletePodFromVolume removes the given pod from the given volume in the
// cache indicating the volume has been successfully unmounted from the pod. // 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. // volumes, depend on this to update the contents of the volume.
// All volume mounting calls should be idempotent so a second mount call for // All volume mounting calls should be idempotent so a second mount call for
// volumes that do not need to update contents should not fail. // 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 // 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 // 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 indicates if device has been globally mounted or is not.
DeviceMountState operationexecutor.DeviceMountState 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 // 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 // persistentVolumeSize records size of the volume when pod was started or
// size after successful completion of volume expansion operation. // size after successful completion of volume expansion operation.
persistentVolumeSize *resource.Quantity 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 // 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 // - VolumeMounted: means volume for pod has been successfully mounted
// - VolumeMountUncertain: means volume for pod may not be mounted, but it must be unmounted // - VolumeMountUncertain: means volume for pod may not be mounted, but it must be unmounted
volumeMountStateForPod operationexecutor.VolumeMountState 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( func (asw *actualStateOfWorld) MarkVolumeAsAttached(
@ -465,13 +482,13 @@ func (asw *actualStateOfWorld) MarkVolumeAsUnmounted(
} }
func (asw *actualStateOfWorld) MarkDeviceAsMounted( func (asw *actualStateOfWorld) MarkDeviceAsMounted(
volumeName v1.UniqueVolumeName, devicePath, deviceMountPath string) error { volumeName v1.UniqueVolumeName, devicePath, deviceMountPath, seLinuxMountContext string) error {
return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceGloballyMounted, devicePath, deviceMountPath) return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceGloballyMounted, devicePath, deviceMountPath, seLinuxMountContext)
} }
func (asw *actualStateOfWorld) MarkDeviceAsUncertain( func (asw *actualStateOfWorld) MarkDeviceAsUncertain(
volumeName v1.UniqueVolumeName, devicePath, deviceMountPath string) error { volumeName v1.UniqueVolumeName, devicePath, deviceMountPath, seLinuxMountContext string) error {
return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceMountUncertain, devicePath, deviceMountPath) return asw.SetDeviceMountState(volumeName, operationexecutor.DeviceMountUncertain, devicePath, deviceMountPath, seLinuxMountContext)
} }
func (asw *actualStateOfWorld) MarkVolumeMountAsUncertain(markVolumeOpts operationexecutor.MarkVolumeOpts) error { func (asw *actualStateOfWorld) MarkVolumeMountAsUncertain(markVolumeOpts operationexecutor.MarkVolumeOpts) error {
@ -481,7 +498,7 @@ func (asw *actualStateOfWorld) MarkVolumeMountAsUncertain(markVolumeOpts operati
func (asw *actualStateOfWorld) MarkDeviceAsUnmounted( func (asw *actualStateOfWorld) MarkDeviceAsUnmounted(
volumeName v1.UniqueVolumeName) error { 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 { func (asw *actualStateOfWorld) GetDeviceMountState(volumeName v1.UniqueVolumeName) operationexecutor.DeviceMountState {
@ -629,6 +646,7 @@ func (asw *actualStateOfWorld) AddPodToVolume(markVolumeOpts operationexecutor.M
volumeGidValue: volumeGidValue, volumeGidValue: volumeGidValue,
volumeSpec: volumeSpec, volumeSpec: volumeSpec,
volumeMountStateForPod: markVolumeOpts.VolumeMountState, volumeMountStateForPod: markVolumeOpts.VolumeMountState,
seLinuxMountContext: markVolumeOpts.SELinuxMountContext,
} }
} }
@ -646,6 +664,15 @@ func (asw *actualStateOfWorld) AddPodToVolume(markVolumeOpts operationexecutor.M
podObj.mounter = mounter podObj.mounter = mounter
} }
asw.attachedVolumes[volumeName].mountedPods[podName] = podObj 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 return nil
} }
@ -685,7 +712,7 @@ func (asw *actualStateOfWorld) MarkRemountRequired(
} }
func (asw *actualStateOfWorld) SetDeviceMountState( 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() asw.Lock()
defer asw.Unlock() defer asw.Unlock()
@ -701,6 +728,11 @@ func (asw *actualStateOfWorld) SetDeviceMountState(
if devicePath != "" { if devicePath != "" {
volumeObj.devicePath = devicePath volumeObj.devicePath = devicePath
} }
if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
if seLinuxMountContext != "" {
volumeObj.seLinuxMountContext = &seLinuxMountContext
}
}
asw.attachedVolumes[volumeName] = volumeObj asw.attachedVolumes[volumeName] = volumeObj
return nil return nil
} }
@ -776,7 +808,7 @@ func (asw *actualStateOfWorld) DeleteVolume(volumeName v1.UniqueVolumeName) erro
return nil 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() asw.RLock()
defer asw.RUnlock() defer asw.RUnlock()
@ -785,6 +817,22 @@ func (asw *actualStateOfWorld) PodExistsInVolume(podName volumetypes.UniquePodNa
return false, "", newVolumeNotAttachedError(volumeName) 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] podObj, podExists := volumeObj.mountedPods[podName]
if podExists { if podExists {
// if volume mount was uncertain we should keep trying to mount the volume // if volume mount was uncertain we should keep trying to mount the volume
@ -905,7 +953,6 @@ func (asw *actualStateOfWorld) GetAllMountedVolumes() []MountedVolume {
mountedVolume, mountedVolume,
getMountedVolume(&podObj, &volumeObj)) getMountedVolume(&podObj, &volumeObj))
} }
} }
} }
@ -1010,6 +1057,12 @@ func (asw *actualStateOfWorld) SyncReconstructedVolume(volumeName v1.UniqueVolum
func (asw *actualStateOfWorld) newAttachedVolume( func (asw *actualStateOfWorld) newAttachedVolume(
attachedVolume *attachedVolume) AttachedVolume { attachedVolume *attachedVolume) AttachedVolume {
seLinuxMountContext := ""
if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
if attachedVolume.seLinuxMountContext != nil {
seLinuxMountContext = *attachedVolume.seLinuxMountContext
}
}
return AttachedVolume{ return AttachedVolume{
AttachedVolume: operationexecutor.AttachedVolume{ AttachedVolume: operationexecutor.AttachedVolume{
VolumeName: attachedVolume.volumeName, VolumeName: attachedVolume.volumeName,
@ -1018,7 +1071,8 @@ func (asw *actualStateOfWorld) newAttachedVolume(
PluginIsAttachable: attachedVolume.pluginIsAttachable, PluginIsAttachable: attachedVolume.pluginIsAttachable,
DevicePath: attachedVolume.devicePath, DevicePath: attachedVolume.devicePath,
DeviceMountPath: attachedVolume.deviceMountPath, DeviceMountPath: attachedVolume.deviceMountPath,
PluginName: attachedVolume.pluginName}, PluginName: attachedVolume.pluginName,
SELinuxMountContext: seLinuxMountContext},
DeviceMountState: attachedVolume.deviceMountState, DeviceMountState: attachedVolume.deviceMountState,
} }
} }
@ -1105,6 +1159,10 @@ func IsFSResizeRequiredError(err error) bool {
// mountedPod and attachedVolume objects. // mountedPod and attachedVolume objects.
func getMountedVolume( func getMountedVolume(
mountedPod *mountedPod, attachedVolume *attachedVolume) MountedVolume { mountedPod *mountedPod, attachedVolume *attachedVolume) MountedVolume {
seLinuxMountContext := ""
if attachedVolume.seLinuxMountContext != nil {
seLinuxMountContext = *attachedVolume.seLinuxMountContext
}
return MountedVolume{ return MountedVolume{
MountedVolume: operationexecutor.MountedVolume{ MountedVolume: operationexecutor.MountedVolume{
PodName: mountedPod.podName, PodName: mountedPod.podName,
@ -1117,5 +1175,32 @@ func getMountedVolume(
BlockVolumeMapper: mountedPod.blockVolumeMapper, BlockVolumeMapper: mountedPod.blockVolumeMapper,
VolumeGidValue: mountedPod.volumeGidValue, VolumeGidValue: mountedPod.volumeGidValue,
VolumeSpec: mountedPod.volumeSpec, 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
} }

View File

@ -737,7 +737,7 @@ func Test_MarkDeviceAsMounted_Positive_NewVolume(t *testing.T) {
} }
// Act // Act
err = asw.MarkDeviceAsMounted(generatedVolumeName, devicePath, deviceMountPath) err = asw.MarkDeviceAsMounted(generatedVolumeName, devicePath, deviceMountPath, "")
// Assert // Assert
if err != nil { 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()) 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 { if volExists {
t.Fatalf("expected volume %s to not exist in asw", generatedVolumeName1) t.Fatalf("expected volume %s to not exist in asw", generatedVolumeName1)
} }
@ -910,7 +910,7 @@ func verifyPodExistsInVolumeAsw(
expectedDevicePath string, expectedDevicePath string,
asw ActualStateOfWorld) { asw ActualStateOfWorld) {
podExistsInVolume, devicePath, err := podExistsInVolume, devicePath, err :=
asw.PodExistsInVolume(expectedPodName, expectedVolumeName, resource.Quantity{}) asw.PodExistsInVolume(expectedPodName, expectedVolumeName, resource.Quantity{}, "")
if err != nil { if err != nil {
t.Fatalf( t.Fatalf(
"ASW PodExistsInVolume failed. Expected: <no error> Actual: <%v>", err) "ASW PodExistsInVolume failed. Expected: <no error> Actual: <%v>", err)
@ -952,7 +952,7 @@ func verifyPodDoesntExistInVolumeAsw(
expectVolumeToExist bool, expectVolumeToExist bool,
asw ActualStateOfWorld) { asw ActualStateOfWorld) {
podExistsInVolume, devicePath, err := podExistsInVolume, devicePath, err :=
asw.PodExistsInVolume(podToCheck, volumeToCheck, resource.Quantity{}) asw.PodExistsInVolume(podToCheck, volumeToCheck, resource.Quantity{}, "")
if !expectVolumeToExist && err == nil { if !expectVolumeToExist && err == nil {
t.Fatalf( t.Fatalf(
"ASW PodExistsInVolume did not return error. Expected: <error indicating volume does not exist> Actual: <%v>", err) "ASW PodExistsInVolume did not return error. Expected: <error indicating volume does not exist> Actual: <%v>", err)

View File

@ -28,7 +28,10 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
apiv1resource "k8s.io/kubernetes/pkg/api/v1/resource" apiv1resource "k8s.io/kubernetes/pkg/api/v1/resource"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor" "k8s.io/kubernetes/pkg/volume/util/operationexecutor"
@ -55,7 +58,7 @@ type DesiredStateOfWorld interface {
// added. // added.
// If a pod with the same unique name already exists under the specified // If a pod with the same unique name already exists under the specified
// volume, this is a no-op. // 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 // MarkVolumesReportedInUse sets the ReportedInUse value to true for the
// reportedVolumes. For volumes not in the reportedVolumes list, 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. // volumes that should be attached to this node.
// If a pod with the same unique name does not exist under the specified // If a pod with the same unique name does not exist under the specified
// volume, false is returned. // 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 // PodExistsInVolume returns true if the given pod exists in the list of
// podsToMount for the given volume in the cache. // podsToMount for the given volume in the cache.
@ -91,7 +94,7 @@ type DesiredStateOfWorld interface {
// volume, false is returned. // volume, false is returned.
// If a volume with the name volumeName does not exist in the list of // If a volume with the name volumeName does not exist in the list of
// attached volumes, false is returned. // 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 // 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 // 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. // persistentVolumeSize records desired size of a persistent volume.
// Usually this value reflects size recorded in pv.Spec.Capacity // Usually this value reflects size recorded in pv.Spec.Capacity
persistentVolumeSize *resource.Quantity 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 // The pod object represents a pod that references the underlying volume and
@ -232,7 +242,8 @@ func (dsw *desiredStateOfWorld) AddPodToVolume(
pod *v1.Pod, pod *v1.Pod,
volumeSpec *volume.Spec, volumeSpec *volume.Spec,
outerVolumeSpecName string, outerVolumeSpecName string,
volumeGidValue string) (v1.UniqueVolumeName, error) { volumeGidValue string,
seLinuxContainerContexts []*v1.SELinuxOptions) (v1.UniqueVolumeName, error) {
dsw.Lock() dsw.Lock()
defer dsw.Unlock() defer dsw.Unlock()
@ -268,7 +279,63 @@ func (dsw *desiredStateOfWorld) AddPodToVolume(
volumeName = util.GetUniqueVolumeNameFromSpecWithPod(podName, volumePlugin, volumeSpec) 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 var sizeLimit *resource.Quantity
if volumeSpec.Volume != nil { if volumeSpec.Volume != nil {
if util.IsLocalEphemeralVolume(*volumeSpec.Volume) { if util.IsLocalEphemeralVolume(*volumeSpec.Volume) {
@ -291,6 +358,7 @@ func (dsw *desiredStateOfWorld) AddPodToVolume(
volumeGidValue: volumeGidValue, volumeGidValue: volumeGidValue,
reportedInUse: false, reportedInUse: false,
desiredSizeLimit: sizeLimit, desiredSizeLimit: sizeLimit,
seLinuxFileLabel: seLinuxFileLabel,
} }
// record desired size of the volume // record desired size of the volume
if volumeSpec.PersistentVolume != nil { if volumeSpec.PersistentVolume != nil {
@ -300,9 +368,24 @@ func (dsw *desiredStateOfWorld) AddPodToVolume(
vmt.persistentVolumeSize = &pvCapCopy vmt.persistentVolumeSize = &pvCapCopy
} }
} }
dsw.volumesToMount[volumeName] = vmt 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] oldPodMount, ok := dsw.volumesToMount[volumeName].podsToMount[podName]
mountRequestTime := time.Now() mountRequestTime := time.Now()
if ok && !volumePlugin.RequiresRemount(volumeSpec) { if ok && !volumePlugin.RequiresRemount(volumeSpec) {
@ -380,16 +463,22 @@ func (dsw *desiredStateOfWorld) UpdatePersistentVolumeSize(volumeName v1.UniqueV
} }
func (dsw *desiredStateOfWorld) VolumeExists( func (dsw *desiredStateOfWorld) VolumeExists(
volumeName v1.UniqueVolumeName) bool { volumeName v1.UniqueVolumeName, seLinuxMountContext string) bool {
dsw.RLock() dsw.RLock()
defer dsw.RUnlock() defer dsw.RUnlock()
_, volumeExists := dsw.volumesToMount[volumeName] vol, volumeExists := dsw.volumesToMount[volumeName]
return volumeExists if !volumeExists {
return false
}
if feature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
return vol.seLinuxFileLabel == seLinuxMountContext
}
return true
} }
func (dsw *desiredStateOfWorld) PodExistsInVolume( func (dsw *desiredStateOfWorld) PodExistsInVolume(
podName types.UniquePodName, volumeName v1.UniqueVolumeName) bool { podName types.UniquePodName, volumeName v1.UniqueVolumeName, seLinuxMountOption string) bool {
dsw.RLock() dsw.RLock()
defer dsw.RUnlock() defer dsw.RUnlock()
@ -398,6 +487,15 @@ func (dsw *desiredStateOfWorld) PodExistsInVolume(
return false 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] _, podExists := volumeObj.podsToMount[podName]
return podExists return podExists
} }
@ -448,6 +546,7 @@ func (dsw *desiredStateOfWorld) GetVolumesToMount() []VolumeToMount {
ReportedInUse: volumeObj.reportedInUse, ReportedInUse: volumeObj.reportedInUse,
MountRequestTime: podObj.mountRequestTime, MountRequestTime: podObj.mountRequestTime,
DesiredSizeLimit: volumeObj.desiredSizeLimit, DesiredSizeLimit: volumeObj.desiredSizeLimit,
SELinuxLabel: volumeObj.seLinuxFileLabel,
}, },
} }
if volumeObj.persistentVolumeSize != nil { if volumeObj.persistentVolumeSize != nil {
@ -504,3 +603,7 @@ func (dsw *desiredStateOfWorld) MarkVolumeAttachability(volumeName v1.UniqueVolu
volumeObj.pluginIsAttachable = attachable volumeObj.pluginIsAttachable = attachable
dsw.volumesToMount[volumeName] = volumeObj dsw.volumesToMount[volumeName] = volumeObj
} }
func (dsw *desiredStateOfWorld) getSELinuxMountSupport(volumeSpec *volume.Spec) (bool, error) {
return util.SupportsSELinuxContextMount(volumeSpec, dsw.volumePluginMgr)
}

View File

@ -17,10 +17,11 @@ limitations under the License.
package cache package cache
import ( import (
"k8s.io/apimachinery/pkg/api/resource"
"testing" "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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumetesting "k8s.io/kubernetes/pkg/volume/testing" volumetesting "k8s.io/kubernetes/pkg/volume/testing"
@ -59,7 +60,7 @@ func Test_AddPodToVolume_Positive_NewPodNewVolume(t *testing.T) {
// Act // Act
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */)
// Assert // Assert
if err != nil { if err != nil {
@ -104,7 +105,7 @@ func Test_AddPodToVolume_Positive_ExistingPodExistingVolume(t *testing.T) {
// Act // Act
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */)
// Assert // Assert
if err != nil { if err != nil {
@ -259,8 +260,8 @@ func Test_AddPodToVolume_Positive_NamesForDifferentPodsAndDifferentVolumes(t *te
for name, v := range testcases { for name, v := range testcases {
volumeSpec1 := &volume.Spec{Volume: &v.pod1.Spec.Volumes[0]} volumeSpec1 := &volume.Spec{Volume: &v.pod1.Spec.Volumes[0]}
volumeSpec2 := &volume.Spec{Volume: &v.pod2.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(), "") 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(), "") generatedVolumeName2, err2 := dsw.AddPodToVolume(util.GetUniquePodName(v.pod2), v.pod2, volumeSpec2, volumeSpec2.Name(), "", nil)
if err1 != nil { if err1 != nil {
t.Fatalf("test %q: AddPodToVolume failed. Expected: <no error> Actual: <%v>", name, err1) t.Fatalf("test %q: AddPodToVolume failed. Expected: <no error> Actual: <%v>", name, err1)
} }
@ -309,7 +310,7 @@ func Test_DeletePodFromVolume_Positive_PodExistsVolumeExists(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */)
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }
@ -407,19 +408,19 @@ func Test_MarkVolumesReportedInUse_Positive_NewPodNewVolume(t *testing.T) {
pod3Name := util.GetUniquePodName(pod3) pod3Name := util.GetUniquePodName(pod3)
generatedVolume1Name, err := dsw.AddPodToVolume( generatedVolume1Name, err := dsw.AddPodToVolume(
pod1Name, pod1, volume1Spec, volume1Spec.Name(), "" /* volumeGidValue */) pod1Name, pod1, volume1Spec, volume1Spec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */)
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }
generatedVolume2Name, err := dsw.AddPodToVolume( generatedVolume2Name, err := dsw.AddPodToVolume(
pod2Name, pod2, volume2Spec, volume2Spec.Name(), "" /* volumeGidValue */) pod2Name, pod2, volume2Spec, volume2Spec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */)
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }
generatedVolume3Name, err := dsw.AddPodToVolume( generatedVolume3Name, err := dsw.AddPodToVolume(
pod3Name, pod3, volume3Spec, volume3Spec.Name(), "" /* volumeGidValue */) pod3Name, pod3, volume3Spec, volume3Spec.Name(), "" /* volumeGidValue */, nil /* seLinuxContainerContexts */)
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }
@ -580,14 +581,14 @@ func Test_AddPodToVolume_WithEmptyDirSizeLimit(t *testing.T) {
} }
for i := range pod1.Spec.Volumes { for i := range pod1.Spec.Volumes {
volumeSpec := &volume.Spec{Volume: &pod1.Spec.Volumes[i]} 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 { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }
} }
for i := range pod2.Spec.Volumes { for i := range pod2.Spec.Volumes {
volumeSpec := &volume.Spec{Volume: &pod2.Spec.Volumes[i]} 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 { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }

View File

@ -19,7 +19,7 @@ package metrics
import ( import (
"testing" "testing"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types" k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubelet/volumemanager/cache" "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache"
@ -56,7 +56,7 @@ func TestMetricCollection(t *testing.T) {
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
// Add one volume to DesiredStateOfWorld // 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 { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
} }

View File

@ -283,7 +283,7 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(
} }
allVolumesAdded := true allVolumesAdded := true
mounts, devices := util.GetPodVolumeNames(pod) mounts, devices, seLinuxContainerContexts := util.GetPodVolumeNames(pod)
// Process volume spec for each volume defined in pod // Process volume spec for each volume defined in pod
for _, podVolume := range pod.Spec.Volumes { for _, podVolume := range pod.Spec.Volumes {
@ -304,7 +304,7 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(
// Add volume to desired state of world // Add volume to desired state of world
uniqueVolumeName, err := dswp.desiredStateOfWorld.AddPodToVolume( uniqueVolumeName, err := dswp.desiredStateOfWorld.AddPodToVolume(
uniquePodName, pod, volumeSpec, podVolume.Name, volumeGidValue) uniquePodName, pod, volumeSpec, podVolume.Name, volumeGidValue, seLinuxContainerContexts[podVolume.Name])
if err != nil { if err != nil {
klog.ErrorS(err, "Failed to add volume to desiredStateOfWorld", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "volumeSpecName", volumeSpec.Name()) 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()) dswp.desiredStateOfWorld.AddErrorToPod(uniquePodName, err.Error())

View File

@ -199,7 +199,7 @@ func (rc *reconciler) reconcile() {
func (rc *reconciler) unmountVolumes() { func (rc *reconciler) unmountVolumes() {
// Ensure volumes that should be unmounted are unmounted. // Ensure volumes that should be unmounted are unmounted.
for _, mountedVolume := range rc.actualStateOfWorld.GetAllMountedVolumes() { 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 // Volume is mounted, unmount it
klog.V(5).InfoS(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountVolume", "")) klog.V(5).InfoS(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountVolume", ""))
err := rc.operationExecutor.UnmountVolume( err := rc.operationExecutor.UnmountVolume(
@ -217,9 +217,14 @@ func (rc *reconciler) unmountVolumes() {
func (rc *reconciler) mountOrAttachVolumes() { func (rc *reconciler) mountOrAttachVolumes() {
// Ensure volumes that should be attached/mounted are attached/mounted. // Ensure volumes that should be attached/mounted are attached/mounted.
for _, volumeToMount := range rc.desiredStateOfWorld.GetVolumesToMount() { 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 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) rc.waitForVolumeAttach(volumeToMount)
} else if !volMounted || cache.IsRemountRequiredError(err) { } else if !volMounted || cache.IsRemountRequiredError(err) {
rc.mountAttachedVolumes(volumeToMount, err) rc.mountAttachedVolumes(volumeToMount, err)
@ -373,7 +378,7 @@ func (rc *reconciler) waitForVolumeAttach(volumeToMount cache.VolumeToMount) {
func (rc *reconciler) unmountDetachDevices() { func (rc *reconciler) unmountDetachDevices() {
for _, attachedVolume := range rc.actualStateOfWorld.GetUnmountedVolumes() { for _, attachedVolume := range rc.actualStateOfWorld.GetUnmountedVolumes() {
// Check IsOperationPending to avoid marking a volume as detached if it's in the process of mounting. // 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) { !rc.operationExecutor.IsOperationPending(attachedVolume.VolumeName, nestedpendingoperations.EmptyUniquePodName, nestedpendingoperations.EmptyNodeName) {
if attachedVolume.DeviceMayBeMounted() { if attachedVolume.DeviceMayBeMounted() {
// Volume is globally mounted to device, unmount it // 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) klog.ErrorS(err, "Could not find device mount path for volume", "volumeName", gvl.volumeName)
continue 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 { if err != nil {
klog.ErrorS(err, "Could not mark device is mounted to actual state of world", "volume", gvl.volumeName) klog.ErrorS(err, "Could not mark device is mounted to actual state of world", "volume", gvl.volumeName)
continue continue

View File

@ -160,7 +160,7 @@ func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -349,7 +349,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
// Assert // Assert
@ -428,7 +428,7 @@ func Test_Run_Negative_VolumeMountControllerAttachEnabled(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -506,7 +506,7 @@ func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -608,7 +608,7 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -715,7 +715,7 @@ func Test_Run_Positive_VolumeAttachAndMap(t *testing.T) {
} }
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -827,7 +827,7 @@ func Test_Run_Positive_BlockVolumeMapControllerAttachEnabled(t *testing.T) {
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
// Assert // Assert
@ -924,7 +924,7 @@ func Test_Run_Positive_BlockVolumeAttachMapUnmapDetach(t *testing.T) {
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -1048,7 +1048,7 @@ func Test_Run_Positive_VolumeUnmapControllerAttachEnabled(t *testing.T) {
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
@ -1319,7 +1319,7 @@ func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) {
volumeSpec := &volume.Spec{PersistentVolume: pv} volumeSpec := &volume.Spec{PersistentVolume: pv}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
volumeName, err := dsw.AddPodToVolume( volumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
@ -1340,7 +1340,7 @@ func Test_Run_Positive_VolumeFSResizeControllerAttachEnabled(t *testing.T) {
// Simulate what DSOWP does // Simulate what DSOWP does
pvWithSize.Spec.Capacity[v1.ResourceStorage] = tc.newPVSize pvWithSize.Spec.Capacity[v1.ResourceStorage] = tc.newPVSize
volumeSpec = &volume.Spec{PersistentVolume: pvWithSize} 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()) t.Logf("Changing size of the volume to %s", tc.newPVSize.String())
newSize := tc.newPVSize.DeepCopy() newSize := tc.newPVSize.DeepCopy()
@ -1573,7 +1573,7 @@ func Test_UncertainDeviceGlobalMounts(t *testing.T) {
volumeSpec := &volume.Spec{PersistentVolume: pv} volumeSpec := &volume.Spec{PersistentVolume: pv}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
volumeName, err := dsw.AddPodToVolume( volumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
@ -1795,7 +1795,7 @@ func Test_UncertainVolumeMountState(t *testing.T) {
volumeSpec := &volume.Spec{PersistentVolume: pv} volumeSpec := &volume.Spec{PersistentVolume: pv}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
volumeName, err := dsw.AddPodToVolume( volumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
// Assert // Assert
if err != nil { if err != nil {
t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err) t.Fatalf("AddPodToVolume failed. Expected: <no error> Actual: <%v>", err)
@ -2133,7 +2133,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) {
volumeSpecCopy := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpecCopy := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
generatedVolumeName, err := dsw.AddPodToVolume( generatedVolumeName, err := dsw.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName}) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeName})
if err != nil { if err != nil {
@ -2158,7 +2158,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabledRace(t *testing.T) {
klog.InfoS("UnmountDevice called") klog.InfoS("UnmountDevice called")
var generatedVolumeNameCopy v1.UniqueVolumeName var generatedVolumeNameCopy v1.UniqueVolumeName
generatedVolumeNameCopy, err = dsw.AddPodToVolume( generatedVolumeNameCopy, err = dsw.AddPodToVolume(
podName, pod, volumeSpecCopy, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpecCopy, volumeSpec.Name(), "" /* volumeGidValue */, nil /* seLinuxLabel */)
dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeNameCopy}) dsw.MarkVolumesReportedInUse([]v1.UniqueVolumeName{generatedVolumeNameCopy})
return nil return nil
} }
@ -2410,7 +2410,7 @@ func TestSyncStates(t *testing.T) {
volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]} volumeSpec := &volume.Spec{Volume: &pod.Spec.Volumes[0]}
podName := util.GetUniquePodName(pod) podName := util.GetUniquePodName(pod)
volumeName, err := rcInstance.desiredStateOfWorld.AddPodToVolume( volumeName, err := rcInstance.desiredStateOfWorld.AddPodToVolume(
podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */) podName, pod, volumeSpec, volumeSpec.Name(), "" /* volumeGidValue */, nil /* SELinuxContext */)
if err != nil { if err != nil {
t.Fatalf("error adding volume %s to dsow: %v", volumeSpec.Name(), err) t.Fatalf("error adding volume %s to dsow: %v", volumeSpec.Name(), err)
} }

View File

@ -536,7 +536,7 @@ func filterUnmountedVolumes(mountedVolumes sets.String, expectedVolumes []string
// getExpectedVolumes returns a list of volumes that must be mounted in order to // getExpectedVolumes returns a list of volumes that must be mounted in order to
// consider the volume setup step for this pod satisfied. // consider the volume setup step for this pod satisfied.
func getExpectedVolumes(pod *v1.Pod) []string { func getExpectedVolumes(pod *v1.Pod) []string {
mounts, devices := util.GetPodVolumeNames(pod) mounts, devices, _ := util.GetPodVolumeNames(pod)
return mounts.Union(devices).UnsortedList() return mounts.Union(devices).UnsortedList()
} }

View File

@ -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 { type MarkVolumeOpts struct {
PodName volumetypes.UniquePodName PodName volumetypes.UniquePodName
PodUID types.UID PodUID types.UID
@ -177,6 +177,7 @@ type MarkVolumeOpts struct {
VolumeGidVolume string VolumeGidVolume string
VolumeSpec *volume.Spec VolumeSpec *volume.Spec
VolumeMountState VolumeMountState VolumeMountState VolumeMountState
SELinuxMountContext string
} }
// ActualStateOfWorldMounterUpdater defines a set of operations updating the actual // ActualStateOfWorldMounterUpdater defines a set of operations updating the actual
@ -192,10 +193,10 @@ type ActualStateOfWorldMounterUpdater interface {
MarkVolumeMountAsUncertain(markVolumeOpts MarkVolumeOpts) error MarkVolumeMountAsUncertain(markVolumeOpts MarkVolumeOpts) error
// Marks the specified volume as having been globally mounted. // 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 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. // Marks the specified volume as having its global mount unmounted.
MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error MarkDeviceAsUnmounted(volumeName v1.UniqueVolumeName) error
@ -446,6 +447,9 @@ type VolumeToMount struct {
// PersistentVolumeSize stores desired size of the volume. // PersistentVolumeSize stores desired size of the volume.
// usually this is the size if pv.Spec.Capacity // usually this is the size if pv.Spec.Capacity
PersistentVolumeSize resource.Quantity PersistentVolumeSize resource.Quantity
// SELinux label that should be used to mount.
SELinuxLabel string
} }
// DeviceMountState represents device mount state in a global path. // 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 // PluginName is the Unescaped Qualified name of the volume plugin used to
// attach and mount this volume. // attach and mount this volume.
PluginName string PluginName string
SELinuxMountContext string
} }
// GenerateMsgDetailed returns detailed msgs for attached volumes // 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 // DeviceMountPath contains the path on the node where the device should
// be mounted after it is attached. // be mounted after it is attached.
DeviceMountPath string 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 // GenerateMsgDetailed returns detailed msgs for mounted volumes

View File

@ -645,7 +645,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
volumeToMount.VolumeSpec, volumeToMount.VolumeSpec,
devicePath, devicePath,
deviceMountPath, deviceMountPath,
volume.DeviceMounterArgs{FsGroup: fsGroup}, volume.DeviceMounterArgs{FsGroup: fsGroup, SELinuxLabel: volumeToMount.SELinuxLabel},
) )
if err != nil { if err != nil {
og.checkForFailedMount(volumeToMount, err) og.checkForFailedMount(volumeToMount, err)
@ -659,7 +659,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
// Update actual state of world to reflect volume is globally mounted // Update actual state of world to reflect volume is globally mounted
markDeviceMountedErr := actualStateOfWorld.MarkDeviceAsMounted( markDeviceMountedErr := actualStateOfWorld.MarkDeviceAsMounted(
volumeToMount.VolumeName, devicePath, deviceMountPath) volumeToMount.VolumeName, devicePath, deviceMountPath, volumeToMount.SELinuxLabel)
if markDeviceMountedErr != nil { if markDeviceMountedErr != nil {
// On failure, return error. Caller will log and retry. // On failure, return error. Caller will log and retry.
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MarkDeviceAsMounted failed", markDeviceMountedErr) eventErr, detailedErr := volumeToMount.GenerateError("MountVolume.MarkDeviceAsMounted failed", markDeviceMountedErr)
@ -688,6 +688,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
FsGroup: hostGID, FsGroup: hostGID,
DesiredSize: volumeToMount.DesiredSizeLimit, DesiredSize: volumeToMount.DesiredSizeLimit,
FSGroupChangePolicy: fsGroupChangePolicy, FSGroupChangePolicy: fsGroupChangePolicy,
SELinuxLabel: volumeToMount.SELinuxLabel,
}) })
// Update actual state of world // Update actual state of world
markOpts := MarkVolumeOpts{ markOpts := MarkVolumeOpts{
@ -699,6 +700,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
VolumeGidVolume: volumeToMount.VolumeGidValue, VolumeGidVolume: volumeToMount.VolumeGidValue,
VolumeSpec: volumeToMount.VolumeSpec, VolumeSpec: volumeToMount.VolumeSpec,
VolumeMountState: VolumeMounted, VolumeMountState: VolumeMounted,
SELinuxMountContext: volumeToMount.SELinuxLabel,
} }
if mountErr != nil { if mountErr != nil {
og.checkForFailedMount(volumeToMount, mountErr) og.checkForFailedMount(volumeToMount, mountErr)
@ -787,7 +789,7 @@ func (og *operationGenerator) markDeviceErrorState(volumeToMount VolumeToMount,
actualStateOfWorld.GetDeviceMountState(volumeToMount.VolumeName) == DeviceNotMounted { actualStateOfWorld.GetDeviceMountState(volumeToMount.VolumeName) == DeviceNotMounted {
// only devices which are not mounted can be marked as uncertain. We do not want to mark a device // 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. // 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 { if markDeviceUncertainError != nil {
klog.Errorf(volumeToMount.GenerateErrorDetailed("MountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainError).Error()) klog.Errorf(volumeToMount.GenerateErrorDetailed("MountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainError).Error())
} }
@ -955,7 +957,7 @@ func (og *operationGenerator) GenerateUnmountDeviceFunc(
unmountDeviceErr := volumeDeviceUnmounter.UnmountDevice(deviceMountPath) unmountDeviceErr := volumeDeviceUnmounter.UnmountDevice(deviceMountPath)
if unmountDeviceErr != nil { if unmountDeviceErr != nil {
// Mark the device as uncertain, so MountDevice is called for new pods. UnmountDevice may be already in progress. // 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 { if markDeviceUncertainErr != nil {
// There is nothing else we can do. Hope that UnmountDevice will be re-tried shortly. // There is nothing else we can do. Hope that UnmountDevice will be re-tried shortly.
klog.Errorf(deviceToDetach.GenerateErrorDetailed("UnmountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr).Error()) 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. // The device is still in use elsewhere. Caller will log and retry.
if deviceOpened { if deviceOpened {
// Mark the device as uncertain, so MountDevice is called for new pods. // 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 { if markDeviceUncertainErr != nil {
// There is nothing else we can do. Hope that UnmountDevice will be re-tried shortly. // There is nothing else we can do. Hope that UnmountDevice will be re-tried shortly.
klog.Errorf(deviceToDetach.GenerateErrorDetailed("UnmountDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr).Error()) 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 // Update actual state of world to reflect volume is globally mounted
markedDevicePath := devicePath markedDevicePath := devicePath
markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted( markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted(
volumeToMount.VolumeName, markedDevicePath, globalMapPath) volumeToMount.VolumeName, markedDevicePath, globalMapPath, "")
if markDeviceMappedErr != nil { if markDeviceMappedErr != nil {
// On failure, return error. Caller will log and retry. // On failure, return error. Caller will log and retry.
eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.MarkDeviceAsMounted failed", markDeviceMappedErr) 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. // TODO: This can be improved after #82492 is merged and ASW has state.
if markedDevicePath != devicePath { if markedDevicePath != devicePath {
markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted( markDeviceMappedErr := actualStateOfWorld.MarkDeviceAsMounted(
volumeToMount.VolumeName, devicePath, globalMapPath) volumeToMount.VolumeName, devicePath, globalMapPath, "")
if markDeviceMappedErr != nil { if markDeviceMappedErr != nil {
// On failure, return error. Caller will log and retry. // On failure, return error. Caller will log and retry.
eventErr, detailedErr := volumeToMount.GenerateError("MapVolume.MarkDeviceAsMounted failed", markDeviceMappedErr) 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 // cases below. The volume is marked as fully un-mapped at the end of this function, when everything
// succeeds. // succeeds.
markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain( markDeviceUncertainErr := actualStateOfWorld.MarkDeviceAsUncertain(
deviceToDetach.VolumeName, deviceToDetach.DevicePath, globalMapPath) deviceToDetach.VolumeName, deviceToDetach.DevicePath, globalMapPath, "" /* seLinuxMountContext */)
if markDeviceUncertainErr != nil { if markDeviceUncertainErr != nil {
// On failure, return error. Caller will log and retry. // On failure, return error. Caller will log and retry.
eventErr, detailedErr := deviceToDetach.GenerateError("UnmapDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr) eventErr, detailedErr := deviceToDetach.GenerateError("UnmapDevice.MarkDeviceAsUncertain failed", markDeviceUncertainErr)

117
pkg/volume/util/selinux.go Normal file
View File

@ -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))
}

View File

@ -275,16 +275,6 @@ func JoinMountOptions(userOptions []string, systemOptions []string) []string {
return allMountOptions.List() 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 // ContainsAccessMode returns whether the requested mode is contained by modes
func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
for _, m := range modes { 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, // GetPodVolumeNames returns names of volumes that are used in a pod,
// either as filesystem mount or raw block device. // either as filesystem mount or raw block device, together with list
func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String) { // 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() mounts = sets.NewString()
devices = 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 { 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 { if container.VolumeMounts != nil {
for _, mount := range container.VolumeMounts { for _, mount := range container.VolumeMounts {
mounts.Insert(mount.Name) mounts.Insert(mount.Name)
if seLinuxOptions != nil {
seLinuxContainerContexts[mount.Name] = append(seLinuxContainerContexts[mount.Name], seLinuxOptions.DeepCopy())
}
} }
} }
if container.VolumeDevices != nil { if container.VolumeDevices != nil {