mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
Merge pull request #121731 from Taction/service-account-token-projected-volume-validation
Fix service account token projected volume validation
This commit is contained in:
commit
fc4d6ac8ea
@ -353,6 +353,24 @@ func hasInvalidTopologySpreadConstraintLabelSelector(spec *api.PodSpec) bool {
|
|||||||
return false
|
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
|
// GetValidationOptionsFromPodSpecAndMeta returns validation options based on pod specs and metadata
|
||||||
func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, podMeta, oldPodMeta *metav1.ObjectMeta) apivalidation.PodValidationOptions {
|
func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, podMeta, oldPodMeta *metav1.ObjectMeta) apivalidation.PodValidationOptions {
|
||||||
// default pod validation options based on feature gate
|
// default pod validation options based on feature gate
|
||||||
@ -366,6 +384,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||||||
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
||||||
AllowMutableNodeSelectorAndNodeAffinity: utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness),
|
AllowMutableNodeSelectorAndNodeAffinity: utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness),
|
||||||
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
||||||
|
AllowNonLocalProjectedTokenPath: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldPodSpec != nil {
|
if oldPodSpec != nil {
|
||||||
@ -378,6 +397,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||||||
opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec)
|
opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec)
|
||||||
// if old spec has invalid labelSelector in topologySpreadConstraint, we must allow it
|
// if old spec has invalid labelSelector in topologySpreadConstraint, we must allow it
|
||||||
opts.AllowInvalidTopologySpreadConstraintLabelSelector = hasInvalidTopologySpreadConstraintLabelSelector(oldPodSpec)
|
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 old spec has invalid sysctl with hostNet or hostIPC, we must allow it when update
|
||||||
if oldPodSpec.SecurityContext != nil && len(oldPodSpec.SecurityContext.Sysctls) != 0 {
|
if oldPodSpec.SecurityContext != nil && len(oldPodSpec.SecurityContext.Sysctls) != 0 {
|
||||||
|
@ -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) {
|
func TestDropInPlacePodVerticalScaling(t *testing.T) {
|
||||||
podWithInPlaceVerticalScaling := func() *api.Pod {
|
podWithInPlaceVerticalScaling := func() *api.Pod {
|
||||||
return &api.Pod{
|
return &api.Pod{
|
||||||
|
@ -942,7 +942,7 @@ func validateKeyToPath(kp *core.KeyToPath, fldPath *field.Path) field.ErrorList
|
|||||||
if len(kp.Path) == 0 {
|
if len(kp.Path) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
|
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) {
|
if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, fileModeErrorMsg))
|
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 {
|
if len(file.Path) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
|
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 {
|
if file.FieldRef != nil {
|
||||||
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
||||||
if file.ResourceFieldRef != nil {
|
if file.ResourceFieldRef != nil {
|
||||||
@ -1153,6 +1153,8 @@ func validateProjectionSources(projection *core.ProjectedVolumeSource, projectio
|
|||||||
}
|
}
|
||||||
if source.ServiceAccountToken.Path == "" {
|
if source.ServiceAccountToken.Path == "" {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("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 {
|
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, 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
|
curPath := source.ClusterTrustBundle.Path
|
||||||
if !allPaths.Has(curPath) {
|
if !allPaths.Has(curPath) {
|
||||||
@ -1319,11 +1321,11 @@ func validateMountPropagation(mountPropagation *core.MountPropagationMode, conta
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// This validate will make sure targetPath:
|
// ValidateLocalNonReservedPath makes sure targetPath:
|
||||||
// 1. is not abs path
|
// 1. is not abs path
|
||||||
// 2. does not contain any '..' elements
|
// 2. does not contain any '..' elements
|
||||||
// 3. does not start with '..'
|
// 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 := field.ErrorList{}
|
||||||
allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...)
|
allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...)
|
||||||
// Don't report this error if the check for .. elements already caught it.
|
// Don't report this error if the check for .. elements already caught it.
|
||||||
@ -3978,6 +3980,8 @@ type PodValidationOptions struct {
|
|||||||
AllowInvalidTopologySpreadConstraintLabelSelector bool
|
AllowInvalidTopologySpreadConstraintLabelSelector bool
|
||||||
// Allow node selector additions for gated pods.
|
// Allow node selector additions for gated pods.
|
||||||
AllowMutableNodeSelectorAndNodeAffinity bool
|
AllowMutableNodeSelectorAndNodeAffinity bool
|
||||||
|
// Allow projected token volumes with non-local paths
|
||||||
|
AllowNonLocalProjectedTokenPath bool
|
||||||
// Allow namespaced sysctls in hostNet and hostIPC pods
|
// Allow namespaced sysctls in hostNet and hostIPC pods
|
||||||
AllowNamespacedSysctlsForHostNetAndHostIPC bool
|
AllowNamespacedSysctlsForHostNetAndHostIPC bool
|
||||||
// The top-level resource being validated is a Pod, not just a PodSpec
|
// The top-level resource being validated is a Pod, not just a PodSpec
|
||||||
|
Loading…
Reference in New Issue
Block a user