diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index dd4ef535c11..ecccf969492 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -1321,6 +1321,32 @@ func validateMountPropagation(mountPropagation *core.MountPropagationMode, conta return allErrs } +// validateMountRecursiveReadOnly validates RecursiveReadOnly mounts. +func validateMountRecursiveReadOnly(mount core.VolumeMount, fldPath *field.Path) field.ErrorList { + if mount.RecursiveReadOnly == nil { + return nil + } + allErrs := field.ErrorList{} + switch *mount.RecursiveReadOnly { + case core.RecursiveReadOnlyDisabled: + // NOP + case core.RecursiveReadOnlyEnabled, core.RecursiveReadOnlyIfPossible: + if !mount.ReadOnly { + allErrs = append(allErrs, field.Forbidden(fldPath, "may only be specified when readOnly is true")) + } + if mount.MountPropagation != nil && *mount.MountPropagation != core.MountPropagationNone { + allErrs = append(allErrs, field.Forbidden(fldPath, "may only be specified when mountPropagation is None or not specified")) + } + default: + supportedRRO := sets.New( + core.RecursiveReadOnlyDisabled, + core.RecursiveReadOnlyIfPossible, + core.RecursiveReadOnlyEnabled) + allErrs = append(allErrs, field.NotSupported(fldPath, *mount.RecursiveReadOnly, sets.List(supportedRRO))) + } + return allErrs +} + // ValidateLocalNonReservedPath makes sure targetPath: // 1. is not abs path // 2. does not contain any '..' elements @@ -2909,6 +2935,7 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]strin if mnt.MountPropagation != nil { allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...) } + allErrs = append(allErrs, validateMountRecursiveReadOnly(mnt, fldPath.Child("recursiveReadOnly"))...) } return allErrs } diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index eaa2a110287..6b837f21164 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -7118,6 +7118,12 @@ func TestValidateVolumeMounts(t *testing.T) { {Name: "abc-123", MountPath: "/bac", SubPath: ".baz"}, {Name: "abc-123", MountPath: "/bad", SubPath: "..baz"}, {Name: "ephemeral", MountPath: "/foobar"}, + {Name: "123", MountPath: "/rro-nil", ReadOnly: true, RecursiveReadOnly: nil}, + {Name: "123", MountPath: "/rro-disabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)}, + {Name: "123", MountPath: "/rro-disabled-2", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyDisabled)}, + {Name: "123", MountPath: "/rro-ifpossible", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}, + {Name: "123", MountPath: "/rro-enabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}, + {Name: "123", MountPath: "/rro-enabled-2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationNone)}, } goodVolumeDevices := []core.VolumeDevice{ {Name: "xyz", DevicePath: "/foofoo"}, @@ -7140,6 +7146,9 @@ func TestValidateVolumeMounts(t *testing.T) { "name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}}, "mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}}, "both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}}, + "rro but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}}, + "rro with incompatible propagation": {{Name: "123", MountPath: "/rro-bad2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationHostToContainer)}}, + "rro-if-possible but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}}, } badVolumeDevice := []core.VolumeDevice{ {Name: "xyz", DevicePath: "/mnt/exists"},