diff --git a/pkg/api/pod/util.go b/pkg/api/pod/util.go index eae41e55b04..2d73f582e7b 100644 --- a/pkg/api/pod/util.go +++ b/pkg/api/pod/util.go @@ -353,6 +353,24 @@ func hasInvalidTopologySpreadConstraintLabelSelector(spec *api.PodSpec) bool { return false } +// hasNonLocalProjectedTokenPath return true if spec.Volumes have any entry with non-local projected token path +func hasNonLocalProjectedTokenPath(spec *api.PodSpec) bool { + for _, volume := range spec.Volumes { + if volume.Projected != nil { + for _, source := range volume.Projected.Sources { + if source.ServiceAccountToken == nil { + continue + } + errs := apivalidation.ValidateLocalNonReservedPath(source.ServiceAccountToken.Path, nil) + if len(errs) != 0 { + return true + } + } + } + } + return false +} + // GetValidationOptionsFromPodSpecAndMeta returns validation options based on pod specs and metadata func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, podMeta, oldPodMeta *metav1.ObjectMeta) apivalidation.PodValidationOptions { // default pod validation options based on feature gate @@ -366,6 +384,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po AllowInvalidTopologySpreadConstraintLabelSelector: false, AllowMutableNodeSelectorAndNodeAffinity: utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness), AllowNamespacedSysctlsForHostNetAndHostIPC: false, + AllowNonLocalProjectedTokenPath: false, } if oldPodSpec != nil { @@ -378,6 +397,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec) // if old spec has invalid labelSelector in topologySpreadConstraint, we must allow it opts.AllowInvalidTopologySpreadConstraintLabelSelector = hasInvalidTopologySpreadConstraintLabelSelector(oldPodSpec) + // if old spec has an invalid projected token volume path, we must allow it + opts.AllowNonLocalProjectedTokenPath = hasNonLocalProjectedTokenPath(oldPodSpec) // if old spec has invalid sysctl with hostNet or hostIPC, we must allow it when update if oldPodSpec.SecurityContext != nil && len(oldPodSpec.SecurityContext.Sysctls) != 0 { diff --git a/pkg/api/pod/util_test.go b/pkg/api/pod/util_test.go index 65815aba06c..4c92a197518 100644 --- a/pkg/api/pod/util_test.go +++ b/pkg/api/pod/util_test.go @@ -2744,6 +2744,88 @@ func TestValidateTopologySpreadConstraintLabelSelectorOption(t *testing.T) { } } +func TestValidateAllowNonLocalProjectedTokenPathOption(t *testing.T) { + testCases := []struct { + name string + oldPodSpec *api.PodSpec + wantOption bool + }{ + { + name: "Create", + wantOption: false, + }, + { + name: "UpdateInvalidProjectedTokenPath", + oldPodSpec: &api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + { + ServiceAccountToken: &api.ServiceAccountTokenProjection{ + Path: "../foo", + }, + }, + }, + }, + }, + }, + }, + }, + wantOption: true, + }, + { + name: "UpdateValidProjectedTokenPath", + oldPodSpec: &api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: &api.ProjectedVolumeSource{ + Sources: []api.VolumeProjection{ + { + ServiceAccountToken: &api.ServiceAccountTokenProjection{ + Path: "foo", + }, + }, + }, + }, + }, + }, + }, + }, + wantOption: false, + }, + { + name: "UpdateEmptyProjectedTokenPath", + oldPodSpec: &api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "foo", + VolumeSource: api.VolumeSource{ + Projected: nil, + HostPath: &api.HostPathVolumeSource{Path: "foo"}, + }, + }, + }, + }, + wantOption: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Pod meta doesn't impact the outcome. + gotOptions := GetValidationOptionsFromPodSpecAndMeta(&api.PodSpec{}, tc.oldPodSpec, nil, nil) + if tc.wantOption != gotOptions.AllowNonLocalProjectedTokenPath { + t.Errorf("Got AllowNonLocalProjectedTokenPath=%t, want %t", gotOptions.AllowNonLocalProjectedTokenPath, tc.wantOption) + } + }) + } +} + func TestDropInPlacePodVerticalScaling(t *testing.T) { podWithInPlaceVerticalScaling := func() *api.Pod { return &api.Pod{ diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index a6f7fef3012..58274dc86d6 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -942,7 +942,7 @@ func validateKeyToPath(kp *core.KeyToPath, fldPath *field.Path) field.ErrorList if len(kp.Path) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) } - allErrs = append(allErrs, validateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...) + allErrs = append(allErrs, ValidateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...) if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) { allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, fileModeErrorMsg)) } @@ -1050,7 +1050,7 @@ func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *fi if len(file.Path) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) } - allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...) + allErrs = append(allErrs, ValidateLocalNonReservedPath(file.Path, fldPath.Child("path"))...) if file.FieldRef != nil { allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...) if file.ResourceFieldRef != nil { @@ -1153,6 +1153,8 @@ func validateProjectionSources(projection *core.ProjectedVolumeSource, projectio } if source.ServiceAccountToken.Path == "" { allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) + } else if !opts.AllowNonLocalProjectedTokenPath { + allErrs = append(allErrs, ValidateLocalNonReservedPath(source.ServiceAccountToken.Path, fldPath.Child("path"))...) } } if projPath := srcPath.Child("clusterTrustBundlePEM"); source.ClusterTrustBundle != nil { @@ -1209,7 +1211,7 @@ func validateProjectionSources(projection *core.ProjectedVolumeSource, projectio allErrs = append(allErrs, field.Required(projPath.Child("path"), "")) } - allErrs = append(allErrs, validateLocalNonReservedPath(source.ClusterTrustBundle.Path, projPath.Child("path"))...) + allErrs = append(allErrs, ValidateLocalNonReservedPath(source.ClusterTrustBundle.Path, projPath.Child("path"))...) curPath := source.ClusterTrustBundle.Path if !allPaths.Has(curPath) { @@ -1319,11 +1321,11 @@ func validateMountPropagation(mountPropagation *core.MountPropagationMode, conta return allErrs } -// This validate will make sure targetPath: +// ValidateLocalNonReservedPath makes sure targetPath: // 1. is not abs path // 2. does not contain any '..' elements // 3. does not start with '..' -func validateLocalNonReservedPath(targetPath string, fldPath *field.Path) field.ErrorList { +func ValidateLocalNonReservedPath(targetPath string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...) // Don't report this error if the check for .. elements already caught it. @@ -3978,6 +3980,8 @@ type PodValidationOptions struct { AllowInvalidTopologySpreadConstraintLabelSelector bool // Allow node selector additions for gated pods. AllowMutableNodeSelectorAndNodeAffinity bool + // Allow projected token volumes with non-local paths + AllowNonLocalProjectedTokenPath bool // Allow namespaced sysctls in hostNet and hostIPC pods AllowNamespacedSysctlsForHostNetAndHostIPC bool // The top-level resource being validated is a Pod, not just a PodSpec