Skip to content

Commit 87f9b38

Browse files
authored
Merge pull request kubernetes#123385 from HirazawaUi/allow-special-characters
Allow almost all printable ASCII characters in environment variables
2 parents 5b4d97d + 01689d0 commit 87f9b38

File tree

13 files changed

+1064
-191
lines changed

13 files changed

+1064
-191
lines changed

pkg/api/pod/util.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
v1 "k8s.io/api/core/v1"
2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2525
metavalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
26+
"k8s.io/apimachinery/pkg/util/sets"
27+
"k8s.io/apimachinery/pkg/util/validation"
2628
utilfeature "k8s.io/apiserver/pkg/util/feature"
2729
api "k8s.io/kubernetes/pkg/apis/core"
2830
"k8s.io/kubernetes/pkg/apis/core/helper"
@@ -386,6 +388,10 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
386388
AllowNonLocalProjectedTokenPath: false,
387389
}
388390

391+
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
392+
// we must allow it
393+
opts.AllowRelaxedEnvironmentVariableValidation = useRelaxedEnvironmentVariableValidation(podSpec, oldPodSpec)
394+
389395
if oldPodSpec != nil {
390396
// if old spec has status.hostIPs downwardAPI set, we must allow it
391397
opts.AllowHostIPsField = opts.AllowHostIPsField || hasUsedDownwardAPIFieldPathWithPodSpec(oldPodSpec, "status.hostIPs")
@@ -419,6 +425,84 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
419425
return opts
420426
}
421427

428+
func useRelaxedEnvironmentVariableValidation(podSpec, oldPodSpec *api.PodSpec) bool {
429+
var oldPodEnvVarNames, podEnvVarNames sets.Set[string]
430+
if oldPodSpec != nil {
431+
oldPodEnvVarNames = gatherPodEnvVarNames(oldPodSpec)
432+
}
433+
434+
if podSpec != nil {
435+
podEnvVarNames = gatherPodEnvVarNames(podSpec)
436+
}
437+
438+
for env := range podEnvVarNames {
439+
if utilfeature.DefaultFeatureGate.Enabled(features.RelaxedEnvironmentVariableValidation) || relaxedEnvVarUsed(env, oldPodEnvVarNames) {
440+
return true
441+
}
442+
}
443+
444+
return false
445+
}
446+
447+
func gatherPodEnvVarNames(podSpec *api.PodSpec) sets.Set[string] {
448+
podEnvVarNames := sets.Set[string]{}
449+
450+
for _, c := range podSpec.Containers {
451+
for _, env := range c.Env {
452+
podEnvVarNames.Insert(env.Name)
453+
}
454+
455+
for _, env := range c.EnvFrom {
456+
podEnvVarNames.Insert(env.Prefix)
457+
}
458+
}
459+
460+
for _, c := range podSpec.InitContainers {
461+
for _, env := range c.Env {
462+
podEnvVarNames.Insert(env.Name)
463+
}
464+
465+
for _, env := range c.EnvFrom {
466+
podEnvVarNames.Insert(env.Prefix)
467+
}
468+
}
469+
470+
for _, c := range podSpec.EphemeralContainers {
471+
for _, env := range c.Env {
472+
podEnvVarNames.Insert(env.Name)
473+
}
474+
475+
for _, env := range c.EnvFrom {
476+
podEnvVarNames.Insert(env.Prefix)
477+
}
478+
}
479+
480+
return podEnvVarNames
481+
}
482+
483+
func relaxedEnvVarUsed(name string, oldPodEnvVarNames sets.Set[string]) bool {
484+
// A length of 0 means this is not an update request,
485+
// or the old pod does not exist in the env.
486+
// We will let the feature gate decide whether to use relaxed rules.
487+
if oldPodEnvVarNames.Len() == 0 {
488+
return false
489+
}
490+
491+
if len(validation.IsEnvVarName(name)) == 0 || len(validation.IsRelaxedEnvVarName(name)) != 0 {
492+
// It's either a valid name by strict rules or an invalid name under relaxed rules.
493+
// Either way, we'll use strict rules to validate.
494+
return false
495+
}
496+
497+
// The name in question failed strict rules but passed relaxed rules.
498+
if oldPodEnvVarNames.Has(name) {
499+
// This relaxed-rules name was already in use.
500+
return true
501+
}
502+
503+
return false
504+
}
505+
422506
func hasUsedDownwardAPIFieldPathWithPodSpec(podSpec *api.PodSpec, fieldPath string) bool {
423507
if podSpec == nil {
424508
return false

pkg/apis/core/types.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2068,7 +2068,11 @@ type VolumeDevice struct {
20682068

20692069
// EnvVar represents an environment variable present in a Container.
20702070
type EnvVar struct {
2071-
// Required: This must be a C_IDENTIFIER.
2071+
// Required: Name of the environment variable.
2072+
// When the RelaxedEnvironmentVariableValidation feature gate is disabled, this must consist of alphabetic characters,
2073+
// digits, '_', '-', or '.', and must not start with a digit.
2074+
// When the RelaxedEnvironmentVariableValidation feature gate is enabled,
2075+
// this may contain any printable ASCII characters except '='.
20722076
Name string
20732077
// Optional: no more than one of the following may be specified.
20742078
// Optional: Defaults to ""; variable references $(VAR_NAME) are expanded

pkg/apis/core/validation/validation.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2557,8 +2557,14 @@ func ValidateEnv(vars []core.EnvVar, fldPath *field.Path, opts PodValidationOpti
25572557
if len(ev.Name) == 0 {
25582558
allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
25592559
} else {
2560-
for _, msg := range validation.IsEnvVarName(ev.Name) {
2561-
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg))
2560+
if opts.AllowRelaxedEnvironmentVariableValidation {
2561+
for _, msg := range validation.IsRelaxedEnvVarName(ev.Name) {
2562+
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg))
2563+
}
2564+
} else {
2565+
for _, msg := range validation.IsEnvVarName(ev.Name) {
2566+
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg))
2567+
}
25622568
}
25632569
}
25642570
allErrs = append(allErrs, validateEnvVarValueFrom(ev, idxPath.Child("valueFrom"), opts)...)
@@ -2703,13 +2709,19 @@ func validateContainerResourceFieldSelector(fs *core.ResourceFieldSelector, expr
27032709
return allErrs
27042710
}
27052711

