@@ -19,6 +19,7 @@ package pod
19
19
import (
20
20
"context"
21
21
"fmt"
22
+ "os"
22
23
"strings"
23
24
24
25
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -168,6 +169,10 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
168
169
}
169
170
}
170
171
172
+ if overlaps := warningsForOverlappingVirtualPaths (podSpec .Volumes ); len (overlaps ) > 0 {
173
+ warnings = append (warnings , overlaps ... )
174
+ }
175
+
171
176
// duplicate hostAliases (#91670, #58477)
172
177
if len (podSpec .HostAliases ) > 1 {
173
178
items := sets .New [string ]()
@@ -354,3 +359,166 @@ func warningsForWeightedPodAffinityTerms(terms []api.WeightedPodAffinityTerm, fi
354
359
}
355
360
return warnings
356
361
}
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