Skip to content

Commit a2a32fc

Browse files
authored
Merge pull request kubernetes#121968 from Peac36/fix/121414
add warnings for cases one of projected volume types get overwritten by service account token
2 parents 60651eb + e1178c4 commit a2a32fc

File tree

2 files changed

+756
-10
lines changed

2 files changed

+756
-10
lines changed

pkg/api/pod/warnings.go

+168
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package pod
1919
import (
2020
"context"
2121
"fmt"
22+
"os"
2223
"strings"
2324

2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -168,6 +169,10 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
168169
}
169170
}
170171

172+
if overlaps := warningsForOverlappingVirtualPaths(podSpec.Volumes); len(overlaps) > 0 {
173+
warnings = append(warnings, overlaps...)
174+
}
175+
171176
// duplicate hostAliases (#91670, #58477)
172177
if len(podSpec.HostAliases) > 1 {
173178
items := sets.New[string]()
@@ -354,3 +359,166 @@ func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fi
354359
}
355360
return warnings
356361
}
362+
363+
// warningsForOverlappingVirtualPaths validates that there are no overlapping paths in single ConfigMapVolume, SecretVolume, DownwardAPIVolume and ProjectedVolume.
364+
// A volume can try to load different keys to the same path which will result in overwriting of the value from the latest registered key
365+
// Another possible scenario is when one of the path contains the other key path. Example:
366+
// configMap:
367+
//
368+
// name: myconfig
369+
// items:
370+
// - key: key1
371+
// path: path
372+
// - key: key2
373+
// path: path/path2
374+
//
375+
// In such cases we either get `is directory` or 'file exists' error message.
376+
func warningsForOverlappingVirtualPaths(volumes []api.Volume) []string {
377+
var warnings []string
378+
379+
mkWarn := func(volName, volDesc, body string) string {
380+
return fmt.Sprintf("volume %q (%s): overlapping paths: %s", volName, volDesc, body)
381+
}
382+
383+
for _, v := range volumes {
384+
if v.ConfigMap != nil && v.ConfigMap.Items != nil {
385+
overlaps := checkVolumeMappingForOverlap(extractPaths(v.ConfigMap.Items, ""))
386+
for _, ol := range overlaps {
387+
warnings = append(warnings, mkWarn(v.Name, fmt.Sprintf("ConfigMap %q", v.ConfigMap.Name), ol))
388+
}
389+
}
390+
391+
if v.Secret != nil && v.Secret.Items != nil {
392+
overlaps := checkVolumeMappingForOverlap(extractPaths(v.Secret.Items, ""))
393+
for _, ol := range overlaps {
394+
warnings = append(warnings, mkWarn(v.Name, fmt.Sprintf("Secret %q", v.Secret.SecretName), ol))
395+
}
396+
}
397+
398+
if v.DownwardAPI != nil && v.DownwardAPI.Items != nil {
399+
overlaps := checkVolumeMappingForOverlap(extractPathsDownwardAPI(v.DownwardAPI.Items, ""))
400+
for _, ol := range overlaps {
401+
warnings = append(warnings, mkWarn(v.Name, "DownwardAPI", ol))
402+
}
403+
}
404+
405+
if v.Projected != nil {
406+
var sourcePaths []pathAndSource
407+
var allPaths []pathAndSource
408+
409+
for _, source := range v.Projected.Sources {
410+
if source == (api.VolumeProjection{}) {
411+
warnings = append(warnings, fmt.Sprintf("volume %q (Projected) has no sources provided", v.Name))
412+
continue
413+
}
414+
415+
switch {
416+
case source.ConfigMap != nil && source.ConfigMap.Items != nil:
417+
sourcePaths = extractPaths(source.ConfigMap.Items, fmt.Sprintf("ConfigMap %q", source.ConfigMap.Name))
418+
case source.Secret != nil && source.Secret.Items != nil:
419+
sourcePaths = extractPaths(source.Secret.Items, fmt.Sprintf("Secret %q", source.Secret.Name))
420+
case source.DownwardAPI != nil && source.DownwardAPI.Items != nil:
421+
sourcePaths = extractPathsDownwardAPI(source.DownwardAPI.Items, "DownwardAPI")
422+
case source.ServiceAccountToken != nil:
423+
sourcePaths = []pathAndSource{{source.ServiceAccountToken.Path, "ServiceAccountToken"}}
424+
case source.ClusterTrustBundle != nil:
425+
name := ""
426+
if source.ClusterTrustBundle.Name != nil {
427+
name = *source.ClusterTrustBundle.Name
428+
} else {
429+
name = *source.ClusterTrustBundle.SignerName
430+
}
431+
sourcePaths = []pathAndSource{{source.ClusterTrustBundle.Path, fmt.Sprintf("ClusterTrustBundle %q", name)}}
432+
}
433+
434+
if len(sourcePaths) == 0 {
435+
continue
436+
}
437+
438+
for _, ps := range sourcePaths {
439+
ps.path = strings.TrimRight(ps.path, string(os.PathSeparator))
440+
if collisions := checkForOverlap(allPaths, ps); len(collisions) > 0 {
441+
for _, c := range collisions {
442+
warnings = append(warnings, mkWarn(v.Name, "Projected", fmt.Sprintf("%s with %s", ps.String(), c.String())))
443+
}
444+
}
445+
allPaths = append(allPaths, ps)
446+
}
447+
}
448+
}
449+
}
450+
return warnings
451+
}
452+
453+
// this lets us track a path and where it came from, for better errors
454+
type pathAndSource struct {
455+
path string
456+
source string
457+
}
458+
459+
func (ps pathAndSource) String() string {
460+
if ps.source != "" {
461+
return fmt.Sprintf("%q (%s)", ps.path, ps.source)
462+
}
463+
return fmt.Sprintf("%q", ps.path)
464+
}
465+
466+
func extractPaths(mapping []api.KeyToPath, source string) []pathAndSource {
467+
result := make([]pathAndSource, 0, len(mapping))
468+
469+
for _, v := range mapping {
470+
result = append(result, pathAndSource{v.Path, source})
471+
}
472+
return result
473+
}
474+
475+
func extractPathsDownwardAPI(mapping []api.DownwardAPIVolumeFile, source string) []pathAndSource {
476+
result := make([]pathAndSource, 0, len(mapping))
477+
478+
for _, v := range mapping {
479+
result = append(result, pathAndSource{v.Path, source})
480+
}
481+
return result
482+
}
483+
484+
func checkVolumeMappingForOverlap(paths []pathAndSource) []string {
485+
pathSeparator := string(os.PathSeparator)
486+
var warnings []string
487+
var allPaths []pathAndSource
488+
489+
for _, ps := range paths {
490+
ps.path = strings.TrimRight(ps.path, pathSeparator)
491+
if collisions := checkForOverlap(allPaths, ps); len(collisions) > 0 {
492+
for _, c := range collisions {
493+
warnings = append(warnings, fmt.Sprintf("%s with %s", ps.String(), c.String()))
494+
}
495+
}
496+
allPaths = append(allPaths, ps)
497+
}
498+
499+
return warnings
500+
}
501+
502+
func checkForOverlap(haystack []pathAndSource, needle pathAndSource) []pathAndSource {
503+
pathSeparator := string(os.PathSeparator)
504+
505+
if needle.path == "" {
506+
return nil
507+
}
508+
509+
var result []pathAndSource
510+
for _, item := range haystack {
511+
switch {
512+
case item.path == "":
513+
continue
514+
case item == needle:
515+
result = append(result, item)
516+
case strings.HasPrefix(item.path+pathSeparator, needle.path+pathSeparator):
517+
result = append(result, item)
518+
case strings.HasPrefix(needle.path+pathSeparator, item.path+pathSeparator):
519+
result = append(result, item)
520+
}
521+
}
522+
523+
return result
524+
}

0 commit comments

Comments
 (0)