2706-
func ValidateEnvFrom(vars []core.EnvFromSource, fldPath *field.Path) field.ErrorList {
2712+
func ValidateEnvFrom(vars []core.EnvFromSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
27072713
allErrs := field.ErrorList{}
27082714
for i, ev := range vars {
27092715
idxPath := fldPath.Index(i)
27102716
if len(ev.Prefix) > 0 {
2711-
for _, msg := range validation.IsEnvVarName(ev.Prefix) {
2712-
allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg))
2717+
if opts.AllowRelaxedEnvironmentVariableValidation {
2718+
for _, msg := range validation.IsRelaxedEnvVarName(ev.Prefix) {
2719+
allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg))
2720+
}
2721+
} else {
2722+
for _, msg := range validation.IsEnvVarName(ev.Prefix) {
2723+
allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg))
2724+
}
27132725
}
27142726
}
27152727

@@ -3532,7 +3544,7 @@ func validateContainerCommon(ctr *core.Container, volumes map[string]core.Volume
35323544
volDevices := GetVolumeDeviceMap(ctr.VolumeDevices)
35333545
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, path.Child("ports"))...)
35343546
allErrs = append(allErrs, ValidateEnv(ctr.Env, path.Child("env"), opts)...)
3535-
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, path.Child("envFrom"))...)
3547+
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, path.Child("envFrom"), opts)...)
35363548
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"))...)
35373549
allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, path.Child("volumeDevices"))...)
35383550
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, path.Child("imagePullPolicy"))...)
@@ -3981,6 +3993,8 @@ type PodValidationOptions struct {
39813993
// The top-level resource being validated is a Pod, not just a PodSpec
39823994
// embedded in some other resource.
39833995
ResourceIsPod bool
3996+
// Allow relaxed validation of environment variable names
3997+
AllowRelaxedEnvironmentVariableValidation bool
39843998
}
39853999

39864000
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,

0 commit comments

Comments
 (0)