k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/core/validation/validation.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validation 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "math" 23 "net" 24 "path" 25 "path/filepath" 26 "reflect" 27 "regexp" 28 "strings" 29 "sync" 30 "unicode" 31 "unicode/utf8" 32 33 "github.com/google/go-cmp/cmp" 34 v1 "k8s.io/api/core/v1" 35 apiequality "k8s.io/apimachinery/pkg/api/equality" 36 "k8s.io/apimachinery/pkg/api/resource" 37 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 39 unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 40 "k8s.io/apimachinery/pkg/conversion" 41 "k8s.io/apimachinery/pkg/labels" 42 "k8s.io/apimachinery/pkg/util/intstr" 43 "k8s.io/apimachinery/pkg/util/sets" 44 "k8s.io/apimachinery/pkg/util/validation" 45 "k8s.io/apimachinery/pkg/util/validation/field" 46 utilfeature "k8s.io/apiserver/pkg/util/feature" 47 utilsysctl "k8s.io/component-helpers/node/util/sysctl" 48 schedulinghelper "k8s.io/component-helpers/scheduling/corev1" 49 kubeletapis "k8s.io/kubelet/pkg/apis" 50 apiservice "k8s.io/kubernetes/pkg/api/service" 51 "k8s.io/kubernetes/pkg/apis/core" 52 "k8s.io/kubernetes/pkg/apis/core/helper" 53 "k8s.io/kubernetes/pkg/apis/core/helper/qos" 54 podshelper "k8s.io/kubernetes/pkg/apis/core/pods" 55 corev1 "k8s.io/kubernetes/pkg/apis/core/v1" 56 "k8s.io/kubernetes/pkg/capabilities" 57 "k8s.io/kubernetes/pkg/cluster/ports" 58 "k8s.io/kubernetes/pkg/features" 59 "k8s.io/kubernetes/pkg/fieldpath" 60 netutils "k8s.io/utils/net" 61 ) 62 63 const isNegativeErrorMsg string = apimachineryvalidation.IsNegativeErrorMsg 64 const isInvalidQuotaResource string = `must be a standard resource for quota` 65 const fieldImmutableErrorMsg string = apimachineryvalidation.FieldImmutableErrorMsg 66 const isNotIntegerErrorMsg string = `must be an integer` 67 const isNotPositiveErrorMsg string = `must be greater than zero` 68 69 var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255) 70 var fileModeErrorMsg = "must be a number between 0 and 0777 (octal), both inclusive" 71 72 // BannedOwners is a black list of object that are not allowed to be owners. 73 var BannedOwners = apimachineryvalidation.BannedOwners 74 75 var iscsiInitiatorIqnRegex = regexp.MustCompile(`iqn\.\d{4}-\d{2}\.([[:alnum:]-.]+)(:[^,;*&$|\s]+)$`) 76 var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`) 77 var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`) 78 79 var allowedEphemeralContainerFields = map[string]bool{ 80 "Name": true, 81 "Image": true, 82 "Command": true, 83 "Args": true, 84 "WorkingDir": true, 85 "Ports": false, 86 "EnvFrom": true, 87 "Env": true, 88 "Resources": false, 89 "VolumeMounts": true, 90 "VolumeDevices": true, 91 "LivenessProbe": false, 92 "ReadinessProbe": false, 93 "StartupProbe": false, 94 "Lifecycle": false, 95 "TerminationMessagePath": true, 96 "TerminationMessagePolicy": true, 97 "ImagePullPolicy": true, 98 "SecurityContext": true, 99 "Stdin": true, 100 "StdinOnce": true, 101 "TTY": true, 102 } 103 104 // validOS stores the set of valid OSes within pod spec. 105 // The valid values currently are linux, windows. 106 // In future, they can be expanded to values from 107 // https://github.com/opencontainers/runtime-spec/blob/master/config.md#platform-specific-configuration 108 var validOS = sets.New(core.Linux, core.Windows) 109 110 // ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue 111 func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList { 112 allErrs := field.ErrorList{} 113 actualValue, found := meta.Labels[key] 114 if !found { 115 allErrs = append(allErrs, field.Required(fldPath.Child("labels").Key(key), 116 fmt.Sprintf("must be '%s'", expectedValue))) 117 return allErrs 118 } 119 if actualValue != expectedValue { 120 allErrs = append(allErrs, field.Invalid(fldPath.Child("labels").Key(key), meta.Labels, 121 fmt.Sprintf("must be '%s'", expectedValue))) 122 } 123 return allErrs 124 } 125 126 // ValidateAnnotations validates that a set of annotations are correctly defined. 127 func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 128 return apimachineryvalidation.ValidateAnnotations(annotations, fldPath) 129 } 130 131 func ValidateDNS1123Label(value string, fldPath *field.Path) field.ErrorList { 132 allErrs := field.ErrorList{} 133 for _, msg := range validation.IsDNS1123Label(value) { 134 allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) 135 } 136 return allErrs 137 } 138 139 // ValidateQualifiedName validates if name is what Kubernetes calls a "qualified name". 140 func ValidateQualifiedName(value string, fldPath *field.Path) field.ErrorList { 141 allErrs := field.ErrorList{} 142 for _, msg := range validation.IsQualifiedName(value) { 143 allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) 144 } 145 return allErrs 146 } 147 148 // ValidateDNS1123Subdomain validates that a name is a proper DNS subdomain. 149 func ValidateDNS1123Subdomain(value string, fldPath *field.Path) field.ErrorList { 150 allErrs := field.ErrorList{} 151 for _, msg := range validation.IsDNS1123Subdomain(value) { 152 allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) 153 } 154 return allErrs 155 } 156 157 func ValidatePodSpecificAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 158 allErrs := field.ErrorList{} 159 160 if value, isMirror := annotations[core.MirrorPodAnnotationKey]; isMirror { 161 if len(spec.NodeName) == 0 { 162 allErrs = append(allErrs, field.Invalid(fldPath.Key(core.MirrorPodAnnotationKey), value, "must set spec.nodeName if mirror pod annotation is set")) 163 } 164 } 165 166 if annotations[core.TolerationsAnnotationKey] != "" { 167 allErrs = append(allErrs, ValidateTolerationsInPodAnnotations(annotations, fldPath)...) 168 } 169 170 if !opts.AllowInvalidPodDeletionCost { 171 if _, err := helper.GetDeletionCostFromPodAnnotations(annotations); err != nil { 172 allErrs = append(allErrs, field.Invalid(fldPath.Key(core.PodDeletionCost), annotations[core.PodDeletionCost], "must be a 32bit integer")) 173 } 174 } 175 176 allErrs = append(allErrs, ValidateSeccompPodAnnotations(annotations, fldPath)...) 177 allErrs = append(allErrs, ValidateAppArmorPodAnnotations(annotations, spec, fldPath)...) 178 179 return allErrs 180 } 181 182 // ValidateTolerationsInPodAnnotations tests that the serialized tolerations in Pod.Annotations has valid data 183 func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 184 allErrs := field.ErrorList{} 185 186 tolerations, err := helper.GetTolerationsFromPodAnnotations(annotations) 187 if err != nil { 188 allErrs = append(allErrs, field.Invalid(fldPath, core.TolerationsAnnotationKey, err.Error())) 189 return allErrs 190 } 191 192 if len(tolerations) > 0 { 193 allErrs = append(allErrs, ValidateTolerations(tolerations, fldPath.Child(core.TolerationsAnnotationKey))...) 194 } 195 196 return allErrs 197 } 198 199 func ValidatePodSpecificAnnotationUpdates(newPod, oldPod *core.Pod, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 200 allErrs := field.ErrorList{} 201 newAnnotations := newPod.Annotations 202 oldAnnotations := oldPod.Annotations 203 for k, oldVal := range oldAnnotations { 204 if newVal, exists := newAnnotations[k]; exists && newVal == oldVal { 205 continue // No change. 206 } 207 if strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) { 208 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update AppArmor annotations")) 209 } 210 if k == core.MirrorPodAnnotationKey { 211 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not remove or update mirror pod annotation")) 212 } 213 } 214 // Check for additions 215 for k := range newAnnotations { 216 if _, ok := oldAnnotations[k]; ok { 217 continue // No change. 218 } 219 if strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) { 220 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add AppArmor annotations")) 221 } 222 if k == core.MirrorPodAnnotationKey { 223 allErrs = append(allErrs, field.Forbidden(fldPath.Key(k), "may not add mirror pod annotation")) 224 } 225 } 226 allErrs = append(allErrs, ValidatePodSpecificAnnotations(newAnnotations, &newPod.Spec, fldPath, opts)...) 227 return allErrs 228 } 229 230 func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 231 allErrs := field.ErrorList{} 232 return allErrs 233 } 234 235 // ValidateNameFunc validates that the provided name is valid for a given resource type. 236 // Not all resources have the same validation rules for names. Prefix is true 237 // if the name will have a value appended to it. If the name is not valid, 238 // this returns a list of descriptions of individual characteristics of the 239 // value that were not valid. Otherwise this returns an empty list or nil. 240 type ValidateNameFunc apimachineryvalidation.ValidateNameFunc 241 242 // ValidatePodName can be used to check whether the given pod name is valid. 243 // Prefix indicates this name will be used as part of generation, in which case 244 // trailing dashes are allowed. 245 var ValidatePodName = apimachineryvalidation.NameIsDNSSubdomain 246 247 // ValidateReplicationControllerName can be used to check whether the given replication 248 // controller name is valid. 249 // Prefix indicates this name will be used as part of generation, in which case 250 // trailing dashes are allowed. 251 var ValidateReplicationControllerName = apimachineryvalidation.NameIsDNSSubdomain 252 253 // ValidateServiceName can be used to check whether the given service name is valid. 254 // Prefix indicates this name will be used as part of generation, in which case 255 // trailing dashes are allowed. 256 var ValidateServiceName = apimachineryvalidation.NameIsDNS1035Label 257 258 // ValidateNodeName can be used to check whether the given node name is valid. 259 // Prefix indicates this name will be used as part of generation, in which case 260 // trailing dashes are allowed. 261 var ValidateNodeName = apimachineryvalidation.NameIsDNSSubdomain 262 263 // ValidateNamespaceName can be used to check whether the given namespace name is valid. 264 // Prefix indicates this name will be used as part of generation, in which case 265 // trailing dashes are allowed. 266 var ValidateNamespaceName = apimachineryvalidation.ValidateNamespaceName 267 268 // ValidateLimitRangeName can be used to check whether the given limit range name is valid. 269 // Prefix indicates this name will be used as part of generation, in which case 270 // trailing dashes are allowed. 271 var ValidateLimitRangeName = apimachineryvalidation.NameIsDNSSubdomain 272 273 // ValidateResourceQuotaName can be used to check whether the given 274 // resource quota name is valid. 275 // Prefix indicates this name will be used as part of generation, in which case 276 // trailing dashes are allowed. 277 var ValidateResourceQuotaName = apimachineryvalidation.NameIsDNSSubdomain 278 279 // ValidateSecretName can be used to check whether the given secret name is valid. 280 // Prefix indicates this name will be used as part of generation, in which case 281 // trailing dashes are allowed. 282 var ValidateSecretName = apimachineryvalidation.NameIsDNSSubdomain 283 284 // ValidateServiceAccountName can be used to check whether the given service account name is valid. 285 // Prefix indicates this name will be used as part of generation, in which case 286 // trailing dashes are allowed. 287 var ValidateServiceAccountName = apimachineryvalidation.ValidateServiceAccountName 288 289 // ValidateEndpointsName can be used to check whether the given endpoints name is valid. 290 // Prefix indicates this name will be used as part of generation, in which case 291 // trailing dashes are allowed. 292 var ValidateEndpointsName = apimachineryvalidation.NameIsDNSSubdomain 293 294 // ValidateClassName can be used to check whether the given class name is valid. 295 // It is defined here to avoid import cycle between pkg/apis/storage/validation 296 // (where it should be) and this file. 297 var ValidateClassName = apimachineryvalidation.NameIsDNSSubdomain 298 299 // ValidatePriorityClassName can be used to check whether the given priority 300 // class name is valid. 301 var ValidatePriorityClassName = apimachineryvalidation.NameIsDNSSubdomain 302 303 // ValidateResourceClaimName can be used to check whether the given 304 // name for a ResourceClaim is valid. 305 var ValidateResourceClaimName = apimachineryvalidation.NameIsDNSSubdomain 306 307 // ValidateResourceClaimTemplateName can be used to check whether the given 308 // name for a ResourceClaimTemplate is valid. 309 var ValidateResourceClaimTemplateName = apimachineryvalidation.NameIsDNSSubdomain 310 311 // ValidateRuntimeClassName can be used to check whether the given RuntimeClass name is valid. 312 // Prefix indicates this name will be used as part of generation, in which case 313 // trailing dashes are allowed. 314 func ValidateRuntimeClassName(name string, fldPath *field.Path) field.ErrorList { 315 var allErrs field.ErrorList 316 for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(name, false) { 317 allErrs = append(allErrs, field.Invalid(fldPath, name, msg)) 318 } 319 return allErrs 320 } 321 322 // validateOverhead can be used to check whether the given Overhead is valid. 323 func validateOverhead(overhead core.ResourceList, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 324 // reuse the ResourceRequirements validation logic 325 return ValidateResourceRequirements(&core.ResourceRequirements{Limits: overhead}, nil, fldPath, opts) 326 } 327 328 // Validates that given value is not negative. 329 func ValidateNonnegativeField(value int64, fldPath *field.Path) field.ErrorList { 330 return apimachineryvalidation.ValidateNonnegativeField(value, fldPath) 331 } 332 333 // Validates that a Quantity is not negative 334 func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList { 335 allErrs := field.ErrorList{} 336 if value.Cmp(resource.Quantity{}) < 0 { 337 allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg)) 338 } 339 return allErrs 340 } 341 342 // Validates that a Quantity is positive 343 func ValidatePositiveQuantityValue(value resource.Quantity, fldPath *field.Path) field.ErrorList { 344 allErrs := field.ErrorList{} 345 if value.Cmp(resource.Quantity{}) <= 0 { 346 allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNotPositiveErrorMsg)) 347 } 348 return allErrs 349 } 350 351 func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { 352 return apimachineryvalidation.ValidateImmutableField(newVal, oldVal, fldPath) 353 } 354 355 func ValidateImmutableAnnotation(newVal string, oldVal string, annotation string, fldPath *field.Path) field.ErrorList { 356 allErrs := field.ErrorList{} 357 358 if oldVal != newVal { 359 allErrs = append(allErrs, field.Invalid(fldPath.Child("annotations", annotation), newVal, fieldImmutableErrorMsg)) 360 } 361 return allErrs 362 } 363 364 // ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already 365 // been performed. 366 // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. 367 // TODO: Remove calls to this method scattered in validations of specific resources, e.g., ValidatePodUpdate. 368 func ValidateObjectMeta(meta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { 369 allErrs := apimachineryvalidation.ValidateObjectMeta(meta, requiresNamespace, apimachineryvalidation.ValidateNameFunc(nameFn), fldPath) 370 // run additional checks for the finalizer name 371 for i := range meta.Finalizers { 372 allErrs = append(allErrs, validateKubeFinalizerName(string(meta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) 373 } 374 return allErrs 375 } 376 377 // ValidateObjectMetaUpdate validates an object's metadata when updated 378 func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { 379 allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(newMeta, oldMeta, fldPath) 380 // run additional checks for the finalizer name 381 for i := range newMeta.Finalizers { 382 allErrs = append(allErrs, validateKubeFinalizerName(string(newMeta.Finalizers[i]), fldPath.Child("finalizers").Index(i))...) 383 } 384 385 return allErrs 386 } 387 388 func ValidateVolumes(volumes []core.Volume, podMeta *metav1.ObjectMeta, fldPath *field.Path, opts PodValidationOptions) (map[string]core.VolumeSource, field.ErrorList) { 389 allErrs := field.ErrorList{} 390 391 allNames := sets.Set[string]{} 392 allCreatedPVCs := sets.Set[string]{} 393 // Determine which PVCs will be created for this pod. We need 394 // the exact name of the pod for this. Without it, this sanity 395 // check has to be skipped. 396 if podMeta != nil && podMeta.Name != "" { 397 for _, vol := range volumes { 398 if vol.VolumeSource.Ephemeral != nil { 399 allCreatedPVCs.Insert(podMeta.Name + "-" + vol.Name) 400 } 401 } 402 } 403 vols := make(map[string]core.VolumeSource) 404 for i, vol := range volumes { 405 idxPath := fldPath.Index(i) 406 namePath := idxPath.Child("name") 407 el := validateVolumeSource(&vol.VolumeSource, idxPath, vol.Name, podMeta, opts) 408 if len(vol.Name) == 0 { 409 el = append(el, field.Required(namePath, "")) 410 } else { 411 el = append(el, ValidateDNS1123Label(vol.Name, namePath)...) 412 } 413 if allNames.Has(vol.Name) { 414 el = append(el, field.Duplicate(namePath, vol.Name)) 415 } 416 if len(el) == 0 { 417 allNames.Insert(vol.Name) 418 vols[vol.Name] = vol.VolumeSource 419 } else { 420 allErrs = append(allErrs, el...) 421 } 422 // A PersistentVolumeClaimSource should not reference a created PVC. That doesn't 423 // make sense. 424 if vol.PersistentVolumeClaim != nil && allCreatedPVCs.Has(vol.PersistentVolumeClaim.ClaimName) { 425 allErrs = append(allErrs, field.Invalid(idxPath.Child("persistentVolumeClaim").Child("claimName"), vol.PersistentVolumeClaim.ClaimName, 426 "must not reference a PVC that gets created for an ephemeral volume")) 427 } 428 } 429 430 return vols, allErrs 431 } 432 433 func IsMatchedVolume(name string, volumes map[string]core.VolumeSource) bool { 434 if _, ok := volumes[name]; ok { 435 return true 436 } 437 return false 438 } 439 440 // isMatched checks whether the volume with the given name is used by a 441 // container and if so, if it involves a PVC. 442 func isMatchedDevice(name string, volumes map[string]core.VolumeSource) (isMatched bool, isPVC bool) { 443 if source, ok := volumes[name]; ok { 444 if source.PersistentVolumeClaim != nil || 445 source.Ephemeral != nil { 446 return true, true 447 } 448 return true, false 449 } 450 return false, false 451 } 452 453 func mountNameAlreadyExists(name string, devices map[string]string) bool { 454 if _, ok := devices[name]; ok { 455 return true 456 } 457 return false 458 } 459 460 func mountPathAlreadyExists(mountPath string, devices map[string]string) bool { 461 for _, devPath := range devices { 462 if mountPath == devPath { 463 return true 464 } 465 } 466 467 return false 468 } 469 470 func deviceNameAlreadyExists(name string, mounts map[string]string) bool { 471 if _, ok := mounts[name]; ok { 472 return true 473 } 474 return false 475 } 476 477 func devicePathAlreadyExists(devicePath string, mounts map[string]string) bool { 478 for _, mountPath := range mounts { 479 if mountPath == devicePath { 480 return true 481 } 482 } 483 484 return false 485 } 486 487 func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volName string, podMeta *metav1.ObjectMeta, opts PodValidationOptions) field.ErrorList { 488 numVolumes := 0 489 allErrs := field.ErrorList{} 490 if source.EmptyDir != nil { 491 numVolumes++ 492 if source.EmptyDir.SizeLimit != nil && source.EmptyDir.SizeLimit.Cmp(resource.Quantity{}) < 0 { 493 allErrs = append(allErrs, field.Forbidden(fldPath.Child("emptyDir").Child("sizeLimit"), "SizeLimit field must be a valid resource quantity")) 494 } 495 } 496 if source.HostPath != nil { 497 if numVolumes > 0 { 498 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPath"), "may not specify more than 1 volume type")) 499 } else { 500 numVolumes++ 501 allErrs = append(allErrs, validateHostPathVolumeSource(source.HostPath, fldPath.Child("hostPath"))...) 502 } 503 } 504 if source.GitRepo != nil { 505 if numVolumes > 0 { 506 allErrs = append(allErrs, field.Forbidden(fldPath.Child("gitRepo"), "may not specify more than 1 volume type")) 507 } else { 508 numVolumes++ 509 allErrs = append(allErrs, validateGitRepoVolumeSource(source.GitRepo, fldPath.Child("gitRepo"))...) 510 } 511 } 512 if source.GCEPersistentDisk != nil { 513 if numVolumes > 0 { 514 allErrs = append(allErrs, field.Forbidden(fldPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type")) 515 } else { 516 numVolumes++ 517 allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(source.GCEPersistentDisk, fldPath.Child("persistentDisk"))...) 518 } 519 } 520 if source.AWSElasticBlockStore != nil { 521 if numVolumes > 0 { 522 allErrs = append(allErrs, field.Forbidden(fldPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type")) 523 } else { 524 numVolumes++ 525 allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(source.AWSElasticBlockStore, fldPath.Child("awsElasticBlockStore"))...) 526 } 527 } 528 if source.Secret != nil { 529 if numVolumes > 0 { 530 allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type")) 531 } else { 532 numVolumes++ 533 allErrs = append(allErrs, validateSecretVolumeSource(source.Secret, fldPath.Child("secret"))...) 534 } 535 } 536 if source.NFS != nil { 537 if numVolumes > 0 { 538 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nfs"), "may not specify more than 1 volume type")) 539 } else { 540 numVolumes++ 541 allErrs = append(allErrs, validateNFSVolumeSource(source.NFS, fldPath.Child("nfs"))...) 542 } 543 } 544 if source.ISCSI != nil { 545 if numVolumes > 0 { 546 allErrs = append(allErrs, field.Forbidden(fldPath.Child("iscsi"), "may not specify more than 1 volume type")) 547 } else { 548 numVolumes++ 549 allErrs = append(allErrs, validateISCSIVolumeSource(source.ISCSI, fldPath.Child("iscsi"))...) 550 } 551 if source.ISCSI.InitiatorName != nil && len(volName+":"+source.ISCSI.TargetPortal) > 64 { 552 tooLongErr := "Total length of <volume name>:<iscsi.targetPortal> must be under 64 characters if iscsi.initiatorName is specified." 553 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, tooLongErr)) 554 } 555 } 556 if source.Glusterfs != nil { 557 if numVolumes > 0 { 558 allErrs = append(allErrs, field.Forbidden(fldPath.Child("glusterfs"), "may not specify more than 1 volume type")) 559 } else { 560 numVolumes++ 561 allErrs = append(allErrs, validateGlusterfsVolumeSource(source.Glusterfs, fldPath.Child("glusterfs"))...) 562 } 563 } 564 if source.Flocker != nil { 565 if numVolumes > 0 { 566 allErrs = append(allErrs, field.Forbidden(fldPath.Child("flocker"), "may not specify more than 1 volume type")) 567 } else { 568 numVolumes++ 569 allErrs = append(allErrs, validateFlockerVolumeSource(source.Flocker, fldPath.Child("flocker"))...) 570 } 571 } 572 if source.PersistentVolumeClaim != nil { 573 if numVolumes > 0 { 574 allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeClaim"), "may not specify more than 1 volume type")) 575 } else { 576 numVolumes++ 577 allErrs = append(allErrs, validatePersistentClaimVolumeSource(source.PersistentVolumeClaim, fldPath.Child("persistentVolumeClaim"))...) 578 } 579 } 580 if source.RBD != nil { 581 if numVolumes > 0 { 582 allErrs = append(allErrs, field.Forbidden(fldPath.Child("rbd"), "may not specify more than 1 volume type")) 583 } else { 584 numVolumes++ 585 allErrs = append(allErrs, validateRBDVolumeSource(source.RBD, fldPath.Child("rbd"))...) 586 } 587 } 588 if source.Cinder != nil { 589 if numVolumes > 0 { 590 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cinder"), "may not specify more than 1 volume type")) 591 } else { 592 numVolumes++ 593 allErrs = append(allErrs, validateCinderVolumeSource(source.Cinder, fldPath.Child("cinder"))...) 594 } 595 } 596 if source.CephFS != nil { 597 if numVolumes > 0 { 598 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cephFS"), "may not specify more than 1 volume type")) 599 } else { 600 numVolumes++ 601 allErrs = append(allErrs, validateCephFSVolumeSource(source.CephFS, fldPath.Child("cephfs"))...) 602 } 603 } 604 if source.Quobyte != nil { 605 if numVolumes > 0 { 606 allErrs = append(allErrs, field.Forbidden(fldPath.Child("quobyte"), "may not specify more than 1 volume type")) 607 } else { 608 numVolumes++ 609 allErrs = append(allErrs, validateQuobyteVolumeSource(source.Quobyte, fldPath.Child("quobyte"))...) 610 } 611 } 612 if source.DownwardAPI != nil { 613 if numVolumes > 0 { 614 allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwarAPI"), "may not specify more than 1 volume type")) 615 } else { 616 numVolumes++ 617 allErrs = append(allErrs, validateDownwardAPIVolumeSource(source.DownwardAPI, fldPath.Child("downwardAPI"), opts)...) 618 } 619 } 620 if source.FC != nil { 621 if numVolumes > 0 { 622 allErrs = append(allErrs, field.Forbidden(fldPath.Child("fc"), "may not specify more than 1 volume type")) 623 } else { 624 numVolumes++ 625 allErrs = append(allErrs, validateFCVolumeSource(source.FC, fldPath.Child("fc"))...) 626 } 627 } 628 if source.FlexVolume != nil { 629 if numVolumes > 0 { 630 allErrs = append(allErrs, field.Forbidden(fldPath.Child("flexVolume"), "may not specify more than 1 volume type")) 631 } else { 632 numVolumes++ 633 allErrs = append(allErrs, validateFlexVolumeSource(source.FlexVolume, fldPath.Child("flexVolume"))...) 634 } 635 } 636 if source.ConfigMap != nil { 637 if numVolumes > 0 { 638 allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type")) 639 } else { 640 numVolumes++ 641 allErrs = append(allErrs, validateConfigMapVolumeSource(source.ConfigMap, fldPath.Child("configMap"))...) 642 } 643 } 644 645 if source.AzureFile != nil { 646 if numVolumes > 0 { 647 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureFile"), "may not specify more than 1 volume type")) 648 } else { 649 numVolumes++ 650 allErrs = append(allErrs, validateAzureFile(source.AzureFile, fldPath.Child("azureFile"))...) 651 } 652 } 653 654 if source.VsphereVolume != nil { 655 if numVolumes > 0 { 656 allErrs = append(allErrs, field.Forbidden(fldPath.Child("vsphereVolume"), "may not specify more than 1 volume type")) 657 } else { 658 numVolumes++ 659 allErrs = append(allErrs, validateVsphereVolumeSource(source.VsphereVolume, fldPath.Child("vsphereVolume"))...) 660 } 661 } 662 if source.PhotonPersistentDisk != nil { 663 if numVolumes > 0 { 664 allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type")) 665 } else { 666 numVolumes++ 667 allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(source.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...) 668 } 669 } 670 if source.PortworxVolume != nil { 671 if numVolumes > 0 { 672 allErrs = append(allErrs, field.Forbidden(fldPath.Child("portworxVolume"), "may not specify more than 1 volume type")) 673 } else { 674 numVolumes++ 675 allErrs = append(allErrs, validatePortworxVolumeSource(source.PortworxVolume, fldPath.Child("portworxVolume"))...) 676 } 677 } 678 if source.AzureDisk != nil { 679 if numVolumes > 0 { 680 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureDisk"), "may not specify more than 1 volume type")) 681 } else { 682 numVolumes++ 683 allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...) 684 } 685 } 686 if source.StorageOS != nil { 687 if numVolumes > 0 { 688 allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type")) 689 } else { 690 numVolumes++ 691 allErrs = append(allErrs, validateStorageOSVolumeSource(source.StorageOS, fldPath.Child("storageos"))...) 692 } 693 } 694 if source.Projected != nil { 695 if numVolumes > 0 { 696 allErrs = append(allErrs, field.Forbidden(fldPath.Child("projected"), "may not specify more than 1 volume type")) 697 } else { 698 numVolumes++ 699 allErrs = append(allErrs, validateProjectedVolumeSource(source.Projected, fldPath.Child("projected"), opts)...) 700 } 701 } 702 if source.ScaleIO != nil { 703 if numVolumes > 0 { 704 allErrs = append(allErrs, field.Forbidden(fldPath.Child("scaleIO"), "may not specify more than 1 volume type")) 705 } else { 706 numVolumes++ 707 allErrs = append(allErrs, validateScaleIOVolumeSource(source.ScaleIO, fldPath.Child("scaleIO"))...) 708 } 709 } 710 if source.CSI != nil { 711 if numVolumes > 0 { 712 allErrs = append(allErrs, field.Forbidden(fldPath.Child("csi"), "may not specify more than 1 volume type")) 713 } else { 714 numVolumes++ 715 allErrs = append(allErrs, validateCSIVolumeSource(source.CSI, fldPath.Child("csi"))...) 716 } 717 } 718 if source.Ephemeral != nil { 719 if numVolumes > 0 { 720 allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeral"), "may not specify more than 1 volume type")) 721 } else { 722 numVolumes++ 723 allErrs = append(allErrs, validateEphemeralVolumeSource(source.Ephemeral, fldPath.Child("ephemeral"))...) 724 // Check the expected name for the PVC. This gets skipped if information is missing, 725 // because that already gets flagged as a problem elsewhere. For example, 726 // ValidateObjectMeta as called by validatePodMetadataAndSpec checks that the name is set. 727 if podMeta != nil && podMeta.Name != "" && volName != "" { 728 pvcName := podMeta.Name + "-" + volName 729 for _, msg := range ValidatePersistentVolumeName(pvcName, false) { 730 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, fmt.Sprintf("PVC name %q: %v", pvcName, msg))) 731 } 732 } 733 } 734 } 735 736 if numVolumes == 0 { 737 allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type")) 738 } 739 740 return allErrs 741 } 742 743 func validateHostPathVolumeSource(hostPath *core.HostPathVolumeSource, fldPath *field.Path) field.ErrorList { 744 allErrs := field.ErrorList{} 745 if len(hostPath.Path) == 0 { 746 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 747 return allErrs 748 } 749 750 allErrs = append(allErrs, validatePathNoBacksteps(hostPath.Path, fldPath.Child("path"))...) 751 allErrs = append(allErrs, validateHostPathType(hostPath.Type, fldPath.Child("type"))...) 752 return allErrs 753 } 754 755 func validateGitRepoVolumeSource(gitRepo *core.GitRepoVolumeSource, fldPath *field.Path) field.ErrorList { 756 allErrs := field.ErrorList{} 757 if len(gitRepo.Repository) == 0 { 758 allErrs = append(allErrs, field.Required(fldPath.Child("repository"), "")) 759 } 760 761 pathErrs := validateLocalDescendingPath(gitRepo.Directory, fldPath.Child("directory")) 762 allErrs = append(allErrs, pathErrs...) 763 return allErrs 764 } 765 766 func validateISCSIVolumeSource(iscsi *core.ISCSIVolumeSource, fldPath *field.Path) field.ErrorList { 767 allErrs := field.ErrorList{} 768 if len(iscsi.TargetPortal) == 0 { 769 allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), "")) 770 } 771 if len(iscsi.IQN) == 0 { 772 allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), "")) 773 } else { 774 if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") { 775 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format starting with iqn, eui, or naa")) 776 } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) { 777 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 778 } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) { 779 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 780 } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) { 781 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 782 } 783 } 784 if iscsi.Lun < 0 || iscsi.Lun > 255 { 785 allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255))) 786 } 787 if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil { 788 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), "")) 789 } 790 if iscsi.InitiatorName != nil { 791 initiator := *iscsi.InitiatorName 792 if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") { 793 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format starting with iqn, eui, or naa")) 794 } 795 if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) { 796 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 797 } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) { 798 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 799 } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) { 800 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 801 } 802 } 803 return allErrs 804 } 805 806 func validateISCSIPersistentVolumeSource(iscsi *core.ISCSIPersistentVolumeSource, pvName string, fldPath *field.Path) field.ErrorList { 807 allErrs := field.ErrorList{} 808 if len(iscsi.TargetPortal) == 0 { 809 allErrs = append(allErrs, field.Required(fldPath.Child("targetPortal"), "")) 810 } 811 if iscsi.InitiatorName != nil && len(pvName+":"+iscsi.TargetPortal) > 64 { 812 tooLongErr := "Total length of <volume name>:<iscsi.targetPortal> must be under 64 characters if iscsi.initiatorName is specified." 813 allErrs = append(allErrs, field.Invalid(fldPath.Child("targetportal"), iscsi.TargetPortal, tooLongErr)) 814 } 815 if len(iscsi.IQN) == 0 { 816 allErrs = append(allErrs, field.Required(fldPath.Child("iqn"), "")) 817 } else { 818 if !strings.HasPrefix(iscsi.IQN, "iqn") && !strings.HasPrefix(iscsi.IQN, "eui") && !strings.HasPrefix(iscsi.IQN, "naa") { 819 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 820 } else if strings.HasPrefix(iscsi.IQN, "iqn") && !iscsiInitiatorIqnRegex.MatchString(iscsi.IQN) { 821 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 822 } else if strings.HasPrefix(iscsi.IQN, "eui") && !iscsiInitiatorEuiRegex.MatchString(iscsi.IQN) { 823 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 824 } else if strings.HasPrefix(iscsi.IQN, "naa") && !iscsiInitiatorNaaRegex.MatchString(iscsi.IQN) { 825 allErrs = append(allErrs, field.Invalid(fldPath.Child("iqn"), iscsi.IQN, "must be valid format")) 826 } 827 } 828 if iscsi.Lun < 0 || iscsi.Lun > 255 { 829 allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), iscsi.Lun, validation.InclusiveRangeError(0, 255))) 830 } 831 if (iscsi.DiscoveryCHAPAuth || iscsi.SessionCHAPAuth) && iscsi.SecretRef == nil { 832 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef"), "")) 833 } 834 if iscsi.SecretRef != nil { 835 if len(iscsi.SecretRef.Name) == 0 { 836 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) 837 } 838 } 839 if iscsi.InitiatorName != nil { 840 initiator := *iscsi.InitiatorName 841 if !strings.HasPrefix(initiator, "iqn") && !strings.HasPrefix(initiator, "eui") && !strings.HasPrefix(initiator, "naa") { 842 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 843 } 844 if strings.HasPrefix(initiator, "iqn") && !iscsiInitiatorIqnRegex.MatchString(initiator) { 845 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 846 } else if strings.HasPrefix(initiator, "eui") && !iscsiInitiatorEuiRegex.MatchString(initiator) { 847 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 848 } else if strings.HasPrefix(initiator, "naa") && !iscsiInitiatorNaaRegex.MatchString(initiator) { 849 allErrs = append(allErrs, field.Invalid(fldPath.Child("initiatorname"), initiator, "must be valid format")) 850 } 851 } 852 return allErrs 853 } 854 855 func validateFCVolumeSource(fc *core.FCVolumeSource, fldPath *field.Path) field.ErrorList { 856 allErrs := field.ErrorList{} 857 if len(fc.TargetWWNs) < 1 && len(fc.WWIDs) < 1 { 858 allErrs = append(allErrs, field.Required(fldPath.Child("targetWWNs"), "must specify either targetWWNs or wwids, but not both")) 859 } 860 861 if len(fc.TargetWWNs) != 0 && len(fc.WWIDs) != 0 { 862 allErrs = append(allErrs, field.Invalid(fldPath.Child("targetWWNs"), fc.TargetWWNs, "targetWWNs and wwids can not be specified simultaneously")) 863 } 864 865 if len(fc.TargetWWNs) != 0 { 866 if fc.Lun == nil { 867 allErrs = append(allErrs, field.Required(fldPath.Child("lun"), "lun is required if targetWWNs is specified")) 868 } else { 869 if *fc.Lun < 0 || *fc.Lun > 255 { 870 allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), fc.Lun, validation.InclusiveRangeError(0, 255))) 871 } 872 } 873 } 874 return allErrs 875 } 876 877 func validateGCEPersistentDiskVolumeSource(pd *core.GCEPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList { 878 allErrs := field.ErrorList{} 879 if len(pd.PDName) == 0 { 880 allErrs = append(allErrs, field.Required(fldPath.Child("pdName"), "")) 881 } 882 if pd.Partition < 0 || pd.Partition > 255 { 883 allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), pd.Partition, pdPartitionErrorMsg)) 884 } 885 return allErrs 886 } 887 888 func validateAWSElasticBlockStoreVolumeSource(PD *core.AWSElasticBlockStoreVolumeSource, fldPath *field.Path) field.ErrorList { 889 allErrs := field.ErrorList{} 890 if len(PD.VolumeID) == 0 { 891 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) 892 } 893 if PD.Partition < 0 || PD.Partition > 255 { 894 allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), PD.Partition, pdPartitionErrorMsg)) 895 } 896 return allErrs 897 } 898 899 func validateSecretVolumeSource(secretSource *core.SecretVolumeSource, fldPath *field.Path) field.ErrorList { 900 allErrs := field.ErrorList{} 901 if len(secretSource.SecretName) == 0 { 902 allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) 903 } 904 905 secretMode := secretSource.DefaultMode 906 if secretMode != nil && (*secretMode > 0777 || *secretMode < 0) { 907 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *secretMode, fileModeErrorMsg)) 908 } 909 910 itemsPath := fldPath.Child("items") 911 for i, kp := range secretSource.Items { 912 itemPath := itemsPath.Index(i) 913 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) 914 } 915 return allErrs 916 } 917 918 func validateConfigMapVolumeSource(configMapSource *core.ConfigMapVolumeSource, fldPath *field.Path) field.ErrorList { 919 allErrs := field.ErrorList{} 920 if len(configMapSource.Name) == 0 { 921 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 922 } 923 924 configMapMode := configMapSource.DefaultMode 925 if configMapMode != nil && (*configMapMode > 0777 || *configMapMode < 0) { 926 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *configMapMode, fileModeErrorMsg)) 927 } 928 929 itemsPath := fldPath.Child("items") 930 for i, kp := range configMapSource.Items { 931 itemPath := itemsPath.Index(i) 932 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) 933 } 934 return allErrs 935 } 936 937 func validateKeyToPath(kp *core.KeyToPath, fldPath *field.Path) field.ErrorList { 938 allErrs := field.ErrorList{} 939 if len(kp.Key) == 0 { 940 allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) 941 } 942 if len(kp.Path) == 0 { 943 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 944 } 945 allErrs = append(allErrs, ValidateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...) 946 if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) { 947 allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, fileModeErrorMsg)) 948 } 949 950 return allErrs 951 } 952 953 func validatePersistentClaimVolumeSource(claim *core.PersistentVolumeClaimVolumeSource, fldPath *field.Path) field.ErrorList { 954 allErrs := field.ErrorList{} 955 if len(claim.ClaimName) == 0 { 956 allErrs = append(allErrs, field.Required(fldPath.Child("claimName"), "")) 957 } 958 return allErrs 959 } 960 961 func validateNFSVolumeSource(nfs *core.NFSVolumeSource, fldPath *field.Path) field.ErrorList { 962 allErrs := field.ErrorList{} 963 if len(nfs.Server) == 0 { 964 allErrs = append(allErrs, field.Required(fldPath.Child("server"), "")) 965 } 966 if len(nfs.Path) == 0 { 967 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 968 } 969 if !path.IsAbs(nfs.Path) { 970 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), nfs.Path, "must be an absolute path")) 971 } 972 return allErrs 973 } 974 975 func validateQuobyteVolumeSource(quobyte *core.QuobyteVolumeSource, fldPath *field.Path) field.ErrorList { 976 allErrs := field.ErrorList{} 977 if len(quobyte.Registry) == 0 { 978 allErrs = append(allErrs, field.Required(fldPath.Child("registry"), "must be a host:port pair or multiple pairs separated by commas")) 979 } else if len(quobyte.Tenant) >= 65 { 980 allErrs = append(allErrs, field.Required(fldPath.Child("tenant"), "must be a UUID and may not exceed a length of 64 characters")) 981 } else { 982 for _, hostPortPair := range strings.Split(quobyte.Registry, ",") { 983 if _, _, err := net.SplitHostPort(hostPortPair); err != nil { 984 allErrs = append(allErrs, field.Invalid(fldPath.Child("registry"), quobyte.Registry, "must be a host:port pair or multiple pairs separated by commas")) 985 } 986 } 987 } 988 989 if len(quobyte.Volume) == 0 { 990 allErrs = append(allErrs, field.Required(fldPath.Child("volume"), "")) 991 } 992 return allErrs 993 } 994 995 func validateGlusterfsVolumeSource(glusterfs *core.GlusterfsVolumeSource, fldPath *field.Path) field.ErrorList { 996 allErrs := field.ErrorList{} 997 if len(glusterfs.EndpointsName) == 0 { 998 allErrs = append(allErrs, field.Required(fldPath.Child("endpoints"), "")) 999 } 1000 if len(glusterfs.Path) == 0 { 1001 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 1002 } 1003 return allErrs 1004 } 1005 func validateGlusterfsPersistentVolumeSource(glusterfs *core.GlusterfsPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1006 allErrs := field.ErrorList{} 1007 if len(glusterfs.EndpointsName) == 0 { 1008 allErrs = append(allErrs, field.Required(fldPath.Child("endpoints"), "")) 1009 } 1010 if len(glusterfs.Path) == 0 { 1011 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 1012 } 1013 if glusterfs.EndpointsNamespace != nil { 1014 endpointNs := glusterfs.EndpointsNamespace 1015 if *endpointNs == "" { 1016 allErrs = append(allErrs, field.Invalid(fldPath.Child("endpointsNamespace"), *endpointNs, "if the endpointnamespace is set, it must be a valid namespace name")) 1017 } else { 1018 for _, msg := range ValidateNamespaceName(*endpointNs, false) { 1019 allErrs = append(allErrs, field.Invalid(fldPath.Child("endpointsNamespace"), *endpointNs, msg)) 1020 } 1021 } 1022 } 1023 return allErrs 1024 } 1025 1026 func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *field.Path) field.ErrorList { 1027 allErrs := field.ErrorList{} 1028 if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 { 1029 // TODO: consider adding a RequiredOneOf() error for this and similar cases 1030 allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required")) 1031 } 1032 if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 { 1033 allErrs = append(allErrs, field.Invalid(fldPath, "resource", "datasetName and datasetUUID can not be specified simultaneously")) 1034 } 1035 if strings.Contains(flocker.DatasetName, "/") { 1036 allErrs = append(allErrs, field.Invalid(fldPath.Child("datasetName"), flocker.DatasetName, "must not contain '/'")) 1037 } 1038 return allErrs 1039 } 1040 1041 var validVolumeDownwardAPIFieldPathExpressions = sets.New( 1042 "metadata.name", 1043 "metadata.namespace", 1044 "metadata.labels", 1045 "metadata.annotations", 1046 "metadata.uid") 1047 1048 func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 1049 allErrs := field.ErrorList{} 1050 if len(file.Path) == 0 { 1051 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 1052 } 1053 allErrs = append(allErrs, ValidateLocalNonReservedPath(file.Path, fldPath.Child("path"))...) 1054 if file.FieldRef != nil { 1055 allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...) 1056 if file.ResourceFieldRef != nil { 1057 allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously")) 1058 } 1059 allErrs = append(allErrs, validateDownwardAPIHostIPs(file.FieldRef, fldPath.Child("fieldRef"), opts)...) 1060 } else if file.ResourceFieldRef != nil { 1061 localValidContainerResourceFieldPathPrefixes := validContainerResourceFieldPathPrefixesWithDownwardAPIHugePages 1062 allErrs = append(allErrs, validateContainerResourceFieldSelector(file.ResourceFieldRef, &validContainerResourceFieldPathExpressions, &localValidContainerResourceFieldPathPrefixes, fldPath.Child("resourceFieldRef"), true)...) 1063 } else { 1064 allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required")) 1065 } 1066 if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) { 1067 allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, fileModeErrorMsg)) 1068 } 1069 1070 return allErrs 1071 } 1072 1073 func validateDownwardAPIVolumeSource(downwardAPIVolume *core.DownwardAPIVolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 1074 allErrs := field.ErrorList{} 1075 1076 downwardAPIMode := downwardAPIVolume.DefaultMode 1077 if downwardAPIMode != nil && (*downwardAPIMode > 0777 || *downwardAPIMode < 0) { 1078 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *downwardAPIMode, fileModeErrorMsg)) 1079 } 1080 1081 for _, file := range downwardAPIVolume.Items { 1082 allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath, opts)...) 1083 } 1084 return allErrs 1085 } 1086 1087 func validateProjectionSources(projection *core.ProjectedVolumeSource, projectionMode *int32, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 1088 allErrs := field.ErrorList{} 1089 allPaths := sets.Set[string]{} 1090 1091 for i, source := range projection.Sources { 1092 numSources := 0 1093 srcPath := fldPath.Child("sources").Index(i) 1094 if projPath := srcPath.Child("secret"); source.Secret != nil { 1095 numSources++ 1096 if len(source.Secret.Name) == 0 { 1097 allErrs = append(allErrs, field.Required(projPath.Child("name"), "")) 1098 } 1099 itemsPath := projPath.Child("items") 1100 for i, kp := range source.Secret.Items { 1101 itemPath := itemsPath.Index(i) 1102 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) 1103 if len(kp.Path) > 0 { 1104 curPath := kp.Path 1105 if !allPaths.Has(curPath) { 1106 allPaths.Insert(curPath) 1107 } else { 1108 allErrs = append(allErrs, field.Invalid(fldPath, source.Secret.Name, "conflicting duplicate paths")) 1109 } 1110 } 1111 } 1112 } 1113 if projPath := srcPath.Child("configMap"); source.ConfigMap != nil { 1114 numSources++ 1115 if len(source.ConfigMap.Name) == 0 { 1116 allErrs = append(allErrs, field.Required(projPath.Child("name"), "")) 1117 } 1118 itemsPath := projPath.Child("items") 1119 for i, kp := range source.ConfigMap.Items { 1120 itemPath := itemsPath.Index(i) 1121 allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...) 1122 if len(kp.Path) > 0 { 1123 curPath := kp.Path 1124 if !allPaths.Has(curPath) { 1125 allPaths.Insert(curPath) 1126 } else { 1127 allErrs = append(allErrs, field.Invalid(fldPath, source.ConfigMap.Name, "conflicting duplicate paths")) 1128 } 1129 } 1130 } 1131 } 1132 if projPath := srcPath.Child("downwardAPI"); source.DownwardAPI != nil { 1133 numSources++ 1134 for _, file := range source.DownwardAPI.Items { 1135 allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, projPath, opts)...) 1136 if len(file.Path) > 0 { 1137 curPath := file.Path 1138 if !allPaths.Has(curPath) { 1139 allPaths.Insert(curPath) 1140 } else { 1141 allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths")) 1142 } 1143 } 1144 } 1145 } 1146 if projPath := srcPath.Child("serviceAccountToken"); source.ServiceAccountToken != nil { 1147 numSources++ 1148 if source.ServiceAccountToken.ExpirationSeconds < 10*60 { 1149 allErrs = append(allErrs, field.Invalid(projPath.Child("expirationSeconds"), source.ServiceAccountToken.ExpirationSeconds, "may not specify a duration less than 10 minutes")) 1150 } 1151 if source.ServiceAccountToken.ExpirationSeconds > 1<<32 { 1152 allErrs = append(allErrs, field.Invalid(projPath.Child("expirationSeconds"), source.ServiceAccountToken.ExpirationSeconds, "may not specify a duration larger than 2^32 seconds")) 1153 } 1154 if source.ServiceAccountToken.Path == "" { 1155 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 1156 } else if !opts.AllowNonLocalProjectedTokenPath { 1157 allErrs = append(allErrs, ValidateLocalNonReservedPath(source.ServiceAccountToken.Path, fldPath.Child("path"))...) 1158 } 1159 } 1160 if projPath := srcPath.Child("clusterTrustBundlePEM"); source.ClusterTrustBundle != nil { 1161 numSources++ 1162 1163 usingName := source.ClusterTrustBundle.Name != nil 1164 usingSignerName := source.ClusterTrustBundle.SignerName != nil 1165 1166 switch { 1167 case usingName && usingSignerName: 1168 allErrs = append(allErrs, field.Invalid(projPath, source.ClusterTrustBundle, "only one of name and signerName may be used")) 1169 case usingName: 1170 if *source.ClusterTrustBundle.Name == "" { 1171 allErrs = append(allErrs, field.Required(projPath.Child("name"), "must be a valid object name")) 1172 } 1173 1174 name := *source.ClusterTrustBundle.Name 1175 if signerName, ok := extractSignerNameFromClusterTrustBundleName(name); ok { 1176 validationFunc := ValidateClusterTrustBundleName(signerName) 1177 errMsgs := validationFunc(name, false) 1178 for _, msg := range errMsgs { 1179 allErrs = append(allErrs, field.Invalid(projPath.Child("name"), name, fmt.Sprintf("not a valid clustertrustbundlename: %v", msg))) 1180 } 1181 } else { 1182 validationFunc := ValidateClusterTrustBundleName("") 1183 errMsgs := validationFunc(name, false) 1184 for _, msg := range errMsgs { 1185 allErrs = append(allErrs, field.Invalid(projPath.Child("name"), name, fmt.Sprintf("not a valid clustertrustbundlename: %v", msg))) 1186 } 1187 } 1188 1189 if source.ClusterTrustBundle.LabelSelector != nil { 1190 allErrs = append(allErrs, field.Invalid(projPath.Child("labelSelector"), source.ClusterTrustBundle.LabelSelector, "labelSelector must be unset if name is specified")) 1191 } 1192 case usingSignerName: 1193 if *source.ClusterTrustBundle.SignerName == "" { 1194 allErrs = append(allErrs, field.Required(projPath.Child("signerName"), "must be a valid signer name")) 1195 } 1196 1197 allErrs = append(allErrs, ValidateSignerName(projPath.Child("signerName"), *source.ClusterTrustBundle.SignerName)...) 1198 1199 labelSelectorErrs := unversionedvalidation.ValidateLabelSelector( 1200 source.ClusterTrustBundle.LabelSelector, 1201 unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, 1202 projPath.Child("labelSelector"), 1203 ) 1204 allErrs = append(allErrs, labelSelectorErrs...) 1205 1206 default: 1207 allErrs = append(allErrs, field.Required(projPath, "either name or signerName must be specified")) 1208 } 1209 1210 if source.ClusterTrustBundle.Path == "" { 1211 allErrs = append(allErrs, field.Required(projPath.Child("path"), "")) 1212 } 1213 1214 allErrs = append(allErrs, ValidateLocalNonReservedPath(source.ClusterTrustBundle.Path, projPath.Child("path"))...) 1215 1216 curPath := source.ClusterTrustBundle.Path 1217 if !allPaths.Has(curPath) { 1218 allPaths.Insert(curPath) 1219 } else { 1220 allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths")) 1221 } 1222 } 1223 if numSources > 1 { 1224 allErrs = append(allErrs, field.Forbidden(srcPath, "may not specify more than 1 volume type")) 1225 } 1226 } 1227 return allErrs 1228 } 1229 1230 func validateProjectedVolumeSource(projection *core.ProjectedVolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 1231 allErrs := field.ErrorList{} 1232 1233 projectionMode := projection.DefaultMode 1234 if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) { 1235 allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, fileModeErrorMsg)) 1236 } 1237 1238 allErrs = append(allErrs, validateProjectionSources(projection, projectionMode, fldPath, opts)...) 1239 return allErrs 1240 } 1241 1242 var supportedHostPathTypes = sets.New( 1243 core.HostPathUnset, 1244 core.HostPathDirectoryOrCreate, 1245 core.HostPathDirectory, 1246 core.HostPathFileOrCreate, 1247 core.HostPathFile, 1248 core.HostPathSocket, 1249 core.HostPathCharDev, 1250 core.HostPathBlockDev) 1251 1252 func validateHostPathType(hostPathType *core.HostPathType, fldPath *field.Path) field.ErrorList { 1253 allErrs := field.ErrorList{} 1254 1255 if hostPathType != nil && !supportedHostPathTypes.Has(*hostPathType) { 1256 allErrs = append(allErrs, field.NotSupported(fldPath, hostPathType, sets.List(supportedHostPathTypes))) 1257 } 1258 1259 return allErrs 1260 } 1261 1262 // This validate will make sure targetPath: 1263 // 1. is not abs path 1264 // 2. does not have any element which is ".." 1265 func validateLocalDescendingPath(targetPath string, fldPath *field.Path) field.ErrorList { 1266 allErrs := field.ErrorList{} 1267 if path.IsAbs(targetPath) { 1268 allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must be a relative path")) 1269 } 1270 1271 allErrs = append(allErrs, validatePathNoBacksteps(targetPath, fldPath)...) 1272 1273 return allErrs 1274 } 1275 1276 // validatePathNoBacksteps makes sure the targetPath does not have any `..` path elements when split 1277 // 1278 // This assumes the OS of the apiserver and the nodes are the same. The same check should be done 1279 // on the node to ensure there are no backsteps. 1280 func validatePathNoBacksteps(targetPath string, fldPath *field.Path) field.ErrorList { 1281 allErrs := field.ErrorList{} 1282 parts := strings.Split(filepath.ToSlash(targetPath), "/") 1283 for _, item := range parts { 1284 if item == ".." { 1285 allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not contain '..'")) 1286 break // even for `../../..`, one error is sufficient to make the point 1287 } 1288 } 1289 return allErrs 1290 } 1291 1292 // validateMountPropagation verifies that MountPropagation field is valid and 1293 // allowed for given container. 1294 func validateMountPropagation(mountPropagation *core.MountPropagationMode, container *core.Container, fldPath *field.Path) field.ErrorList { 1295 allErrs := field.ErrorList{} 1296 1297 if mountPropagation == nil { 1298 return allErrs 1299 } 1300 1301 supportedMountPropagations := sets.New( 1302 core.MountPropagationBidirectional, 1303 core.MountPropagationHostToContainer, 1304 core.MountPropagationNone) 1305 1306 if !supportedMountPropagations.Has(*mountPropagation) { 1307 allErrs = append(allErrs, field.NotSupported(fldPath, *mountPropagation, sets.List(supportedMountPropagations))) 1308 } 1309 1310 if container == nil { 1311 // The container is not available yet. 1312 // Stop validation now, Pod validation will refuse final 1313 // Pods with Bidirectional propagation in non-privileged containers. 1314 return allErrs 1315 } 1316 1317 privileged := container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged 1318 if *mountPropagation == core.MountPropagationBidirectional && !privileged { 1319 allErrs = append(allErrs, field.Forbidden(fldPath, "Bidirectional mount propagation is available only to privileged containers")) 1320 } 1321 return allErrs 1322 } 1323 1324 // validateMountRecursiveReadOnly validates RecursiveReadOnly mounts. 1325 func validateMountRecursiveReadOnly(mount core.VolumeMount, fldPath *field.Path) field.ErrorList { 1326 if mount.RecursiveReadOnly == nil { 1327 return nil 1328 } 1329 allErrs := field.ErrorList{} 1330 switch *mount.RecursiveReadOnly { 1331 case core.RecursiveReadOnlyDisabled: 1332 // NOP 1333 case core.RecursiveReadOnlyEnabled, core.RecursiveReadOnlyIfPossible: 1334 if !mount.ReadOnly { 1335 allErrs = append(allErrs, field.Forbidden(fldPath, "may only be specified when readOnly is true")) 1336 } 1337 if mount.MountPropagation != nil && *mount.MountPropagation != core.MountPropagationNone { 1338 allErrs = append(allErrs, field.Forbidden(fldPath, "may only be specified when mountPropagation is None or not specified")) 1339 } 1340 default: 1341 supportedRRO := sets.New( 1342 core.RecursiveReadOnlyDisabled, 1343 core.RecursiveReadOnlyIfPossible, 1344 core.RecursiveReadOnlyEnabled) 1345 allErrs = append(allErrs, field.NotSupported(fldPath, *mount.RecursiveReadOnly, sets.List(supportedRRO))) 1346 } 1347 return allErrs 1348 } 1349 1350 // ValidateLocalNonReservedPath makes sure targetPath: 1351 // 1. is not abs path 1352 // 2. does not contain any '..' elements 1353 // 3. does not start with '..' 1354 func ValidateLocalNonReservedPath(targetPath string, fldPath *field.Path) field.ErrorList { 1355 allErrs := field.ErrorList{} 1356 allErrs = append(allErrs, validateLocalDescendingPath(targetPath, fldPath)...) 1357 // Don't report this error if the check for .. elements already caught it. 1358 if strings.HasPrefix(targetPath, "..") && !strings.HasPrefix(targetPath, "../") { 1359 allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not start with '..'")) 1360 } 1361 return allErrs 1362 } 1363 1364 func validateRBDVolumeSource(rbd *core.RBDVolumeSource, fldPath *field.Path) field.ErrorList { 1365 allErrs := field.ErrorList{} 1366 if len(rbd.CephMonitors) == 0 { 1367 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) 1368 } 1369 if len(rbd.RBDImage) == 0 { 1370 allErrs = append(allErrs, field.Required(fldPath.Child("image"), "")) 1371 } 1372 return allErrs 1373 } 1374 1375 func validateRBDPersistentVolumeSource(rbd *core.RBDPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1376 allErrs := field.ErrorList{} 1377 if len(rbd.CephMonitors) == 0 { 1378 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) 1379 } 1380 if len(rbd.RBDImage) == 0 { 1381 allErrs = append(allErrs, field.Required(fldPath.Child("image"), "")) 1382 } 1383 return allErrs 1384 } 1385 1386 func validateCinderVolumeSource(cd *core.CinderVolumeSource, fldPath *field.Path) field.ErrorList { 1387 allErrs := field.ErrorList{} 1388 if len(cd.VolumeID) == 0 { 1389 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) 1390 } 1391 if cd.SecretRef != nil { 1392 if len(cd.SecretRef.Name) == 0 { 1393 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) 1394 } 1395 } 1396 return allErrs 1397 } 1398 1399 func validateCinderPersistentVolumeSource(cd *core.CinderPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1400 allErrs := field.ErrorList{} 1401 if len(cd.VolumeID) == 0 { 1402 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) 1403 } 1404 if cd.SecretRef != nil { 1405 if len(cd.SecretRef.Name) == 0 { 1406 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) 1407 } 1408 if len(cd.SecretRef.Namespace) == 0 { 1409 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), "")) 1410 } 1411 } 1412 return allErrs 1413 } 1414 1415 func validateCephFSVolumeSource(cephfs *core.CephFSVolumeSource, fldPath *field.Path) field.ErrorList { 1416 allErrs := field.ErrorList{} 1417 if len(cephfs.Monitors) == 0 { 1418 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) 1419 } 1420 return allErrs 1421 } 1422 1423 func validateCephFSPersistentVolumeSource(cephfs *core.CephFSPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1424 allErrs := field.ErrorList{} 1425 if len(cephfs.Monitors) == 0 { 1426 allErrs = append(allErrs, field.Required(fldPath.Child("monitors"), "")) 1427 } 1428 return allErrs 1429 } 1430 1431 func validateFlexVolumeSource(fv *core.FlexVolumeSource, fldPath *field.Path) field.ErrorList { 1432 allErrs := field.ErrorList{} 1433 if len(fv.Driver) == 0 { 1434 allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) 1435 } 1436 1437 // Make sure user-specified options don't use kubernetes namespaces 1438 for k := range fv.Options { 1439 namespace := k 1440 if parts := strings.SplitN(k, "/", 2); len(parts) == 2 { 1441 namespace = parts[0] 1442 } 1443 normalized := "." + strings.ToLower(namespace) 1444 if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") { 1445 allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved")) 1446 } 1447 } 1448 1449 return allErrs 1450 } 1451 1452 func validateFlexPersistentVolumeSource(fv *core.FlexPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1453 allErrs := field.ErrorList{} 1454 if len(fv.Driver) == 0 { 1455 allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) 1456 } 1457 1458 // Make sure user-specified options don't use kubernetes namespaces 1459 for k := range fv.Options { 1460 namespace := k 1461 if parts := strings.SplitN(k, "/", 2); len(parts) == 2 { 1462 namespace = parts[0] 1463 } 1464 normalized := "." + strings.ToLower(namespace) 1465 if strings.HasSuffix(normalized, ".kubernetes.io") || strings.HasSuffix(normalized, ".k8s.io") { 1466 allErrs = append(allErrs, field.Invalid(fldPath.Child("options").Key(k), k, "kubernetes.io and k8s.io namespaces are reserved")) 1467 } 1468 } 1469 1470 return allErrs 1471 } 1472 1473 func validateAzureFile(azure *core.AzureFileVolumeSource, fldPath *field.Path) field.ErrorList { 1474 allErrs := field.ErrorList{} 1475 if azure.SecretName == "" { 1476 allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) 1477 } 1478 if azure.ShareName == "" { 1479 allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), "")) 1480 } 1481 return allErrs 1482 } 1483 1484 func validateAzureFilePV(azure *core.AzureFilePersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1485 allErrs := field.ErrorList{} 1486 if azure.SecretName == "" { 1487 allErrs = append(allErrs, field.Required(fldPath.Child("secretName"), "")) 1488 } 1489 if azure.ShareName == "" { 1490 allErrs = append(allErrs, field.Required(fldPath.Child("shareName"), "")) 1491 } 1492 if azure.SecretNamespace != nil { 1493 if len(*azure.SecretNamespace) == 0 { 1494 allErrs = append(allErrs, field.Required(fldPath.Child("secretNamespace"), "")) 1495 } 1496 } 1497 return allErrs 1498 } 1499 1500 func validateAzureDisk(azure *core.AzureDiskVolumeSource, fldPath *field.Path) field.ErrorList { 1501 var supportedCachingModes = sets.New( 1502 core.AzureDataDiskCachingNone, 1503 core.AzureDataDiskCachingReadOnly, 1504 core.AzureDataDiskCachingReadWrite) 1505 1506 var supportedDiskKinds = sets.New( 1507 core.AzureSharedBlobDisk, 1508 core.AzureDedicatedBlobDisk, 1509 core.AzureManagedDisk) 1510 1511 diskURISupportedManaged := []string{"/subscriptions/{sub-id}/resourcegroups/{group-name}/providers/microsoft.compute/disks/{disk-id}"} 1512 diskURISupportedblob := []string{"https://{account-name}.blob.core.windows.net/{container-name}/{disk-name}.vhd"} 1513 1514 allErrs := field.ErrorList{} 1515 if azure.DiskName == "" { 1516 allErrs = append(allErrs, field.Required(fldPath.Child("diskName"), "")) 1517 } 1518 1519 if azure.DataDiskURI == "" { 1520 allErrs = append(allErrs, field.Required(fldPath.Child("diskURI"), "")) 1521 } 1522 1523 if azure.CachingMode != nil && !supportedCachingModes.Has(*azure.CachingMode) { 1524 allErrs = append(allErrs, field.NotSupported(fldPath.Child("cachingMode"), *azure.CachingMode, sets.List(supportedCachingModes))) 1525 } 1526 1527 if azure.Kind != nil && !supportedDiskKinds.Has(*azure.Kind) { 1528 allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), *azure.Kind, sets.List(supportedDiskKinds))) 1529 } 1530 1531 // validate that DiskUri is the correct format 1532 if azure.Kind != nil && *azure.Kind == core.AzureManagedDisk && strings.Index(azure.DataDiskURI, "/subscriptions/") != 0 { 1533 allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskURISupportedManaged)) 1534 } 1535 1536 if azure.Kind != nil && *azure.Kind != core.AzureManagedDisk && strings.Index(azure.DataDiskURI, "https://") != 0 { 1537 allErrs = append(allErrs, field.NotSupported(fldPath.Child("diskURI"), azure.DataDiskURI, diskURISupportedblob)) 1538 } 1539 1540 return allErrs 1541 } 1542 1543 func validateVsphereVolumeSource(cd *core.VsphereVirtualDiskVolumeSource, fldPath *field.Path) field.ErrorList { 1544 allErrs := field.ErrorList{} 1545 if len(cd.VolumePath) == 0 { 1546 allErrs = append(allErrs, field.Required(fldPath.Child("volumePath"), "")) 1547 } 1548 return allErrs 1549 } 1550 1551 func validatePhotonPersistentDiskVolumeSource(cd *core.PhotonPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList { 1552 allErrs := field.ErrorList{} 1553 if len(cd.PdID) == 0 { 1554 allErrs = append(allErrs, field.Required(fldPath.Child("pdID"), "")) 1555 } 1556 return allErrs 1557 } 1558 1559 func validatePortworxVolumeSource(pwx *core.PortworxVolumeSource, fldPath *field.Path) field.ErrorList { 1560 allErrs := field.ErrorList{} 1561 if len(pwx.VolumeID) == 0 { 1562 allErrs = append(allErrs, field.Required(fldPath.Child("volumeID"), "")) 1563 } 1564 return allErrs 1565 } 1566 1567 func validateScaleIOVolumeSource(sio *core.ScaleIOVolumeSource, fldPath *field.Path) field.ErrorList { 1568 allErrs := field.ErrorList{} 1569 if sio.Gateway == "" { 1570 allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), "")) 1571 } 1572 if sio.System == "" { 1573 allErrs = append(allErrs, field.Required(fldPath.Child("system"), "")) 1574 } 1575 if sio.VolumeName == "" { 1576 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) 1577 } 1578 return allErrs 1579 } 1580 1581 func validateScaleIOPersistentVolumeSource(sio *core.ScaleIOPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1582 allErrs := field.ErrorList{} 1583 if sio.Gateway == "" { 1584 allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), "")) 1585 } 1586 if sio.System == "" { 1587 allErrs = append(allErrs, field.Required(fldPath.Child("system"), "")) 1588 } 1589 if sio.VolumeName == "" { 1590 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) 1591 } 1592 return allErrs 1593 } 1594 1595 func validateLocalVolumeSource(ls *core.LocalVolumeSource, fldPath *field.Path) field.ErrorList { 1596 allErrs := field.ErrorList{} 1597 if ls.Path == "" { 1598 allErrs = append(allErrs, field.Required(fldPath.Child("path"), "")) 1599 return allErrs 1600 } 1601 1602 allErrs = append(allErrs, validatePathNoBacksteps(ls.Path, fldPath.Child("path"))...) 1603 return allErrs 1604 } 1605 1606 func validateStorageOSVolumeSource(storageos *core.StorageOSVolumeSource, fldPath *field.Path) field.ErrorList { 1607 allErrs := field.ErrorList{} 1608 if len(storageos.VolumeName) == 0 { 1609 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) 1610 } else { 1611 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...) 1612 } 1613 if len(storageos.VolumeNamespace) > 0 { 1614 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...) 1615 } 1616 if storageos.SecretRef != nil { 1617 if len(storageos.SecretRef.Name) == 0 { 1618 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) 1619 } 1620 } 1621 return allErrs 1622 } 1623 1624 func validateStorageOSPersistentVolumeSource(storageos *core.StorageOSPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1625 allErrs := field.ErrorList{} 1626 if len(storageos.VolumeName) == 0 { 1627 allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) 1628 } else { 1629 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...) 1630 } 1631 if len(storageos.VolumeNamespace) > 0 { 1632 allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...) 1633 } 1634 if storageos.SecretRef != nil { 1635 if len(storageos.SecretRef.Name) == 0 { 1636 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), "")) 1637 } 1638 if len(storageos.SecretRef.Namespace) == 0 { 1639 allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), "")) 1640 } 1641 } 1642 return allErrs 1643 } 1644 1645 // validatePVSecretReference check whether provided SecretReference object is valid in terms of secret name and namespace. 1646 1647 func validatePVSecretReference(secretRef *core.SecretReference, fldPath *field.Path) field.ErrorList { 1648 var allErrs field.ErrorList 1649 if len(secretRef.Name) == 0 { 1650 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 1651 } else { 1652 allErrs = append(allErrs, ValidateDNS1123Subdomain(secretRef.Name, fldPath.Child("name"))...) 1653 } 1654 1655 if len(secretRef.Namespace) == 0 { 1656 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) 1657 } else { 1658 allErrs = append(allErrs, ValidateDNS1123Label(secretRef.Namespace, fldPath.Child("namespace"))...) 1659 } 1660 return allErrs 1661 } 1662 1663 func ValidateCSIDriverName(driverName string, fldPath *field.Path) field.ErrorList { 1664 allErrs := field.ErrorList{} 1665 1666 if len(driverName) == 0 { 1667 allErrs = append(allErrs, field.Required(fldPath, "")) 1668 } 1669 1670 if len(driverName) > 63 { 1671 allErrs = append(allErrs, field.TooLong(fldPath, driverName, 63)) 1672 } 1673 1674 for _, msg := range validation.IsDNS1123Subdomain(strings.ToLower(driverName)) { 1675 allErrs = append(allErrs, field.Invalid(fldPath, driverName, msg)) 1676 } 1677 1678 return allErrs 1679 } 1680 1681 func validateCSIPersistentVolumeSource(csi *core.CSIPersistentVolumeSource, fldPath *field.Path) field.ErrorList { 1682 allErrs := field.ErrorList{} 1683 1684 allErrs = append(allErrs, ValidateCSIDriverName(csi.Driver, fldPath.Child("driver"))...) 1685 1686 if len(csi.VolumeHandle) == 0 { 1687 allErrs = append(allErrs, field.Required(fldPath.Child("volumeHandle"), "")) 1688 } 1689 if csi.ControllerPublishSecretRef != nil { 1690 allErrs = append(allErrs, validatePVSecretReference(csi.ControllerPublishSecretRef, fldPath.Child("controllerPublishSecretRef"))...) 1691 } 1692 if csi.ControllerExpandSecretRef != nil { 1693 allErrs = append(allErrs, validatePVSecretReference(csi.ControllerExpandSecretRef, fldPath.Child("controllerExpandSecretRef"))...) 1694 } 1695 if csi.NodePublishSecretRef != nil { 1696 allErrs = append(allErrs, validatePVSecretReference(csi.NodePublishSecretRef, fldPath.Child("nodePublishSecretRef"))...) 1697 } 1698 if csi.NodeExpandSecretRef != nil { 1699 allErrs = append(allErrs, validatePVSecretReference(csi.NodeExpandSecretRef, fldPath.Child("nodeExpandSecretRef"))...) 1700 } 1701 return allErrs 1702 } 1703 1704 func validateCSIVolumeSource(csi *core.CSIVolumeSource, fldPath *field.Path) field.ErrorList { 1705 allErrs := field.ErrorList{} 1706 allErrs = append(allErrs, ValidateCSIDriverName(csi.Driver, fldPath.Child("driver"))...) 1707 1708 if csi.NodePublishSecretRef != nil { 1709 if len(csi.NodePublishSecretRef.Name) == 0 { 1710 allErrs = append(allErrs, field.Required(fldPath.Child("nodePublishSecretRef", "name"), "")) 1711 } else { 1712 for _, msg := range ValidateSecretName(csi.NodePublishSecretRef.Name, false) { 1713 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), csi.NodePublishSecretRef.Name, msg)) 1714 } 1715 } 1716 } 1717 1718 return allErrs 1719 } 1720 1721 func validateEphemeralVolumeSource(ephemeral *core.EphemeralVolumeSource, fldPath *field.Path) field.ErrorList { 1722 allErrs := field.ErrorList{} 1723 if ephemeral.VolumeClaimTemplate == nil { 1724 allErrs = append(allErrs, field.Required(fldPath.Child("volumeClaimTemplate"), "")) 1725 } else { 1726 opts := ValidationOptionsForPersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, nil) 1727 allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"), opts)...) 1728 } 1729 return allErrs 1730 } 1731 1732 // ValidatePersistentVolumeClaimTemplate verifies that the embedded object meta and spec are valid. 1733 // Checking of the object data is very minimal because only labels and annotations are used. 1734 func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList { 1735 allErrs := ValidateTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata")) 1736 allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"), opts)...) 1737 return allErrs 1738 } 1739 1740 func ValidateTemplateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { 1741 allErrs := apimachineryvalidation.ValidateAnnotations(objMeta.Annotations, fldPath.Child("annotations")) 1742 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(objMeta.Labels, fldPath.Child("labels"))...) 1743 // All other fields are not supported and thus must not be set 1744 // to avoid confusion. We could reject individual fields, 1745 // but then adding a new one to ObjectMeta wouldn't be checked 1746 // unless this code gets updated. Instead, we ensure that 1747 // only allowed fields are set via reflection. 1748 allErrs = append(allErrs, validateFieldAllowList(*objMeta, allowedTemplateObjectMetaFields, "cannot be set", fldPath)...) 1749 return allErrs 1750 } 1751 1752 var allowedTemplateObjectMetaFields = map[string]bool{ 1753 "Annotations": true, 1754 "Labels": true, 1755 } 1756 1757 // PersistentVolumeSpecValidationOptions contains the different settings for PeristentVolume validation 1758 type PersistentVolumeSpecValidationOptions struct { 1759 // Allow users to modify the class of volume attributes 1760 EnableVolumeAttributesClass bool 1761 } 1762 1763 // ValidatePersistentVolumeName checks that a name is appropriate for a 1764 // PersistentVolumeName object. 1765 var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain 1766 1767 var supportedAccessModes = sets.New( 1768 core.ReadWriteOnce, 1769 core.ReadOnlyMany, 1770 core.ReadWriteMany, 1771 core.ReadWriteOncePod) 1772 1773 var supportedReclaimPolicy = sets.New( 1774 core.PersistentVolumeReclaimDelete, 1775 core.PersistentVolumeReclaimRecycle, 1776 core.PersistentVolumeReclaimRetain) 1777 1778 var supportedVolumeModes = sets.New(core.PersistentVolumeBlock, core.PersistentVolumeFilesystem) 1779 1780 func ValidationOptionsForPersistentVolume(pv, oldPv *core.PersistentVolume) PersistentVolumeSpecValidationOptions { 1781 opts := PersistentVolumeSpecValidationOptions{ 1782 EnableVolumeAttributesClass: utilfeature.DefaultMutableFeatureGate.Enabled(features.VolumeAttributesClass), 1783 } 1784 if oldPv != nil && oldPv.Spec.VolumeAttributesClassName != nil { 1785 opts.EnableVolumeAttributesClass = true 1786 } 1787 return opts 1788 } 1789 1790 func ValidatePersistentVolumeSpec(pvSpec *core.PersistentVolumeSpec, pvName string, validateInlinePersistentVolumeSpec bool, fldPath *field.Path, opts PersistentVolumeSpecValidationOptions) field.ErrorList { 1791 allErrs := field.ErrorList{} 1792 1793 if validateInlinePersistentVolumeSpec { 1794 if pvSpec.ClaimRef != nil { 1795 allErrs = append(allErrs, field.Forbidden(fldPath.Child("claimRef"), "may not be specified in the context of inline volumes")) 1796 } 1797 if len(pvSpec.Capacity) != 0 { 1798 allErrs = append(allErrs, field.Forbidden(fldPath.Child("capacity"), "may not be specified in the context of inline volumes")) 1799 } 1800 if pvSpec.CSI == nil { 1801 allErrs = append(allErrs, field.Required(fldPath.Child("csi"), "has to be specified in the context of inline volumes")) 1802 } 1803 } 1804 1805 if len(pvSpec.AccessModes) == 0 { 1806 allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "")) 1807 } 1808 1809 foundReadWriteOncePod, foundNonReadWriteOncePod := false, false 1810 for _, mode := range pvSpec.AccessModes { 1811 if !supportedAccessModes.Has(mode) { 1812 allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, sets.List(supportedAccessModes))) 1813 } 1814 1815 if mode == core.ReadWriteOncePod { 1816 foundReadWriteOncePod = true 1817 } else if supportedAccessModes.Has(mode) { 1818 foundNonReadWriteOncePod = true 1819 } 1820 } 1821 if foundReadWriteOncePod && foundNonReadWriteOncePod { 1822 allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes")) 1823 } 1824 1825 if !validateInlinePersistentVolumeSpec { 1826 if len(pvSpec.Capacity) == 0 { 1827 allErrs = append(allErrs, field.Required(fldPath.Child("capacity"), "")) 1828 } 1829 1830 if _, ok := pvSpec.Capacity[core.ResourceStorage]; !ok || len(pvSpec.Capacity) > 1 { 1831 allErrs = append(allErrs, field.NotSupported(fldPath.Child("capacity"), pvSpec.Capacity, []core.ResourceName{core.ResourceStorage})) 1832 } 1833 capPath := fldPath.Child("capacity") 1834 for r, qty := range pvSpec.Capacity { 1835 allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) 1836 allErrs = append(allErrs, ValidatePositiveQuantityValue(qty, capPath.Key(string(r)))...) 1837 } 1838 } 1839 1840 if len(pvSpec.PersistentVolumeReclaimPolicy) > 0 { 1841 if validateInlinePersistentVolumeSpec { 1842 if pvSpec.PersistentVolumeReclaimPolicy != core.PersistentVolumeReclaimRetain { 1843 allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeReclaimPolicy"), "may only be "+string(core.PersistentVolumeReclaimRetain)+" in the context of inline volumes")) 1844 } 1845 } else { 1846 if !supportedReclaimPolicy.Has(pvSpec.PersistentVolumeReclaimPolicy) { 1847 allErrs = append(allErrs, field.NotSupported(fldPath.Child("persistentVolumeReclaimPolicy"), pvSpec.PersistentVolumeReclaimPolicy, sets.List(supportedReclaimPolicy))) 1848 } 1849 } 1850 } 1851 1852 var nodeAffinitySpecified bool 1853 var errs field.ErrorList 1854 if pvSpec.NodeAffinity != nil { 1855 if validateInlinePersistentVolumeSpec { 1856 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeAffinity"), "may not be specified in the context of inline volumes")) 1857 } else { 1858 nodeAffinitySpecified, errs = validateVolumeNodeAffinity(pvSpec.NodeAffinity, fldPath.Child("nodeAffinity")) 1859 allErrs = append(allErrs, errs...) 1860 } 1861 } 1862 numVolumes := 0 1863 if pvSpec.HostPath != nil { 1864 if numVolumes > 0 { 1865 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPath"), "may not specify more than 1 volume type")) 1866 } else { 1867 numVolumes++ 1868 allErrs = append(allErrs, validateHostPathVolumeSource(pvSpec.HostPath, fldPath.Child("hostPath"))...) 1869 } 1870 } 1871 if pvSpec.GCEPersistentDisk != nil { 1872 if numVolumes > 0 { 1873 allErrs = append(allErrs, field.Forbidden(fldPath.Child("gcePersistentDisk"), "may not specify more than 1 volume type")) 1874 } else { 1875 numVolumes++ 1876 allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(pvSpec.GCEPersistentDisk, fldPath.Child("persistentDisk"))...) 1877 } 1878 } 1879 if pvSpec.AWSElasticBlockStore != nil { 1880 if numVolumes > 0 { 1881 allErrs = append(allErrs, field.Forbidden(fldPath.Child("awsElasticBlockStore"), "may not specify more than 1 volume type")) 1882 } else { 1883 numVolumes++ 1884 allErrs = append(allErrs, validateAWSElasticBlockStoreVolumeSource(pvSpec.AWSElasticBlockStore, fldPath.Child("awsElasticBlockStore"))...) 1885 } 1886 } 1887 if pvSpec.Glusterfs != nil { 1888 if numVolumes > 0 { 1889 allErrs = append(allErrs, field.Forbidden(fldPath.Child("glusterfs"), "may not specify more than 1 volume type")) 1890 } else { 1891 numVolumes++ 1892 allErrs = append(allErrs, validateGlusterfsPersistentVolumeSource(pvSpec.Glusterfs, fldPath.Child("glusterfs"))...) 1893 } 1894 } 1895 if pvSpec.Flocker != nil { 1896 if numVolumes > 0 { 1897 allErrs = append(allErrs, field.Forbidden(fldPath.Child("flocker"), "may not specify more than 1 volume type")) 1898 } else { 1899 numVolumes++ 1900 allErrs = append(allErrs, validateFlockerVolumeSource(pvSpec.Flocker, fldPath.Child("flocker"))...) 1901 } 1902 } 1903 if pvSpec.NFS != nil { 1904 if numVolumes > 0 { 1905 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nfs"), "may not specify more than 1 volume type")) 1906 } else { 1907 numVolumes++ 1908 allErrs = append(allErrs, validateNFSVolumeSource(pvSpec.NFS, fldPath.Child("nfs"))...) 1909 } 1910 } 1911 if pvSpec.RBD != nil { 1912 if numVolumes > 0 { 1913 allErrs = append(allErrs, field.Forbidden(fldPath.Child("rbd"), "may not specify more than 1 volume type")) 1914 } else { 1915 numVolumes++ 1916 allErrs = append(allErrs, validateRBDPersistentVolumeSource(pvSpec.RBD, fldPath.Child("rbd"))...) 1917 } 1918 } 1919 if pvSpec.Quobyte != nil { 1920 if numVolumes > 0 { 1921 allErrs = append(allErrs, field.Forbidden(fldPath.Child("quobyte"), "may not specify more than 1 volume type")) 1922 } else { 1923 numVolumes++ 1924 allErrs = append(allErrs, validateQuobyteVolumeSource(pvSpec.Quobyte, fldPath.Child("quobyte"))...) 1925 } 1926 } 1927 if pvSpec.CephFS != nil { 1928 if numVolumes > 0 { 1929 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cephFS"), "may not specify more than 1 volume type")) 1930 } else { 1931 numVolumes++ 1932 allErrs = append(allErrs, validateCephFSPersistentVolumeSource(pvSpec.CephFS, fldPath.Child("cephfs"))...) 1933 } 1934 } 1935 if pvSpec.ISCSI != nil { 1936 if numVolumes > 0 { 1937 allErrs = append(allErrs, field.Forbidden(fldPath.Child("iscsi"), "may not specify more than 1 volume type")) 1938 } else { 1939 numVolumes++ 1940 allErrs = append(allErrs, validateISCSIPersistentVolumeSource(pvSpec.ISCSI, pvName, fldPath.Child("iscsi"))...) 1941 } 1942 } 1943 if pvSpec.Cinder != nil { 1944 if numVolumes > 0 { 1945 allErrs = append(allErrs, field.Forbidden(fldPath.Child("cinder"), "may not specify more than 1 volume type")) 1946 } else { 1947 numVolumes++ 1948 allErrs = append(allErrs, validateCinderPersistentVolumeSource(pvSpec.Cinder, fldPath.Child("cinder"))...) 1949 } 1950 } 1951 if pvSpec.FC != nil { 1952 if numVolumes > 0 { 1953 allErrs = append(allErrs, field.Forbidden(fldPath.Child("fc"), "may not specify more than 1 volume type")) 1954 } else { 1955 numVolumes++ 1956 allErrs = append(allErrs, validateFCVolumeSource(pvSpec.FC, fldPath.Child("fc"))...) 1957 } 1958 } 1959 if pvSpec.FlexVolume != nil { 1960 numVolumes++ 1961 allErrs = append(allErrs, validateFlexPersistentVolumeSource(pvSpec.FlexVolume, fldPath.Child("flexVolume"))...) 1962 } 1963 if pvSpec.AzureFile != nil { 1964 if numVolumes > 0 { 1965 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureFile"), "may not specify more than 1 volume type")) 1966 1967 } else { 1968 numVolumes++ 1969 allErrs = append(allErrs, validateAzureFilePV(pvSpec.AzureFile, fldPath.Child("azureFile"))...) 1970 } 1971 } 1972 1973 if pvSpec.VsphereVolume != nil { 1974 if numVolumes > 0 { 1975 allErrs = append(allErrs, field.Forbidden(fldPath.Child("vsphereVolume"), "may not specify more than 1 volume type")) 1976 } else { 1977 numVolumes++ 1978 allErrs = append(allErrs, validateVsphereVolumeSource(pvSpec.VsphereVolume, fldPath.Child("vsphereVolume"))...) 1979 } 1980 } 1981 if pvSpec.PhotonPersistentDisk != nil { 1982 if numVolumes > 0 { 1983 allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type")) 1984 } else { 1985 numVolumes++ 1986 allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(pvSpec.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...) 1987 } 1988 } 1989 if pvSpec.PortworxVolume != nil { 1990 if numVolumes > 0 { 1991 allErrs = append(allErrs, field.Forbidden(fldPath.Child("portworxVolume"), "may not specify more than 1 volume type")) 1992 } else { 1993 numVolumes++ 1994 allErrs = append(allErrs, validatePortworxVolumeSource(pvSpec.PortworxVolume, fldPath.Child("portworxVolume"))...) 1995 } 1996 } 1997 if pvSpec.AzureDisk != nil { 1998 if numVolumes > 0 { 1999 allErrs = append(allErrs, field.Forbidden(fldPath.Child("azureDisk"), "may not specify more than 1 volume type")) 2000 } else { 2001 numVolumes++ 2002 allErrs = append(allErrs, validateAzureDisk(pvSpec.AzureDisk, fldPath.Child("azureDisk"))...) 2003 } 2004 } 2005 if pvSpec.ScaleIO != nil { 2006 if numVolumes > 0 { 2007 allErrs = append(allErrs, field.Forbidden(fldPath.Child("scaleIO"), "may not specify more than 1 volume type")) 2008 } else { 2009 numVolumes++ 2010 allErrs = append(allErrs, validateScaleIOPersistentVolumeSource(pvSpec.ScaleIO, fldPath.Child("scaleIO"))...) 2011 } 2012 } 2013 if pvSpec.Local != nil { 2014 if numVolumes > 0 { 2015 allErrs = append(allErrs, field.Forbidden(fldPath.Child("local"), "may not specify more than 1 volume type")) 2016 } else { 2017 numVolumes++ 2018 allErrs = append(allErrs, validateLocalVolumeSource(pvSpec.Local, fldPath.Child("local"))...) 2019 // NodeAffinity is required 2020 if !nodeAffinitySpecified { 2021 allErrs = append(allErrs, field.Required(fldPath.Child("nodeAffinity"), "Local volume requires node affinity")) 2022 } 2023 } 2024 } 2025 if pvSpec.StorageOS != nil { 2026 if numVolumes > 0 { 2027 allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type")) 2028 } else { 2029 numVolumes++ 2030 allErrs = append(allErrs, validateStorageOSPersistentVolumeSource(pvSpec.StorageOS, fldPath.Child("storageos"))...) 2031 } 2032 } 2033 2034 if pvSpec.CSI != nil { 2035 if numVolumes > 0 { 2036 allErrs = append(allErrs, field.Forbidden(fldPath.Child("csi"), "may not specify more than 1 volume type")) 2037 } else { 2038 numVolumes++ 2039 allErrs = append(allErrs, validateCSIPersistentVolumeSource(pvSpec.CSI, fldPath.Child("csi"))...) 2040 } 2041 } 2042 2043 if numVolumes == 0 { 2044 allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type")) 2045 } 2046 2047 // do not allow hostPath mounts of '/' to have a 'recycle' reclaim policy 2048 if pvSpec.HostPath != nil && path.Clean(pvSpec.HostPath.Path) == "/" && pvSpec.PersistentVolumeReclaimPolicy == core.PersistentVolumeReclaimRecycle { 2049 allErrs = append(allErrs, field.Forbidden(fldPath.Child("persistentVolumeReclaimPolicy"), "may not be 'recycle' for a hostPath mount of '/'")) 2050 } 2051 2052 if len(pvSpec.StorageClassName) > 0 { 2053 if validateInlinePersistentVolumeSpec { 2054 allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageClassName"), "may not be specified in the context of inline volumes")) 2055 } else { 2056 for _, msg := range ValidateClassName(pvSpec.StorageClassName, false) { 2057 allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), pvSpec.StorageClassName, msg)) 2058 } 2059 } 2060 } 2061 if pvSpec.VolumeMode != nil { 2062 if validateInlinePersistentVolumeSpec { 2063 if *pvSpec.VolumeMode != core.PersistentVolumeFilesystem { 2064 allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeMode"), "may not specify volumeMode other than "+string(core.PersistentVolumeFilesystem)+" in the context of inline volumes")) 2065 } 2066 } else { 2067 if !supportedVolumeModes.Has(*pvSpec.VolumeMode) { 2068 allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *pvSpec.VolumeMode, sets.List(supportedVolumeModes))) 2069 } 2070 } 2071 } 2072 if pvSpec.VolumeAttributesClassName != nil && opts.EnableVolumeAttributesClass { 2073 if len(*pvSpec.VolumeAttributesClassName) == 0 { 2074 allErrs = append(allErrs, field.Required(fldPath.Child("volumeAttributesClassName"), "an empty string is disallowed")) 2075 } else { 2076 for _, msg := range ValidateClassName(*pvSpec.VolumeAttributesClassName, false) { 2077 allErrs = append(allErrs, field.Invalid(fldPath.Child("volumeAttributesClassName"), *pvSpec.VolumeAttributesClassName, msg)) 2078 } 2079 } 2080 if pvSpec.CSI == nil { 2081 allErrs = append(allErrs, field.Required(fldPath.Child("csi"), "has to be specified when using volumeAttributesClassName")) 2082 } 2083 } 2084 return allErrs 2085 } 2086 2087 func ValidatePersistentVolume(pv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList { 2088 metaPath := field.NewPath("metadata") 2089 allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath) 2090 allErrs = append(allErrs, ValidatePersistentVolumeSpec(&pv.Spec, pv.ObjectMeta.Name, false, field.NewPath("spec"), opts)...) 2091 return allErrs 2092 } 2093 2094 // ValidatePersistentVolumeUpdate tests to see if the update is legal for an end user to make. 2095 // newPv is updated with fields that cannot be changed. 2096 func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume, opts PersistentVolumeSpecValidationOptions) field.ErrorList { 2097 allErrs := ValidatePersistentVolume(newPv, opts) 2098 2099 // if oldPV does not have ControllerExpandSecretRef then allow it to be set 2100 if (oldPv.Spec.CSI != nil && oldPv.Spec.CSI.ControllerExpandSecretRef == nil) && 2101 (newPv.Spec.CSI != nil && newPv.Spec.CSI.ControllerExpandSecretRef != nil) { 2102 newPv = newPv.DeepCopy() 2103 newPv.Spec.CSI.ControllerExpandSecretRef = nil 2104 } 2105 2106 // PersistentVolumeSource should be immutable after creation. 2107 if !apiequality.Semantic.DeepEqual(newPv.Spec.PersistentVolumeSource, oldPv.Spec.PersistentVolumeSource) { 2108 pvcSourceDiff := cmp.Diff(oldPv.Spec.PersistentVolumeSource, newPv.Spec.PersistentVolumeSource) 2109 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "persistentvolumesource"), fmt.Sprintf("spec.persistentvolumesource is immutable after creation\n%v", pvcSourceDiff))) 2110 } 2111 allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...) 2112 2113 // Allow setting NodeAffinity if oldPv NodeAffinity was not set 2114 if oldPv.Spec.NodeAffinity != nil { 2115 allErrs = append(allErrs, validatePvNodeAffinity(newPv.Spec.NodeAffinity, oldPv.Spec.NodeAffinity, field.NewPath("nodeAffinity"))...) 2116 } 2117 2118 if !apiequality.Semantic.DeepEqual(oldPv.Spec.VolumeAttributesClassName, newPv.Spec.VolumeAttributesClassName) { 2119 if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) { 2120 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update is forbidden when the VolumeAttributesClass feature gate is disabled")) 2121 } 2122 if opts.EnableVolumeAttributesClass { 2123 if oldPv.Spec.VolumeAttributesClassName != nil && newPv.Spec.VolumeAttributesClassName == nil { 2124 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to nil is forbidden")) 2125 } 2126 } 2127 } 2128 2129 return allErrs 2130 } 2131 2132 // ValidatePersistentVolumeStatusUpdate tests to see if the status update is legal for an end user to make. 2133 func ValidatePersistentVolumeStatusUpdate(newPv, oldPv *core.PersistentVolume) field.ErrorList { 2134 allErrs := ValidateObjectMetaUpdate(&newPv.ObjectMeta, &oldPv.ObjectMeta, field.NewPath("metadata")) 2135 if len(newPv.ResourceVersion) == 0 { 2136 allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) 2137 } 2138 return allErrs 2139 } 2140 2141 type PersistentVolumeClaimSpecValidationOptions struct { 2142 // Allow users to recover from previously failing expansion operation 2143 EnableRecoverFromExpansionFailure bool 2144 // Allow to validate the label value of the label selector 2145 AllowInvalidLabelValueInSelector bool 2146 // Allow to validate the API group of the data source and data source reference 2147 AllowInvalidAPIGroupInDataSourceOrRef bool 2148 // Allow users to modify the class of volume attributes 2149 EnableVolumeAttributesClass bool 2150 } 2151 2152 func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions { 2153 opts := PersistentVolumeClaimSpecValidationOptions{ 2154 EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure), 2155 AllowInvalidLabelValueInSelector: false, 2156 EnableVolumeAttributesClass: utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass), 2157 } 2158 if oldPvc == nil { 2159 // If there's no old PVC, use the options based solely on feature enablement 2160 return opts 2161 } 2162 2163 // If the old object had an invalid API group in the data source or data source reference, continue to allow it in the new object 2164 opts.AllowInvalidAPIGroupInDataSourceOrRef = allowInvalidAPIGroupInDataSourceOrRef(&oldPvc.Spec) 2165 2166 if oldPvc.Spec.VolumeAttributesClassName != nil { 2167 // If the old object had a volume attributes class, continue to validate it in the new object. 2168 opts.EnableVolumeAttributesClass = true 2169 } 2170 2171 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{ 2172 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector, 2173 } 2174 if len(unversionedvalidation.ValidateLabelSelector(oldPvc.Spec.Selector, labelSelectorValidationOpts, nil)) > 0 { 2175 // If the old object had an invalid label selector, continue to allow it in the new object 2176 opts.AllowInvalidLabelValueInSelector = true 2177 } 2178 2179 if helper.ClaimContainsAllocatedResources(oldPvc) || 2180 helper.ClaimContainsAllocatedResourceStatus(oldPvc) { 2181 opts.EnableRecoverFromExpansionFailure = true 2182 } 2183 return opts 2184 } 2185 2186 func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions { 2187 opts := PersistentVolumeClaimSpecValidationOptions{ 2188 AllowInvalidLabelValueInSelector: false, 2189 EnableVolumeAttributesClass: utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass), 2190 } 2191 if oldClaimTemplate == nil { 2192 // If there's no old PVC template, use the options based solely on feature enablement 2193 return opts 2194 } 2195 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{ 2196 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector, 2197 } 2198 if len(unversionedvalidation.ValidateLabelSelector(oldClaimTemplate.Spec.Selector, labelSelectorValidationOpts, nil)) > 0 { 2199 // If the old object had an invalid label selector, continue to allow it in the new object 2200 opts.AllowInvalidLabelValueInSelector = true 2201 } 2202 return opts 2203 } 2204 2205 // allowInvalidAPIGroupInDataSourceOrRef returns true if the spec contains a data source or data source reference with an API group 2206 func allowInvalidAPIGroupInDataSourceOrRef(spec *core.PersistentVolumeClaimSpec) bool { 2207 if spec.DataSource != nil && spec.DataSource.APIGroup != nil { 2208 return true 2209 } 2210 if spec.DataSourceRef != nil && spec.DataSourceRef.APIGroup != nil { 2211 return true 2212 } 2213 return false 2214 } 2215 2216 // ValidatePersistentVolumeClaim validates a PersistentVolumeClaim 2217 func ValidatePersistentVolumeClaim(pvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList { 2218 allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName, field.NewPath("metadata")) 2219 allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&pvc.Spec, field.NewPath("spec"), opts)...) 2220 return allErrs 2221 } 2222 2223 // validateDataSource validates a DataSource/DataSourceRef in a PersistentVolumeClaimSpec 2224 func validateDataSource(dataSource *core.TypedLocalObjectReference, fldPath *field.Path, allowInvalidAPIGroupInDataSourceOrRef bool) field.ErrorList { 2225 allErrs := field.ErrorList{} 2226 2227 if len(dataSource.Name) == 0 { 2228 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 2229 } 2230 if len(dataSource.Kind) == 0 { 2231 allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) 2232 } 2233 apiGroup := "" 2234 if dataSource.APIGroup != nil { 2235 apiGroup = *dataSource.APIGroup 2236 } 2237 if len(apiGroup) == 0 && dataSource.Kind != "PersistentVolumeClaim" { 2238 allErrs = append(allErrs, field.Invalid(fldPath, dataSource.Kind, "must be 'PersistentVolumeClaim' when referencing the default apiGroup")) 2239 } 2240 if len(apiGroup) > 0 && !allowInvalidAPIGroupInDataSourceOrRef { 2241 for _, errString := range validation.IsDNS1123Subdomain(apiGroup) { 2242 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), apiGroup, errString)) 2243 } 2244 } 2245 2246 return allErrs 2247 } 2248 2249 // validateDataSourceRef validates a DataSourceRef in a PersistentVolumeClaimSpec 2250 func validateDataSourceRef(dataSourceRef *core.TypedObjectReference, fldPath *field.Path, allowInvalidAPIGroupInDataSourceOrRef bool) field.ErrorList { 2251 allErrs := field.ErrorList{} 2252 2253 if len(dataSourceRef.Name) == 0 { 2254 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 2255 } 2256 if len(dataSourceRef.Kind) == 0 { 2257 allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "")) 2258 } 2259 apiGroup := "" 2260 if dataSourceRef.APIGroup != nil { 2261 apiGroup = *dataSourceRef.APIGroup 2262 } 2263 if len(apiGroup) == 0 && dataSourceRef.Kind != "PersistentVolumeClaim" { 2264 allErrs = append(allErrs, field.Invalid(fldPath, dataSourceRef.Kind, "must be 'PersistentVolumeClaim' when referencing the default apiGroup")) 2265 } 2266 if len(apiGroup) > 0 && !allowInvalidAPIGroupInDataSourceOrRef { 2267 for _, errString := range validation.IsDNS1123Subdomain(apiGroup) { 2268 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), apiGroup, errString)) 2269 } 2270 } 2271 2272 if dataSourceRef.Namespace != nil && len(*dataSourceRef.Namespace) > 0 { 2273 for _, msg := range ValidateNameFunc(ValidateNamespaceName)(*dataSourceRef.Namespace, false) { 2274 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), *dataSourceRef.Namespace, msg)) 2275 } 2276 } 2277 2278 return allErrs 2279 } 2280 2281 // ValidatePersistentVolumeClaimSpec validates a PersistentVolumeClaimSpec 2282 func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fldPath *field.Path, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList { 2283 allErrs := field.ErrorList{} 2284 if len(spec.AccessModes) == 0 { 2285 allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required")) 2286 } 2287 if spec.Selector != nil { 2288 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{ 2289 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector, 2290 } 2291 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...) 2292 } 2293 2294 foundReadWriteOncePod, foundNonReadWriteOncePod := false, false 2295 for _, mode := range spec.AccessModes { 2296 if !supportedAccessModes.Has(mode) { 2297 allErrs = append(allErrs, field.NotSupported(fldPath.Child("accessModes"), mode, sets.List(supportedAccessModes))) 2298 } 2299 2300 if mode == core.ReadWriteOncePod { 2301 foundReadWriteOncePod = true 2302 } else if supportedAccessModes.Has(mode) { 2303 foundNonReadWriteOncePod = true 2304 } 2305 } 2306 if foundReadWriteOncePod && foundNonReadWriteOncePod { 2307 allErrs = append(allErrs, field.Forbidden(fldPath.Child("accessModes"), "may not use ReadWriteOncePod with other access modes")) 2308 } 2309 2310 storageValue, ok := spec.Resources.Requests[core.ResourceStorage] 2311 if !ok { 2312 allErrs = append(allErrs, field.Required(fldPath.Child("resources").Key(string(core.ResourceStorage)), "")) 2313 } else if errs := ValidatePositiveQuantityValue(storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage))); len(errs) > 0 { 2314 allErrs = append(allErrs, errs...) 2315 } else { 2316 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceStorage, storageValue, fldPath.Child("resources").Key(string(core.ResourceStorage)))...) 2317 } 2318 2319 if spec.StorageClassName != nil && len(*spec.StorageClassName) > 0 { 2320 for _, msg := range ValidateClassName(*spec.StorageClassName, false) { 2321 allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg)) 2322 } 2323 } 2324 if spec.VolumeMode != nil && !supportedVolumeModes.Has(*spec.VolumeMode) { 2325 allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *spec.VolumeMode, sets.List(supportedVolumeModes))) 2326 } 2327 2328 if spec.DataSource != nil { 2329 allErrs = append(allErrs, validateDataSource(spec.DataSource, fldPath.Child("dataSource"), opts.AllowInvalidAPIGroupInDataSourceOrRef)...) 2330 } 2331 if spec.DataSourceRef != nil { 2332 allErrs = append(allErrs, validateDataSourceRef(spec.DataSourceRef, fldPath.Child("dataSourceRef"), opts.AllowInvalidAPIGroupInDataSourceOrRef)...) 2333 } 2334 if spec.DataSourceRef != nil && spec.DataSourceRef.Namespace != nil && len(*spec.DataSourceRef.Namespace) > 0 { 2335 if spec.DataSource != nil { 2336 allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"), 2337 "may not be specified when dataSourceRef.namespace is specified")) 2338 } 2339 } else if spec.DataSource != nil && spec.DataSourceRef != nil { 2340 if !isDataSourceEqualDataSourceRef(spec.DataSource, spec.DataSourceRef) { 2341 allErrs = append(allErrs, field.Invalid(fldPath, fldPath.Child("dataSource"), 2342 "must match dataSourceRef")) 2343 } 2344 } 2345 if spec.VolumeAttributesClassName != nil && len(*spec.VolumeAttributesClassName) > 0 && opts.EnableVolumeAttributesClass { 2346 for _, msg := range ValidateClassName(*spec.VolumeAttributesClassName, false) { 2347 allErrs = append(allErrs, field.Invalid(fldPath.Child("volumeAttributesClassName"), *spec.VolumeAttributesClassName, msg)) 2348 } 2349 } 2350 2351 return allErrs 2352 } 2353 2354 func isDataSourceEqualDataSourceRef(dataSource *core.TypedLocalObjectReference, dataSourceRef *core.TypedObjectReference) bool { 2355 return reflect.DeepEqual(dataSource.APIGroup, dataSourceRef.APIGroup) && dataSource.Kind == dataSourceRef.Kind && dataSource.Name == dataSourceRef.Name 2356 } 2357 2358 // ValidatePersistentVolumeClaimUpdate validates an update to a PersistentVolumeClaim 2359 func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, opts PersistentVolumeClaimSpecValidationOptions) field.ErrorList { 2360 allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) 2361 allErrs = append(allErrs, ValidatePersistentVolumeClaim(newPvc, opts)...) 2362 newPvcClone := newPvc.DeepCopy() 2363 oldPvcClone := oldPvc.DeepCopy() 2364 2365 // PVController needs to update PVC.Spec w/ VolumeName. 2366 // Claims are immutable in order to enforce quota, range limits, etc. without gaming the system. 2367 if len(oldPvc.Spec.VolumeName) == 0 { 2368 // volumeName changes are allowed once. 2369 oldPvcClone.Spec.VolumeName = newPvcClone.Spec.VolumeName // +k8s:verify-mutation:reason=clone 2370 } 2371 2372 if validateStorageClassUpgradeFromAnnotation(oldPvcClone.Annotations, newPvcClone.Annotations, 2373 oldPvcClone.Spec.StorageClassName, newPvcClone.Spec.StorageClassName) { 2374 newPvcClone.Spec.StorageClassName = nil 2375 metav1.SetMetaDataAnnotation(&newPvcClone.ObjectMeta, core.BetaStorageClassAnnotation, oldPvcClone.Annotations[core.BetaStorageClassAnnotation]) 2376 } else { 2377 // storageclass annotation should be immutable after creation 2378 // TODO: remove Beta when no longer needed 2379 allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...) 2380 2381 // If update from annotation to attribute failed we can attempt try to validate update from nil value. 2382 if validateStorageClassUpgradeFromNil(oldPvc.Annotations, oldPvc.Spec.StorageClassName, newPvc.Spec.StorageClassName, opts) { 2383 newPvcClone.Spec.StorageClassName = oldPvcClone.Spec.StorageClassName // +k8s:verify-mutation:reason=clone 2384 } 2385 // TODO: add a specific error with a hint that storage class name can not be changed 2386 // (instead of letting spec comparison below return generic field forbidden error) 2387 } 2388 2389 // lets make sure storage values are same. 2390 if newPvc.Status.Phase == core.ClaimBound && newPvcClone.Spec.Resources.Requests != nil { 2391 newPvcClone.Spec.Resources.Requests["storage"] = oldPvc.Spec.Resources.Requests["storage"] // +k8s:verify-mutation:reason=clone 2392 } 2393 // lets make sure volume attributes class name is same. 2394 newPvcClone.Spec.VolumeAttributesClassName = oldPvcClone.Spec.VolumeAttributesClassName // +k8s:verify-mutation:reason=clone 2395 2396 oldSize := oldPvc.Spec.Resources.Requests["storage"] 2397 newSize := newPvc.Spec.Resources.Requests["storage"] 2398 statusSize := oldPvc.Status.Capacity["storage"] 2399 2400 if !apiequality.Semantic.DeepEqual(newPvcClone.Spec, oldPvcClone.Spec) { 2401 specDiff := cmp.Diff(oldPvcClone.Spec, newPvcClone.Spec) 2402 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), fmt.Sprintf("spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims\n%v", specDiff))) 2403 } 2404 if newSize.Cmp(oldSize) < 0 { 2405 if !opts.EnableRecoverFromExpansionFailure { 2406 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than previous value")) 2407 } else { 2408 // This validation permits reducing pvc requested size up to capacity recorded in pvc.status 2409 // so that users can recover from volume expansion failure, but Kubernetes does not actually 2410 // support volume shrinking 2411 if newSize.Cmp(statusSize) <= 0 { 2412 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "resources", "requests", "storage"), "field can not be less than status.capacity")) 2413 } 2414 } 2415 } 2416 2417 allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...) 2418 2419 if !apiequality.Semantic.DeepEqual(oldPvc.Spec.VolumeAttributesClassName, newPvc.Spec.VolumeAttributesClassName) { 2420 if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeAttributesClass) { 2421 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update is forbidden when the VolumeAttributesClass feature gate is disabled")) 2422 } 2423 if opts.EnableVolumeAttributesClass { 2424 if oldPvc.Spec.VolumeAttributesClassName != nil { 2425 if newPvc.Spec.VolumeAttributesClassName == nil { 2426 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to nil is forbidden")) 2427 } else if len(*newPvc.Spec.VolumeAttributesClassName) == 0 { 2428 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "volumeAttributesClassName"), "update from non-nil value to an empty string is forbidden")) 2429 } 2430 } 2431 } 2432 } 2433 2434 return allErrs 2435 } 2436 2437 // Provide an upgrade path from PVC with storage class specified in beta 2438 // annotation to storage class specified in attribute. We allow update of 2439 // StorageClassName only if following four conditions are met at the same time: 2440 // 1. The old pvc's StorageClassAnnotation is set 2441 // 2. The old pvc's StorageClassName is not set 2442 // 3. The new pvc's StorageClassName is set and equal to the old value in annotation 2443 // 4. If the new pvc's StorageClassAnnotation is set,it must be equal to the old pv/pvc's StorageClassAnnotation 2444 func validateStorageClassUpgradeFromAnnotation(oldAnnotations, newAnnotations map[string]string, oldScName, newScName *string) bool { 2445 oldSc, oldAnnotationExist := oldAnnotations[core.BetaStorageClassAnnotation] 2446 newScInAnnotation, newAnnotationExist := newAnnotations[core.BetaStorageClassAnnotation] 2447 return oldAnnotationExist /* condition 1 */ && 2448 oldScName == nil /* condition 2*/ && 2449 (newScName != nil && *newScName == oldSc) /* condition 3 */ && 2450 (!newAnnotationExist || newScInAnnotation == oldSc) /* condition 4 */ 2451 } 2452 2453 // Provide an upgrade path from PVC with nil storage class. We allow update of 2454 // StorageClassName only if following four conditions are met at the same time: 2455 // 1. The new pvc's StorageClassName is not nil 2456 // 2. The old pvc's StorageClassName is nil 2457 // 3. The old pvc either does not have beta annotation set, or the beta annotation matches new pvc's StorageClassName 2458 func validateStorageClassUpgradeFromNil(oldAnnotations map[string]string, oldScName, newScName *string, opts PersistentVolumeClaimSpecValidationOptions) bool { 2459 oldAnnotation, oldAnnotationExist := oldAnnotations[core.BetaStorageClassAnnotation] 2460 return newScName != nil /* condition 1 */ && 2461 oldScName == nil /* condition 2 */ && 2462 (!oldAnnotationExist || *newScName == oldAnnotation) /* condition 3 */ 2463 } 2464 2465 func validatePersistentVolumeClaimResourceKey(value string, fldPath *field.Path) field.ErrorList { 2466 allErrs := field.ErrorList{} 2467 for _, msg := range validation.IsQualifiedName(value) { 2468 allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) 2469 } 2470 if len(allErrs) != 0 { 2471 return allErrs 2472 } 2473 // For native resource names such as - either unprefixed names or with kubernetes.io prefix, 2474 // only allowed value is storage 2475 if helper.IsNativeResource(core.ResourceName(value)) { 2476 if core.ResourceName(value) != core.ResourceStorage { 2477 return append(allErrs, field.NotSupported(fldPath, value, []core.ResourceName{core.ResourceStorage})) 2478 } 2479 } 2480 return allErrs 2481 } 2482 2483 var resizeStatusSet = sets.New(core.PersistentVolumeClaimControllerResizeInProgress, 2484 core.PersistentVolumeClaimControllerResizeFailed, 2485 core.PersistentVolumeClaimNodeResizePending, 2486 core.PersistentVolumeClaimNodeResizeInProgress, 2487 core.PersistentVolumeClaimNodeResizeFailed) 2488 2489 // ValidatePersistentVolumeClaimStatusUpdate validates an update to status of a PersistentVolumeClaim 2490 func ValidatePersistentVolumeClaimStatusUpdate(newPvc, oldPvc *core.PersistentVolumeClaim, validationOpts PersistentVolumeClaimSpecValidationOptions) field.ErrorList { 2491 allErrs := ValidateObjectMetaUpdate(&newPvc.ObjectMeta, &oldPvc.ObjectMeta, field.NewPath("metadata")) 2492 if len(newPvc.ResourceVersion) == 0 { 2493 allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) 2494 } 2495 if len(newPvc.Spec.AccessModes) == 0 { 2496 allErrs = append(allErrs, field.Required(field.NewPath("Spec", "accessModes"), "")) 2497 } 2498 2499 capPath := field.NewPath("status", "capacity") 2500 for r, qty := range newPvc.Status.Capacity { 2501 allErrs = append(allErrs, validateBasicResource(qty, capPath.Key(string(r)))...) 2502 } 2503 if validationOpts.EnableRecoverFromExpansionFailure { 2504 resizeStatusPath := field.NewPath("status", "allocatedResourceStatus") 2505 if newPvc.Status.AllocatedResourceStatuses != nil { 2506 resizeStatus := newPvc.Status.AllocatedResourceStatuses 2507 for k, v := range resizeStatus { 2508 if errs := validatePersistentVolumeClaimResourceKey(k.String(), resizeStatusPath); len(errs) > 0 { 2509 allErrs = append(allErrs, errs...) 2510 } 2511 if !resizeStatusSet.Has(v) { 2512 allErrs = append(allErrs, field.NotSupported(resizeStatusPath, k, sets.List(resizeStatusSet))) 2513 continue 2514 } 2515 } 2516 } 2517 allocPath := field.NewPath("status", "allocatedResources") 2518 for r, qty := range newPvc.Status.AllocatedResources { 2519 if errs := validatePersistentVolumeClaimResourceKey(r.String(), allocPath); len(errs) > 0 { 2520 allErrs = append(allErrs, errs...) 2521 continue 2522 } 2523 2524 if errs := validateBasicResource(qty, allocPath.Key(string(r))); len(errs) > 0 { 2525 allErrs = append(allErrs, errs...) 2526 } else { 2527 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceStorage, qty, allocPath.Key(string(r)))...) 2528 } 2529 } 2530 } 2531 return allErrs 2532 } 2533 2534 var supportedPortProtocols = sets.New( 2535 core.ProtocolTCP, 2536 core.ProtocolUDP, 2537 core.ProtocolSCTP) 2538 2539 func validateContainerPorts(ports []core.ContainerPort, fldPath *field.Path) field.ErrorList { 2540 allErrs := field.ErrorList{} 2541 2542 allNames := sets.Set[string]{} 2543 for i, port := range ports { 2544 idxPath := fldPath.Index(i) 2545 if len(port.Name) > 0 { 2546 if msgs := validation.IsValidPortName(port.Name); len(msgs) != 0 { 2547 for i = range msgs { 2548 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), port.Name, msgs[i])) 2549 } 2550 } else if allNames.Has(port.Name) { 2551 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), port.Name)) 2552 } else { 2553 allNames.Insert(port.Name) 2554 } 2555 } 2556 if port.ContainerPort == 0 { 2557 allErrs = append(allErrs, field.Required(idxPath.Child("containerPort"), "")) 2558 } else { 2559 for _, msg := range validation.IsValidPortNum(int(port.ContainerPort)) { 2560 allErrs = append(allErrs, field.Invalid(idxPath.Child("containerPort"), port.ContainerPort, msg)) 2561 } 2562 } 2563 if port.HostPort != 0 { 2564 for _, msg := range validation.IsValidPortNum(int(port.HostPort)) { 2565 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostPort"), port.HostPort, msg)) 2566 } 2567 } 2568 if len(port.Protocol) == 0 { 2569 allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), "")) 2570 } else if !supportedPortProtocols.Has(port.Protocol) { 2571 allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), port.Protocol, sets.List(supportedPortProtocols))) 2572 } 2573 } 2574 return allErrs 2575 } 2576 2577 // ValidateEnv validates env vars 2578 func ValidateEnv(vars []core.EnvVar, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 2579 allErrs := field.ErrorList{} 2580 2581 for i, ev := range vars { 2582 idxPath := fldPath.Index(i) 2583 if len(ev.Name) == 0 { 2584 allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) 2585 } else { 2586 if opts.AllowRelaxedEnvironmentVariableValidation { 2587 for _, msg := range validation.IsRelaxedEnvVarName(ev.Name) { 2588 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg)) 2589 } 2590 } else { 2591 for _, msg := range validation.IsEnvVarName(ev.Name) { 2592 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), ev.Name, msg)) 2593 } 2594 } 2595 } 2596 allErrs = append(allErrs, validateEnvVarValueFrom(ev, idxPath.Child("valueFrom"), opts)...) 2597 } 2598 return allErrs 2599 } 2600 2601 var validEnvDownwardAPIFieldPathExpressions = sets.New( 2602 "metadata.name", 2603 "metadata.namespace", 2604 "metadata.uid", 2605 "spec.nodeName", 2606 "spec.serviceAccountName", 2607 "status.hostIP", 2608 "status.hostIPs", 2609 "status.podIP", 2610 "status.podIPs", 2611 ) 2612 2613 var validContainerResourceFieldPathExpressions = sets.New( 2614 "limits.cpu", 2615 "limits.memory", 2616 "limits.ephemeral-storage", 2617 "requests.cpu", 2618 "requests.memory", 2619 "requests.ephemeral-storage", 2620 ) 2621 2622 var validContainerResourceFieldPathPrefixesWithDownwardAPIHugePages = sets.New(hugepagesRequestsPrefixDownwardAPI, hugepagesLimitsPrefixDownwardAPI) 2623 2624 const hugepagesRequestsPrefixDownwardAPI string = `requests.hugepages-` 2625 const hugepagesLimitsPrefixDownwardAPI string = `limits.hugepages-` 2626 2627 func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 2628 allErrs := field.ErrorList{} 2629 2630 if ev.ValueFrom == nil { 2631 return allErrs 2632 } 2633 2634 numSources := 0 2635 2636 if ev.ValueFrom.FieldRef != nil { 2637 numSources++ 2638 allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validEnvDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...) 2639 allErrs = append(allErrs, validateDownwardAPIHostIPs(ev.ValueFrom.FieldRef, fldPath.Child("fieldRef"), opts)...) 2640 } 2641 if ev.ValueFrom.ResourceFieldRef != nil { 2642 numSources++ 2643 localValidContainerResourceFieldPathPrefixes := validContainerResourceFieldPathPrefixesWithDownwardAPIHugePages 2644 allErrs = append(allErrs, validateContainerResourceFieldSelector(ev.ValueFrom.ResourceFieldRef, &validContainerResourceFieldPathExpressions, &localValidContainerResourceFieldPathPrefixes, fldPath.Child("resourceFieldRef"), false)...) 2645 } 2646 if ev.ValueFrom.ConfigMapKeyRef != nil { 2647 numSources++ 2648 allErrs = append(allErrs, validateConfigMapKeySelector(ev.ValueFrom.ConfigMapKeyRef, fldPath.Child("configMapKeyRef"))...) 2649 } 2650 if ev.ValueFrom.SecretKeyRef != nil { 2651 numSources++ 2652 allErrs = append(allErrs, validateSecretKeySelector(ev.ValueFrom.SecretKeyRef, fldPath.Child("secretKeyRef"))...) 2653 } 2654 2655 if numSources == 0 { 2656 allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `fieldRef`, `resourceFieldRef`, `configMapKeyRef` or `secretKeyRef`")) 2657 } else if len(ev.Value) != 0 { 2658 if numSources != 0 { 2659 allErrs = append(allErrs, field.Invalid(fldPath, "", "may not be specified when `value` is not empty")) 2660 } 2661 } else if numSources > 1 { 2662 allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) 2663 } 2664 2665 return allErrs 2666 } 2667 2668 func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets.Set[string], fldPath *field.Path) field.ErrorList { 2669 allErrs := field.ErrorList{} 2670 2671 if len(fs.APIVersion) == 0 { 2672 allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), "")) 2673 return allErrs 2674 } 2675 if len(fs.FieldPath) == 0 { 2676 allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), "")) 2677 return allErrs 2678 } 2679 2680 internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "") 2681 if err != nil { 2682 allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err))) 2683 return allErrs 2684 } 2685 2686 if path, subscript, ok := fieldpath.SplitMaybeSubscriptedPath(internalFieldPath); ok { 2687 switch path { 2688 case "metadata.annotations": 2689 allErrs = append(allErrs, ValidateQualifiedName(strings.ToLower(subscript), fldPath)...) 2690 case "metadata.labels": 2691 allErrs = append(allErrs, ValidateQualifiedName(subscript, fldPath)...) 2692 default: 2693 allErrs = append(allErrs, field.Invalid(fldPath, path, "does not support subscript")) 2694 } 2695 } else if !expressions.Has(path) { 2696 allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), path, sets.List(*expressions))) 2697 return allErrs 2698 } 2699 2700 return allErrs 2701 } 2702 2703 func validateDownwardAPIHostIPs(fieldSel *core.ObjectFieldSelector, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 2704 allErrs := field.ErrorList{} 2705 if !opts.AllowHostIPsField { 2706 if fieldSel.FieldPath == "status.hostIPs" { 2707 allErrs = append(allErrs, field.Forbidden(fldPath, "may not be set when feature gate 'PodHostIPs' is not enabled")) 2708 } 2709 } 2710 return allErrs 2711 } 2712 2713 func validateContainerResourceFieldSelector(fs *core.ResourceFieldSelector, expressions *sets.Set[string], prefixes *sets.Set[string], fldPath *field.Path, volume bool) field.ErrorList { 2714 allErrs := field.ErrorList{} 2715 2716 if volume && len(fs.ContainerName) == 0 { 2717 allErrs = append(allErrs, field.Required(fldPath.Child("containerName"), "")) 2718 } else if len(fs.Resource) == 0 { 2719 allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "")) 2720 } else if !expressions.Has(fs.Resource) { 2721 // check if the prefix is present 2722 foundPrefix := false 2723 if prefixes != nil { 2724 for _, prefix := range sets.List(*prefixes) { 2725 if strings.HasPrefix(fs.Resource, prefix) { 2726 foundPrefix = true 2727 } 2728 } 2729 } 2730 if !foundPrefix { 2731 allErrs = append(allErrs, field.NotSupported(fldPath.Child("resource"), fs.Resource, sets.List(*expressions))) 2732 } 2733 } 2734 allErrs = append(allErrs, validateContainerResourceDivisor(fs.Resource, fs.Divisor, fldPath)...) 2735 return allErrs 2736 } 2737 2738 func ValidateEnvFrom(vars []core.EnvFromSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 2739 allErrs := field.ErrorList{} 2740 for i, ev := range vars { 2741 idxPath := fldPath.Index(i) 2742 if len(ev.Prefix) > 0 { 2743 if opts.AllowRelaxedEnvironmentVariableValidation { 2744 for _, msg := range validation.IsRelaxedEnvVarName(ev.Prefix) { 2745 allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg)) 2746 } 2747 } else { 2748 for _, msg := range validation.IsEnvVarName(ev.Prefix) { 2749 allErrs = append(allErrs, field.Invalid(idxPath.Child("prefix"), ev.Prefix, msg)) 2750 } 2751 } 2752 } 2753 2754 numSources := 0 2755 if ev.ConfigMapRef != nil { 2756 numSources++ 2757 allErrs = append(allErrs, validateConfigMapEnvSource(ev.ConfigMapRef, idxPath.Child("configMapRef"))...) 2758 } 2759 if ev.SecretRef != nil { 2760 numSources++ 2761 allErrs = append(allErrs, validateSecretEnvSource(ev.SecretRef, idxPath.Child("secretRef"))...) 2762 } 2763 2764 if numSources == 0 { 2765 allErrs = append(allErrs, field.Invalid(fldPath, "", "must specify one of: `configMapRef` or `secretRef`")) 2766 } else if numSources > 1 { 2767 allErrs = append(allErrs, field.Invalid(fldPath, "", "may not have more than one field specified at a time")) 2768 } 2769 } 2770 return allErrs 2771 } 2772 2773 func validateConfigMapEnvSource(configMapSource *core.ConfigMapEnvSource, fldPath *field.Path) field.ErrorList { 2774 allErrs := field.ErrorList{} 2775 if len(configMapSource.Name) == 0 { 2776 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 2777 } else { 2778 for _, msg := range ValidateConfigMapName(configMapSource.Name, true) { 2779 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), configMapSource.Name, msg)) 2780 } 2781 } 2782 return allErrs 2783 } 2784 2785 func validateSecretEnvSource(secretSource *core.SecretEnvSource, fldPath *field.Path) field.ErrorList { 2786 allErrs := field.ErrorList{} 2787 if len(secretSource.Name) == 0 { 2788 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 2789 } else { 2790 for _, msg := range ValidateSecretName(secretSource.Name, true) { 2791 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), secretSource.Name, msg)) 2792 } 2793 } 2794 return allErrs 2795 } 2796 2797 var validContainerResourceDivisorForCPU = sets.New("1m", "1") 2798 var validContainerResourceDivisorForMemory = sets.New( 2799 "1", 2800 "1k", "1M", "1G", "1T", "1P", "1E", 2801 "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") 2802 var validContainerResourceDivisorForHugePages = sets.New( 2803 "1", 2804 "1k", "1M", "1G", "1T", "1P", "1E", 2805 "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") 2806 var validContainerResourceDivisorForEphemeralStorage = sets.New( 2807 "1", 2808 "1k", "1M", "1G", "1T", "1P", "1E", 2809 "1Ki", "1Mi", "1Gi", "1Ti", "1Pi", "1Ei") 2810 2811 func validateContainerResourceDivisor(rName string, divisor resource.Quantity, fldPath *field.Path) field.ErrorList { 2812 allErrs := field.ErrorList{} 2813 unsetDivisor := resource.Quantity{} 2814 if unsetDivisor.Cmp(divisor) == 0 { 2815 return allErrs 2816 } 2817 switch rName { 2818 case "limits.cpu", "requests.cpu": 2819 if !validContainerResourceDivisorForCPU.Has(divisor.String()) { 2820 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1m and 1 are supported with the cpu resource")) 2821 } 2822 case "limits.memory", "requests.memory": 2823 if !validContainerResourceDivisorForMemory.Has(divisor.String()) { 2824 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the memory resource")) 2825 } 2826 case "limits.ephemeral-storage", "requests.ephemeral-storage": 2827 if !validContainerResourceDivisorForEphemeralStorage.Has(divisor.String()) { 2828 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the local ephemeral storage resource")) 2829 } 2830 } 2831 if strings.HasPrefix(rName, hugepagesRequestsPrefixDownwardAPI) || strings.HasPrefix(rName, hugepagesLimitsPrefixDownwardAPI) { 2832 if !validContainerResourceDivisorForHugePages.Has(divisor.String()) { 2833 allErrs = append(allErrs, field.Invalid(fldPath.Child("divisor"), rName, "only divisor's values 1, 1k, 1M, 1G, 1T, 1P, 1E, 1Ki, 1Mi, 1Gi, 1Ti, 1Pi, 1Ei are supported with the hugepages resource")) 2834 } 2835 } 2836 return allErrs 2837 } 2838 2839 func validateConfigMapKeySelector(s *core.ConfigMapKeySelector, fldPath *field.Path) field.ErrorList { 2840 allErrs := field.ErrorList{} 2841 2842 nameFn := ValidateNameFunc(ValidateSecretName) 2843 for _, msg := range nameFn(s.Name, false) { 2844 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) 2845 } 2846 if len(s.Key) == 0 { 2847 allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) 2848 } else { 2849 for _, msg := range validation.IsConfigMapKey(s.Key) { 2850 allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) 2851 } 2852 } 2853 2854 return allErrs 2855 } 2856 2857 func validateSecretKeySelector(s *core.SecretKeySelector, fldPath *field.Path) field.ErrorList { 2858 allErrs := field.ErrorList{} 2859 2860 nameFn := ValidateNameFunc(ValidateSecretName) 2861 for _, msg := range nameFn(s.Name, false) { 2862 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), s.Name, msg)) 2863 } 2864 if len(s.Key) == 0 { 2865 allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) 2866 } else { 2867 for _, msg := range validation.IsConfigMapKey(s.Key) { 2868 allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, msg)) 2869 } 2870 } 2871 2872 return allErrs 2873 } 2874 2875 func GetVolumeMountMap(mounts []core.VolumeMount) map[string]string { 2876 volmounts := make(map[string]string) 2877 2878 for _, mnt := range mounts { 2879 volmounts[mnt.Name] = mnt.MountPath 2880 } 2881 2882 return volmounts 2883 } 2884 2885 func GetVolumeDeviceMap(devices []core.VolumeDevice) map[string]string { 2886 volDevices := make(map[string]string) 2887 2888 for _, dev := range devices { 2889 volDevices[dev.Name] = dev.DevicePath 2890 } 2891 2892 return volDevices 2893 } 2894 2895 func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path) field.ErrorList { 2896 allErrs := field.ErrorList{} 2897 mountpoints := sets.New[string]() 2898 2899 for i, mnt := range mounts { 2900 idxPath := fldPath.Index(i) 2901 if len(mnt.Name) == 0 { 2902 allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) 2903 } 2904 if !IsMatchedVolume(mnt.Name, volumes) { 2905 allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), mnt.Name)) 2906 } 2907 if len(mnt.MountPath) == 0 { 2908 allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), "")) 2909 } 2910 if mountpoints.Has(mnt.MountPath) { 2911 allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) 2912 } 2913 mountpoints.Insert(mnt.MountPath) 2914 2915 // check for overlap with VolumeDevice 2916 if mountNameAlreadyExists(mnt.Name, voldevices) { 2917 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), mnt.Name, "must not already exist in volumeDevices")) 2918 } 2919 if mountPathAlreadyExists(mnt.MountPath, voldevices) { 2920 allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not already exist as a path in volumeDevices")) 2921 } 2922 2923 if len(mnt.SubPath) > 0 { 2924 allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...) 2925 } 2926 2927 if len(mnt.SubPathExpr) > 0 { 2928 if len(mnt.SubPath) > 0 { 2929 allErrs = append(allErrs, field.Invalid(idxPath.Child("subPathExpr"), mnt.SubPathExpr, "subPathExpr and subPath are mutually exclusive")) 2930 } 2931 2932 allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPathExpr, fldPath.Child("subPathExpr"))...) 2933 } 2934 2935 if mnt.MountPropagation != nil { 2936 allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...) 2937 } 2938 allErrs = append(allErrs, validateMountRecursiveReadOnly(mnt, fldPath.Child("recursiveReadOnly"))...) 2939 } 2940 return allErrs 2941 } 2942 2943 func ValidateVolumeDevices(devices []core.VolumeDevice, volmounts map[string]string, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList { 2944 allErrs := field.ErrorList{} 2945 devicepath := sets.New[string]() 2946 devicename := sets.New[string]() 2947 2948 for i, dev := range devices { 2949 idxPath := fldPath.Index(i) 2950 devName := dev.Name 2951 devPath := dev.DevicePath 2952 didMatch, isPVC := isMatchedDevice(devName, volumes) 2953 if len(devName) == 0 { 2954 allErrs = append(allErrs, field.Required(idxPath.Child("name"), "")) 2955 } 2956 if devicename.Has(devName) { 2957 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must be unique")) 2958 } 2959 // Must be based on PersistentVolumeClaim (PVC reference or generic ephemeral inline volume) 2960 if didMatch && !isPVC { 2961 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "can only use volume source type of PersistentVolumeClaim or Ephemeral for block mode")) 2962 } 2963 if !didMatch { 2964 allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), devName)) 2965 } 2966 if len(devPath) == 0 { 2967 allErrs = append(allErrs, field.Required(idxPath.Child("devicePath"), "")) 2968 } 2969 if devicepath.Has(devPath) { 2970 allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must be unique")) 2971 } 2972 if len(devPath) > 0 && len(validatePathNoBacksteps(devPath, fldPath.Child("devicePath"))) > 0 { 2973 allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "can not contain backsteps ('..')")) 2974 } else { 2975 devicepath.Insert(devPath) 2976 } 2977 // check for overlap with VolumeMount 2978 if deviceNameAlreadyExists(devName, volmounts) { 2979 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must not already exist in volumeMounts")) 2980 } 2981 if devicePathAlreadyExists(devPath, volmounts) { 2982 allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must not already exist as a path in volumeMounts")) 2983 } 2984 if len(devName) > 0 { 2985 devicename.Insert(devName) 2986 } 2987 } 2988 return allErrs 2989 } 2990 2991 func validatePodResourceClaims(podMeta *metav1.ObjectMeta, claims []core.PodResourceClaim, fldPath *field.Path) field.ErrorList { 2992 var allErrs field.ErrorList 2993 podClaimNames := sets.New[string]() 2994 for i, claim := range claims { 2995 allErrs = append(allErrs, validatePodResourceClaim(podMeta, claim, &podClaimNames, fldPath.Index(i))...) 2996 } 2997 return allErrs 2998 } 2999 3000 // gatherPodResourceClaimNames returns a set of all non-empty 3001 // PodResourceClaim.Name values. Validation that those names are valid is 3002 // handled by validatePodResourceClaims. 3003 func gatherPodResourceClaimNames(claims []core.PodResourceClaim) sets.Set[string] { 3004 podClaimNames := sets.Set[string]{} 3005 for _, claim := range claims { 3006 if claim.Name != "" { 3007 podClaimNames.Insert(claim.Name) 3008 } 3009 } 3010 return podClaimNames 3011 } 3012 3013 func validatePodResourceClaim(podMeta *metav1.ObjectMeta, claim core.PodResourceClaim, podClaimNames *sets.Set[string], fldPath *field.Path) field.ErrorList { 3014 var allErrs field.ErrorList 3015 if claim.Name == "" { 3016 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 3017 } else if podClaimNames.Has(claim.Name) { 3018 allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), claim.Name)) 3019 } else { 3020 nameErrs := ValidateDNS1123Label(claim.Name, fldPath.Child("name")) 3021 if len(nameErrs) > 0 { 3022 allErrs = append(allErrs, nameErrs...) 3023 } else if podMeta != nil && claim.Source.ResourceClaimTemplateName != nil { 3024 claimName := podMeta.Name + "-" + claim.Name 3025 for _, detail := range ValidateResourceClaimName(claimName, false) { 3026 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), claimName, "final ResourceClaim name: "+detail)) 3027 } 3028 } 3029 podClaimNames.Insert(claim.Name) 3030 } 3031 allErrs = append(allErrs, validatePodResourceClaimSource(claim.Source, fldPath.Child("source"))...) 3032 3033 return allErrs 3034 } 3035 3036 func validatePodResourceClaimSource(claimSource core.ClaimSource, fldPath *field.Path) field.ErrorList { 3037 var allErrs field.ErrorList 3038 if claimSource.ResourceClaimName != nil && claimSource.ResourceClaimTemplateName != nil { 3039 allErrs = append(allErrs, field.Invalid(fldPath, claimSource, "at most one of `resourceClaimName` or `resourceClaimTemplateName` may be specified")) 3040 } 3041 if claimSource.ResourceClaimName == nil && claimSource.ResourceClaimTemplateName == nil { 3042 allErrs = append(allErrs, field.Invalid(fldPath, claimSource, "must specify one of: `resourceClaimName`, `resourceClaimTemplateName`")) 3043 } 3044 if claimSource.ResourceClaimName != nil { 3045 for _, detail := range ValidateResourceClaimName(*claimSource.ResourceClaimName, false) { 3046 allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClaimName"), *claimSource.ResourceClaimName, detail)) 3047 } 3048 } 3049 if claimSource.ResourceClaimTemplateName != nil { 3050 for _, detail := range ValidateResourceClaimTemplateName(*claimSource.ResourceClaimTemplateName, false) { 3051 allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClaimTemplateName"), *claimSource.ResourceClaimTemplateName, detail)) 3052 } 3053 } 3054 return allErrs 3055 } 3056 3057 func validateLivenessProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3058 allErrs := field.ErrorList{} 3059 3060 if probe == nil { 3061 return allErrs 3062 } 3063 allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...) 3064 if probe.SuccessThreshold != 1 { 3065 allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1")) 3066 } 3067 return allErrs 3068 } 3069 3070 func validateReadinessProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3071 allErrs := field.ErrorList{} 3072 3073 if probe == nil { 3074 return allErrs 3075 } 3076 allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...) 3077 if probe.TerminationGracePeriodSeconds != nil { 3078 allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), probe.TerminationGracePeriodSeconds, "must not be set for readinessProbes")) 3079 } 3080 return allErrs 3081 } 3082 3083 func validateStartupProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3084 allErrs := field.ErrorList{} 3085 3086 if probe == nil { 3087 return allErrs 3088 } 3089 allErrs = append(allErrs, validateProbe(probe, gracePeriod, fldPath)...) 3090 if probe.SuccessThreshold != 1 { 3091 allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1")) 3092 } 3093 return allErrs 3094 } 3095 3096 func validateProbe(probe *core.Probe, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3097 allErrs := field.ErrorList{} 3098 3099 if probe == nil { 3100 return allErrs 3101 } 3102 allErrs = append(allErrs, validateHandler(handlerFromProbe(&probe.ProbeHandler), gracePeriod, fldPath)...) 3103 3104 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.InitialDelaySeconds), fldPath.Child("initialDelaySeconds"))...) 3105 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.TimeoutSeconds), fldPath.Child("timeoutSeconds"))...) 3106 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.PeriodSeconds), fldPath.Child("periodSeconds"))...) 3107 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.SuccessThreshold), fldPath.Child("successThreshold"))...) 3108 allErrs = append(allErrs, ValidateNonnegativeField(int64(probe.FailureThreshold), fldPath.Child("failureThreshold"))...) 3109 if probe.TerminationGracePeriodSeconds != nil && *probe.TerminationGracePeriodSeconds <= 0 { 3110 allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), *probe.TerminationGracePeriodSeconds, "must be greater than 0")) 3111 } 3112 return allErrs 3113 } 3114 3115 func validateInitContainerRestartPolicy(restartPolicy *core.ContainerRestartPolicy, fldPath *field.Path) field.ErrorList { 3116 var allErrors field.ErrorList 3117 3118 if restartPolicy == nil { 3119 return allErrors 3120 } 3121 switch *restartPolicy { 3122 case core.ContainerRestartPolicyAlways: 3123 break 3124 default: 3125 validValues := []core.ContainerRestartPolicy{core.ContainerRestartPolicyAlways} 3126 allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues)) 3127 } 3128 3129 return allErrors 3130 } 3131 3132 type commonHandler struct { 3133 Exec *core.ExecAction 3134 HTTPGet *core.HTTPGetAction 3135 TCPSocket *core.TCPSocketAction 3136 GRPC *core.GRPCAction 3137 Sleep *core.SleepAction 3138 } 3139 3140 func handlerFromProbe(ph *core.ProbeHandler) commonHandler { 3141 return commonHandler{ 3142 Exec: ph.Exec, 3143 HTTPGet: ph.HTTPGet, 3144 TCPSocket: ph.TCPSocket, 3145 GRPC: ph.GRPC, 3146 } 3147 } 3148 3149 func handlerFromLifecycle(lh *core.LifecycleHandler) commonHandler { 3150 return commonHandler{ 3151 Exec: lh.Exec, 3152 HTTPGet: lh.HTTPGet, 3153 TCPSocket: lh.TCPSocket, 3154 Sleep: lh.Sleep, 3155 } 3156 } 3157 3158 func validateSleepAction(sleep *core.SleepAction, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3159 allErrors := field.ErrorList{} 3160 if sleep.Seconds <= 0 || sleep.Seconds > gracePeriod { 3161 invalidStr := fmt.Sprintf("must be greater than 0 and less than terminationGracePeriodSeconds (%d)", gracePeriod) 3162 allErrors = append(allErrors, field.Invalid(fldPath, sleep.Seconds, invalidStr)) 3163 } 3164 return allErrors 3165 } 3166 3167 func validateClientIPAffinityConfig(config *core.SessionAffinityConfig, fldPath *field.Path) field.ErrorList { 3168 allErrs := field.ErrorList{} 3169 if config == nil { 3170 allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP))) 3171 return allErrs 3172 } 3173 if config.ClientIP == nil { 3174 allErrs = append(allErrs, field.Required(fldPath.Child("clientIP"), fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP))) 3175 return allErrs 3176 } 3177 if config.ClientIP.TimeoutSeconds == nil { 3178 allErrs = append(allErrs, field.Required(fldPath.Child("clientIP").Child("timeoutSeconds"), fmt.Sprintf("when session affinity type is %s", core.ServiceAffinityClientIP))) 3179 return allErrs 3180 } 3181 allErrs = append(allErrs, validateAffinityTimeout(config.ClientIP.TimeoutSeconds, fldPath.Child("clientIP").Child("timeoutSeconds"))...) 3182 3183 return allErrs 3184 } 3185 3186 func validateAffinityTimeout(timeout *int32, fldPath *field.Path) field.ErrorList { 3187 allErrs := field.ErrorList{} 3188 if *timeout <= 0 || *timeout > core.MaxClientIPServiceAffinitySeconds { 3189 allErrs = append(allErrs, field.Invalid(fldPath, timeout, fmt.Sprintf("must be greater than 0 and less than %d", core.MaxClientIPServiceAffinitySeconds))) 3190 } 3191 return allErrs 3192 } 3193 3194 // AccumulateUniqueHostPorts extracts each HostPort of each Container, 3195 // accumulating the results and returning an error if any ports conflict. 3196 func AccumulateUniqueHostPorts(containers []core.Container, accumulator *sets.Set[string], fldPath *field.Path) field.ErrorList { 3197 allErrs := field.ErrorList{} 3198 3199 for ci, ctr := range containers { 3200 idxPath := fldPath.Index(ci) 3201 portsPath := idxPath.Child("ports") 3202 for pi := range ctr.Ports { 3203 idxPath := portsPath.Index(pi) 3204 port := ctr.Ports[pi].HostPort 3205 if port == 0 { 3206 continue 3207 } 3208 str := fmt.Sprintf("%s/%s/%d", ctr.Ports[pi].Protocol, ctr.Ports[pi].HostIP, port) 3209 if accumulator.Has(str) { 3210 allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str)) 3211 } else { 3212 accumulator.Insert(str) 3213 } 3214 } 3215 } 3216 return allErrs 3217 } 3218 3219 // checkHostPortConflicts checks for colliding Port.HostPort values across 3220 // a slice of containers. 3221 func checkHostPortConflicts(containers []core.Container, fldPath *field.Path) field.ErrorList { 3222 allPorts := sets.Set[string]{} 3223 return AccumulateUniqueHostPorts(containers, &allPorts, fldPath) 3224 } 3225 3226 func validateExecAction(exec *core.ExecAction, fldPath *field.Path) field.ErrorList { 3227 allErrors := field.ErrorList{} 3228 if len(exec.Command) == 0 { 3229 allErrors = append(allErrors, field.Required(fldPath.Child("command"), "")) 3230 } 3231 return allErrors 3232 } 3233 3234 var supportedHTTPSchemes = sets.New(core.URISchemeHTTP, core.URISchemeHTTPS) 3235 3236 func validateHTTPGetAction(http *core.HTTPGetAction, fldPath *field.Path) field.ErrorList { 3237 allErrors := field.ErrorList{} 3238 if len(http.Path) == 0 { 3239 allErrors = append(allErrors, field.Required(fldPath.Child("path"), "")) 3240 } 3241 allErrors = append(allErrors, ValidatePortNumOrName(http.Port, fldPath.Child("port"))...) 3242 if !supportedHTTPSchemes.Has(http.Scheme) { 3243 allErrors = append(allErrors, field.NotSupported(fldPath.Child("scheme"), http.Scheme, sets.List(supportedHTTPSchemes))) 3244 } 3245 for _, header := range http.HTTPHeaders { 3246 for _, msg := range validation.IsHTTPHeaderName(header.Name) { 3247 allErrors = append(allErrors, field.Invalid(fldPath.Child("httpHeaders"), header.Name, msg)) 3248 } 3249 } 3250 return allErrors 3251 } 3252 3253 func ValidatePortNumOrName(port intstr.IntOrString, fldPath *field.Path) field.ErrorList { 3254 allErrs := field.ErrorList{} 3255 if port.Type == intstr.Int { 3256 for _, msg := range validation.IsValidPortNum(port.IntValue()) { 3257 allErrs = append(allErrs, field.Invalid(fldPath, port.IntValue(), msg)) 3258 } 3259 } else if port.Type == intstr.String { 3260 for _, msg := range validation.IsValidPortName(port.StrVal) { 3261 allErrs = append(allErrs, field.Invalid(fldPath, port.StrVal, msg)) 3262 } 3263 } else { 3264 allErrs = append(allErrs, field.InternalError(fldPath, fmt.Errorf("unknown type: %v", port.Type))) 3265 } 3266 return allErrs 3267 } 3268 3269 func validateTCPSocketAction(tcp *core.TCPSocketAction, fldPath *field.Path) field.ErrorList { 3270 return ValidatePortNumOrName(tcp.Port, fldPath.Child("port")) 3271 } 3272 func validateGRPCAction(grpc *core.GRPCAction, fldPath *field.Path) field.ErrorList { 3273 return ValidatePortNumOrName(intstr.FromInt32(grpc.Port), fldPath.Child("port")) 3274 } 3275 func validateHandler(handler commonHandler, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3276 numHandlers := 0 3277 allErrors := field.ErrorList{} 3278 if handler.Exec != nil { 3279 if numHandlers > 0 { 3280 allErrors = append(allErrors, field.Forbidden(fldPath.Child("exec"), "may not specify more than 1 handler type")) 3281 } else { 3282 numHandlers++ 3283 allErrors = append(allErrors, validateExecAction(handler.Exec, fldPath.Child("exec"))...) 3284 } 3285 } 3286 if handler.HTTPGet != nil { 3287 if numHandlers > 0 { 3288 allErrors = append(allErrors, field.Forbidden(fldPath.Child("httpGet"), "may not specify more than 1 handler type")) 3289 } else { 3290 numHandlers++ 3291 allErrors = append(allErrors, validateHTTPGetAction(handler.HTTPGet, fldPath.Child("httpGet"))...) 3292 } 3293 } 3294 if handler.TCPSocket != nil { 3295 if numHandlers > 0 { 3296 allErrors = append(allErrors, field.Forbidden(fldPath.Child("tcpSocket"), "may not specify more than 1 handler type")) 3297 } else { 3298 numHandlers++ 3299 allErrors = append(allErrors, validateTCPSocketAction(handler.TCPSocket, fldPath.Child("tcpSocket"))...) 3300 } 3301 } 3302 if handler.GRPC != nil { 3303 if numHandlers > 0 { 3304 allErrors = append(allErrors, field.Forbidden(fldPath.Child("grpc"), "may not specify more than 1 handler type")) 3305 } else { 3306 numHandlers++ 3307 allErrors = append(allErrors, validateGRPCAction(handler.GRPC, fldPath.Child("grpc"))...) 3308 } 3309 } 3310 if handler.Sleep != nil { 3311 if numHandlers > 0 { 3312 allErrors = append(allErrors, field.Forbidden(fldPath.Child("sleep"), "may not specify more than 1 handler type")) 3313 } else { 3314 numHandlers++ 3315 allErrors = append(allErrors, validateSleepAction(handler.Sleep, gracePeriod, fldPath.Child("sleep"))...) 3316 } 3317 } 3318 if numHandlers == 0 { 3319 allErrors = append(allErrors, field.Required(fldPath, "must specify a handler type")) 3320 } 3321 return allErrors 3322 } 3323 3324 func validateLifecycle(lifecycle *core.Lifecycle, gracePeriod int64, fldPath *field.Path) field.ErrorList { 3325 allErrs := field.ErrorList{} 3326 if lifecycle.PostStart != nil { 3327 allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PostStart), gracePeriod, fldPath.Child("postStart"))...) 3328 } 3329 if lifecycle.PreStop != nil { 3330 allErrs = append(allErrs, validateHandler(handlerFromLifecycle(lifecycle.PreStop), gracePeriod, fldPath.Child("preStop"))...) 3331 } 3332 return allErrs 3333 } 3334 3335 var supportedPullPolicies = sets.New( 3336 core.PullAlways, 3337 core.PullIfNotPresent, 3338 core.PullNever) 3339 3340 func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.ErrorList { 3341 allErrors := field.ErrorList{} 3342 3343 switch policy { 3344 case core.PullAlways, core.PullIfNotPresent, core.PullNever: 3345 break 3346 case "": 3347 allErrors = append(allErrors, field.Required(fldPath, "")) 3348 default: 3349 allErrors = append(allErrors, field.NotSupported(fldPath, policy, sets.List(supportedPullPolicies))) 3350 } 3351 3352 return allErrors 3353 } 3354 3355 var supportedResizeResources = sets.New(core.ResourceCPU, core.ResourceMemory) 3356 var supportedResizePolicies = sets.New(core.NotRequired, core.RestartContainer) 3357 3358 func validateResizePolicy(policyList []core.ContainerResizePolicy, fldPath *field.Path, podRestartPolicy *core.RestartPolicy) field.ErrorList { 3359 allErrors := field.ErrorList{} 3360 3361 // validate that resource name is not repeated, supported resource names and policy values are specified 3362 resources := make(map[core.ResourceName]bool) 3363 for i, p := range policyList { 3364 if _, found := resources[p.ResourceName]; found { 3365 allErrors = append(allErrors, field.Duplicate(fldPath.Index(i), p.ResourceName)) 3366 } 3367 resources[p.ResourceName] = true 3368 switch p.ResourceName { 3369 case core.ResourceCPU, core.ResourceMemory: 3370 case "": 3371 allErrors = append(allErrors, field.Required(fldPath, "")) 3372 default: 3373 allErrors = append(allErrors, field.NotSupported(fldPath, p.ResourceName, sets.List(supportedResizeResources))) 3374 } 3375 switch p.RestartPolicy { 3376 case core.NotRequired, core.RestartContainer: 3377 case "": 3378 allErrors = append(allErrors, field.Required(fldPath, "")) 3379 default: 3380 allErrors = append(allErrors, field.NotSupported(fldPath, p.RestartPolicy, sets.List(supportedResizePolicies))) 3381 } 3382 3383 if *podRestartPolicy == core.RestartPolicyNever && p.RestartPolicy != core.NotRequired { 3384 allErrors = append(allErrors, field.Invalid(fldPath, p.RestartPolicy, "must be 'NotRequired' when `restartPolicy` is 'Never'")) 3385 } 3386 } 3387 return allErrors 3388 } 3389 3390 // validateEphemeralContainers is called by pod spec and template validation to validate the list of ephemeral containers. 3391 // Note that this is called for pod template even though ephemeral containers aren't allowed in pod templates. 3392 func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer, containers, initContainers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], fldPath *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList { 3393 var allErrs field.ErrorList 3394 3395 if len(ephemeralContainers) == 0 { 3396 return allErrs 3397 } 3398 3399 otherNames, allNames := sets.Set[string]{}, sets.Set[string]{} 3400 for _, c := range containers { 3401 otherNames.Insert(c.Name) 3402 allNames.Insert(c.Name) 3403 } 3404 for _, c := range initContainers { 3405 otherNames.Insert(c.Name) 3406 allNames.Insert(c.Name) 3407 } 3408 3409 for i, ec := range ephemeralContainers { 3410 idxPath := fldPath.Index(i) 3411 3412 c := (*core.Container)(&ec.EphemeralContainerCommon) 3413 allErrs = append(allErrs, validateContainerCommon(c, volumes, podClaimNames, idxPath, opts, podRestartPolicy, hostUsers)...) 3414 // Ephemeral containers don't need looser constraints for pod templates, so it's convenient to apply both validations 3415 // here where we've already converted EphemeralContainerCommon to Container. 3416 allErrs = append(allErrs, validateContainerOnlyForPod(c, idxPath)...) 3417 3418 // Ephemeral containers must have a name unique across all container types. 3419 if allNames.Has(ec.Name) { 3420 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ec.Name)) 3421 } else { 3422 allNames.Insert(ec.Name) 3423 } 3424 3425 // The target container name must exist and be non-ephemeral. 3426 if ec.TargetContainerName != "" && !otherNames.Has(ec.TargetContainerName) { 3427 allErrs = append(allErrs, field.NotFound(idxPath.Child("targetContainerName"), ec.TargetContainerName)) 3428 } 3429 3430 // Ephemeral containers should not be relied upon for fundamental pod services, so fields such as 3431 // Lifecycle, probes, resources and ports should be disallowed. This is implemented as a list 3432 // of allowed fields so that new fields will be given consideration prior to inclusion in ephemeral containers. 3433 allErrs = append(allErrs, validateFieldAllowList(ec.EphemeralContainerCommon, allowedEphemeralContainerFields, "cannot be set for an Ephemeral Container", idxPath)...) 3434 3435 // VolumeMount subpaths have the potential to leak resources since they're implemented with bind mounts 3436 // that aren't cleaned up until the pod exits. Since they also imply that the container is being used 3437 // as part of the workload, they're disallowed entirely. 3438 for i, vm := range ec.VolumeMounts { 3439 if vm.SubPath != "" { 3440 allErrs = append(allErrs, field.Forbidden(idxPath.Child("volumeMounts").Index(i).Child("subPath"), "cannot be set for an Ephemeral Container")) 3441 } 3442 if vm.SubPathExpr != "" { 3443 allErrs = append(allErrs, field.Forbidden(idxPath.Child("volumeMounts").Index(i).Child("subPathExpr"), "cannot be set for an Ephemeral Container")) 3444 } 3445 } 3446 } 3447 3448 return allErrs 3449 } 3450 3451 // ValidateFieldAcceptList checks that only allowed fields are set. 3452 // The value must be a struct (not a pointer to a struct!). 3453 func validateFieldAllowList(value interface{}, allowedFields map[string]bool, errorText string, fldPath *field.Path) field.ErrorList { 3454 var allErrs field.ErrorList 3455 3456 reflectType, reflectValue := reflect.TypeOf(value), reflect.ValueOf(value) 3457 for i := 0; i < reflectType.NumField(); i++ { 3458 f := reflectType.Field(i) 3459 if allowedFields[f.Name] { 3460 continue 3461 } 3462 3463 // Compare the value of this field to its zero value to determine if it has been set 3464 if !reflect.DeepEqual(reflectValue.Field(i).Interface(), reflect.Zero(f.Type).Interface()) { 3465 r, n := utf8.DecodeRuneInString(f.Name) 3466 lcName := string(unicode.ToLower(r)) + f.Name[n:] 3467 allErrs = append(allErrs, field.Forbidden(fldPath.Child(lcName), errorText)) 3468 } 3469 } 3470 3471 return allErrs 3472 } 3473 3474 // validateInitContainers is called by pod spec and template validation to validate the list of init containers 3475 func validateInitContainers(containers []core.Container, regularContainers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], gracePeriod int64, fldPath *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList { 3476 var allErrs field.ErrorList 3477 3478 allNames := sets.Set[string]{} 3479 for _, ctr := range regularContainers { 3480 allNames.Insert(ctr.Name) 3481 } 3482 for i, ctr := range containers { 3483 idxPath := fldPath.Index(i) 3484 3485 // Apply the validation common to all container types 3486 allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, idxPath, opts, podRestartPolicy, hostUsers)...) 3487 3488 restartAlways := false 3489 // Apply the validation specific to init containers 3490 if ctr.RestartPolicy != nil { 3491 allErrs = append(allErrs, validateInitContainerRestartPolicy(ctr.RestartPolicy, idxPath.Child("restartPolicy"))...) 3492 restartAlways = *ctr.RestartPolicy == core.ContainerRestartPolicyAlways 3493 } 3494 3495 // Names must be unique within regular and init containers. Collisions with ephemeral containers 3496 // will be detected by validateEphemeralContainers(). 3497 if allNames.Has(ctr.Name) { 3498 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ctr.Name)) 3499 } else if len(ctr.Name) > 0 { 3500 allNames.Insert(ctr.Name) 3501 } 3502 3503 // Check for port conflicts in init containers individually since init containers run one-by-one. 3504 allErrs = append(allErrs, checkHostPortConflicts([]core.Container{ctr}, fldPath)...) 3505 3506 switch { 3507 case restartAlways: 3508 if ctr.Lifecycle != nil { 3509 allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, idxPath.Child("lifecycle"))...) 3510 } 3511 allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, idxPath.Child("livenessProbe"))...) 3512 allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, idxPath.Child("readinessProbe"))...) 3513 allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, idxPath.Child("startupProbe"))...) 3514 3515 default: 3516 // These fields are disallowed for init containers. 3517 if ctr.Lifecycle != nil { 3518 allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers without restartPolicy=Always")) 3519 } 3520 if ctr.LivenessProbe != nil { 3521 allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers without restartPolicy=Always")) 3522 } 3523 if ctr.ReadinessProbe != nil { 3524 allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers without restartPolicy=Always")) 3525 } 3526 if ctr.StartupProbe != nil { 3527 allErrs = append(allErrs, field.Forbidden(idxPath.Child("startupProbe"), "may not be set for init containers without restartPolicy=Always")) 3528 } 3529 } 3530 3531 if len(ctr.ResizePolicy) > 0 { 3532 allErrs = append(allErrs, field.Invalid(idxPath.Child("resizePolicy"), ctr.ResizePolicy, "must not be set for init containers")) 3533 } 3534 } 3535 3536 return allErrs 3537 } 3538 3539 // validateContainerCommon applies validation common to all container types. It's called by regular, init, and ephemeral 3540 // container list validation to require a properly formatted name, image, etc. 3541 func validateContainerCommon(ctr *core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], path *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList { 3542 var allErrs field.ErrorList 3543 3544 namePath := path.Child("name") 3545 if len(ctr.Name) == 0 { 3546 allErrs = append(allErrs, field.Required(namePath, "")) 3547 } else { 3548 allErrs = append(allErrs, ValidateDNS1123Label(ctr.Name, namePath)...) 3549 } 3550 3551 // TODO: do not validate leading and trailing whitespace to preserve backward compatibility. 3552 // for example: https://github.com/openshift/origin/issues/14659 image = " " is special token in pod template 3553 // others may have done similar 3554 if len(ctr.Image) == 0 { 3555 allErrs = append(allErrs, field.Required(path.Child("image"), "")) 3556 } 3557 3558 switch ctr.TerminationMessagePolicy { 3559 case core.TerminationMessageReadFile, core.TerminationMessageFallbackToLogsOnError: 3560 case "": 3561 allErrs = append(allErrs, field.Required(path.Child("terminationMessagePolicy"), "")) 3562 default: 3563 supported := []core.TerminationMessagePolicy{ 3564 core.TerminationMessageReadFile, 3565 core.TerminationMessageFallbackToLogsOnError, 3566 } 3567 allErrs = append(allErrs, field.NotSupported(path.Child("terminationMessagePolicy"), ctr.TerminationMessagePolicy, supported)) 3568 } 3569 3570 volMounts := GetVolumeMountMap(ctr.VolumeMounts) 3571 volDevices := GetVolumeDeviceMap(ctr.VolumeDevices) 3572 allErrs = append(allErrs, validateContainerPorts(ctr.Ports, path.Child("ports"))...) 3573 allErrs = append(allErrs, ValidateEnv(ctr.Env, path.Child("env"), opts)...) 3574 allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, path.Child("envFrom"), opts)...) 3575 allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"))...) 3576 allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, path.Child("volumeDevices"))...) 3577 allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, path.Child("imagePullPolicy"))...) 3578 allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, podClaimNames, path.Child("resources"), opts)...) 3579 allErrs = append(allErrs, validateResizePolicy(ctr.ResizePolicy, path.Child("resizePolicy"), podRestartPolicy)...) 3580 allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, path.Child("securityContext"), hostUsers)...) 3581 return allErrs 3582 } 3583 3584 func validateHostUsers(spec *core.PodSpec, fldPath *field.Path) field.ErrorList { 3585 allErrs := field.ErrorList{} 3586 3587 // Only make the following checks if hostUsers is false (otherwise, the container uses the 3588 // same userns as the host, and so there isn't anything to check). 3589 if spec.SecurityContext == nil || spec.SecurityContext.HostUsers == nil || *spec.SecurityContext.HostUsers { 3590 return allErrs 3591 } 3592 3593 // We decided to restrict the usage of userns with other host namespaces: 3594 // https://github.com/kubernetes/kubernetes/pull/111090#discussion_r935994282 3595 // The tl;dr is: you can easily run into permission issues that seem unexpected, we don't 3596 // know of any good use case and we can always enable them later. 3597 3598 // Note we already validated above spec.SecurityContext is not nil. 3599 if spec.SecurityContext.HostNetwork { 3600 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostNetwork"), "when `pod.Spec.HostUsers` is false")) 3601 } 3602 if spec.SecurityContext.HostPID { 3603 allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostPID"), "when `pod.Spec.HostUsers` is false")) 3604 } 3605 if spec.SecurityContext.HostIPC { 3606 allErrs = append(allErrs, field.Forbidden(fldPath.Child("HostIPC"), "when `pod.Spec.HostUsers` is false")) 3607 } 3608 3609 return allErrs 3610 } 3611 3612 // validateContainers is called by pod spec and template validation to validate the list of regular containers. 3613 func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, podClaimNames sets.Set[string], gracePeriod int64, fldPath *field.Path, opts PodValidationOptions, podRestartPolicy *core.RestartPolicy, hostUsers bool) field.ErrorList { 3614 allErrs := field.ErrorList{} 3615 3616 if len(containers) == 0 { 3617 return append(allErrs, field.Required(fldPath, "")) 3618 } 3619 3620 allNames := sets.Set[string]{} 3621 for i, ctr := range containers { 3622 path := fldPath.Index(i) 3623 3624 // Apply validation common to all containers 3625 allErrs = append(allErrs, validateContainerCommon(&ctr, volumes, podClaimNames, path, opts, podRestartPolicy, hostUsers)...) 3626 3627 // Container names must be unique within the list of regular containers. 3628 // Collisions with init or ephemeral container names will be detected by the init or ephemeral 3629 // container validation to prevent duplicate error messages. 3630 if allNames.Has(ctr.Name) { 3631 allErrs = append(allErrs, field.Duplicate(path.Child("name"), ctr.Name)) 3632 } else { 3633 allNames.Insert(ctr.Name) 3634 } 3635 3636 // These fields are allowed for regular containers and restartable init 3637 // containers. 3638 // Regular init container and ephemeral container validation will return 3639 // field.Forbidden() for these paths. 3640 if ctr.Lifecycle != nil { 3641 allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, gracePeriod, path.Child("lifecycle"))...) 3642 } 3643 allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, gracePeriod, path.Child("livenessProbe"))...) 3644 allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, gracePeriod, path.Child("readinessProbe"))...) 3645 allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, gracePeriod, path.Child("startupProbe"))...) 3646 3647 // These fields are disallowed for regular containers 3648 if ctr.RestartPolicy != nil { 3649 allErrs = append(allErrs, field.Forbidden(path.Child("restartPolicy"), "may not be set for non-init containers")) 3650 } 3651 } 3652 3653 // Port conflicts are checked across all containers 3654 allErrs = append(allErrs, checkHostPortConflicts(containers, fldPath)...) 3655 3656 return allErrs 3657 } 3658 3659 func validateRestartPolicy(restartPolicy *core.RestartPolicy, fldPath *field.Path) field.ErrorList { 3660 allErrors := field.ErrorList{} 3661 switch *restartPolicy { 3662 case core.RestartPolicyAlways, core.RestartPolicyOnFailure, core.RestartPolicyNever: 3663 break 3664 case "": 3665 allErrors = append(allErrors, field.Required(fldPath, "")) 3666 default: 3667 validValues := []core.RestartPolicy{core.RestartPolicyAlways, core.RestartPolicyOnFailure, core.RestartPolicyNever} 3668 allErrors = append(allErrors, field.NotSupported(fldPath, *restartPolicy, validValues)) 3669 } 3670 3671 return allErrors 3672 } 3673 3674 func ValidatePreemptionPolicy(preemptionPolicy *core.PreemptionPolicy, fldPath *field.Path) field.ErrorList { 3675 allErrors := field.ErrorList{} 3676 switch *preemptionPolicy { 3677 case core.PreemptLowerPriority, core.PreemptNever: 3678 case "": 3679 allErrors = append(allErrors, field.Required(fldPath, "")) 3680 default: 3681 validValues := []core.PreemptionPolicy{core.PreemptLowerPriority, core.PreemptNever} 3682 allErrors = append(allErrors, field.NotSupported(fldPath, preemptionPolicy, validValues)) 3683 } 3684 return allErrors 3685 } 3686 3687 func validateDNSPolicy(dnsPolicy *core.DNSPolicy, fldPath *field.Path) field.ErrorList { 3688 allErrors := field.ErrorList{} 3689 switch *dnsPolicy { 3690 case core.DNSClusterFirstWithHostNet, core.DNSClusterFirst, core.DNSDefault, core.DNSNone: 3691 case "": 3692 allErrors = append(allErrors, field.Required(fldPath, "")) 3693 default: 3694 validValues := []core.DNSPolicy{core.DNSClusterFirstWithHostNet, core.DNSClusterFirst, core.DNSDefault, core.DNSNone} 3695 allErrors = append(allErrors, field.NotSupported(fldPath, dnsPolicy, validValues)) 3696 } 3697 return allErrors 3698 } 3699 3700 var validFSGroupChangePolicies = sets.New(core.FSGroupChangeOnRootMismatch, core.FSGroupChangeAlways) 3701 3702 func validateFSGroupChangePolicy(fsGroupPolicy *core.PodFSGroupChangePolicy, fldPath *field.Path) field.ErrorList { 3703 allErrors := field.ErrorList{} 3704 if !validFSGroupChangePolicies.Has(*fsGroupPolicy) { 3705 allErrors = append(allErrors, field.NotSupported(fldPath, fsGroupPolicy, sets.List(validFSGroupChangePolicies))) 3706 } 3707 return allErrors 3708 } 3709 3710 const ( 3711 // Limits on various DNS parameters. These are derived from 3712 // restrictions in Linux libc name resolution handling. 3713 // Max number of DNS name servers. 3714 MaxDNSNameservers = 3 3715 // Max number of domains in the search path list. 3716 MaxDNSSearchPaths = 32 3717 // Max number of characters in the search path. 3718 MaxDNSSearchListChars = 2048 3719 ) 3720 3721 func validateReadinessGates(readinessGates []core.PodReadinessGate, fldPath *field.Path) field.ErrorList { 3722 allErrs := field.ErrorList{} 3723 for i, value := range readinessGates { 3724 allErrs = append(allErrs, ValidateQualifiedName(string(value.ConditionType), fldPath.Index(i).Child("conditionType"))...) 3725 } 3726 return allErrs 3727 } 3728 3729 func validateSchedulingGates(schedulingGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList { 3730 allErrs := field.ErrorList{} 3731 // There should be no duplicates in the list of scheduling gates. 3732 seen := sets.Set[string]{} 3733 for i, schedulingGate := range schedulingGates { 3734 allErrs = append(allErrs, ValidateQualifiedName(schedulingGate.Name, fldPath.Index(i))...) 3735 if seen.Has(schedulingGate.Name) { 3736 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), schedulingGate.Name)) 3737 } 3738 seen.Insert(schedulingGate.Name) 3739 } 3740 return allErrs 3741 } 3742 3743 func validatePodDNSConfig(dnsConfig *core.PodDNSConfig, dnsPolicy *core.DNSPolicy, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 3744 allErrs := field.ErrorList{} 3745 3746 // Validate DNSNone case. Must provide at least one DNS name server. 3747 if dnsPolicy != nil && *dnsPolicy == core.DNSNone { 3748 if dnsConfig == nil { 3749 return append(allErrs, field.Required(fldPath, fmt.Sprintf("must provide `dnsConfig` when `dnsPolicy` is %s", core.DNSNone))) 3750 } 3751 if len(dnsConfig.Nameservers) == 0 { 3752 return append(allErrs, field.Required(fldPath.Child("nameservers"), fmt.Sprintf("must provide at least one DNS nameserver when `dnsPolicy` is %s", core.DNSNone))) 3753 } 3754 } 3755 3756 if dnsConfig != nil { 3757 // Validate nameservers. 3758 if len(dnsConfig.Nameservers) > MaxDNSNameservers { 3759 allErrs = append(allErrs, field.Invalid(fldPath.Child("nameservers"), dnsConfig.Nameservers, fmt.Sprintf("must not have more than %v nameservers", MaxDNSNameservers))) 3760 } 3761 for i, ns := range dnsConfig.Nameservers { 3762 allErrs = append(allErrs, validation.IsValidIP(fldPath.Child("nameservers").Index(i), ns)...) 3763 } 3764 // Validate searches. 3765 if len(dnsConfig.Searches) > MaxDNSSearchPaths { 3766 allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v search paths", MaxDNSSearchPaths))) 3767 } 3768 // Include the space between search paths. 3769 if len(strings.Join(dnsConfig.Searches, " ")) > MaxDNSSearchListChars { 3770 allErrs = append(allErrs, field.Invalid(fldPath.Child("searches"), dnsConfig.Searches, fmt.Sprintf("must not have more than %v characters (including spaces) in the search list", MaxDNSSearchListChars))) 3771 } 3772 for i, search := range dnsConfig.Searches { 3773 // it is fine to have a trailing dot 3774 search = strings.TrimSuffix(search, ".") 3775 allErrs = append(allErrs, ValidateDNS1123Subdomain(search, fldPath.Child("searches").Index(i))...) 3776 } 3777 // Validate options. 3778 for i, option := range dnsConfig.Options { 3779 if len(option.Name) == 0 { 3780 allErrs = append(allErrs, field.Required(fldPath.Child("options").Index(i), "must not be empty")) 3781 } 3782 } 3783 } 3784 return allErrs 3785 } 3786 3787 // validatePodHostNetworkDeps checks fields which depend on whether HostNetwork is 3788 // true or not. It should be called on all PodSpecs, but opts can change what 3789 // is enforce. E.g. opts.ResourceIsPod should only be set when called in the 3790 // context of a Pod, and not on PodSpecs which are embedded in other resources 3791 // (e.g. Deployments). 3792 func validatePodHostNetworkDeps(spec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 3793 // For <reasons> we keep `.HostNetwork` in .SecurityContext on the internal 3794 // version of Pod. 3795 hostNetwork := false 3796 if spec.SecurityContext != nil { 3797 hostNetwork = spec.SecurityContext.HostNetwork 3798 } 3799 3800 allErrors := field.ErrorList{} 3801 3802 if hostNetwork { 3803 fldPath := fldPath.Child("containers") 3804 for i, container := range spec.Containers { 3805 portsPath := fldPath.Index(i).Child("ports") 3806 for i, port := range container.Ports { 3807 idxPath := portsPath.Index(i) 3808 // At this point, we know that HostNetwork is true. If this 3809 // PodSpec is in a Pod (opts.ResourceIsPod), then HostPort must 3810 // be the same value as ContainerPort. If this PodSpec is in 3811 // some other resource (e.g. Deployment) we allow 0 (i.e. 3812 // unspecified) because it will be defaulted when the Pod is 3813 // ultimately created, but we do not allow any other values. 3814 if hp, cp := port.HostPort, port.ContainerPort; (opts.ResourceIsPod || hp != 0) && hp != cp { 3815 allErrors = append(allErrors, field.Invalid(idxPath.Child("hostPort"), port.HostPort, "must match `containerPort` when `hostNetwork` is true")) 3816 } 3817 } 3818 } 3819 } 3820 return allErrors 3821 } 3822 3823 // validateImagePullSecrets checks to make sure the pull secrets are well 3824 // formed. Right now, we only expect name to be set (it's the only field). If 3825 // this ever changes and someone decides to set those fields, we'd like to 3826 // know. 3827 func validateImagePullSecrets(imagePullSecrets []core.LocalObjectReference, fldPath *field.Path) field.ErrorList { 3828 allErrors := field.ErrorList{} 3829 for i, currPullSecret := range imagePullSecrets { 3830 idxPath := fldPath.Index(i) 3831 strippedRef := core.LocalObjectReference{Name: currPullSecret.Name} 3832 if !reflect.DeepEqual(strippedRef, currPullSecret) { 3833 allErrors = append(allErrors, field.Invalid(idxPath, currPullSecret, "only name may be set")) 3834 } 3835 } 3836 return allErrors 3837 } 3838 3839 // validateAffinity checks if given affinities are valid 3840 func validateAffinity(affinity *core.Affinity, opts PodValidationOptions, fldPath *field.Path) field.ErrorList { 3841 allErrs := field.ErrorList{} 3842 3843 if affinity != nil { 3844 if affinity.NodeAffinity != nil { 3845 allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...) 3846 } 3847 if affinity.PodAffinity != nil { 3848 allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAffinity"))...) 3849 } 3850 if affinity.PodAntiAffinity != nil { 3851 allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAntiAffinity"))...) 3852 } 3853 } 3854 3855 return allErrs 3856 } 3857 3858 func validateTaintEffect(effect *core.TaintEffect, allowEmpty bool, fldPath *field.Path) field.ErrorList { 3859 if !allowEmpty && len(*effect) == 0 { 3860 return field.ErrorList{field.Required(fldPath, "")} 3861 } 3862 3863 allErrors := field.ErrorList{} 3864 switch *effect { 3865 // TODO: Replace next line with subsequent commented-out line when implement TaintEffectNoScheduleNoAdmit. 3866 case core.TaintEffectNoSchedule, core.TaintEffectPreferNoSchedule, core.TaintEffectNoExecute: 3867 // case core.TaintEffectNoSchedule, core.TaintEffectPreferNoSchedule, core.TaintEffectNoScheduleNoAdmit, core.TaintEffectNoExecute: 3868 default: 3869 validValues := []core.TaintEffect{ 3870 core.TaintEffectNoSchedule, 3871 core.TaintEffectPreferNoSchedule, 3872 core.TaintEffectNoExecute, 3873 // TODO: Uncomment this block when implement TaintEffectNoScheduleNoAdmit. 3874 // core.TaintEffectNoScheduleNoAdmit, 3875 } 3876 allErrors = append(allErrors, field.NotSupported(fldPath, *effect, validValues)) 3877 } 3878 return allErrors 3879 } 3880 3881 // validateOnlyAddedTolerations validates updated pod tolerations. 3882 func validateOnlyAddedTolerations(newTolerations []core.Toleration, oldTolerations []core.Toleration, fldPath *field.Path) field.ErrorList { 3883 allErrs := field.ErrorList{} 3884 for _, old := range oldTolerations { 3885 found := false 3886 oldTolerationClone := old.DeepCopy() 3887 for _, newToleration := range newTolerations { 3888 // assign to our clone before doing a deep equal so we can allow tolerationseconds to change. 3889 oldTolerationClone.TolerationSeconds = newToleration.TolerationSeconds // +k8s:verify-mutation:reason=clone 3890 if reflect.DeepEqual(*oldTolerationClone, newToleration) { 3891 found = true 3892 break 3893 } 3894 } 3895 if !found { 3896 allErrs = append(allErrs, field.Forbidden(fldPath, "existing toleration can not be modified except its tolerationSeconds")) 3897 return allErrs 3898 } 3899 } 3900 3901 allErrs = append(allErrs, ValidateTolerations(newTolerations, fldPath)...) 3902 return allErrs 3903 } 3904 3905 func validateOnlyDeletedSchedulingGates(newGates, oldGates []core.PodSchedulingGate, fldPath *field.Path) field.ErrorList { 3906 allErrs := field.ErrorList{} 3907 if len(newGates) == 0 { 3908 return allErrs 3909 } 3910 3911 additionalGates := make(map[string]int) 3912 for i, newGate := range newGates { 3913 additionalGates[newGate.Name] = i 3914 } 3915 3916 for _, oldGate := range oldGates { 3917 delete(additionalGates, oldGate.Name) 3918 } 3919 3920 for gate, i := range additionalGates { 3921 allErrs = append(allErrs, field.Forbidden(fldPath.Index(i).Child("name"), fmt.Sprintf("only deletion is allowed, but found new scheduling gate '%s'", gate))) 3922 } 3923 3924 return allErrs 3925 } 3926 3927 func ValidateHostAliases(hostAliases []core.HostAlias, fldPath *field.Path) field.ErrorList { 3928 allErrs := field.ErrorList{} 3929 for i, hostAlias := range hostAliases { 3930 allErrs = append(allErrs, validation.IsValidIP(fldPath.Index(i).Child("ip"), hostAlias.IP)...) 3931 for j, hostname := range hostAlias.Hostnames { 3932 allErrs = append(allErrs, ValidateDNS1123Subdomain(hostname, fldPath.Index(i).Child("hostnames").Index(j))...) 3933 } 3934 } 3935 return allErrs 3936 } 3937 3938 // ValidateTolerations tests if given tolerations have valid data. 3939 func ValidateTolerations(tolerations []core.Toleration, fldPath *field.Path) field.ErrorList { 3940 allErrors := field.ErrorList{} 3941 for i, toleration := range tolerations { 3942 idxPath := fldPath.Index(i) 3943 // validate the toleration key 3944 if len(toleration.Key) > 0 { 3945 allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(toleration.Key, idxPath.Child("key"))...) 3946 } 3947 3948 // empty toleration key with Exists operator and empty value means match all taints 3949 if len(toleration.Key) == 0 && toleration.Operator != core.TolerationOpExists { 3950 allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Operator, 3951 "operator must be Exists when `key` is empty, which means \"match all values and all keys\"")) 3952 } 3953 3954 if toleration.TolerationSeconds != nil && toleration.Effect != core.TaintEffectNoExecute { 3955 allErrors = append(allErrors, field.Invalid(idxPath.Child("effect"), toleration.Effect, 3956 "effect must be 'NoExecute' when `tolerationSeconds` is set")) 3957 } 3958 3959 // validate toleration operator and value 3960 switch toleration.Operator { 3961 // empty operator means Equal 3962 case core.TolerationOpEqual, "": 3963 if errs := validation.IsValidLabelValue(toleration.Value); len(errs) != 0 { 3964 allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration.Value, strings.Join(errs, ";"))) 3965 } 3966 case core.TolerationOpExists: 3967 if len(toleration.Value) > 0 { 3968 allErrors = append(allErrors, field.Invalid(idxPath.Child("operator"), toleration, "value must be empty when `operator` is 'Exists'")) 3969 } 3970 default: 3971 validValues := []core.TolerationOperator{core.TolerationOpEqual, core.TolerationOpExists} 3972 allErrors = append(allErrors, field.NotSupported(idxPath.Child("operator"), toleration.Operator, validValues)) 3973 } 3974 3975 // validate toleration effect, empty toleration effect means match all taint effects 3976 if len(toleration.Effect) > 0 { 3977 allErrors = append(allErrors, validateTaintEffect(&toleration.Effect, true, idxPath.Child("effect"))...) 3978 } 3979 } 3980 return allErrors 3981 } 3982 3983 // validateContainersOnlyForPod does additional validation for containers on a pod versus a pod template 3984 // it only does additive validation of fields not covered in validateContainers and is not called for 3985 // ephemeral containers which require a conversion to core.Container. 3986 func validateContainersOnlyForPod(containers []core.Container, fldPath *field.Path) field.ErrorList { 3987 allErrs := field.ErrorList{} 3988 for i, ctr := range containers { 3989 allErrs = append(allErrs, validateContainerOnlyForPod(&ctr, fldPath.Index(i))...) 3990 } 3991 return allErrs 3992 } 3993 3994 // validateContainerOnlyForPod does pod-only (i.e. not pod template) validation for a single container. 3995 // This is called by validateContainersOnlyForPod and validateEphemeralContainers directly. 3996 func validateContainerOnlyForPod(ctr *core.Container, path *field.Path) field.ErrorList { 3997 allErrs := field.ErrorList{} 3998 if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) { 3999 allErrs = append(allErrs, field.Invalid(path.Child("image"), ctr.Image, "must not have leading or trailing whitespace")) 4000 } 4001 return allErrs 4002 } 4003 4004 // PodValidationOptions contains the different settings for pod validation 4005 type PodValidationOptions struct { 4006 // Allow invalid pod-deletion-cost annotation value for backward compatibility. 4007 AllowInvalidPodDeletionCost bool 4008 // Allow invalid label-value in LabelSelector 4009 AllowInvalidLabelValueInSelector bool 4010 // Allow pod spec to use non-integer multiple of huge page unit size 4011 AllowIndivisibleHugePagesValues bool 4012 // Allow pod spec to use status.hostIPs in downward API if feature is enabled 4013 AllowHostIPsField bool 4014 // Allow invalid topologySpreadConstraint labelSelector for backward compatibility 4015 AllowInvalidTopologySpreadConstraintLabelSelector bool 4016 // Allow projected token volumes with non-local paths 4017 AllowNonLocalProjectedTokenPath bool 4018 // Allow namespaced sysctls in hostNet and hostIPC pods 4019 AllowNamespacedSysctlsForHostNetAndHostIPC bool 4020 // The top-level resource being validated is a Pod, not just a PodSpec 4021 // embedded in some other resource. 4022 ResourceIsPod bool 4023 // Allow relaxed validation of environment variable names 4024 AllowRelaxedEnvironmentVariableValidation bool 4025 } 4026 4027 // validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set, 4028 // and is called by ValidatePodCreate and ValidatePodUpdate. 4029 func validatePodMetadataAndSpec(pod *core.Pod, opts PodValidationOptions) field.ErrorList { 4030 metaPath := field.NewPath("metadata") 4031 specPath := field.NewPath("spec") 4032 4033 allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, metaPath) 4034 allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, metaPath.Child("annotations"), opts)...) 4035 allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, &pod.ObjectMeta, specPath, opts)...) 4036 4037 // we do additional validation only pertinent for pods and not pod templates 4038 // this was done to preserve backwards compatibility 4039 4040 if pod.Spec.ServiceAccountName == "" { 4041 for vi, volume := range pod.Spec.Volumes { 4042 path := specPath.Child("volumes").Index(vi).Child("projected") 4043 if volume.Projected != nil { 4044 for si, source := range volume.Projected.Sources { 4045 saPath := path.Child("sources").Index(si).Child("serviceAccountToken") 4046 if source.ServiceAccountToken != nil { 4047 allErrs = append(allErrs, field.Forbidden(saPath, "must not be specified when serviceAccountName is not set")) 4048 } 4049 } 4050 } 4051 } 4052 } 4053 4054 allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...) 4055 allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...) 4056 // validateContainersOnlyForPod() is checked for ephemeral containers by validateEphemeralContainers() 4057 4058 return allErrs 4059 } 4060 4061 // validatePodIPs validates IPs in pod status 4062 func validatePodIPs(pod *core.Pod) field.ErrorList { 4063 allErrs := field.ErrorList{} 4064 4065 podIPsField := field.NewPath("status", "podIPs") 4066 4067 // all PodIPs must be valid IPs 4068 for i, podIP := range pod.Status.PodIPs { 4069 allErrs = append(allErrs, validation.IsValidIP(podIPsField.Index(i), podIP.IP)...) 4070 } 4071 4072 // if we have more than one Pod.PodIP then 4073 // - validate for dual stack 4074 // - validate for duplication 4075 if len(pod.Status.PodIPs) > 1 { 4076 podIPs := make([]string, 0, len(pod.Status.PodIPs)) 4077 for _, podIP := range pod.Status.PodIPs { 4078 podIPs = append(podIPs, podIP.IP) 4079 } 4080 4081 dualStack, err := netutils.IsDualStackIPStrings(podIPs) 4082 if err != nil { 4083 allErrs = append(allErrs, field.InternalError(podIPsField, fmt.Errorf("failed to check for dual stack with error:%v", err))) 4084 } 4085 4086 // We only support one from each IP family (i.e. max two IPs in this list). 4087 if !dualStack || len(podIPs) > 2 { 4088 allErrs = append(allErrs, field.Invalid(podIPsField, pod.Status.PodIPs, "may specify no more than one IP for each IP family")) 4089 } 4090 4091 // There should be no duplicates in list of Pod.PodIPs 4092 seen := sets.Set[string]{} // := make(map[string]int) 4093 for i, podIP := range pod.Status.PodIPs { 4094 if seen.Has(podIP.IP) { 4095 allErrs = append(allErrs, field.Duplicate(podIPsField.Index(i), podIP)) 4096 } 4097 seen.Insert(podIP.IP) 4098 } 4099 } 4100 4101 return allErrs 4102 } 4103 4104 // validateHostIPs validates IPs in pod status 4105 func validateHostIPs(pod *core.Pod) field.ErrorList { 4106 allErrs := field.ErrorList{} 4107 4108 if len(pod.Status.HostIPs) == 0 { 4109 return allErrs 4110 } 4111 4112 hostIPsField := field.NewPath("status", "hostIPs") 4113 4114 // hostIP must be equal to hostIPs[0].IP 4115 if pod.Status.HostIP != pod.Status.HostIPs[0].IP { 4116 allErrs = append(allErrs, field.Invalid(hostIPsField.Index(0).Child("ip"), pod.Status.HostIPs[0].IP, "must be equal to `hostIP`")) 4117 } 4118 4119 // all HostPs must be valid IPs 4120 for i, hostIP := range pod.Status.HostIPs { 4121 allErrs = append(allErrs, validation.IsValidIP(hostIPsField.Index(i), hostIP.IP)...) 4122 } 4123 4124 // if we have more than one Pod.HostIP then 4125 // - validate for dual stack 4126 // - validate for duplication 4127 if len(pod.Status.HostIPs) > 1 { 4128 seen := sets.Set[string]{} 4129 hostIPs := make([]string, 0, len(pod.Status.HostIPs)) 4130 4131 // There should be no duplicates in list of Pod.HostIPs 4132 for i, hostIP := range pod.Status.HostIPs { 4133 hostIPs = append(hostIPs, hostIP.IP) 4134 if seen.Has(hostIP.IP) { 4135 allErrs = append(allErrs, field.Duplicate(hostIPsField.Index(i), hostIP)) 4136 } 4137 seen.Insert(hostIP.IP) 4138 } 4139 4140 dualStack, err := netutils.IsDualStackIPStrings(hostIPs) 4141 if err != nil { 4142 allErrs = append(allErrs, field.InternalError(hostIPsField, fmt.Errorf("failed to check for dual stack with error:%v", err))) 4143 } 4144 4145 // We only support one from each IP family (i.e. max two IPs in this list). 4146 if !dualStack || len(hostIPs) > 2 { 4147 allErrs = append(allErrs, field.Invalid(hostIPsField, pod.Status.HostIPs, "may specify no more than one IP for each IP family")) 4148 } 4149 } 4150 4151 return allErrs 4152 } 4153 4154 // ValidatePodSpec tests that the specified PodSpec has valid data. 4155 // This includes checking formatting and uniqueness. It also canonicalizes the 4156 // structure by setting default values and implementing any backwards-compatibility 4157 // tricks. 4158 // The pod metadata is needed to validate generic ephemeral volumes. It is optional 4159 // and should be left empty unless the spec is from a real pod object. 4160 func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 4161 allErrs := field.ErrorList{} 4162 4163 var gracePeriod int64 4164 if spec.TerminationGracePeriodSeconds != nil { 4165 // this could happen in tests 4166 gracePeriod = *spec.TerminationGracePeriodSeconds 4167 } 4168 4169 // The default for hostUsers is true, so a spec with no SecurityContext or no HostUsers field will be true. 4170 // If the default ever changes, this condition will need to be changed. 4171 hostUsers := spec.SecurityContext == nil || spec.SecurityContext.HostUsers == nil || *spec.SecurityContext.HostUsers 4172 4173 vols, vErrs := ValidateVolumes(spec.Volumes, podMeta, fldPath.Child("volumes"), opts) 4174 allErrs = append(allErrs, vErrs...) 4175 podClaimNames := gatherPodResourceClaimNames(spec.ResourceClaims) 4176 allErrs = append(allErrs, validatePodResourceClaims(podMeta, spec.ResourceClaims, fldPath.Child("resourceClaims"))...) 4177 allErrs = append(allErrs, validateContainers(spec.Containers, vols, podClaimNames, gracePeriod, fldPath.Child("containers"), opts, &spec.RestartPolicy, hostUsers)...) 4178 allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, podClaimNames, gracePeriod, fldPath.Child("initContainers"), opts, &spec.RestartPolicy, hostUsers)...) 4179 allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, podClaimNames, fldPath.Child("ephemeralContainers"), opts, &spec.RestartPolicy, hostUsers)...) 4180 allErrs = append(allErrs, validatePodHostNetworkDeps(spec, fldPath, opts)...) 4181 allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...) 4182 allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...) 4183 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...) 4184 allErrs = append(allErrs, validatePodSpecSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...) 4185 allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...) 4186 allErrs = append(allErrs, validateAffinity(spec.Affinity, opts, fldPath.Child("affinity"))...) 4187 allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...) 4188 allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...) 4189 allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...) 4190 allErrs = append(allErrs, validateTopologySpreadConstraints(spec.TopologySpreadConstraints, fldPath.Child("topologySpreadConstraints"), opts)...) 4191 allErrs = append(allErrs, validateWindowsHostProcessPod(spec, fldPath)...) 4192 allErrs = append(allErrs, validateHostUsers(spec, fldPath)...) 4193 if len(spec.ServiceAccountName) > 0 { 4194 for _, msg := range ValidateServiceAccountName(spec.ServiceAccountName, false) { 4195 allErrs = append(allErrs, field.Invalid(fldPath.Child("serviceAccountName"), spec.ServiceAccountName, msg)) 4196 } 4197 } 4198 4199 if len(spec.NodeName) > 0 { 4200 for _, msg := range ValidateNodeName(spec.NodeName, false) { 4201 allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), spec.NodeName, msg)) 4202 } 4203 } 4204 4205 if spec.ActiveDeadlineSeconds != nil { 4206 value := *spec.ActiveDeadlineSeconds 4207 if value < 1 || value > math.MaxInt32 { 4208 allErrs = append(allErrs, field.Invalid(fldPath.Child("activeDeadlineSeconds"), value, validation.InclusiveRangeError(1, math.MaxInt32))) 4209 } 4210 } 4211 4212 if len(spec.Hostname) > 0 { 4213 allErrs = append(allErrs, ValidateDNS1123Label(spec.Hostname, fldPath.Child("hostname"))...) 4214 } 4215 4216 if len(spec.Subdomain) > 0 { 4217 allErrs = append(allErrs, ValidateDNS1123Label(spec.Subdomain, fldPath.Child("subdomain"))...) 4218 } 4219 4220 if len(spec.Tolerations) > 0 { 4221 allErrs = append(allErrs, ValidateTolerations(spec.Tolerations, fldPath.Child("tolerations"))...) 4222 } 4223 4224 if len(spec.HostAliases) > 0 { 4225 allErrs = append(allErrs, ValidateHostAliases(spec.HostAliases, fldPath.Child("hostAliases"))...) 4226 } 4227 4228 if len(spec.PriorityClassName) > 0 { 4229 for _, msg := range ValidatePriorityClassName(spec.PriorityClassName, false) { 4230 allErrs = append(allErrs, field.Invalid(fldPath.Child("priorityClassName"), spec.PriorityClassName, msg)) 4231 } 4232 } 4233 4234 if spec.RuntimeClassName != nil { 4235 allErrs = append(allErrs, ValidateRuntimeClassName(*spec.RuntimeClassName, fldPath.Child("runtimeClassName"))...) 4236 } 4237 4238 if spec.PreemptionPolicy != nil { 4239 allErrs = append(allErrs, ValidatePreemptionPolicy(spec.PreemptionPolicy, fldPath.Child("preemptionPolicy"))...) 4240 } 4241 4242 if spec.Overhead != nil { 4243 allErrs = append(allErrs, validateOverhead(spec.Overhead, fldPath.Child("overhead"), opts)...) 4244 } 4245 4246 if spec.OS != nil { 4247 osErrs := validateOS(spec, fldPath.Child("os"), opts) 4248 switch { 4249 case len(osErrs) > 0: 4250 allErrs = append(allErrs, osErrs...) 4251 case spec.OS.Name == core.Linux: 4252 allErrs = append(allErrs, validateLinux(spec, fldPath)...) 4253 case spec.OS.Name == core.Windows: 4254 allErrs = append(allErrs, validateWindows(spec, fldPath)...) 4255 } 4256 } 4257 return allErrs 4258 } 4259 4260 func validateLinux(spec *core.PodSpec, fldPath *field.Path) field.ErrorList { 4261 allErrs := field.ErrorList{} 4262 securityContext := spec.SecurityContext 4263 if securityContext != nil && securityContext.WindowsOptions != nil { 4264 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("windowsOptions"), "windows options cannot be set for a linux pod")) 4265 } 4266 podshelper.VisitContainersWithPath(spec, fldPath, func(c *core.Container, cFldPath *field.Path) bool { 4267 sc := c.SecurityContext 4268 if sc != nil && sc.WindowsOptions != nil { 4269 fldPath := cFldPath.Child("securityContext") 4270 allErrs = append(allErrs, field.Forbidden(fldPath.Child("windowsOptions"), "windows options cannot be set for a linux pod")) 4271 } 4272 return true 4273 }) 4274 return allErrs 4275 } 4276 4277 func validateWindows(spec *core.PodSpec, fldPath *field.Path) field.ErrorList { 4278 allErrs := field.ErrorList{} 4279 securityContext := spec.SecurityContext 4280 // validate Pod SecurityContext 4281 if securityContext != nil { 4282 if securityContext.AppArmorProfile != nil { 4283 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("appArmorProfile"), "cannot be set for a windows pod")) 4284 } 4285 if securityContext.SELinuxOptions != nil { 4286 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("seLinuxOptions"), "cannot be set for a windows pod")) 4287 } 4288 if securityContext.HostUsers != nil { 4289 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostUsers"), "cannot be set for a windows pod")) 4290 } 4291 if securityContext.HostPID { 4292 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostPID"), "cannot be set for a windows pod")) 4293 } 4294 if securityContext.HostIPC { 4295 allErrs = append(allErrs, field.Forbidden(fldPath.Child("hostIPC"), "cannot be set for a windows pod")) 4296 } 4297 if securityContext.SeccompProfile != nil { 4298 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("seccompProfile"), "cannot be set for a windows pod")) 4299 } 4300 if securityContext.FSGroup != nil { 4301 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("fsGroup"), "cannot be set for a windows pod")) 4302 } 4303 if securityContext.FSGroupChangePolicy != nil { 4304 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("fsGroupChangePolicy"), "cannot be set for a windows pod")) 4305 } 4306 if len(securityContext.Sysctls) > 0 { 4307 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("sysctls"), "cannot be set for a windows pod")) 4308 } 4309 if securityContext.ShareProcessNamespace != nil { 4310 allErrs = append(allErrs, field.Forbidden(fldPath.Child("shareProcessNamespace"), "cannot be set for a windows pod")) 4311 } 4312 if securityContext.RunAsUser != nil { 4313 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("runAsUser"), "cannot be set for a windows pod")) 4314 } 4315 if securityContext.RunAsGroup != nil { 4316 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("runAsGroup"), "cannot be set for a windows pod")) 4317 } 4318 if securityContext.SupplementalGroups != nil { 4319 allErrs = append(allErrs, field.Forbidden(fldPath.Child("securityContext").Child("supplementalGroups"), "cannot be set for a windows pod")) 4320 } 4321 } 4322 podshelper.VisitContainersWithPath(spec, fldPath, func(c *core.Container, cFldPath *field.Path) bool { 4323 // validate container security context 4324 sc := c.SecurityContext 4325 // OS based podSecurityContext validation 4326 // There is some naming overlap between Windows and Linux Security Contexts but all the Windows Specific options 4327 // are set via securityContext.WindowsOptions which we validate below 4328 // TODO: Think if we need to relax this restriction or some of the restrictions 4329 if sc != nil { 4330 fldPath := cFldPath.Child("securityContext") 4331 if sc.AppArmorProfile != nil { 4332 allErrs = append(allErrs, field.Forbidden(fldPath.Child("appArmorProfile"), "cannot be set for a windows pod")) 4333 } 4334 if sc.SELinuxOptions != nil { 4335 allErrs = append(allErrs, field.Forbidden(fldPath.Child("seLinuxOptions"), "cannot be set for a windows pod")) 4336 } 4337 if sc.SeccompProfile != nil { 4338 allErrs = append(allErrs, field.Forbidden(fldPath.Child("seccompProfile"), "cannot be set for a windows pod")) 4339 } 4340 if sc.Capabilities != nil { 4341 allErrs = append(allErrs, field.Forbidden(fldPath.Child("capabilities"), "cannot be set for a windows pod")) 4342 } 4343 if sc.ReadOnlyRootFilesystem != nil { 4344 allErrs = append(allErrs, field.Forbidden(fldPath.Child("readOnlyRootFilesystem"), "cannot be set for a windows pod")) 4345 } 4346 if sc.Privileged != nil { 4347 allErrs = append(allErrs, field.Forbidden(fldPath.Child("privileged"), "cannot be set for a windows pod")) 4348 } 4349 if sc.AllowPrivilegeEscalation != nil { 4350 allErrs = append(allErrs, field.Forbidden(fldPath.Child("allowPrivilegeEscalation"), "cannot be set for a windows pod")) 4351 } 4352 if sc.ProcMount != nil { 4353 allErrs = append(allErrs, field.Forbidden(fldPath.Child("procMount"), "cannot be set for a windows pod")) 4354 } 4355 if sc.RunAsUser != nil { 4356 allErrs = append(allErrs, field.Forbidden(fldPath.Child("runAsUser"), "cannot be set for a windows pod")) 4357 } 4358 if sc.RunAsGroup != nil { 4359 allErrs = append(allErrs, field.Forbidden(fldPath.Child("runAsGroup"), "cannot be set for a windows pod")) 4360 } 4361 } 4362 return true 4363 }) 4364 return allErrs 4365 } 4366 4367 // ValidateNodeSelectorRequirement tests that the specified NodeSelectorRequirement fields has valid data 4368 func ValidateNodeSelectorRequirement(rq core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList { 4369 allErrs := field.ErrorList{} 4370 switch rq.Operator { 4371 case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn: 4372 if len(rq.Values) == 0 { 4373 allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) 4374 } 4375 case core.NodeSelectorOpExists, core.NodeSelectorOpDoesNotExist: 4376 if len(rq.Values) > 0 { 4377 allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) 4378 } 4379 4380 case core.NodeSelectorOpGt, core.NodeSelectorOpLt: 4381 if len(rq.Values) != 1 { 4382 allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified single value when `operator` is 'Lt' or 'Gt'")) 4383 } 4384 default: 4385 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), rq.Operator, "not a valid selector operator")) 4386 } 4387 4388 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...) 4389 4390 return allErrs 4391 } 4392 4393 var nodeFieldSelectorValidators = map[string]func(string, bool) []string{ 4394 metav1.ObjectNameField: ValidateNodeName, 4395 } 4396 4397 // ValidateNodeFieldSelectorRequirement tests that the specified NodeSelectorRequirement fields has valid data 4398 func ValidateNodeFieldSelectorRequirement(req core.NodeSelectorRequirement, fldPath *field.Path) field.ErrorList { 4399 allErrs := field.ErrorList{} 4400 4401 switch req.Operator { 4402 case core.NodeSelectorOpIn, core.NodeSelectorOpNotIn: 4403 if len(req.Values) != 1 { 4404 allErrs = append(allErrs, field.Required(fldPath.Child("values"), 4405 "must be only one value when `operator` is 'In' or 'NotIn' for node field selector")) 4406 } 4407 default: 4408 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator")) 4409 } 4410 4411 if vf, found := nodeFieldSelectorValidators[req.Key]; !found { 4412 allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), req.Key, "not a valid field selector key")) 4413 } else { 4414 for i, v := range req.Values { 4415 for _, msg := range vf(v, false) { 4416 allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(i), v, msg)) 4417 } 4418 } 4419 } 4420 4421 return allErrs 4422 } 4423 4424 // ValidateNodeSelectorTerm tests that the specified node selector term has valid data 4425 func ValidateNodeSelectorTerm(term core.NodeSelectorTerm, fldPath *field.Path) field.ErrorList { 4426 allErrs := field.ErrorList{} 4427 4428 for j, req := range term.MatchExpressions { 4429 allErrs = append(allErrs, ValidateNodeSelectorRequirement(req, fldPath.Child("matchExpressions").Index(j))...) 4430 } 4431 4432 for j, req := range term.MatchFields { 4433 allErrs = append(allErrs, ValidateNodeFieldSelectorRequirement(req, fldPath.Child("matchFields").Index(j))...) 4434 } 4435 4436 return allErrs 4437 } 4438 4439 // ValidateNodeSelector tests that the specified nodeSelector fields has valid data 4440 func ValidateNodeSelector(nodeSelector *core.NodeSelector, fldPath *field.Path) field.ErrorList { 4441 allErrs := field.ErrorList{} 4442 4443 termFldPath := fldPath.Child("nodeSelectorTerms") 4444 if len(nodeSelector.NodeSelectorTerms) == 0 { 4445 return append(allErrs, field.Required(termFldPath, "must have at least one node selector term")) 4446 } 4447 4448 for i, term := range nodeSelector.NodeSelectorTerms { 4449 allErrs = append(allErrs, ValidateNodeSelectorTerm(term, termFldPath.Index(i))...) 4450 } 4451 4452 return allErrs 4453 } 4454 4455 // validateTopologySelectorLabelRequirement tests that the specified TopologySelectorLabelRequirement fields has valid data, 4456 // and constructs a set containing all of its Values. 4457 func validateTopologySelectorLabelRequirement(rq core.TopologySelectorLabelRequirement, fldPath *field.Path) (sets.Set[string], field.ErrorList) { 4458 allErrs := field.ErrorList{} 4459 valueSet := make(sets.Set[string]) 4460 valuesPath := fldPath.Child("values") 4461 if len(rq.Values) == 0 { 4462 allErrs = append(allErrs, field.Required(valuesPath, "")) 4463 } 4464 4465 // Validate set property of Values field 4466 for i, value := range rq.Values { 4467 if valueSet.Has(value) { 4468 allErrs = append(allErrs, field.Duplicate(valuesPath.Index(i), value)) 4469 } 4470 valueSet.Insert(value) 4471 } 4472 4473 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(rq.Key, fldPath.Child("key"))...) 4474 4475 return valueSet, allErrs 4476 } 4477 4478 // ValidateTopologySelectorTerm tests that the specified topology selector term has valid data, 4479 // and constructs a map representing the term in raw form. 4480 func ValidateTopologySelectorTerm(term core.TopologySelectorTerm, fldPath *field.Path) (map[string]sets.Set[string], field.ErrorList) { 4481 allErrs := field.ErrorList{} 4482 exprMap := make(map[string]sets.Set[string]) 4483 exprPath := fldPath.Child("matchLabelExpressions") 4484 4485 // Allow empty MatchLabelExpressions, in case this field becomes optional in the future. 4486 for i, req := range term.MatchLabelExpressions { 4487 idxPath := exprPath.Index(i) 4488 valueSet, exprErrs := validateTopologySelectorLabelRequirement(req, idxPath) 4489 allErrs = append(allErrs, exprErrs...) 4490 4491 // Validate no duplicate keys exist. 4492 if _, exists := exprMap[req.Key]; exists { 4493 allErrs = append(allErrs, field.Duplicate(idxPath.Child("key"), req.Key)) 4494 } 4495 exprMap[req.Key] = valueSet 4496 } 4497 4498 return exprMap, allErrs 4499 } 4500 4501 // ValidateAvoidPodsInNodeAnnotations tests that the serialized AvoidPods in Node.Annotations has valid data 4502 func ValidateAvoidPodsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 4503 allErrs := field.ErrorList{} 4504 4505 v1Avoids, err := schedulinghelper.GetAvoidPodsFromNodeAnnotations(annotations) 4506 if err != nil { 4507 allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), core.PreferAvoidPodsAnnotationKey, err.Error())) 4508 return allErrs 4509 } 4510 var avoids core.AvoidPods 4511 if err := corev1.Convert_v1_AvoidPods_To_core_AvoidPods(&v1Avoids, &avoids, nil); err != nil { 4512 allErrs = append(allErrs, field.Invalid(fldPath.Child("AvoidPods"), core.PreferAvoidPodsAnnotationKey, err.Error())) 4513 return allErrs 4514 } 4515 4516 if len(avoids.PreferAvoidPods) != 0 { 4517 for i, pa := range avoids.PreferAvoidPods { 4518 idxPath := fldPath.Child(core.PreferAvoidPodsAnnotationKey).Index(i) 4519 allErrs = append(allErrs, validatePreferAvoidPodsEntry(pa, idxPath)...) 4520 } 4521 } 4522 4523 return allErrs 4524 } 4525 4526 // validatePreferAvoidPodsEntry tests if given PreferAvoidPodsEntry has valid data. 4527 func validatePreferAvoidPodsEntry(avoidPodEntry core.PreferAvoidPodsEntry, fldPath *field.Path) field.ErrorList { 4528 allErrors := field.ErrorList{} 4529 if avoidPodEntry.PodSignature.PodController == nil { 4530 allErrors = append(allErrors, field.Required(fldPath.Child("PodSignature"), "")) 4531 } else { 4532 if !*(avoidPodEntry.PodSignature.PodController.Controller) { 4533 allErrors = append(allErrors, 4534 field.Invalid(fldPath.Child("PodSignature").Child("PodController").Child("Controller"), 4535 *(avoidPodEntry.PodSignature.PodController.Controller), "must point to a controller")) 4536 } 4537 } 4538 return allErrors 4539 } 4540 4541 // ValidatePreferredSchedulingTerms tests that the specified SoftNodeAffinity fields has valid data 4542 func ValidatePreferredSchedulingTerms(terms []core.PreferredSchedulingTerm, fldPath *field.Path) field.ErrorList { 4543 allErrs := field.ErrorList{} 4544 4545 for i, term := range terms { 4546 if term.Weight <= 0 || term.Weight > 100 { 4547 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("weight"), term.Weight, "must be in the range 1-100")) 4548 } 4549 4550 allErrs = append(allErrs, ValidateNodeSelectorTerm(term.Preference, fldPath.Index(i).Child("preference"))...) 4551 } 4552 return allErrs 4553 } 4554 4555 // validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data 4556 func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { 4557 allErrs := field.ErrorList{} 4558 4559 allErrs = append(allErrs, ValidatePodAffinityTermSelector(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath)...) 4560 for _, name := range podAffinityTerm.Namespaces { 4561 for _, msg := range ValidateNamespaceName(name, false) { 4562 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg)) 4563 } 4564 } 4565 allErrs = append(allErrs, validateMatchLabelKeysAndMismatchLabelKeys(fldPath, podAffinityTerm.MatchLabelKeys, podAffinityTerm.MismatchLabelKeys, podAffinityTerm.LabelSelector)...) 4566 if len(podAffinityTerm.TopologyKey) == 0 { 4567 allErrs = append(allErrs, field.Required(fldPath.Child("topologyKey"), "can not be empty")) 4568 } 4569 return append(allErrs, unversionedvalidation.ValidateLabelName(podAffinityTerm.TopologyKey, fldPath.Child("topologyKey"))...) 4570 } 4571 4572 // validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data 4573 func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { 4574 allErrs := field.ErrorList{} 4575 for i, podAffinityTerm := range podAffinityTerms { 4576 allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(i))...) 4577 } 4578 return allErrs 4579 } 4580 4581 // validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data 4582 func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { 4583 allErrs := field.ErrorList{} 4584 for j, weightedTerm := range weightedPodAffinityTerms { 4585 if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 { 4586 allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100")) 4587 } 4588 allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(j).Child("podAffinityTerm"))...) 4589 } 4590 return allErrs 4591 } 4592 4593 // validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data 4594 func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { 4595 allErrs := field.ErrorList{} 4596 // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. 4597 // if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { 4598 // allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, 4599 // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) 4600 // } 4601 if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { 4602 allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, 4603 fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) 4604 } 4605 if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { 4606 allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, 4607 fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) 4608 } 4609 return allErrs 4610 } 4611 4612 // validateNodeAffinity tests that the specified nodeAffinity fields have valid data 4613 func validateNodeAffinity(na *core.NodeAffinity, fldPath *field.Path) field.ErrorList { 4614 allErrs := field.ErrorList{} 4615 // TODO: Uncomment the next three lines once RequiredDuringSchedulingRequiredDuringExecution is implemented. 4616 // if na.RequiredDuringSchedulingRequiredDuringExecution != nil { 4617 // allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingRequiredDuringExecution, fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) 4618 // } 4619 if na.RequiredDuringSchedulingIgnoredDuringExecution != nil { 4620 allErrs = append(allErrs, ValidateNodeSelector(na.RequiredDuringSchedulingIgnoredDuringExecution, fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) 4621 } 4622 if len(na.PreferredDuringSchedulingIgnoredDuringExecution) > 0 { 4623 allErrs = append(allErrs, ValidatePreferredSchedulingTerms(na.PreferredDuringSchedulingIgnoredDuringExecution, fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) 4624 } 4625 return allErrs 4626 } 4627 4628 // validatePodAffinity tests that the specified podAffinity fields have valid data 4629 func validatePodAffinity(podAffinity *core.PodAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { 4630 allErrs := field.ErrorList{} 4631 // TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented. 4632 // if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil { 4633 // allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingRequiredDuringExecution, false, 4634 // fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...) 4635 // } 4636 if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { 4637 allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, 4638 fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...) 4639 } 4640 if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil { 4641 allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector, 4642 fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...) 4643 } 4644 return allErrs 4645 } 4646 4647 func validateSeccompProfileField(sp *core.SeccompProfile, fldPath *field.Path) field.ErrorList { 4648 allErrs := field.ErrorList{} 4649 if sp == nil { 4650 return allErrs 4651 } 4652 4653 if err := validateSeccompProfileType(fldPath.Child("type"), sp.Type); err != nil { 4654 allErrs = append(allErrs, err) 4655 } 4656 4657 if sp.Type == core.SeccompProfileTypeLocalhost { 4658 if sp.LocalhostProfile == nil { 4659 allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when seccomp type is Localhost")) 4660 } else { 4661 allErrs = append(allErrs, validateLocalDescendingPath(*sp.LocalhostProfile, fldPath.Child("localhostProfile"))...) 4662 } 4663 } else { 4664 if sp.LocalhostProfile != nil { 4665 allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), sp, "can only be set when seccomp type is Localhost")) 4666 } 4667 } 4668 4669 return allErrs 4670 } 4671 4672 func ValidateSeccompProfile(p string, fldPath *field.Path) field.ErrorList { 4673 if p == core.SeccompProfileRuntimeDefault || p == core.DeprecatedSeccompProfileDockerDefault { 4674 return nil 4675 } 4676 if p == v1.SeccompProfileNameUnconfined { 4677 return nil 4678 } 4679 if strings.HasPrefix(p, v1.SeccompLocalhostProfileNamePrefix) { 4680 return validateLocalDescendingPath(strings.TrimPrefix(p, v1.SeccompLocalhostProfileNamePrefix), fldPath) 4681 } 4682 return field.ErrorList{field.Invalid(fldPath, p, "must be a valid seccomp profile")} 4683 } 4684 4685 func ValidateSeccompPodAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 4686 allErrs := field.ErrorList{} 4687 if p, exists := annotations[core.SeccompPodAnnotationKey]; exists { 4688 allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(core.SeccompPodAnnotationKey))...) 4689 } 4690 for k, p := range annotations { 4691 if strings.HasPrefix(k, core.SeccompContainerAnnotationKeyPrefix) { 4692 allErrs = append(allErrs, ValidateSeccompProfile(p, fldPath.Child(k))...) 4693 } 4694 } 4695 4696 return allErrs 4697 } 4698 4699 // ValidateSeccompProfileType tests that the argument is a valid SeccompProfileType. 4700 func validateSeccompProfileType(fldPath *field.Path, seccompProfileType core.SeccompProfileType) *field.Error { 4701 switch seccompProfileType { 4702 case core.SeccompProfileTypeLocalhost, core.SeccompProfileTypeRuntimeDefault, core.SeccompProfileTypeUnconfined: 4703 return nil 4704 case "": 4705 return field.Required(fldPath, "type is required when seccompProfile is set") 4706 default: 4707 return field.NotSupported(fldPath, seccompProfileType, []core.SeccompProfileType{core.SeccompProfileTypeLocalhost, core.SeccompProfileTypeRuntimeDefault, core.SeccompProfileTypeUnconfined}) 4708 } 4709 } 4710 4711 func ValidateAppArmorProfileField(profile *core.AppArmorProfile, fldPath *field.Path) field.ErrorList { 4712 if profile == nil { 4713 return nil 4714 } 4715 4716 allErrs := field.ErrorList{} 4717 4718 switch profile.Type { 4719 case core.AppArmorProfileTypeLocalhost: 4720 if profile.LocalhostProfile == nil { 4721 allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when AppArmor type is Localhost")) 4722 } else { 4723 localhostProfile := strings.TrimSpace(*profile.LocalhostProfile) 4724 if localhostProfile != *profile.LocalhostProfile { 4725 allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), *profile.LocalhostProfile, "must not be padded with whitespace")) 4726 } else if localhostProfile == "" { 4727 allErrs = append(allErrs, field.Required(fldPath.Child("localhostProfile"), "must be set when AppArmor type is Localhost")) 4728 } 4729 4730 const maxLocalhostProfileLength = 4095 // PATH_MAX - 1 4731 if len(*profile.LocalhostProfile) > maxLocalhostProfileLength { 4732 allErrs = append(allErrs, field.TooLongMaxLength(fldPath.Child("localhostProfile"), *profile.LocalhostProfile, maxLocalhostProfileLength)) 4733 } 4734 } 4735 4736 case core.AppArmorProfileTypeRuntimeDefault, core.AppArmorProfileTypeUnconfined: 4737 if profile.LocalhostProfile != nil { 4738 allErrs = append(allErrs, field.Invalid(fldPath.Child("localhostProfile"), profile.LocalhostProfile, "can only be set when AppArmor type is Localhost")) 4739 } 4740 4741 case "": 4742 allErrs = append(allErrs, field.Required(fldPath.Child("type"), "type is required when appArmorProfile is set")) 4743 4744 default: 4745 allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), profile.Type, 4746 []core.AppArmorProfileType{core.AppArmorProfileTypeLocalhost, core.AppArmorProfileTypeRuntimeDefault, core.AppArmorProfileTypeUnconfined})) 4747 } 4748 4749 return allErrs 4750 4751 } 4752 4753 func ValidateAppArmorPodAnnotations(annotations map[string]string, spec *core.PodSpec, fldPath *field.Path) field.ErrorList { 4754 allErrs := field.ErrorList{} 4755 for k, p := range annotations { 4756 if !strings.HasPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) { 4757 continue 4758 } 4759 containerName := strings.TrimPrefix(k, v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix) 4760 if !podSpecHasContainer(spec, containerName) { 4761 allErrs = append(allErrs, field.Invalid(fldPath.Key(k), containerName, "container not found")) 4762 } 4763 4764 if err := ValidateAppArmorProfileFormat(p); err != nil { 4765 allErrs = append(allErrs, field.Invalid(fldPath.Key(k), p, err.Error())) 4766 } 4767 } 4768 4769 return allErrs 4770 } 4771 4772 func ValidateAppArmorProfileFormat(profile string) error { 4773 if profile == "" || profile == v1.DeprecatedAppArmorBetaProfileRuntimeDefault || profile == v1.DeprecatedAppArmorBetaProfileNameUnconfined { 4774 return nil 4775 } 4776 if !strings.HasPrefix(profile, v1.DeprecatedAppArmorBetaProfileNamePrefix) { 4777 return fmt.Errorf("invalid AppArmor profile name: %q", profile) 4778 } 4779 return nil 4780 } 4781 4782 // validateAppArmorAnnotationsAndFieldsMatchOnCreate validates that AppArmor fields and annotations are consistent. 4783 func validateAppArmorAnnotationsAndFieldsMatchOnCreate(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList { 4784 if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) { 4785 return nil 4786 } 4787 if podSpec.OS != nil && podSpec.OS.Name == core.Windows { 4788 // Skip consistency check for windows pods. 4789 return nil 4790 } 4791 4792 allErrs := field.ErrorList{} 4793 4794 var podProfile *core.AppArmorProfile 4795 if podSpec.SecurityContext != nil { 4796 podProfile = podSpec.SecurityContext.AppArmorProfile 4797 } 4798 podshelper.VisitContainersWithPath(podSpec, specPath, func(c *core.Container, cFldPath *field.Path) bool { 4799 containerProfile := podProfile 4800 if c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil { 4801 containerProfile = c.SecurityContext.AppArmorProfile 4802 } 4803 4804 if containerProfile == nil { 4805 return true 4806 } 4807 4808 key := core.DeprecatedAppArmorAnnotationKeyPrefix + c.Name 4809 if annotation, found := objectMeta.Annotations[key]; found { 4810 apparmorPath := cFldPath.Child("securityContext").Child("appArmorProfile") 4811 4812 switch containerProfile.Type { 4813 case core.AppArmorProfileTypeUnconfined: 4814 if annotation != core.DeprecatedAppArmorAnnotationValueUnconfined { 4815 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match")) 4816 } 4817 4818 case core.AppArmorProfileTypeRuntimeDefault: 4819 if annotation != core.DeprecatedAppArmorAnnotationValueRuntimeDefault { 4820 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match")) 4821 } 4822 4823 case core.AppArmorProfileTypeLocalhost: 4824 if !strings.HasPrefix(annotation, core.DeprecatedAppArmorAnnotationValueLocalhostPrefix) { 4825 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("type"), "apparmor type in annotation and field must match")) 4826 } else if containerProfile.LocalhostProfile == nil || strings.TrimPrefix(annotation, core.DeprecatedAppArmorAnnotationValueLocalhostPrefix) != *containerProfile.LocalhostProfile { 4827 allErrs = append(allErrs, field.Forbidden(apparmorPath.Child("localhostProfile"), "apparmor profile in annotation and field must match")) 4828 } 4829 } 4830 } 4831 return true 4832 }) 4833 4834 return allErrs 4835 } 4836 4837 func podSpecHasContainer(spec *core.PodSpec, containerName string) bool { 4838 var hasContainer bool 4839 podshelper.VisitContainersWithPath(spec, field.NewPath("spec"), func(c *core.Container, _ *field.Path) bool { 4840 if c.Name == containerName { 4841 hasContainer = true 4842 return false 4843 } 4844 return true 4845 }) 4846 return hasContainer 4847 } 4848 4849 const ( 4850 // a sysctl segment regex, concatenated with dots to form a sysctl name 4851 SysctlSegmentFmt string = "[a-z0-9]([-_a-z0-9]*[a-z0-9])?" 4852 4853 // a sysctl name regex with slash allowed 4854 SysctlContainSlashFmt string = "(" + SysctlSegmentFmt + "[\\./])*" + SysctlSegmentFmt 4855 4856 // the maximal length of a sysctl name 4857 SysctlMaxLength int = 253 4858 ) 4859 4860 var sysctlContainSlashRegexp = regexp.MustCompile("^" + SysctlContainSlashFmt + "$") 4861 4862 // IsValidSysctlName checks that the given string is a valid sysctl name, 4863 // i.e. matches SysctlContainSlashFmt. 4864 // More info: 4865 // 4866 // https://man7.org/linux/man-pages/man8/sysctl.8.html 4867 // https://man7.org/linux/man-pages/man5/sysctl.d.5.html 4868 func IsValidSysctlName(name string) bool { 4869 if len(name) > SysctlMaxLength { 4870 return false 4871 } 4872 return sysctlContainSlashRegexp.MatchString(name) 4873 } 4874 4875 func validateSysctls(securityContext *core.PodSecurityContext, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 4876 allErrs := field.ErrorList{} 4877 names := make(map[string]struct{}) 4878 for i, s := range securityContext.Sysctls { 4879 if len(s.Name) == 0 { 4880 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("name"), "")) 4881 } else if !IsValidSysctlName(s.Name) { 4882 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("name"), s.Name, fmt.Sprintf("must have at most %d characters and match regex %s", SysctlMaxLength, sysctlContainSlashRegexp))) 4883 } else if _, ok := names[s.Name]; ok { 4884 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("name"), s.Name)) 4885 } 4886 if !opts.AllowNamespacedSysctlsForHostNetAndHostIPC { 4887 err := ValidateHostSysctl(s.Name, securityContext, fldPath.Index(i).Child("name")) 4888 if err != nil { 4889 allErrs = append(allErrs, err) 4890 } 4891 } 4892 names[s.Name] = struct{}{} 4893 } 4894 return allErrs 4895 } 4896 4897 // ValidateHostSysctl will return error if namespaced sysctls is applied to pod sharing the respective namespaces with the host. 4898 func ValidateHostSysctl(sysctl string, securityContext *core.PodSecurityContext, fldPath *field.Path) *field.Error { 4899 ns, _, _ := utilsysctl.GetNamespace(sysctl) 4900 switch { 4901 case securityContext.HostNetwork && ns == utilsysctl.NetNamespace: 4902 return field.Invalid(fldPath, sysctl, "may not be specified when 'hostNetwork' is true") 4903 case securityContext.HostIPC && ns == utilsysctl.IPCNamespace: 4904 return field.Invalid(fldPath, sysctl, "may not be specified when 'hostIPC' is true") 4905 } 4906 return nil 4907 } 4908 4909 // validatePodSpecSecurityContext verifies the SecurityContext of a PodSpec, 4910 // whether that is defined in a Pod or in an embedded PodSpec (e.g. a 4911 // Deployment's pod template). 4912 func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 4913 allErrs := field.ErrorList{} 4914 4915 if securityContext != nil { 4916 if securityContext.FSGroup != nil { 4917 for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) { 4918 allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg)) 4919 } 4920 } 4921 if securityContext.RunAsUser != nil { 4922 for _, msg := range validation.IsValidUserID(*securityContext.RunAsUser) { 4923 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *(securityContext.RunAsUser), msg)) 4924 } 4925 } 4926 if securityContext.RunAsGroup != nil { 4927 for _, msg := range validation.IsValidGroupID(*securityContext.RunAsGroup) { 4928 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *(securityContext.RunAsGroup), msg)) 4929 } 4930 } 4931 for g, gid := range securityContext.SupplementalGroups { 4932 for _, msg := range validation.IsValidGroupID(gid) { 4933 allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg)) 4934 } 4935 } 4936 if securityContext.ShareProcessNamespace != nil && securityContext.HostPID && *securityContext.ShareProcessNamespace { 4937 allErrs = append(allErrs, field.Invalid(fldPath.Child("shareProcessNamespace"), *securityContext.ShareProcessNamespace, "ShareProcessNamespace and HostPID cannot both be enabled")) 4938 } 4939 4940 if len(securityContext.Sysctls) != 0 { 4941 allErrs = append(allErrs, validateSysctls(securityContext, fldPath.Child("sysctls"), opts)...) 4942 } 4943 4944 if securityContext.FSGroupChangePolicy != nil { 4945 allErrs = append(allErrs, validateFSGroupChangePolicy(securityContext.FSGroupChangePolicy, fldPath.Child("fsGroupChangePolicy"))...) 4946 } 4947 4948 allErrs = append(allErrs, validateSeccompProfileField(securityContext.SeccompProfile, fldPath.Child("seccompProfile"))...) 4949 allErrs = append(allErrs, validateWindowsSecurityContextOptions(securityContext.WindowsOptions, fldPath.Child("windowsOptions"))...) 4950 allErrs = append(allErrs, ValidateAppArmorProfileField(securityContext.AppArmorProfile, fldPath.Child("appArmorProfile"))...) 4951 } 4952 4953 return allErrs 4954 } 4955 4956 func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fldPath *field.Path) (allErrs field.ErrorList, stop bool) { 4957 allErrs = field.ErrorList{} 4958 if len(newContainers) != len(oldContainers) { 4959 // TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff 4960 allErrs = append(allErrs, field.Forbidden(fldPath, "pod updates may not add or remove containers")) 4961 return allErrs, true 4962 } 4963 4964 // validate updated container images 4965 for i, ctr := range newContainers { 4966 if len(ctr.Image) == 0 { 4967 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("image"), "")) 4968 } 4969 // this is only called from ValidatePodUpdate so its safe to check leading/trailing whitespace. 4970 if len(strings.TrimSpace(ctr.Image)) != len(ctr.Image) { 4971 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("image"), ctr.Image, "must not have leading or trailing whitespace")) 4972 } 4973 } 4974 return allErrs, false 4975 } 4976 4977 // ValidatePodCreate validates a pod in the context of its initial create 4978 func ValidatePodCreate(pod *core.Pod, opts PodValidationOptions) field.ErrorList { 4979 allErrs := validatePodMetadataAndSpec(pod, opts) 4980 4981 fldPath := field.NewPath("spec") 4982 // EphemeralContainers can only be set on update using the ephemeralcontainers subresource 4983 if len(pod.Spec.EphemeralContainers) > 0 { 4984 allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create")) 4985 } 4986 // A Pod cannot be assigned a Node if there are remaining scheduling gates. 4987 if pod.Spec.NodeName != "" && len(pod.Spec.SchedulingGates) != 0 { 4988 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "cannot be set until all schedulingGates have been cleared")) 4989 } 4990 allErrs = append(allErrs, validateSeccompAnnotationsAndFields(pod.ObjectMeta, &pod.Spec, fldPath)...) 4991 allErrs = append(allErrs, validateAppArmorAnnotationsAndFieldsMatchOnCreate(pod.ObjectMeta, &pod.Spec, fldPath)...) 4992 4993 return allErrs 4994 } 4995 4996 // validateSeccompAnnotationsAndFields iterates through all containers and ensure that when both seccompProfile and seccomp annotations exist they match. 4997 func validateSeccompAnnotationsAndFields(objectMeta metav1.ObjectMeta, podSpec *core.PodSpec, specPath *field.Path) field.ErrorList { 4998 allErrs := field.ErrorList{} 4999 5000 if podSpec.SecurityContext != nil && podSpec.SecurityContext.SeccompProfile != nil { 5001 // If both seccomp annotations and fields are specified, the values must match. 5002 if annotation, found := objectMeta.Annotations[v1.SeccompPodAnnotationKey]; found { 5003 seccompPath := specPath.Child("securityContext").Child("seccompProfile") 5004 err := validateSeccompAnnotationsAndFieldsMatch(annotation, podSpec.SecurityContext.SeccompProfile, seccompPath) 5005 if err != nil { 5006 allErrs = append(allErrs, err) 5007 } 5008 } 5009 } 5010 5011 podshelper.VisitContainersWithPath(podSpec, specPath, func(c *core.Container, cFldPath *field.Path) bool { 5012 var field *core.SeccompProfile 5013 if c.SecurityContext != nil { 5014 field = c.SecurityContext.SeccompProfile 5015 } 5016 5017 if field == nil { 5018 return true 5019 } 5020 5021 key := v1.SeccompContainerAnnotationKeyPrefix + c.Name 5022 if annotation, found := objectMeta.Annotations[key]; found { 5023 seccompPath := cFldPath.Child("securityContext").Child("seccompProfile") 5024 err := validateSeccompAnnotationsAndFieldsMatch(annotation, field, seccompPath) 5025 if err != nil { 5026 allErrs = append(allErrs, err) 5027 } 5028 } 5029 return true 5030 }) 5031 5032 return allErrs 5033 } 5034 5035 func validateSeccompAnnotationsAndFieldsMatch(annotationValue string, seccompField *core.SeccompProfile, fldPath *field.Path) *field.Error { 5036 if seccompField == nil { 5037 return nil 5038 } 5039 5040 switch seccompField.Type { 5041 case core.SeccompProfileTypeUnconfined: 5042 if annotationValue != v1.SeccompProfileNameUnconfined { 5043 return field.Forbidden(fldPath.Child("type"), "seccomp type in annotation and field must match") 5044 } 5045 5046 case core.SeccompProfileTypeRuntimeDefault: 5047 if annotationValue != v1.SeccompProfileRuntimeDefault && annotationValue != v1.DeprecatedSeccompProfileDockerDefault { 5048 return field.Forbidden(fldPath.Child("type"), "seccomp type in annotation and field must match") 5049 } 5050 5051 case core.SeccompProfileTypeLocalhost: 5052 if !strings.HasPrefix(annotationValue, v1.SeccompLocalhostProfileNamePrefix) { 5053 return field.Forbidden(fldPath.Child("type"), "seccomp type in annotation and field must match") 5054 } else if seccompField.LocalhostProfile == nil || strings.TrimPrefix(annotationValue, v1.SeccompLocalhostProfileNamePrefix) != *seccompField.LocalhostProfile { 5055 return field.Forbidden(fldPath.Child("localhostProfile"), "seccomp profile in annotation and field must match") 5056 } 5057 } 5058 5059 return nil 5060 } 5061 5062 var updatablePodSpecFields = []string{ 5063 "`spec.containers[*].image`", 5064 "`spec.initContainers[*].image`", 5065 "`spec.activeDeadlineSeconds`", 5066 "`spec.tolerations` (only additions to existing tolerations)", 5067 "`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)", 5068 "`spec.containers[*].resources` (for CPU/memory only)", 5069 } 5070 5071 // TODO(vinaykul,InPlacePodVerticalScaling): Drop this var once InPlacePodVerticalScaling goes GA and featuregate is gone. 5072 var updatablePodSpecFieldsNoResources = []string{ 5073 "`spec.containers[*].image`", 5074 "`spec.initContainers[*].image`", 5075 "`spec.activeDeadlineSeconds`", 5076 "`spec.tolerations` (only additions to existing tolerations)", 5077 "`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)", 5078 } 5079 5080 // ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields 5081 // that cannot be changed. 5082 func ValidatePodUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList { 5083 fldPath := field.NewPath("metadata") 5084 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) 5085 allErrs = append(allErrs, validatePodMetadataAndSpec(newPod, opts)...) 5086 allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...) 5087 specPath := field.NewPath("spec") 5088 5089 // validate updateable fields: 5090 // 1. spec.containers[*].image 5091 // 2. spec.initContainers[*].image 5092 // 3. spec.activeDeadlineSeconds 5093 // 4. spec.terminationGracePeriodSeconds 5094 // 5. spec.schedulingGates 5095 5096 containerErrs, stop := ValidateContainerUpdates(newPod.Spec.Containers, oldPod.Spec.Containers, specPath.Child("containers")) 5097 allErrs = append(allErrs, containerErrs...) 5098 if stop { 5099 return allErrs 5100 } 5101 containerErrs, stop = ValidateContainerUpdates(newPod.Spec.InitContainers, oldPod.Spec.InitContainers, specPath.Child("initContainers")) 5102 allErrs = append(allErrs, containerErrs...) 5103 if stop { 5104 return allErrs 5105 } 5106 5107 // validate updated spec.activeDeadlineSeconds. two types of updates are allowed: 5108 // 1. from nil to a positive value 5109 // 2. from a positive value to a lesser, non-negative value 5110 if newPod.Spec.ActiveDeadlineSeconds != nil { 5111 newActiveDeadlineSeconds := *newPod.Spec.ActiveDeadlineSeconds 5112 if newActiveDeadlineSeconds < 0 || newActiveDeadlineSeconds > math.MaxInt32 { 5113 allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, validation.InclusiveRangeError(0, math.MaxInt32))) 5114 return allErrs 5115 } 5116 if oldPod.Spec.ActiveDeadlineSeconds != nil { 5117 oldActiveDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds 5118 if oldActiveDeadlineSeconds < newActiveDeadlineSeconds { 5119 allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newActiveDeadlineSeconds, "must be less than or equal to previous value")) 5120 return allErrs 5121 } 5122 } 5123 } else if oldPod.Spec.ActiveDeadlineSeconds != nil { 5124 allErrs = append(allErrs, field.Invalid(specPath.Child("activeDeadlineSeconds"), newPod.Spec.ActiveDeadlineSeconds, "must not update from a positive integer to nil value")) 5125 } 5126 5127 // Allow only additions to tolerations updates. 5128 allErrs = append(allErrs, validateOnlyAddedTolerations(newPod.Spec.Tolerations, oldPod.Spec.Tolerations, specPath.Child("tolerations"))...) 5129 5130 // Allow only deletions to schedulingGates updates. 5131 allErrs = append(allErrs, validateOnlyDeletedSchedulingGates(newPod.Spec.SchedulingGates, oldPod.Spec.SchedulingGates, specPath.Child("schedulingGates"))...) 5132 5133 // the last thing to check is pod spec equality. If the pod specs are equal, then we can simply return the errors we have 5134 // so far and save the cost of a deep copy. 5135 if apiequality.Semantic.DeepEqual(newPod.Spec, oldPod.Spec) { 5136 return allErrs 5137 } 5138 5139 if qos.GetPodQOS(oldPod) != qos.ComputePodQOS(newPod) { 5140 allErrs = append(allErrs, field.Invalid(fldPath, newPod.Status.QOSClass, "Pod QoS is immutable")) 5141 } 5142 5143 // handle updateable fields by munging those fields prior to deep equal comparison. 5144 mungedPodSpec := *newPod.Spec.DeepCopy() 5145 // munge spec.containers[*].image 5146 var newContainers []core.Container 5147 for ix, container := range mungedPodSpec.Containers { 5148 container.Image = oldPod.Spec.Containers[ix].Image // +k8s:verify-mutation:reason=clone 5149 // When the feature-gate is turned off, any new requests attempting to update CPU or memory 5150 // resource values will result in validation failure. 5151 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { 5152 // Resources are mutable for CPU & memory only 5153 // - user can now modify Resources to express new desired Resources 5154 mungeCpuMemResources := func(resourceList, oldResourceList core.ResourceList) core.ResourceList { 5155 if oldResourceList == nil { 5156 return nil 5157 } 5158 var mungedResourceList core.ResourceList 5159 if resourceList == nil { 5160 mungedResourceList = make(core.ResourceList) 5161 } else { 5162 mungedResourceList = resourceList.DeepCopy() 5163 } 5164 delete(mungedResourceList, core.ResourceCPU) 5165 delete(mungedResourceList, core.ResourceMemory) 5166 if cpu, found := oldResourceList[core.ResourceCPU]; found { 5167 mungedResourceList[core.ResourceCPU] = cpu 5168 } 5169 if mem, found := oldResourceList[core.ResourceMemory]; found { 5170 mungedResourceList[core.ResourceMemory] = mem 5171 } 5172 return mungedResourceList 5173 } 5174 lim := mungeCpuMemResources(container.Resources.Limits, oldPod.Spec.Containers[ix].Resources.Limits) 5175 req := mungeCpuMemResources(container.Resources.Requests, oldPod.Spec.Containers[ix].Resources.Requests) 5176 container.Resources = core.ResourceRequirements{Limits: lim, Requests: req} 5177 } 5178 newContainers = append(newContainers, container) 5179 } 5180 mungedPodSpec.Containers = newContainers 5181 // munge spec.initContainers[*].image 5182 var newInitContainers []core.Container 5183 for ix, container := range mungedPodSpec.InitContainers { 5184 container.Image = oldPod.Spec.InitContainers[ix].Image // +k8s:verify-mutation:reason=clone 5185 newInitContainers = append(newInitContainers, container) 5186 } 5187 mungedPodSpec.InitContainers = newInitContainers 5188 // munge spec.activeDeadlineSeconds 5189 mungedPodSpec.ActiveDeadlineSeconds = nil 5190 if oldPod.Spec.ActiveDeadlineSeconds != nil { 5191 activeDeadlineSeconds := *oldPod.Spec.ActiveDeadlineSeconds 5192 mungedPodSpec.ActiveDeadlineSeconds = &activeDeadlineSeconds 5193 } 5194 // munge spec.schedulingGates 5195 mungedPodSpec.SchedulingGates = oldPod.Spec.SchedulingGates // +k8s:verify-mutation:reason=clone 5196 // tolerations are checked before the deep copy, so munge those too 5197 mungedPodSpec.Tolerations = oldPod.Spec.Tolerations // +k8s:verify-mutation:reason=clone 5198 5199 // Relax validation of immutable fields to allow it to be set to 1 if it was previously negative. 5200 if oldPod.Spec.TerminationGracePeriodSeconds != nil && *oldPod.Spec.TerminationGracePeriodSeconds < 0 && 5201 mungedPodSpec.TerminationGracePeriodSeconds != nil && *mungedPodSpec.TerminationGracePeriodSeconds == 1 { 5202 mungedPodSpec.TerminationGracePeriodSeconds = oldPod.Spec.TerminationGracePeriodSeconds // +k8s:verify-mutation:reason=clone 5203 } 5204 5205 // Handle validations specific to gated pods. 5206 podIsGated := len(oldPod.Spec.SchedulingGates) > 0 5207 if podIsGated { 5208 // Additions to spec.nodeSelector are allowed (no deletions or mutations) for gated pods. 5209 if !apiequality.Semantic.DeepEqual(mungedPodSpec.NodeSelector, oldPod.Spec.NodeSelector) { 5210 allErrs = append(allErrs, validateNodeSelectorMutation(specPath.Child("nodeSelector"), mungedPodSpec.NodeSelector, oldPod.Spec.NodeSelector)...) 5211 mungedPodSpec.NodeSelector = oldPod.Spec.NodeSelector // +k8s:verify-mutation:reason=clone 5212 } 5213 5214 // Validate node affinity mutations. 5215 var oldNodeAffinity *core.NodeAffinity 5216 if oldPod.Spec.Affinity != nil { 5217 oldNodeAffinity = oldPod.Spec.Affinity.NodeAffinity // +k8s:verify-mutation:reason=clone 5218 } 5219 5220 var mungedNodeAffinity *core.NodeAffinity 5221 if mungedPodSpec.Affinity != nil { 5222 mungedNodeAffinity = mungedPodSpec.Affinity.NodeAffinity // +k8s:verify-mutation:reason=clone 5223 } 5224 5225 if !apiequality.Semantic.DeepEqual(oldNodeAffinity, mungedNodeAffinity) { 5226 allErrs = append(allErrs, validateNodeAffinityMutation(specPath.Child("affinity").Child("nodeAffinity"), mungedNodeAffinity, oldNodeAffinity)...) 5227 switch { 5228 case mungedPodSpec.Affinity == nil && oldNodeAffinity == nil: 5229 // already effectively nil, no change needed 5230 case mungedPodSpec.Affinity == nil && oldNodeAffinity != nil: 5231 mungedPodSpec.Affinity = &core.Affinity{NodeAffinity: oldNodeAffinity} // +k8s:verify-mutation:reason=clone 5232 case mungedPodSpec.Affinity != nil && oldPod.Spec.Affinity == nil && 5233 mungedPodSpec.Affinity.PodAntiAffinity == nil && mungedPodSpec.Affinity.PodAffinity == nil: 5234 // We ensure no other fields are being changed, but the NodeAffinity. If that's the case, and the 5235 // old pod's affinity is nil, we set the mungedPodSpec's affinity to nil. 5236 mungedPodSpec.Affinity = nil // +k8s:verify-mutation:reason=clone 5237 default: 5238 // The node affinity is being updated and the old pod Affinity is not nil. 5239 // We set the mungedPodSpec's node affinity to the old pod's node affinity. 5240 mungedPodSpec.Affinity.NodeAffinity = oldNodeAffinity // +k8s:verify-mutation:reason=clone 5241 } 5242 } 5243 5244 // Note: Unlike NodeAffinity and NodeSelector, we cannot make PodAffinity/PodAntiAffinity mutable due to the presence of the matchLabelKeys/mismatchLabelKeys feature. 5245 // Those features automatically generate the matchExpressions in labelSelector for PodAffinity/PodAntiAffinity when the Pod is created. 5246 // When we make them mutable, we need to make sure things like how to handle/validate matchLabelKeys, 5247 // and what if the fieldManager/A sets matchexpressions and fieldManager/B sets matchLabelKeys later. (could it lead the understandable conflict, etc) 5248 } 5249 5250 if !apiequality.Semantic.DeepEqual(mungedPodSpec, oldPod.Spec) { 5251 // This diff isn't perfect, but it's a helluva lot better an "I'm not going to tell you what the difference is". 5252 // TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff 5253 specDiff := cmp.Diff(oldPod.Spec, mungedPodSpec) 5254 errs := field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than %s\n%v", strings.Join(updatablePodSpecFieldsNoResources, ","), specDiff)) 5255 if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) { 5256 errs = field.Forbidden(specPath, fmt.Sprintf("pod updates may not change fields other than %s\n%v", strings.Join(updatablePodSpecFields, ","), specDiff)) 5257 } 5258 allErrs = append(allErrs, errs) 5259 } 5260 return allErrs 5261 } 5262 5263 // ValidateContainerStateTransition test to if any illegal container state transitions are being attempted 5264 func ValidateContainerStateTransition(newStatuses, oldStatuses []core.ContainerStatus, fldpath *field.Path, restartPolicy core.RestartPolicy) field.ErrorList { 5265 allErrs := field.ErrorList{} 5266 // If we should always restart, containers are allowed to leave the terminated state 5267 if restartPolicy == core.RestartPolicyAlways { 5268 return allErrs 5269 } 5270 for i, oldStatus := range oldStatuses { 5271 // Skip any container that is not terminated 5272 if oldStatus.State.Terminated == nil { 5273 continue 5274 } 5275 // Skip any container that failed but is allowed to restart 5276 if oldStatus.State.Terminated.ExitCode != 0 && restartPolicy == core.RestartPolicyOnFailure { 5277 continue 5278 } 5279 for _, newStatus := range newStatuses { 5280 if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { 5281 allErrs = append(allErrs, field.Forbidden(fldpath.Index(i).Child("state"), "may not be transitioned to non-terminated state")) 5282 } 5283 } 5284 } 5285 return allErrs 5286 } 5287 5288 // ValidateInitContainerStateTransition test to if any illegal init container state transitions are being attempted 5289 func ValidateInitContainerStateTransition(newStatuses, oldStatuses []core.ContainerStatus, fldpath *field.Path, podSpec *core.PodSpec) field.ErrorList { 5290 allErrs := field.ErrorList{} 5291 // If we should always restart, containers are allowed to leave the terminated state 5292 if podSpec.RestartPolicy == core.RestartPolicyAlways { 5293 return allErrs 5294 } 5295 for i, oldStatus := range oldStatuses { 5296 // Skip any container that is not terminated 5297 if oldStatus.State.Terminated == nil { 5298 continue 5299 } 5300 // Skip any container that failed but is allowed to restart 5301 if oldStatus.State.Terminated.ExitCode != 0 && podSpec.RestartPolicy == core.RestartPolicyOnFailure { 5302 continue 5303 } 5304 5305 // Skip any restartable init container that is allowed to restart 5306 isRestartableInitContainer := false 5307 for _, c := range podSpec.InitContainers { 5308 if oldStatus.Name == c.Name { 5309 if c.RestartPolicy != nil && *c.RestartPolicy == core.ContainerRestartPolicyAlways { 5310 isRestartableInitContainer = true 5311 } 5312 break 5313 } 5314 } 5315 if isRestartableInitContainer { 5316 continue 5317 } 5318 5319 for _, newStatus := range newStatuses { 5320 if oldStatus.Name == newStatus.Name && newStatus.State.Terminated == nil { 5321 allErrs = append(allErrs, field.Forbidden(fldpath.Index(i).Child("state"), "may not be transitioned to non-terminated state")) 5322 } 5323 } 5324 } 5325 return allErrs 5326 } 5327 5328 // ValidatePodStatusUpdate checks for changes to status that shouldn't occur in normal operation. 5329 func ValidatePodStatusUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList { 5330 fldPath := field.NewPath("metadata") 5331 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) 5332 allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...) 5333 allErrs = append(allErrs, validatePodConditions(newPod.Status.Conditions, fldPath.Child("conditions"))...) 5334 5335 fldPath = field.NewPath("status") 5336 if newPod.Spec.NodeName != oldPod.Spec.NodeName { 5337 allErrs = append(allErrs, field.Forbidden(fldPath.Child("nodeName"), "may not be changed directly")) 5338 } 5339 5340 if newPod.Status.NominatedNodeName != oldPod.Status.NominatedNodeName && len(newPod.Status.NominatedNodeName) > 0 { 5341 for _, msg := range ValidateNodeName(newPod.Status.NominatedNodeName, false) { 5342 allErrs = append(allErrs, field.Invalid(fldPath.Child("nominatedNodeName"), newPod.Status.NominatedNodeName, msg)) 5343 } 5344 } 5345 5346 // If pod should not restart, make sure the status update does not transition 5347 // any terminated containers to a non-terminated state. 5348 allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.ContainerStatuses, oldPod.Status.ContainerStatuses, fldPath.Child("containerStatuses"), oldPod.Spec.RestartPolicy)...) 5349 allErrs = append(allErrs, ValidateInitContainerStateTransition(newPod.Status.InitContainerStatuses, oldPod.Status.InitContainerStatuses, fldPath.Child("initContainerStatuses"), &oldPod.Spec)...) 5350 // The kubelet will never restart ephemeral containers, so treat them like they have an implicit RestartPolicyNever. 5351 allErrs = append(allErrs, ValidateContainerStateTransition(newPod.Status.EphemeralContainerStatuses, oldPod.Status.EphemeralContainerStatuses, fldPath.Child("ephemeralContainerStatuses"), core.RestartPolicyNever)...) 5352 allErrs = append(allErrs, validatePodResourceClaimStatuses(newPod.Status.ResourceClaimStatuses, newPod.Spec.ResourceClaims, fldPath.Child("resourceClaimStatuses"))...) 5353 5354 if newIPErrs := validatePodIPs(newPod); len(newIPErrs) > 0 { 5355 allErrs = append(allErrs, newIPErrs...) 5356 } 5357 5358 if newIPErrs := validateHostIPs(newPod); len(newIPErrs) > 0 { 5359 allErrs = append(allErrs, newIPErrs...) 5360 } 5361 5362 return allErrs 5363 } 5364 5365 // validatePodConditions tests if the custom pod conditions are valid. 5366 func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path) field.ErrorList { 5367 allErrs := field.ErrorList{} 5368 systemConditions := sets.New( 5369 core.PodScheduled, 5370 core.PodReady, 5371 core.PodInitialized) 5372 for i, condition := range conditions { 5373 if systemConditions.Has(condition.Type) { 5374 continue 5375 } 5376 allErrs = append(allErrs, ValidateQualifiedName(string(condition.Type), fldPath.Index(i).Child("Type"))...) 5377 } 5378 return allErrs 5379 } 5380 5381 // validatePodResourceClaimStatuses validates the ResourceClaimStatuses slice in a pod status. 5382 func validatePodResourceClaimStatuses(statuses []core.PodResourceClaimStatus, podClaims []core.PodResourceClaim, fldPath *field.Path) field.ErrorList { 5383 var allErrs field.ErrorList 5384 5385 claimNames := sets.New[string]() 5386 for i, status := range statuses { 5387 idxPath := fldPath.Index(i) 5388 // There's no need to check the content of the name. If it matches an entry, 5389 // then it is valid, otherwise we reject it here. 5390 if !havePodClaim(podClaims, status.Name) { 5391 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), status.Name, "must match the name of an entry in `spec.resourceClaims`")) 5392 } 5393 if claimNames.Has(status.Name) { 5394 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), status.Name)) 5395 } else { 5396 claimNames.Insert(status.Name) 5397 } 5398 if status.ResourceClaimName != nil { 5399 for _, detail := range ValidateResourceClaimName(*status.ResourceClaimName, false) { 5400 allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), status.ResourceClaimName, detail)) 5401 } 5402 } 5403 } 5404 5405 return allErrs 5406 } 5407 5408 func havePodClaim(podClaims []core.PodResourceClaim, name string) bool { 5409 for _, podClaim := range podClaims { 5410 if podClaim.Name == name { 5411 return true 5412 } 5413 } 5414 return false 5415 } 5416 5417 // ValidatePodEphemeralContainersUpdate tests that a user update to EphemeralContainers is valid. 5418 // newPod and oldPod must only differ in their EphemeralContainers. 5419 func ValidatePodEphemeralContainersUpdate(newPod, oldPod *core.Pod, opts PodValidationOptions) field.ErrorList { 5420 // Part 1: Validate newPod's spec and updates to metadata 5421 fldPath := field.NewPath("metadata") 5422 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) 5423 allErrs = append(allErrs, validatePodMetadataAndSpec(newPod, opts)...) 5424 allErrs = append(allErrs, ValidatePodSpecificAnnotationUpdates(newPod, oldPod, fldPath.Child("annotations"), opts)...) 5425 5426 // static pods don't support ephemeral containers #113935 5427 if _, ok := oldPod.Annotations[core.MirrorPodAnnotationKey]; ok { 5428 return field.ErrorList{field.Forbidden(field.NewPath(""), "static pods do not support ephemeral containers")} 5429 } 5430 5431 // Part 2: Validate that the changes between oldPod.Spec.EphemeralContainers and 5432 // newPod.Spec.EphemeralContainers are allowed. 5433 // 5434 // Existing EphemeralContainers may not be changed. Order isn't preserved by patch, so check each individually. 5435 newContainerIndex := make(map[string]*core.EphemeralContainer) 5436 specPath := field.NewPath("spec").Child("ephemeralContainers") 5437 for i := range newPod.Spec.EphemeralContainers { 5438 newContainerIndex[newPod.Spec.EphemeralContainers[i].Name] = &newPod.Spec.EphemeralContainers[i] 5439 } 5440 for _, old := range oldPod.Spec.EphemeralContainers { 5441 if new, ok := newContainerIndex[old.Name]; !ok { 5442 allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be removed\n", old.Name))) 5443 } else if !apiequality.Semantic.DeepEqual(old, *new) { 5444 specDiff := cmp.Diff(old, *new) 5445 allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be changed\n%v", old.Name, specDiff))) 5446 } 5447 } 5448 5449 return allErrs 5450 } 5451 5452 // ValidatePodBinding tests if required fields in the pod binding are legal. 5453 func ValidatePodBinding(binding *core.Binding) field.ErrorList { 5454 allErrs := field.ErrorList{} 5455 5456 if len(binding.Target.Kind) != 0 && binding.Target.Kind != "Node" { 5457 // TODO: When validation becomes versioned, this gets more complicated. 5458 allErrs = append(allErrs, field.NotSupported(field.NewPath("target", "kind"), binding.Target.Kind, []string{"Node", "<empty>"})) 5459 } 5460 if len(binding.Target.Name) == 0 { 5461 // TODO: When validation becomes versioned, this gets more complicated. 5462 allErrs = append(allErrs, field.Required(field.NewPath("target", "name"), "")) 5463 } 5464 5465 return allErrs 5466 } 5467 5468 // ValidatePodTemplate tests if required fields in the pod template are set. 5469 func ValidatePodTemplate(pod *core.PodTemplate, opts PodValidationOptions) field.ErrorList { 5470 allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, field.NewPath("metadata")) 5471 allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, field.NewPath("template"), opts)...) 5472 return allErrs 5473 } 5474 5475 // ValidatePodTemplateUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields 5476 // that cannot be changed. 5477 func ValidatePodTemplateUpdate(newPod, oldPod *core.PodTemplate, opts PodValidationOptions) field.ErrorList { 5478 allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, field.NewPath("metadata")) 5479 allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, field.NewPath("template"), opts)...) 5480 return allErrs 5481 } 5482 5483 var supportedSessionAffinityType = sets.New(core.ServiceAffinityClientIP, core.ServiceAffinityNone) 5484 var supportedServiceType = sets.New(core.ServiceTypeClusterIP, core.ServiceTypeNodePort, 5485 core.ServiceTypeLoadBalancer, core.ServiceTypeExternalName) 5486 5487 var supportedServiceInternalTrafficPolicy = sets.New(core.ServiceInternalTrafficPolicyCluster, core.ServiceInternalTrafficPolicyLocal) 5488 5489 var supportedServiceIPFamily = sets.New(core.IPv4Protocol, core.IPv6Protocol) 5490 var supportedServiceIPFamilyPolicy = sets.New( 5491 core.IPFamilyPolicySingleStack, 5492 core.IPFamilyPolicyPreferDualStack, 5493 core.IPFamilyPolicyRequireDualStack) 5494 5495 // ValidateService tests if required fields/annotations of a Service are valid. 5496 func ValidateService(service *core.Service) field.ErrorList { 5497 metaPath := field.NewPath("metadata") 5498 allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, metaPath) 5499 5500 topologyHintsVal, topologyHintsSet := service.Annotations[core.DeprecatedAnnotationTopologyAwareHints] 5501 topologyModeVal, topologyModeSet := service.Annotations[core.AnnotationTopologyMode] 5502 5503 if topologyModeSet && topologyHintsSet && topologyModeVal != topologyHintsVal { 5504 message := fmt.Sprintf("must match annotations[%s] when both are specified", core.DeprecatedAnnotationTopologyAwareHints) 5505 allErrs = append(allErrs, field.Invalid(metaPath.Child("annotations").Key(core.AnnotationTopologyMode), topologyModeVal, message)) 5506 } 5507 5508 specPath := field.NewPath("spec") 5509 5510 if len(service.Spec.Ports) == 0 && !isHeadlessService(service) && service.Spec.Type != core.ServiceTypeExternalName { 5511 allErrs = append(allErrs, field.Required(specPath.Child("ports"), "")) 5512 } 5513 switch service.Spec.Type { 5514 case core.ServiceTypeLoadBalancer: 5515 for ix := range service.Spec.Ports { 5516 port := &service.Spec.Ports[ix] 5517 // This is a workaround for broken cloud environments that 5518 // over-open firewalls. Hopefully it can go away when more clouds 5519 // understand containers better. 5520 if port.Port == ports.KubeletPort { 5521 portPath := specPath.Child("ports").Index(ix) 5522 allErrs = append(allErrs, field.Invalid(portPath, port.Port, fmt.Sprintf("may not expose port %v externally since it is used by kubelet", ports.KubeletPort))) 5523 } 5524 } 5525 if isHeadlessService(service) { 5526 allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIPs").Index(0), service.Spec.ClusterIPs[0], "may not be set to 'None' for LoadBalancer services")) 5527 } 5528 case core.ServiceTypeNodePort: 5529 if isHeadlessService(service) { 5530 allErrs = append(allErrs, field.Invalid(specPath.Child("clusterIPs").Index(0), service.Spec.ClusterIPs[0], "may not be set to 'None' for NodePort services")) 5531 } 5532 case core.ServiceTypeExternalName: 5533 // must have len(.spec.ClusterIPs) == 0 // note: strategy sets ClusterIPs based on ClusterIP 5534 if len(service.Spec.ClusterIPs) > 0 { 5535 allErrs = append(allErrs, field.Forbidden(specPath.Child("clusterIPs"), "may not be set for ExternalName services")) 5536 } 5537 5538 // must have nil families and nil policy 5539 if len(service.Spec.IPFamilies) > 0 { 5540 allErrs = append(allErrs, field.Forbidden(specPath.Child("ipFamilies"), "may not be set for ExternalName services")) 5541 } 5542 if service.Spec.IPFamilyPolicy != nil { 5543 allErrs = append(allErrs, field.Forbidden(specPath.Child("ipFamilyPolicy"), "may not be set for ExternalName services")) 5544 } 5545 5546 // The value (a CNAME) may have a trailing dot to denote it as fully qualified 5547 cname := strings.TrimSuffix(service.Spec.ExternalName, ".") 5548 if len(cname) > 0 { 5549 allErrs = append(allErrs, ValidateDNS1123Subdomain(cname, specPath.Child("externalName"))...) 5550 } else { 5551 allErrs = append(allErrs, field.Required(specPath.Child("externalName"), "")) 5552 } 5553 } 5554 5555 allPortNames := sets.Set[string]{} 5556 portsPath := specPath.Child("ports") 5557 for i := range service.Spec.Ports { 5558 portPath := portsPath.Index(i) 5559 allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, isHeadlessService(service), &allPortNames, portPath)...) 5560 } 5561 5562 if service.Spec.Selector != nil { 5563 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(service.Spec.Selector, specPath.Child("selector"))...) 5564 } 5565 5566 if len(service.Spec.SessionAffinity) == 0 { 5567 allErrs = append(allErrs, field.Required(specPath.Child("sessionAffinity"), "")) 5568 } else if !supportedSessionAffinityType.Has(service.Spec.SessionAffinity) { 5569 allErrs = append(allErrs, field.NotSupported(specPath.Child("sessionAffinity"), service.Spec.SessionAffinity, sets.List(supportedSessionAffinityType))) 5570 } 5571 5572 if service.Spec.SessionAffinity == core.ServiceAffinityClientIP { 5573 allErrs = append(allErrs, validateClientIPAffinityConfig(service.Spec.SessionAffinityConfig, specPath.Child("sessionAffinityConfig"))...) 5574 } else if service.Spec.SessionAffinity == core.ServiceAffinityNone { 5575 if service.Spec.SessionAffinityConfig != nil { 5576 allErrs = append(allErrs, field.Forbidden(specPath.Child("sessionAffinityConfig"), fmt.Sprintf("must not be set when session affinity is %s", core.ServiceAffinityNone))) 5577 } 5578 } 5579 5580 // dualstack <-> ClusterIPs <-> ipfamilies 5581 allErrs = append(allErrs, ValidateServiceClusterIPsRelatedFields(service)...) 5582 5583 ipPath := specPath.Child("externalIPs") 5584 for i, ip := range service.Spec.ExternalIPs { 5585 idxPath := ipPath.Index(i) 5586 if errs := validation.IsValidIP(idxPath, ip); len(errs) != 0 { 5587 allErrs = append(allErrs, errs...) 5588 } else { 5589 allErrs = append(allErrs, ValidateNonSpecialIP(ip, idxPath)...) 5590 } 5591 } 5592 5593 if len(service.Spec.Type) == 0 { 5594 allErrs = append(allErrs, field.Required(specPath.Child("type"), "")) 5595 } else if !supportedServiceType.Has(service.Spec.Type) { 5596 allErrs = append(allErrs, field.NotSupported(specPath.Child("type"), service.Spec.Type, sets.List(supportedServiceType))) 5597 } 5598 5599 if service.Spec.Type == core.ServiceTypeClusterIP { 5600 portsPath := specPath.Child("ports") 5601 for i := range service.Spec.Ports { 5602 portPath := portsPath.Index(i) 5603 if service.Spec.Ports[i].NodePort != 0 { 5604 allErrs = append(allErrs, field.Forbidden(portPath.Child("nodePort"), "may not be used when `type` is 'ClusterIP'")) 5605 } 5606 } 5607 } 5608 5609 // Check for duplicate NodePorts, considering (protocol,port) pairs 5610 portsPath = specPath.Child("ports") 5611 nodePorts := make(map[core.ServicePort]bool) 5612 for i := range service.Spec.Ports { 5613 port := &service.Spec.Ports[i] 5614 if port.NodePort == 0 { 5615 continue 5616 } 5617 portPath := portsPath.Index(i) 5618 var key core.ServicePort 5619 key.Protocol = port.Protocol 5620 key.NodePort = port.NodePort 5621 _, found := nodePorts[key] 5622 if found { 5623 allErrs = append(allErrs, field.Duplicate(portPath.Child("nodePort"), port.NodePort)) 5624 } 5625 nodePorts[key] = true 5626 } 5627 5628 // Check for duplicate Ports, considering (protocol,port) pairs 5629 portsPath = specPath.Child("ports") 5630 ports := make(map[core.ServicePort]bool) 5631 for i, port := range service.Spec.Ports { 5632 portPath := portsPath.Index(i) 5633 key := core.ServicePort{Protocol: port.Protocol, Port: port.Port} 5634 _, found := ports[key] 5635 if found { 5636 allErrs = append(allErrs, field.Duplicate(portPath, key)) 5637 } 5638 ports[key] = true 5639 } 5640 5641 // Validate SourceRanges field or annotation. 5642 if len(service.Spec.LoadBalancerSourceRanges) > 0 { 5643 fieldPath := specPath.Child("LoadBalancerSourceRanges") 5644 5645 if service.Spec.Type != core.ServiceTypeLoadBalancer { 5646 allErrs = append(allErrs, field.Forbidden(fieldPath, "may only be used when `type` is 'LoadBalancer'")) 5647 } 5648 for idx, value := range service.Spec.LoadBalancerSourceRanges { 5649 // Note: due to a historical accident around transition from the 5650 // annotation value, these values are allowed to be space-padded. 5651 value = strings.TrimSpace(value) 5652 allErrs = append(allErrs, validation.IsValidCIDR(fieldPath.Index(idx), value)...) 5653 } 5654 } else if val, annotationSet := service.Annotations[core.AnnotationLoadBalancerSourceRangesKey]; annotationSet { 5655 fieldPath := field.NewPath("metadata", "annotations").Key(core.AnnotationLoadBalancerSourceRangesKey) 5656 if service.Spec.Type != core.ServiceTypeLoadBalancer { 5657 allErrs = append(allErrs, field.Forbidden(fieldPath, "may only be used when `type` is 'LoadBalancer'")) 5658 } 5659 5660 val = strings.TrimSpace(val) 5661 if val != "" { 5662 cidrs := strings.Split(val, ",") 5663 for _, value := range cidrs { 5664 value = strings.TrimSpace(value) 5665 allErrs = append(allErrs, validation.IsValidCIDR(fieldPath, value)...) 5666 } 5667 } 5668 } 5669 5670 if service.Spec.AllocateLoadBalancerNodePorts != nil && service.Spec.Type != core.ServiceTypeLoadBalancer { 5671 allErrs = append(allErrs, field.Forbidden(specPath.Child("allocateLoadBalancerNodePorts"), "may only be used when `type` is 'LoadBalancer'")) 5672 } 5673 5674 if service.Spec.Type == core.ServiceTypeLoadBalancer && service.Spec.AllocateLoadBalancerNodePorts == nil { 5675 allErrs = append(allErrs, field.Required(field.NewPath("allocateLoadBalancerNodePorts"), "")) 5676 } 5677 5678 // validate LoadBalancerClass field 5679 allErrs = append(allErrs, validateLoadBalancerClassField(nil, service)...) 5680 5681 // external traffic policy fields 5682 allErrs = append(allErrs, validateServiceExternalTrafficPolicy(service)...) 5683 5684 // internal traffic policy field 5685 allErrs = append(allErrs, validateServiceInternalTrafficFieldsValue(service)...) 5686 5687 // traffic distribution field 5688 allErrs = append(allErrs, validateServiceTrafficDistribution(service)...) 5689 5690 return allErrs 5691 } 5692 5693 func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bool, allNames *sets.Set[string], fldPath *field.Path) field.ErrorList { 5694 allErrs := field.ErrorList{} 5695 5696 if requireName && len(sp.Name) == 0 { 5697 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 5698 } else if len(sp.Name) != 0 { 5699 allErrs = append(allErrs, ValidateDNS1123Label(sp.Name, fldPath.Child("name"))...) 5700 if allNames.Has(sp.Name) { 5701 allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), sp.Name)) 5702 } else { 5703 allNames.Insert(sp.Name) 5704 } 5705 } 5706 5707 for _, msg := range validation.IsValidPortNum(int(sp.Port)) { 5708 allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), sp.Port, msg)) 5709 } 5710 5711 if len(sp.Protocol) == 0 { 5712 allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) 5713 } else if !supportedPortProtocols.Has(sp.Protocol) { 5714 allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), sp.Protocol, sets.List(supportedPortProtocols))) 5715 } 5716 5717 allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...) 5718 5719 if sp.AppProtocol != nil { 5720 allErrs = append(allErrs, ValidateQualifiedName(*sp.AppProtocol, fldPath.Child("appProtocol"))...) 5721 } 5722 5723 // in the v1 API, targetPorts on headless services were tolerated. 5724 // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. 5725 // 5726 // if isHeadlessService { 5727 // if sp.TargetPort.Type == intstr.String || (sp.TargetPort.Type == intstr.Int && sp.Port != sp.TargetPort.IntValue()) { 5728 // allErrs = append(allErrs, field.Invalid(fldPath.Child("targetPort"), sp.TargetPort, "must be equal to the value of 'port' when clusterIP = None")) 5729 // } 5730 // } 5731 5732 return allErrs 5733 } 5734 5735 var validExternalTrafficPolicies = sets.New(core.ServiceExternalTrafficPolicyCluster, core.ServiceExternalTrafficPolicyLocal) 5736 5737 func validateServiceExternalTrafficPolicy(service *core.Service) field.ErrorList { 5738 allErrs := field.ErrorList{} 5739 5740 fldPath := field.NewPath("spec") 5741 5742 if !apiservice.ExternallyAccessible(service) { 5743 if service.Spec.ExternalTrafficPolicy != "" { 5744 allErrs = append(allErrs, field.Invalid(fldPath.Child("externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy, 5745 "may only be set for externally-accessible services")) 5746 } 5747 } else { 5748 if service.Spec.ExternalTrafficPolicy == "" { 5749 allErrs = append(allErrs, field.Required(fldPath.Child("externalTrafficPolicy"), "")) 5750 } else if !validExternalTrafficPolicies.Has(service.Spec.ExternalTrafficPolicy) { 5751 allErrs = append(allErrs, field.NotSupported(fldPath.Child("externalTrafficPolicy"), 5752 service.Spec.ExternalTrafficPolicy, sets.List(validExternalTrafficPolicies))) 5753 } 5754 } 5755 5756 if !apiservice.NeedsHealthCheck(service) { 5757 if service.Spec.HealthCheckNodePort != 0 { 5758 allErrs = append(allErrs, field.Invalid(fldPath.Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort, 5759 "may only be set when `type` is 'LoadBalancer' and `externalTrafficPolicy` is 'Local'")) 5760 } 5761 } else { 5762 if service.Spec.HealthCheckNodePort == 0 { 5763 allErrs = append(allErrs, field.Required(fldPath.Child("healthCheckNodePort"), "")) 5764 } else { 5765 for _, msg := range validation.IsValidPortNum(int(service.Spec.HealthCheckNodePort)) { 5766 allErrs = append(allErrs, field.Invalid(fldPath.Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort, msg)) 5767 } 5768 } 5769 } 5770 5771 return allErrs 5772 } 5773 5774 func validateServiceExternalTrafficFieldsUpdate(before, after *core.Service) field.ErrorList { 5775 allErrs := field.ErrorList{} 5776 5777 if apiservice.NeedsHealthCheck(before) && apiservice.NeedsHealthCheck(after) { 5778 if after.Spec.HealthCheckNodePort != before.Spec.HealthCheckNodePort { 5779 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "healthCheckNodePort"), "field is immutable")) 5780 } 5781 } 5782 5783 return allErrs 5784 } 5785 5786 // validateServiceInternalTrafficFieldsValue validates InternalTraffic related 5787 // spec have legal value. 5788 func validateServiceInternalTrafficFieldsValue(service *core.Service) field.ErrorList { 5789 allErrs := field.ErrorList{} 5790 5791 if service.Spec.InternalTrafficPolicy == nil { 5792 // We do not forbid internalTrafficPolicy on other Service types because of historical reasons. 5793 // We did not check that before it went beta and we don't want to invalidate existing stored objects. 5794 if service.Spec.Type == core.ServiceTypeNodePort || 5795 service.Spec.Type == core.ServiceTypeLoadBalancer || service.Spec.Type == core.ServiceTypeClusterIP { 5796 allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("internalTrafficPolicy"), "")) 5797 } 5798 } 5799 5800 if service.Spec.InternalTrafficPolicy != nil && !supportedServiceInternalTrafficPolicy.Has(*service.Spec.InternalTrafficPolicy) { 5801 allErrs = append(allErrs, field.NotSupported(field.NewPath("spec").Child("internalTrafficPolicy"), *service.Spec.InternalTrafficPolicy, sets.List(supportedServiceInternalTrafficPolicy))) 5802 } 5803 5804 return allErrs 5805 } 5806 5807 // validateServiceTrafficDistribution validates the values for the 5808 // trafficDistribution field. 5809 func validateServiceTrafficDistribution(service *core.Service) field.ErrorList { 5810 allErrs := field.ErrorList{} 5811 5812 if service.Spec.TrafficDistribution == nil { 5813 return allErrs 5814 } 5815 5816 if *service.Spec.TrafficDistribution != v1.ServiceTrafficDistributionPreferClose { 5817 allErrs = append(allErrs, field.NotSupported(field.NewPath("spec").Child("trafficDistribution"), *service.Spec.TrafficDistribution, []string{v1.ServiceTrafficDistributionPreferClose})) 5818 } 5819 5820 return allErrs 5821 } 5822 5823 // ValidateServiceCreate validates Services as they are created. 5824 func ValidateServiceCreate(service *core.Service) field.ErrorList { 5825 return ValidateService(service) 5826 } 5827 5828 // ValidateServiceUpdate tests if required fields in the service are set during an update 5829 func ValidateServiceUpdate(service, oldService *core.Service) field.ErrorList { 5830 allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata")) 5831 5832 // User can upgrade (add another clusterIP or ipFamily) 5833 // can downgrade (remove secondary clusterIP or ipFamily) 5834 // but *CAN NOT* change primary/secondary clusterIP || ipFamily *UNLESS* 5835 // they are changing from/to/ON ExternalName 5836 5837 upgradeDowngradeClusterIPsErrs := validateUpgradeDowngradeClusterIPs(oldService, service) 5838 allErrs = append(allErrs, upgradeDowngradeClusterIPsErrs...) 5839 5840 upgradeDowngradeIPFamiliesErrs := validateUpgradeDowngradeIPFamilies(oldService, service) 5841 allErrs = append(allErrs, upgradeDowngradeIPFamiliesErrs...) 5842 5843 upgradeDowngradeLoadBalancerClassErrs := validateLoadBalancerClassField(oldService, service) 5844 allErrs = append(allErrs, upgradeDowngradeLoadBalancerClassErrs...) 5845 5846 allErrs = append(allErrs, validateServiceExternalTrafficFieldsUpdate(oldService, service)...) 5847 5848 return append(allErrs, ValidateService(service)...) 5849 } 5850 5851 // ValidateServiceStatusUpdate tests if required fields in the Service are set when updating status. 5852 func ValidateServiceStatusUpdate(service, oldService *core.Service) field.ErrorList { 5853 allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata")) 5854 allErrs = append(allErrs, ValidateLoadBalancerStatus(&service.Status.LoadBalancer, field.NewPath("status", "loadBalancer"), &service.Spec)...) 5855 return allErrs 5856 } 5857 5858 // ValidateReplicationController tests if required fields in the replication controller are set. 5859 func ValidateReplicationController(controller *core.ReplicationController, opts PodValidationOptions) field.ErrorList { 5860 allErrs := ValidateObjectMeta(&controller.ObjectMeta, true, ValidateReplicationControllerName, field.NewPath("metadata")) 5861 allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, nil, field.NewPath("spec"), opts)...) 5862 return allErrs 5863 } 5864 5865 // ValidateReplicationControllerUpdate tests if required fields in the replication controller are set. 5866 func ValidateReplicationControllerUpdate(controller, oldController *core.ReplicationController, opts PodValidationOptions) field.ErrorList { 5867 allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) 5868 allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec, &oldController.Spec, field.NewPath("spec"), opts)...) 5869 return allErrs 5870 } 5871 5872 // ValidateReplicationControllerStatusUpdate tests if required fields in the replication controller are set. 5873 func ValidateReplicationControllerStatusUpdate(controller, oldController *core.ReplicationController) field.ErrorList { 5874 allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) 5875 allErrs = append(allErrs, ValidateReplicationControllerStatus(controller.Status, field.NewPath("status"))...) 5876 return allErrs 5877 } 5878 5879 func ValidateReplicationControllerStatus(status core.ReplicationControllerStatus, statusPath *field.Path) field.ErrorList { 5880 allErrs := field.ErrorList{} 5881 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.Replicas), statusPath.Child("replicas"))...) 5882 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.FullyLabeledReplicas), statusPath.Child("fullyLabeledReplicas"))...) 5883 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ReadyReplicas), statusPath.Child("readyReplicas"))...) 5884 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.AvailableReplicas), statusPath.Child("availableReplicas"))...) 5885 allErrs = append(allErrs, ValidateNonnegativeField(int64(status.ObservedGeneration), statusPath.Child("observedGeneration"))...) 5886 msg := "cannot be greater than status.replicas" 5887 if status.FullyLabeledReplicas > status.Replicas { 5888 allErrs = append(allErrs, field.Invalid(statusPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg)) 5889 } 5890 if status.ReadyReplicas > status.Replicas { 5891 allErrs = append(allErrs, field.Invalid(statusPath.Child("readyReplicas"), status.ReadyReplicas, msg)) 5892 } 5893 if status.AvailableReplicas > status.Replicas { 5894 allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, msg)) 5895 } 5896 if status.AvailableReplicas > status.ReadyReplicas { 5897 allErrs = append(allErrs, field.Invalid(statusPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas")) 5898 } 5899 return allErrs 5900 } 5901 5902 // Validates that the given selector is non-empty. 5903 func ValidateNonEmptySelector(selectorMap map[string]string, fldPath *field.Path) field.ErrorList { 5904 allErrs := field.ErrorList{} 5905 selector := labels.Set(selectorMap).AsSelector() 5906 if selector.Empty() { 5907 allErrs = append(allErrs, field.Required(fldPath, "")) 5908 } 5909 return allErrs 5910 } 5911 5912 // Validates the given template and ensures that it is in accordance with the desired selector and replicas. 5913 func ValidatePodTemplateSpecForRC(template *core.PodTemplateSpec, selectorMap map[string]string, replicas int32, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 5914 allErrs := field.ErrorList{} 5915 if template == nil { 5916 allErrs = append(allErrs, field.Required(fldPath, "")) 5917 } else { 5918 selector := labels.Set(selectorMap).AsSelector() 5919 if !selector.Empty() { 5920 // Verify that the RC selector matches the labels in template. 5921 labels := labels.Set(template.Labels) 5922 if !selector.Matches(labels) { 5923 allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`")) 5924 } 5925 } 5926 allErrs = append(allErrs, ValidatePodTemplateSpec(template, fldPath, opts)...) 5927 // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). 5928 if template.Spec.RestartPolicy != core.RestartPolicyAlways { 5929 allErrs = append(allErrs, field.NotSupported(fldPath.Child("spec", "restartPolicy"), template.Spec.RestartPolicy, []core.RestartPolicy{core.RestartPolicyAlways})) 5930 } 5931 if template.Spec.ActiveDeadlineSeconds != nil { 5932 allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "activeDeadlineSeconds"), "activeDeadlineSeconds in ReplicationController is not Supported")) 5933 } 5934 } 5935 return allErrs 5936 } 5937 5938 // ValidateReplicationControllerSpec tests if required fields in the replication controller spec are set. 5939 func ValidateReplicationControllerSpec(spec, oldSpec *core.ReplicationControllerSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 5940 allErrs := field.ErrorList{} 5941 allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...) 5942 allErrs = append(allErrs, ValidateNonEmptySelector(spec.Selector, fldPath.Child("selector"))...) 5943 allErrs = append(allErrs, ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) 5944 allErrs = append(allErrs, ValidatePodTemplateSpecForRC(spec.Template, spec.Selector, spec.Replicas, fldPath.Child("template"), opts)...) 5945 return allErrs 5946 } 5947 5948 // ValidatePodTemplateSpec validates the spec of a pod template 5949 func ValidatePodTemplateSpec(spec *core.PodTemplateSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 5950 allErrs := field.ErrorList{} 5951 allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...) 5952 allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...) 5953 allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"), opts)...) 5954 allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, nil, fldPath.Child("spec"), opts)...) 5955 allErrs = append(allErrs, validateSeccompAnnotationsAndFields(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...) 5956 allErrs = append(allErrs, validateAppArmorAnnotationsAndFieldsMatchOnCreate(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...) 5957 5958 if len(spec.Spec.EphemeralContainers) > 0 { 5959 allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "ephemeralContainers"), "ephemeral containers not allowed in pod template")) 5960 } 5961 5962 return allErrs 5963 } 5964 5965 // ValidateTaintsInNodeAnnotations tests that the serialized taints in Node.Annotations has valid data 5966 func ValidateTaintsInNodeAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 5967 allErrs := field.ErrorList{} 5968 5969 taints, err := helper.GetTaintsFromNodeAnnotations(annotations) 5970 if err != nil { 5971 allErrs = append(allErrs, field.Invalid(fldPath, core.TaintsAnnotationKey, err.Error())) 5972 return allErrs 5973 } 5974 5975 if len(taints) > 0 { 5976 allErrs = append(allErrs, validateNodeTaints(taints, fldPath.Child(core.TaintsAnnotationKey))...) 5977 } 5978 5979 return allErrs 5980 } 5981 5982 // validateNodeTaints tests if given taints have valid data. 5983 func validateNodeTaints(taints []core.Taint, fldPath *field.Path) field.ErrorList { 5984 allErrors := field.ErrorList{} 5985 5986 uniqueTaints := map[core.TaintEffect]sets.Set[string]{} 5987 5988 for i, currTaint := range taints { 5989 idxPath := fldPath.Index(i) 5990 // validate the taint key 5991 allErrors = append(allErrors, unversionedvalidation.ValidateLabelName(currTaint.Key, idxPath.Child("key"))...) 5992 // validate the taint value 5993 if errs := validation.IsValidLabelValue(currTaint.Value); len(errs) != 0 { 5994 allErrors = append(allErrors, field.Invalid(idxPath.Child("value"), currTaint.Value, strings.Join(errs, ";"))) 5995 } 5996 // validate the taint effect 5997 allErrors = append(allErrors, validateTaintEffect(&currTaint.Effect, false, idxPath.Child("effect"))...) 5998 5999 // validate if taint is unique by <key, effect> 6000 if len(uniqueTaints[currTaint.Effect]) > 0 && uniqueTaints[currTaint.Effect].Has(currTaint.Key) { 6001 duplicatedError := field.Duplicate(idxPath, currTaint) 6002 duplicatedError.Detail = "taints must be unique by key and effect pair" 6003 allErrors = append(allErrors, duplicatedError) 6004 continue 6005 } 6006 6007 // add taint to existingTaints for uniqueness check 6008 if len(uniqueTaints[currTaint.Effect]) == 0 { 6009 uniqueTaints[currTaint.Effect] = sets.Set[string]{} 6010 } 6011 uniqueTaints[currTaint.Effect].Insert(currTaint.Key) 6012 } 6013 return allErrors 6014 } 6015 6016 func ValidateNodeSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { 6017 allErrs := field.ErrorList{} 6018 6019 if annotations[core.TaintsAnnotationKey] != "" { 6020 allErrs = append(allErrs, ValidateTaintsInNodeAnnotations(annotations, fldPath)...) 6021 } 6022 6023 if annotations[core.PreferAvoidPodsAnnotationKey] != "" { 6024 allErrs = append(allErrs, ValidateAvoidPodsInNodeAnnotations(annotations, fldPath)...) 6025 } 6026 return allErrs 6027 } 6028 6029 // ValidateNode tests if required fields in the node are set. 6030 func ValidateNode(node *core.Node) field.ErrorList { 6031 fldPath := field.NewPath("metadata") 6032 allErrs := ValidateObjectMeta(&node.ObjectMeta, false, ValidateNodeName, fldPath) 6033 allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) 6034 if len(node.Spec.Taints) > 0 { 6035 allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) 6036 } 6037 6038 // Only validate spec. 6039 // All status fields are optional and can be updated later. 6040 // That said, if specified, we need to ensure they are valid. 6041 allErrs = append(allErrs, ValidateNodeResources(node)...) 6042 6043 // validate PodCIDRS only if we need to 6044 if len(node.Spec.PodCIDRs) > 0 { 6045 podCIDRsField := field.NewPath("spec", "podCIDRs") 6046 6047 // all PodCIDRs should be valid ones 6048 for idx, value := range node.Spec.PodCIDRs { 6049 allErrs = append(allErrs, validation.IsValidCIDR(podCIDRsField.Index(idx), value)...) 6050 } 6051 6052 // if more than PodCIDR then 6053 // - validate for dual stack 6054 // - validate for duplication 6055 if len(node.Spec.PodCIDRs) > 1 { 6056 dualStack, err := netutils.IsDualStackCIDRStrings(node.Spec.PodCIDRs) 6057 if err != nil { 6058 allErrs = append(allErrs, field.InternalError(podCIDRsField, fmt.Errorf("invalid PodCIDRs. failed to check with dual stack with error:%v", err))) 6059 } 6060 if !dualStack || len(node.Spec.PodCIDRs) > 2 { 6061 allErrs = append(allErrs, field.Invalid(podCIDRsField, node.Spec.PodCIDRs, "may specify no more than one CIDR for each IP family")) 6062 } 6063 6064 // PodCIDRs must not contain duplicates 6065 seen := sets.Set[string]{} 6066 for i, value := range node.Spec.PodCIDRs { 6067 if seen.Has(value) { 6068 allErrs = append(allErrs, field.Duplicate(podCIDRsField.Index(i), value)) 6069 } 6070 seen.Insert(value) 6071 } 6072 } 6073 } 6074 6075 return allErrs 6076 } 6077 6078 // ValidateNodeResources is used to make sure a node has valid capacity and allocatable values. 6079 func ValidateNodeResources(node *core.Node) field.ErrorList { 6080 allErrs := field.ErrorList{} 6081 6082 // Validate resource quantities in capacity. 6083 for k, v := range node.Status.Capacity { 6084 resPath := field.NewPath("status", "capacity", string(k)) 6085 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6086 } 6087 6088 // Validate resource quantities in allocatable. 6089 for k, v := range node.Status.Allocatable { 6090 resPath := field.NewPath("status", "allocatable", string(k)) 6091 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6092 } 6093 return allErrs 6094 } 6095 6096 // ValidateNodeUpdate tests to make sure a node update can be applied. Modifies oldNode. 6097 func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList { 6098 fldPath := field.NewPath("metadata") 6099 allErrs := ValidateObjectMetaUpdate(&node.ObjectMeta, &oldNode.ObjectMeta, fldPath) 6100 allErrs = append(allErrs, ValidateNodeSpecificAnnotations(node.ObjectMeta.Annotations, fldPath.Child("annotations"))...) 6101 6102 // TODO: Enable the code once we have better core object.status update model. Currently, 6103 // anyone can update node status. 6104 // if !apiequality.Semantic.DeepEqual(node.Status, core.NodeStatus{}) { 6105 // allErrs = append(allErrs, field.Invalid("status", node.Status, "must be empty")) 6106 // } 6107 6108 allErrs = append(allErrs, ValidateNodeResources(node)...) 6109 6110 // Validate no duplicate addresses in node status. 6111 addresses := make(map[core.NodeAddress]bool) 6112 for i, address := range node.Status.Addresses { 6113 if _, ok := addresses[address]; ok { 6114 allErrs = append(allErrs, field.Duplicate(field.NewPath("status", "addresses").Index(i), address)) 6115 } 6116 addresses[address] = true 6117 } 6118 6119 // Allow the controller manager to assign a CIDR to a node if it doesn't have one. 6120 if len(oldNode.Spec.PodCIDRs) > 0 { 6121 // compare the entire slice 6122 if len(oldNode.Spec.PodCIDRs) != len(node.Spec.PodCIDRs) { 6123 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDRs"), "node updates may not change podCIDR except from \"\" to valid")) 6124 } else { 6125 for idx, value := range oldNode.Spec.PodCIDRs { 6126 if value != node.Spec.PodCIDRs[idx] { 6127 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "podCIDRs"), "node updates may not change podCIDR except from \"\" to valid")) 6128 } 6129 } 6130 } 6131 } 6132 6133 // Allow controller manager updating provider ID when not set 6134 if len(oldNode.Spec.ProviderID) > 0 && oldNode.Spec.ProviderID != node.Spec.ProviderID { 6135 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "providerID"), "node updates may not change providerID except from \"\" to valid")) 6136 } 6137 6138 if node.Spec.ConfigSource != nil { 6139 allErrs = append(allErrs, validateNodeConfigSourceSpec(node.Spec.ConfigSource, field.NewPath("spec", "configSource"))...) 6140 } 6141 if node.Status.Config != nil { 6142 allErrs = append(allErrs, validateNodeConfigStatus(node.Status.Config, field.NewPath("status", "config"))...) 6143 } 6144 6145 // update taints 6146 if len(node.Spec.Taints) > 0 { 6147 allErrs = append(allErrs, validateNodeTaints(node.Spec.Taints, fldPath.Child("taints"))...) 6148 } 6149 6150 if node.Spec.DoNotUseExternalID != oldNode.Spec.DoNotUseExternalID { 6151 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "externalID"), "may not be updated")) 6152 } 6153 6154 // status and metadata are allowed change (barring restrictions above), so separately test spec field. 6155 // spec only has a few fields, so check the ones we don't allow changing 6156 // 1. PodCIDRs - immutable after first set - checked above 6157 // 2. ProviderID - immutable after first set - checked above 6158 // 3. Unschedulable - allowed to change 6159 // 4. Taints - allowed to change 6160 // 5. ConfigSource - allowed to change (and checked above) 6161 // 6. DoNotUseExternalID - immutable - checked above 6162 6163 return allErrs 6164 } 6165 6166 // validation specific to Node.Spec.ConfigSource 6167 // The field ConfigSource is deprecated and will not be used. The validation is kept in place 6168 // for the backward compatibility 6169 func validateNodeConfigSourceSpec(source *core.NodeConfigSource, fldPath *field.Path) field.ErrorList { 6170 allErrs := field.ErrorList{} 6171 count := int(0) 6172 if source.ConfigMap != nil { 6173 count++ 6174 allErrs = append(allErrs, validateConfigMapNodeConfigSourceSpec(source.ConfigMap, fldPath.Child("configMap"))...) 6175 } 6176 // add more subfields here in the future as they are added to NodeConfigSource 6177 6178 // exactly one reference subfield must be non-nil 6179 if count != 1 { 6180 allErrs = append(allErrs, field.Invalid(fldPath, source, "exactly one reference subfield must be non-nil")) 6181 } 6182 return allErrs 6183 } 6184 6185 // validation specific to Node.Spec.ConfigSource.ConfigMap 6186 // The field ConfigSource is deprecated and will not be used. The validation is kept in place 6187 // for the backward compatibility 6188 func validateConfigMapNodeConfigSourceSpec(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList { 6189 allErrs := field.ErrorList{} 6190 // uid and resourceVersion must not be set in spec 6191 if string(source.UID) != "" { 6192 allErrs = append(allErrs, field.Forbidden(fldPath.Child("uid"), "uid must not be set in spec")) 6193 } 6194 if source.ResourceVersion != "" { 6195 allErrs = append(allErrs, field.Forbidden(fldPath.Child("resourceVersion"), "resourceVersion must not be set in spec")) 6196 } 6197 return append(allErrs, validateConfigMapNodeConfigSource(source, fldPath)...) 6198 } 6199 6200 // validation specififc to Node.Status.Config 6201 func validateNodeConfigStatus(status *core.NodeConfigStatus, fldPath *field.Path) field.ErrorList { 6202 allErrs := field.ErrorList{} 6203 if status.Assigned != nil { 6204 allErrs = append(allErrs, validateNodeConfigSourceStatus(status.Assigned, fldPath.Child("assigned"))...) 6205 } 6206 if status.Active != nil { 6207 allErrs = append(allErrs, validateNodeConfigSourceStatus(status.Active, fldPath.Child("active"))...) 6208 } 6209 if status.LastKnownGood != nil { 6210 allErrs = append(allErrs, validateNodeConfigSourceStatus(status.LastKnownGood, fldPath.Child("lastKnownGood"))...) 6211 } 6212 return allErrs 6213 } 6214 6215 // validation specific to Node.Status.Config.(Active|Assigned|LastKnownGood) 6216 func validateNodeConfigSourceStatus(source *core.NodeConfigSource, fldPath *field.Path) field.ErrorList { 6217 allErrs := field.ErrorList{} 6218 count := int(0) 6219 if source.ConfigMap != nil { 6220 count++ 6221 allErrs = append(allErrs, validateConfigMapNodeConfigSourceStatus(source.ConfigMap, fldPath.Child("configMap"))...) 6222 } 6223 // add more subfields here in the future as they are added to NodeConfigSource 6224 6225 // exactly one reference subfield must be non-nil 6226 if count != 1 { 6227 allErrs = append(allErrs, field.Invalid(fldPath, source, "exactly one reference subfield must be non-nil")) 6228 } 6229 return allErrs 6230 } 6231 6232 // validation specific to Node.Status.Config.(Active|Assigned|LastKnownGood).ConfigMap 6233 func validateConfigMapNodeConfigSourceStatus(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList { 6234 allErrs := field.ErrorList{} 6235 // uid and resourceVersion must be set in status 6236 if string(source.UID) == "" { 6237 allErrs = append(allErrs, field.Required(fldPath.Child("uid"), "uid must be set in status")) 6238 } 6239 if source.ResourceVersion == "" { 6240 allErrs = append(allErrs, field.Required(fldPath.Child("resourceVersion"), "resourceVersion must be set in status")) 6241 } 6242 return append(allErrs, validateConfigMapNodeConfigSource(source, fldPath)...) 6243 } 6244 6245 // common validation 6246 func validateConfigMapNodeConfigSource(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList { 6247 allErrs := field.ErrorList{} 6248 // validate target configmap namespace 6249 if source.Namespace == "" { 6250 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "namespace must be set")) 6251 } else { 6252 for _, msg := range ValidateNameFunc(ValidateNamespaceName)(source.Namespace, false) { 6253 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), source.Namespace, msg)) 6254 } 6255 } 6256 // validate target configmap name 6257 if source.Name == "" { 6258 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name must be set")) 6259 } else { 6260 for _, msg := range ValidateNameFunc(ValidateConfigMapName)(source.Name, false) { 6261 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), source.Name, msg)) 6262 } 6263 } 6264 // validate kubeletConfigKey against rules for configMap key names 6265 if source.KubeletConfigKey == "" { 6266 allErrs = append(allErrs, field.Required(fldPath.Child("kubeletConfigKey"), "kubeletConfigKey must be set")) 6267 } else { 6268 for _, msg := range validation.IsConfigMapKey(source.KubeletConfigKey) { 6269 allErrs = append(allErrs, field.Invalid(fldPath.Child("kubeletConfigKey"), source.KubeletConfigKey, msg)) 6270 } 6271 } 6272 return allErrs 6273 } 6274 6275 // Validate compute resource typename. 6276 // Refer to docs/design/resources.md for more details. 6277 func validateResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList { 6278 allErrs := field.ErrorList{} 6279 for _, msg := range validation.IsQualifiedName(string(value)) { 6280 allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) 6281 } 6282 if len(allErrs) != 0 { 6283 return allErrs 6284 } 6285 6286 if len(strings.Split(string(value), "/")) == 1 { 6287 if !helper.IsStandardResourceName(value) { 6288 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified")) 6289 } 6290 } 6291 6292 return allErrs 6293 } 6294 6295 // Validate container resource name 6296 // Refer to docs/design/resources.md for more details. 6297 func validateContainerResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList { 6298 allErrs := validateResourceName(value, fldPath) 6299 6300 if len(strings.Split(string(value), "/")) == 1 { 6301 if !helper.IsStandardContainerResourceName(value) { 6302 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) 6303 } 6304 } else if !helper.IsNativeResource(value) { 6305 if !helper.IsExtendedResourceName(value) { 6306 return append(allErrs, field.Invalid(fldPath, value, "doesn't follow extended resource name standard")) 6307 } 6308 } 6309 return allErrs 6310 } 6311 6312 // Validate resource names that can go in a resource quota 6313 // Refer to docs/design/resources.md for more details. 6314 func ValidateResourceQuotaResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList { 6315 allErrs := validateResourceName(value, fldPath) 6316 6317 if len(strings.Split(string(value), "/")) == 1 { 6318 if !helper.IsStandardQuotaResourceName(value) { 6319 return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource)) 6320 } 6321 } 6322 return allErrs 6323 } 6324 6325 // Validate limit range types 6326 func validateLimitRangeTypeName(value core.LimitType, fldPath *field.Path) field.ErrorList { 6327 allErrs := field.ErrorList{} 6328 for _, msg := range validation.IsQualifiedName(string(value)) { 6329 allErrs = append(allErrs, field.Invalid(fldPath, value, msg)) 6330 } 6331 if len(allErrs) != 0 { 6332 return allErrs 6333 } 6334 6335 if len(strings.Split(string(value), "/")) == 1 { 6336 if !helper.IsStandardLimitRangeType(value) { 6337 return append(allErrs, field.Invalid(fldPath, value, "must be a standard limit type or fully qualified")) 6338 } 6339 } 6340 6341 return allErrs 6342 } 6343 6344 // Validate limit range resource name 6345 // limit types (other than Pod/Container) could contain storage not just cpu or memory 6346 func validateLimitRangeResourceName(limitType core.LimitType, value core.ResourceName, fldPath *field.Path) field.ErrorList { 6347 switch limitType { 6348 case core.LimitTypePod, core.LimitTypeContainer: 6349 return validateContainerResourceName(value, fldPath) 6350 default: 6351 return validateResourceName(value, fldPath) 6352 } 6353 } 6354 6355 // ValidateLimitRange tests if required fields in the LimitRange are set. 6356 func ValidateLimitRange(limitRange *core.LimitRange) field.ErrorList { 6357 allErrs := ValidateObjectMeta(&limitRange.ObjectMeta, true, ValidateLimitRangeName, field.NewPath("metadata")) 6358 6359 // ensure resource names are properly qualified per docs/design/resources.md 6360 limitTypeSet := map[core.LimitType]bool{} 6361 fldPath := field.NewPath("spec", "limits") 6362 for i := range limitRange.Spec.Limits { 6363 idxPath := fldPath.Index(i) 6364 limit := &limitRange.Spec.Limits[i] 6365 allErrs = append(allErrs, validateLimitRangeTypeName(limit.Type, idxPath.Child("type"))...) 6366 6367 _, found := limitTypeSet[limit.Type] 6368 if found { 6369 allErrs = append(allErrs, field.Duplicate(idxPath.Child("type"), limit.Type)) 6370 } 6371 limitTypeSet[limit.Type] = true 6372 6373 keys := sets.Set[string]{} 6374 min := map[string]resource.Quantity{} 6375 max := map[string]resource.Quantity{} 6376 defaults := map[string]resource.Quantity{} 6377 defaultRequests := map[string]resource.Quantity{} 6378 maxLimitRequestRatios := map[string]resource.Quantity{} 6379 6380 for k, q := range limit.Max { 6381 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("max").Key(string(k)))...) 6382 keys.Insert(string(k)) 6383 max[string(k)] = q 6384 } 6385 for k, q := range limit.Min { 6386 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("min").Key(string(k)))...) 6387 keys.Insert(string(k)) 6388 min[string(k)] = q 6389 } 6390 6391 if limit.Type == core.LimitTypePod { 6392 if len(limit.Default) > 0 { 6393 allErrs = append(allErrs, field.Forbidden(idxPath.Child("default"), "may not be specified when `type` is 'Pod'")) 6394 } 6395 if len(limit.DefaultRequest) > 0 { 6396 allErrs = append(allErrs, field.Forbidden(idxPath.Child("defaultRequest"), "may not be specified when `type` is 'Pod'")) 6397 } 6398 } else { 6399 for k, q := range limit.Default { 6400 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("default").Key(string(k)))...) 6401 keys.Insert(string(k)) 6402 defaults[string(k)] = q 6403 } 6404 for k, q := range limit.DefaultRequest { 6405 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("defaultRequest").Key(string(k)))...) 6406 keys.Insert(string(k)) 6407 defaultRequests[string(k)] = q 6408 } 6409 } 6410 6411 if limit.Type == core.LimitTypePersistentVolumeClaim { 6412 _, minQuantityFound := limit.Min[core.ResourceStorage] 6413 _, maxQuantityFound := limit.Max[core.ResourceStorage] 6414 if !minQuantityFound && !maxQuantityFound { 6415 allErrs = append(allErrs, field.Required(idxPath.Child("limits"), "either minimum or maximum storage value is required, but neither was provided")) 6416 } 6417 } 6418 6419 for k, q := range limit.MaxLimitRequestRatio { 6420 allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, k, idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) 6421 keys.Insert(string(k)) 6422 maxLimitRequestRatios[string(k)] = q 6423 } 6424 6425 for k := range keys { 6426 minQuantity, minQuantityFound := min[k] 6427 maxQuantity, maxQuantityFound := max[k] 6428 defaultQuantity, defaultQuantityFound := defaults[k] 6429 defaultRequestQuantity, defaultRequestQuantityFound := defaultRequests[k] 6430 maxRatio, maxRatioFound := maxLimitRequestRatios[k] 6431 6432 if minQuantityFound && maxQuantityFound && minQuantity.Cmp(maxQuantity) > 0 { 6433 allErrs = append(allErrs, field.Invalid(idxPath.Child("min").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than max value %s", minQuantity.String(), maxQuantity.String()))) 6434 } 6435 6436 if defaultRequestQuantityFound && minQuantityFound && minQuantity.Cmp(defaultRequestQuantity) > 0 { 6437 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("min value %s is greater than default request value %s", minQuantity.String(), defaultRequestQuantity.String()))) 6438 } 6439 6440 if defaultRequestQuantityFound && maxQuantityFound && defaultRequestQuantity.Cmp(maxQuantity) > 0 { 6441 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than max value %s", defaultRequestQuantity.String(), maxQuantity.String()))) 6442 } 6443 6444 if defaultRequestQuantityFound && defaultQuantityFound && defaultRequestQuantity.Cmp(defaultQuantity) > 0 { 6445 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default request value %s is greater than default limit value %s", defaultRequestQuantity.String(), defaultQuantity.String()))) 6446 } 6447 6448 if defaultQuantityFound && minQuantityFound && minQuantity.Cmp(defaultQuantity) > 0 { 6449 allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), minQuantity, fmt.Sprintf("min value %s is greater than default value %s", minQuantity.String(), defaultQuantity.String()))) 6450 } 6451 6452 if defaultQuantityFound && maxQuantityFound && defaultQuantity.Cmp(maxQuantity) > 0 { 6453 allErrs = append(allErrs, field.Invalid(idxPath.Child("default").Key(string(k)), maxQuantity, fmt.Sprintf("default value %s is greater than max value %s", defaultQuantity.String(), maxQuantity.String()))) 6454 } 6455 if maxRatioFound && maxRatio.Cmp(*resource.NewQuantity(1, resource.DecimalSI)) < 0 { 6456 allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is less than 1", maxRatio.String()))) 6457 } 6458 if maxRatioFound && minQuantityFound && maxQuantityFound { 6459 maxRatioValue := float64(maxRatio.Value()) 6460 minQuantityValue := minQuantity.Value() 6461 maxQuantityValue := maxQuantity.Value() 6462 if maxRatio.Value() < resource.MaxMilliValue && minQuantityValue < resource.MaxMilliValue && maxQuantityValue < resource.MaxMilliValue { 6463 maxRatioValue = float64(maxRatio.MilliValue()) / 1000 6464 minQuantityValue = minQuantity.MilliValue() 6465 maxQuantityValue = maxQuantity.MilliValue() 6466 } 6467 maxRatioLimit := float64(maxQuantityValue) / float64(minQuantityValue) 6468 if maxRatioValue > maxRatioLimit { 6469 allErrs = append(allErrs, field.Invalid(idxPath.Child("maxLimitRequestRatio").Key(string(k)), maxRatio, fmt.Sprintf("ratio %s is greater than max/min = %f", maxRatio.String(), maxRatioLimit))) 6470 } 6471 } 6472 6473 // for GPU, hugepages and other resources that are not allowed to overcommit, 6474 // the default value and defaultRequest value must match if both are specified 6475 if !helper.IsOvercommitAllowed(core.ResourceName(k)) && defaultQuantityFound && defaultRequestQuantityFound && defaultQuantity.Cmp(defaultRequestQuantity) != 0 { 6476 allErrs = append(allErrs, field.Invalid(idxPath.Child("defaultRequest").Key(string(k)), defaultRequestQuantity, fmt.Sprintf("default value %s must equal to defaultRequest value %s in %s", defaultQuantity.String(), defaultRequestQuantity.String(), k))) 6477 } 6478 } 6479 } 6480 6481 return allErrs 6482 } 6483 6484 // ValidateServiceAccount tests if required fields in the ServiceAccount are set. 6485 func ValidateServiceAccount(serviceAccount *core.ServiceAccount) field.ErrorList { 6486 allErrs := ValidateObjectMeta(&serviceAccount.ObjectMeta, true, ValidateServiceAccountName, field.NewPath("metadata")) 6487 return allErrs 6488 } 6489 6490 // ValidateServiceAccountUpdate tests if required fields in the ServiceAccount are set. 6491 func ValidateServiceAccountUpdate(newServiceAccount, oldServiceAccount *core.ServiceAccount) field.ErrorList { 6492 allErrs := ValidateObjectMetaUpdate(&newServiceAccount.ObjectMeta, &oldServiceAccount.ObjectMeta, field.NewPath("metadata")) 6493 allErrs = append(allErrs, ValidateServiceAccount(newServiceAccount)...) 6494 return allErrs 6495 } 6496 6497 // ValidateSecret tests if required fields in the Secret are set. 6498 func ValidateSecret(secret *core.Secret) field.ErrorList { 6499 allErrs := ValidateObjectMeta(&secret.ObjectMeta, true, ValidateSecretName, field.NewPath("metadata")) 6500 6501 dataPath := field.NewPath("data") 6502 totalSize := 0 6503 for key, value := range secret.Data { 6504 for _, msg := range validation.IsConfigMapKey(key) { 6505 allErrs = append(allErrs, field.Invalid(dataPath.Key(key), key, msg)) 6506 } 6507 totalSize += len(value) 6508 } 6509 if totalSize > core.MaxSecretSize { 6510 allErrs = append(allErrs, field.TooLong(dataPath, "", core.MaxSecretSize)) 6511 } 6512 6513 switch secret.Type { 6514 case core.SecretTypeServiceAccountToken: 6515 // Only require Annotations[kubernetes.io/service-account.name] 6516 // Additional fields (like Annotations[kubernetes.io/service-account.uid] and Data[token]) might be contributed later by a controller loop 6517 if value := secret.Annotations[core.ServiceAccountNameKey]; len(value) == 0 { 6518 allErrs = append(allErrs, field.Required(field.NewPath("metadata", "annotations").Key(core.ServiceAccountNameKey), "")) 6519 } 6520 case core.SecretTypeOpaque, "": 6521 // no-op 6522 case core.SecretTypeDockercfg: 6523 dockercfgBytes, exists := secret.Data[core.DockerConfigKey] 6524 if !exists { 6525 allErrs = append(allErrs, field.Required(dataPath.Key(core.DockerConfigKey), "")) 6526 break 6527 } 6528 6529 // make sure that the content is well-formed json. 6530 if err := json.Unmarshal(dockercfgBytes, &map[string]interface{}{}); err != nil { 6531 allErrs = append(allErrs, field.Invalid(dataPath.Key(core.DockerConfigKey), "<secret contents redacted>", err.Error())) 6532 } 6533 case core.SecretTypeDockerConfigJSON: 6534 dockerConfigJSONBytes, exists := secret.Data[core.DockerConfigJSONKey] 6535 if !exists { 6536 allErrs = append(allErrs, field.Required(dataPath.Key(core.DockerConfigJSONKey), "")) 6537 break 6538 } 6539 6540 // make sure that the content is well-formed json. 6541 if err := json.Unmarshal(dockerConfigJSONBytes, &map[string]interface{}{}); err != nil { 6542 allErrs = append(allErrs, field.Invalid(dataPath.Key(core.DockerConfigJSONKey), "<secret contents redacted>", err.Error())) 6543 } 6544 case core.SecretTypeBasicAuth: 6545 _, usernameFieldExists := secret.Data[core.BasicAuthUsernameKey] 6546 _, passwordFieldExists := secret.Data[core.BasicAuthPasswordKey] 6547 6548 // username or password might be empty, but the field must be present 6549 if !usernameFieldExists && !passwordFieldExists { 6550 allErrs = append(allErrs, field.Required(dataPath.Key(core.BasicAuthUsernameKey), "")) 6551 allErrs = append(allErrs, field.Required(dataPath.Key(core.BasicAuthPasswordKey), "")) 6552 break 6553 } 6554 case core.SecretTypeSSHAuth: 6555 if len(secret.Data[core.SSHAuthPrivateKey]) == 0 { 6556 allErrs = append(allErrs, field.Required(dataPath.Key(core.SSHAuthPrivateKey), "")) 6557 break 6558 } 6559 6560 case core.SecretTypeTLS: 6561 if _, exists := secret.Data[core.TLSCertKey]; !exists { 6562 allErrs = append(allErrs, field.Required(dataPath.Key(core.TLSCertKey), "")) 6563 } 6564 if _, exists := secret.Data[core.TLSPrivateKeyKey]; !exists { 6565 allErrs = append(allErrs, field.Required(dataPath.Key(core.TLSPrivateKeyKey), "")) 6566 } 6567 default: 6568 // no-op 6569 } 6570 6571 return allErrs 6572 } 6573 6574 // ValidateSecretUpdate tests if required fields in the Secret are set. 6575 func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList { 6576 allErrs := ValidateObjectMetaUpdate(&newSecret.ObjectMeta, &oldSecret.ObjectMeta, field.NewPath("metadata")) 6577 6578 allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...) 6579 if oldSecret.Immutable != nil && *oldSecret.Immutable { 6580 if newSecret.Immutable == nil || !*newSecret.Immutable { 6581 allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set")) 6582 } 6583 if !reflect.DeepEqual(newSecret.Data, oldSecret.Data) { 6584 allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set")) 6585 } 6586 // We don't validate StringData, as it was already converted back to Data 6587 // before validation is happening. 6588 } 6589 6590 allErrs = append(allErrs, ValidateSecret(newSecret)...) 6591 return allErrs 6592 } 6593 6594 // ValidateConfigMapName can be used to check whether the given ConfigMap name is valid. 6595 // Prefix indicates this name will be used as part of generation, in which case 6596 // trailing dashes are allowed. 6597 var ValidateConfigMapName = apimachineryvalidation.NameIsDNSSubdomain 6598 6599 // ValidateConfigMap tests whether required fields in the ConfigMap are set. 6600 func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList { 6601 allErrs := field.ErrorList{} 6602 allErrs = append(allErrs, ValidateObjectMeta(&cfg.ObjectMeta, true, ValidateConfigMapName, field.NewPath("metadata"))...) 6603 6604 totalSize := 0 6605 6606 for key, value := range cfg.Data { 6607 for _, msg := range validation.IsConfigMapKey(key) { 6608 allErrs = append(allErrs, field.Invalid(field.NewPath("data").Key(key), key, msg)) 6609 } 6610 // check if we have a duplicate key in the other bag 6611 if _, isValue := cfg.BinaryData[key]; isValue { 6612 msg := "duplicate of key present in binaryData" 6613 allErrs = append(allErrs, field.Invalid(field.NewPath("data").Key(key), key, msg)) 6614 } 6615 totalSize += len(value) 6616 } 6617 for key, value := range cfg.BinaryData { 6618 for _, msg := range validation.IsConfigMapKey(key) { 6619 allErrs = append(allErrs, field.Invalid(field.NewPath("binaryData").Key(key), key, msg)) 6620 } 6621 totalSize += len(value) 6622 } 6623 if totalSize > core.MaxSecretSize { 6624 // pass back "" to indicate that the error refers to the whole object. 6625 allErrs = append(allErrs, field.TooLong(field.NewPath(""), cfg, core.MaxSecretSize)) 6626 } 6627 6628 return allErrs 6629 } 6630 6631 // ValidateConfigMapUpdate tests if required fields in the ConfigMap are set. 6632 func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList { 6633 allErrs := field.ErrorList{} 6634 allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...) 6635 6636 if oldCfg.Immutable != nil && *oldCfg.Immutable { 6637 if newCfg.Immutable == nil || !*newCfg.Immutable { 6638 allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set")) 6639 } 6640 if !reflect.DeepEqual(newCfg.Data, oldCfg.Data) { 6641 allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set")) 6642 } 6643 if !reflect.DeepEqual(newCfg.BinaryData, oldCfg.BinaryData) { 6644 allErrs = append(allErrs, field.Forbidden(field.NewPath("binaryData"), "field is immutable when `immutable` is set")) 6645 } 6646 } 6647 6648 allErrs = append(allErrs, ValidateConfigMap(newCfg)...) 6649 return allErrs 6650 } 6651 6652 func validateBasicResource(quantity resource.Quantity, fldPath *field.Path) field.ErrorList { 6653 if quantity.Value() < 0 { 6654 return field.ErrorList{field.Invalid(fldPath, quantity.Value(), "must be a valid resource quantity")} 6655 } 6656 return field.ErrorList{} 6657 } 6658 6659 // Validates resource requirement spec. 6660 func ValidateResourceRequirements(requirements *core.ResourceRequirements, podClaimNames sets.Set[string], fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 6661 allErrs := field.ErrorList{} 6662 limPath := fldPath.Child("limits") 6663 reqPath := fldPath.Child("requests") 6664 limContainsCPUOrMemory := false 6665 reqContainsCPUOrMemory := false 6666 limContainsHugePages := false 6667 reqContainsHugePages := false 6668 supportedQoSComputeResources := sets.New(core.ResourceCPU, core.ResourceMemory) 6669 for resourceName, quantity := range requirements.Limits { 6670 6671 fldPath := limPath.Key(string(resourceName)) 6672 // Validate resource name. 6673 allErrs = append(allErrs, validateContainerResourceName(resourceName, fldPath)...) 6674 6675 // Validate resource quantity. 6676 allErrs = append(allErrs, ValidateResourceQuantityValue(resourceName, quantity, fldPath)...) 6677 6678 if helper.IsHugePageResourceName(resourceName) { 6679 limContainsHugePages = true 6680 if err := validateResourceQuantityHugePageValue(resourceName, quantity, opts); err != nil { 6681 allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), err.Error())) 6682 } 6683 } 6684 6685 if supportedQoSComputeResources.Has(resourceName) { 6686 limContainsCPUOrMemory = true 6687 } 6688 } 6689 for resourceName, quantity := range requirements.Requests { 6690 fldPath := reqPath.Key(string(resourceName)) 6691 // Validate resource name. 6692 allErrs = append(allErrs, validateContainerResourceName(resourceName, fldPath)...) 6693 // Validate resource quantity. 6694 allErrs = append(allErrs, ValidateResourceQuantityValue(resourceName, quantity, fldPath)...) 6695 6696 // Check that request <= limit. 6697 limitQuantity, exists := requirements.Limits[resourceName] 6698 if exists { 6699 // For non overcommitable resources, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. 6700 if quantity.Cmp(limitQuantity) != 0 && !helper.IsOvercommitAllowed(resourceName) { 6701 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit of %s", resourceName, limitQuantity.String()))) 6702 } else if quantity.Cmp(limitQuantity) > 0 { 6703 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit of %s", resourceName, limitQuantity.String()))) 6704 } 6705 } else if !helper.IsOvercommitAllowed(resourceName) { 6706 allErrs = append(allErrs, field.Required(limPath, "Limit must be set for non overcommitable resources")) 6707 } 6708 if helper.IsHugePageResourceName(resourceName) { 6709 reqContainsHugePages = true 6710 if err := validateResourceQuantityHugePageValue(resourceName, quantity, opts); err != nil { 6711 allErrs = append(allErrs, field.Invalid(fldPath, quantity.String(), err.Error())) 6712 } 6713 } 6714 if supportedQoSComputeResources.Has(resourceName) { 6715 reqContainsCPUOrMemory = true 6716 } 6717 6718 } 6719 if !limContainsCPUOrMemory && !reqContainsCPUOrMemory && (reqContainsHugePages || limContainsHugePages) { 6720 allErrs = append(allErrs, field.Forbidden(fldPath, "HugePages require cpu or memory")) 6721 } 6722 6723 allErrs = append(allErrs, validateResourceClaimNames(requirements.Claims, podClaimNames, fldPath.Child("claims"))...) 6724 6725 return allErrs 6726 } 6727 6728 // validateResourceClaimNames checks that the names in 6729 // ResourceRequirements.Claims have a corresponding entry in 6730 // PodSpec.ResourceClaims. 6731 func validateResourceClaimNames(claims []core.ResourceClaim, podClaimNames sets.Set[string], fldPath *field.Path) field.ErrorList { 6732 var allErrs field.ErrorList 6733 names := sets.Set[string]{} 6734 for i, claim := range claims { 6735 name := claim.Name 6736 if name == "" { 6737 allErrs = append(allErrs, field.Required(fldPath.Index(i), "")) 6738 } else { 6739 if names.Has(name) { 6740 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), name)) 6741 } else { 6742 names.Insert(name) 6743 } 6744 if !podClaimNames.Has(name) { 6745 // field.NotFound doesn't accept an 6746 // explanation. Adding one here is more 6747 // user-friendly. 6748 error := field.NotFound(fldPath.Index(i), name) 6749 error.Detail = "must be one of the names in pod.spec.resourceClaims" 6750 if len(podClaimNames) == 0 { 6751 error.Detail += " which is empty" 6752 } else { 6753 error.Detail += ": " + strings.Join(sets.List(podClaimNames), ", ") 6754 } 6755 allErrs = append(allErrs, error) 6756 } 6757 } 6758 } 6759 return allErrs 6760 } 6761 6762 func validateResourceQuantityHugePageValue(name core.ResourceName, quantity resource.Quantity, opts PodValidationOptions) error { 6763 if !helper.IsHugePageResourceName(name) { 6764 return nil 6765 } 6766 6767 if !opts.AllowIndivisibleHugePagesValues && !helper.IsHugePageResourceValueDivisible(name, quantity) { 6768 return fmt.Errorf("%s is not positive integer multiple of %s", quantity.String(), name) 6769 } 6770 6771 return nil 6772 } 6773 6774 // validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes 6775 func validateResourceQuotaScopes(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList { 6776 allErrs := field.ErrorList{} 6777 if len(resourceQuotaSpec.Scopes) == 0 { 6778 return allErrs 6779 } 6780 hardLimits := sets.New[core.ResourceName]() 6781 for k := range resourceQuotaSpec.Hard { 6782 hardLimits.Insert(k) 6783 } 6784 fldPath := fld.Child("scopes") 6785 scopeSet := sets.New[core.ResourceQuotaScope]() 6786 for _, scope := range resourceQuotaSpec.Scopes { 6787 if !helper.IsStandardResourceQuotaScope(scope) { 6788 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope")) 6789 } 6790 for _, k := range sets.List(hardLimits) { 6791 if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(scope, k) { 6792 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "unsupported scope applied to resource")) 6793 } 6794 } 6795 scopeSet.Insert(scope) 6796 } 6797 invalidScopePairs := []sets.Set[core.ResourceQuotaScope]{ 6798 sets.New(core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort), 6799 sets.New(core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating), 6800 } 6801 for _, invalidScopePair := range invalidScopePairs { 6802 if scopeSet.HasAll(sets.List(invalidScopePair)...) { 6803 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes")) 6804 } 6805 } 6806 return allErrs 6807 } 6808 6809 // validateScopedResourceSelectorRequirement tests that the match expressions has valid data 6810 func validateScopedResourceSelectorRequirement(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList { 6811 allErrs := field.ErrorList{} 6812 hardLimits := sets.New[core.ResourceName]() 6813 for k := range resourceQuotaSpec.Hard { 6814 hardLimits.Insert(k) 6815 } 6816 fldPath := fld.Child("matchExpressions") 6817 scopeSet := sets.New[core.ResourceQuotaScope]() 6818 for _, req := range resourceQuotaSpec.ScopeSelector.MatchExpressions { 6819 if !helper.IsStandardResourceQuotaScope(req.ScopeName) { 6820 allErrs = append(allErrs, field.Invalid(fldPath.Child("scopeName"), req.ScopeName, "unsupported scope")) 6821 } 6822 for _, k := range sets.List(hardLimits) { 6823 if helper.IsStandardQuotaResourceName(k) && !helper.IsResourceQuotaScopeValidForResource(req.ScopeName, k) { 6824 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.ScopeSelector, "unsupported scope applied to resource")) 6825 } 6826 } 6827 switch req.ScopeName { 6828 case core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort, core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeCrossNamespacePodAffinity: 6829 if req.Operator != core.ScopeSelectorOpExists { 6830 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, 6831 "must be 'Exist' when scope is any of ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeBestEffort, ResourceQuotaScopeNotBestEffort or ResourceQuotaScopeCrossNamespacePodAffinity")) 6832 } 6833 } 6834 6835 switch req.Operator { 6836 case core.ScopeSelectorOpIn, core.ScopeSelectorOpNotIn: 6837 if len(req.Values) == 0 { 6838 allErrs = append(allErrs, field.Required(fldPath.Child("values"), 6839 "must be at least one value when `operator` is 'In' or 'NotIn' for scope selector")) 6840 } 6841 case core.ScopeSelectorOpExists, core.ScopeSelectorOpDoesNotExist: 6842 if len(req.Values) != 0 { 6843 allErrs = append(allErrs, field.Invalid(fldPath.Child("values"), req.Values, 6844 "must be no value when `operator` is 'Exist' or 'DoesNotExist' for scope selector")) 6845 } 6846 default: 6847 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), req.Operator, "not a valid selector operator")) 6848 } 6849 scopeSet.Insert(req.ScopeName) 6850 } 6851 invalidScopePairs := []sets.Set[core.ResourceQuotaScope]{ 6852 sets.New(core.ResourceQuotaScopeBestEffort, core.ResourceQuotaScopeNotBestEffort), 6853 sets.New(core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating), 6854 } 6855 for _, invalidScopePair := range invalidScopePairs { 6856 if scopeSet.HasAll(sets.List(invalidScopePair)...) { 6857 allErrs = append(allErrs, field.Invalid(fldPath, resourceQuotaSpec.Scopes, "conflicting scopes")) 6858 } 6859 } 6860 6861 return allErrs 6862 } 6863 6864 // validateScopeSelector tests that the specified scope selector has valid data 6865 func validateScopeSelector(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList { 6866 allErrs := field.ErrorList{} 6867 if resourceQuotaSpec.ScopeSelector == nil { 6868 return allErrs 6869 } 6870 allErrs = append(allErrs, validateScopedResourceSelectorRequirement(resourceQuotaSpec, fld.Child("scopeSelector"))...) 6871 return allErrs 6872 } 6873 6874 // ValidateResourceQuota tests if required fields in the ResourceQuota are set. 6875 func ValidateResourceQuota(resourceQuota *core.ResourceQuota) field.ErrorList { 6876 allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata")) 6877 6878 allErrs = append(allErrs, ValidateResourceQuotaSpec(&resourceQuota.Spec, field.NewPath("spec"))...) 6879 allErrs = append(allErrs, ValidateResourceQuotaStatus(&resourceQuota.Status, field.NewPath("status"))...) 6880 6881 return allErrs 6882 } 6883 6884 func ValidateResourceQuotaStatus(status *core.ResourceQuotaStatus, fld *field.Path) field.ErrorList { 6885 allErrs := field.ErrorList{} 6886 6887 fldPath := fld.Child("hard") 6888 for k, v := range status.Hard { 6889 resPath := fldPath.Key(string(k)) 6890 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...) 6891 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6892 } 6893 fldPath = fld.Child("used") 6894 for k, v := range status.Used { 6895 resPath := fldPath.Key(string(k)) 6896 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...) 6897 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6898 } 6899 6900 return allErrs 6901 } 6902 6903 func ValidateResourceQuotaSpec(resourceQuotaSpec *core.ResourceQuotaSpec, fld *field.Path) field.ErrorList { 6904 allErrs := field.ErrorList{} 6905 6906 fldPath := fld.Child("hard") 6907 for k, v := range resourceQuotaSpec.Hard { 6908 resPath := fldPath.Key(string(k)) 6909 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...) 6910 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6911 } 6912 6913 allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuotaSpec, fld)...) 6914 allErrs = append(allErrs, validateScopeSelector(resourceQuotaSpec, fld)...) 6915 6916 return allErrs 6917 } 6918 6919 // ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource 6920 func ValidateResourceQuantityValue(resource core.ResourceName, value resource.Quantity, fldPath *field.Path) field.ErrorList { 6921 allErrs := field.ErrorList{} 6922 allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...) 6923 if helper.IsIntegerResourceName(resource) { 6924 if value.MilliValue()%int64(1000) != int64(0) { 6925 allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg)) 6926 } 6927 } 6928 return allErrs 6929 } 6930 6931 // ValidateResourceQuotaUpdate tests to see if the update is legal for an end user to make. 6932 func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList { 6933 allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) 6934 allErrs = append(allErrs, ValidateResourceQuotaSpec(&newResourceQuota.Spec, field.NewPath("spec"))...) 6935 6936 // ensure scopes cannot change, and that resources are still valid for scope 6937 fldPath := field.NewPath("spec", "scopes") 6938 oldScopes := sets.New[string]() 6939 newScopes := sets.New[string]() 6940 for _, scope := range newResourceQuota.Spec.Scopes { 6941 newScopes.Insert(string(scope)) 6942 } 6943 for _, scope := range oldResourceQuota.Spec.Scopes { 6944 oldScopes.Insert(string(scope)) 6945 } 6946 if !oldScopes.Equal(newScopes) { 6947 allErrs = append(allErrs, field.Invalid(fldPath, newResourceQuota.Spec.Scopes, fieldImmutableErrorMsg)) 6948 } 6949 6950 return allErrs 6951 } 6952 6953 // ValidateResourceQuotaStatusUpdate tests to see if the status update is legal for an end user to make. 6954 func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *core.ResourceQuota) field.ErrorList { 6955 allErrs := ValidateObjectMetaUpdate(&newResourceQuota.ObjectMeta, &oldResourceQuota.ObjectMeta, field.NewPath("metadata")) 6956 if len(newResourceQuota.ResourceVersion) == 0 { 6957 allErrs = append(allErrs, field.Required(field.NewPath("resourceVersion"), "")) 6958 } 6959 fldPath := field.NewPath("status", "hard") 6960 for k, v := range newResourceQuota.Status.Hard { 6961 resPath := fldPath.Key(string(k)) 6962 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...) 6963 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6964 } 6965 fldPath = field.NewPath("status", "used") 6966 for k, v := range newResourceQuota.Status.Used { 6967 resPath := fldPath.Key(string(k)) 6968 allErrs = append(allErrs, ValidateResourceQuotaResourceName(k, resPath)...) 6969 allErrs = append(allErrs, ValidateResourceQuantityValue(k, v, resPath)...) 6970 } 6971 return allErrs 6972 } 6973 6974 // ValidateNamespace tests if required fields are set. 6975 func ValidateNamespace(namespace *core.Namespace) field.ErrorList { 6976 allErrs := ValidateObjectMeta(&namespace.ObjectMeta, false, ValidateNamespaceName, field.NewPath("metadata")) 6977 for i := range namespace.Spec.Finalizers { 6978 allErrs = append(allErrs, validateFinalizerName(string(namespace.Spec.Finalizers[i]), field.NewPath("spec", "finalizers"))...) 6979 } 6980 return allErrs 6981 } 6982 6983 // Validate finalizer names 6984 func validateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { 6985 allErrs := apimachineryvalidation.ValidateFinalizerName(stringValue, fldPath) 6986 allErrs = append(allErrs, validateKubeFinalizerName(stringValue, fldPath)...) 6987 return allErrs 6988 } 6989 6990 // validateKubeFinalizerName checks for "standard" names of legacy finalizer 6991 func validateKubeFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { 6992 allErrs := field.ErrorList{} 6993 if len(strings.Split(stringValue, "/")) == 1 { 6994 if !helper.IsStandardFinalizerName(stringValue) { 6995 return append(allErrs, field.Invalid(fldPath, stringValue, "name is neither a standard finalizer name nor is it fully qualified")) 6996 } 6997 } 6998 6999 return allErrs 7000 } 7001 7002 // ValidateNamespaceUpdate tests to make sure a namespace update can be applied. 7003 func ValidateNamespaceUpdate(newNamespace *core.Namespace, oldNamespace *core.Namespace) field.ErrorList { 7004 allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) 7005 return allErrs 7006 } 7007 7008 // ValidateNamespaceStatusUpdate tests to see if the update is legal for an end user to make. 7009 func ValidateNamespaceStatusUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList { 7010 allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) 7011 if newNamespace.DeletionTimestamp.IsZero() { 7012 if newNamespace.Status.Phase != core.NamespaceActive { 7013 allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Active' if `deletionTimestamp` is empty")) 7014 } 7015 } else { 7016 if newNamespace.Status.Phase != core.NamespaceTerminating { 7017 allErrs = append(allErrs, field.Invalid(field.NewPath("status", "Phase"), newNamespace.Status.Phase, "may only be 'Terminating' if `deletionTimestamp` is not empty")) 7018 } 7019 } 7020 return allErrs 7021 } 7022 7023 // ValidateNamespaceFinalizeUpdate tests to see if the update is legal for an end user to make. 7024 func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *core.Namespace) field.ErrorList { 7025 allErrs := ValidateObjectMetaUpdate(&newNamespace.ObjectMeta, &oldNamespace.ObjectMeta, field.NewPath("metadata")) 7026 7027 fldPath := field.NewPath("spec", "finalizers") 7028 for i := range newNamespace.Spec.Finalizers { 7029 idxPath := fldPath.Index(i) 7030 allErrs = append(allErrs, validateFinalizerName(string(newNamespace.Spec.Finalizers[i]), idxPath)...) 7031 } 7032 return allErrs 7033 } 7034 7035 // ValidateEndpoints validates Endpoints on create and update. 7036 func ValidateEndpoints(endpoints *core.Endpoints) field.ErrorList { 7037 allErrs := ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName, field.NewPath("metadata")) 7038 allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(endpoints.Annotations, field.NewPath("annotations"))...) 7039 allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, field.NewPath("subsets"))...) 7040 return allErrs 7041 } 7042 7043 // ValidateEndpointsCreate validates Endpoints on create. 7044 func ValidateEndpointsCreate(endpoints *core.Endpoints) field.ErrorList { 7045 return ValidateEndpoints(endpoints) 7046 } 7047 7048 // ValidateEndpointsUpdate validates Endpoints on update. NodeName changes are 7049 // allowed during update to accommodate the case where nodeIP or PodCIDR is 7050 // reused. An existing endpoint ip will have a different nodeName if this 7051 // happens. 7052 func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *core.Endpoints) field.ErrorList { 7053 allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata")) 7054 allErrs = append(allErrs, ValidateEndpoints(newEndpoints)...) 7055 return allErrs 7056 } 7057 7058 func validateEndpointSubsets(subsets []core.EndpointSubset, fldPath *field.Path) field.ErrorList { 7059 allErrs := field.ErrorList{} 7060 for i := range subsets { 7061 ss := &subsets[i] 7062 idxPath := fldPath.Index(i) 7063 7064 // EndpointSubsets must include endpoint address. For headless service, we allow its endpoints not to have ports. 7065 if len(ss.Addresses) == 0 && len(ss.NotReadyAddresses) == 0 { 7066 // TODO: consider adding a RequiredOneOf() error for this and similar cases 7067 allErrs = append(allErrs, field.Required(idxPath, "must specify `addresses` or `notReadyAddresses`")) 7068 } 7069 for addr := range ss.Addresses { 7070 allErrs = append(allErrs, validateEndpointAddress(&ss.Addresses[addr], idxPath.Child("addresses").Index(addr))...) 7071 } 7072 for addr := range ss.NotReadyAddresses { 7073 allErrs = append(allErrs, validateEndpointAddress(&ss.NotReadyAddresses[addr], idxPath.Child("notReadyAddresses").Index(addr))...) 7074 } 7075 for port := range ss.Ports { 7076 allErrs = append(allErrs, validateEndpointPort(&ss.Ports[port], len(ss.Ports) > 1, idxPath.Child("ports").Index(port))...) 7077 } 7078 } 7079 7080 return allErrs 7081 } 7082 7083 func validateEndpointAddress(address *core.EndpointAddress, fldPath *field.Path) field.ErrorList { 7084 allErrs := field.ErrorList{} 7085 allErrs = append(allErrs, validation.IsValidIP(fldPath.Child("ip"), address.IP)...) 7086 if len(address.Hostname) > 0 { 7087 allErrs = append(allErrs, ValidateDNS1123Label(address.Hostname, fldPath.Child("hostname"))...) 7088 } 7089 // During endpoint update, verify that NodeName is a DNS subdomain and transition rules allow the update 7090 if address.NodeName != nil { 7091 for _, msg := range ValidateNodeName(*address.NodeName, false) { 7092 allErrs = append(allErrs, field.Invalid(fldPath.Child("nodeName"), *address.NodeName, msg)) 7093 } 7094 } 7095 allErrs = append(allErrs, ValidateNonSpecialIP(address.IP, fldPath.Child("ip"))...) 7096 return allErrs 7097 } 7098 7099 // ValidateNonSpecialIP is used to validate Endpoints, EndpointSlices, and 7100 // external IPs. Specifically, this disallows unspecified and loopback addresses 7101 // are nonsensical and link-local addresses tend to be used for node-centric 7102 // purposes (e.g. metadata service). 7103 // 7104 // IPv6 references 7105 // - https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml 7106 // - https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml 7107 func ValidateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList { 7108 allErrs := field.ErrorList{} 7109 ip := netutils.ParseIPSloppy(ipAddress) 7110 if ip == nil { 7111 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "must be a valid IP address")) 7112 return allErrs 7113 } 7114 if ip.IsUnspecified() { 7115 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, fmt.Sprintf("may not be unspecified (%v)", ipAddress))) 7116 } 7117 if ip.IsLoopback() { 7118 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the loopback range (127.0.0.0/8, ::1/128)")) 7119 } 7120 if ip.IsLinkLocalUnicast() { 7121 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local range (169.254.0.0/16, fe80::/10)")) 7122 } 7123 if ip.IsLinkLocalMulticast() { 7124 allErrs = append(allErrs, field.Invalid(fldPath, ipAddress, "may not be in the link-local multicast range (224.0.0.0/24, ff02::/10)")) 7125 } 7126 return allErrs 7127 } 7128 7129 func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *field.Path) field.ErrorList { 7130 allErrs := field.ErrorList{} 7131 if requireName && len(port.Name) == 0 { 7132 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 7133 } else if len(port.Name) != 0 { 7134 allErrs = append(allErrs, ValidateDNS1123Label(port.Name, fldPath.Child("name"))...) 7135 } 7136 for _, msg := range validation.IsValidPortNum(int(port.Port)) { 7137 allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), port.Port, msg)) 7138 } 7139 if len(port.Protocol) == 0 { 7140 allErrs = append(allErrs, field.Required(fldPath.Child("protocol"), "")) 7141 } else if !supportedPortProtocols.Has(port.Protocol) { 7142 allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, sets.List(supportedPortProtocols))) 7143 } 7144 if port.AppProtocol != nil { 7145 allErrs = append(allErrs, ValidateQualifiedName(*port.AppProtocol, fldPath.Child("appProtocol"))...) 7146 } 7147 return allErrs 7148 } 7149 7150 // ValidateSecurityContext ensures the security context contains valid settings 7151 func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path, hostUsers bool) field.ErrorList { 7152 allErrs := field.ErrorList{} 7153 // this should only be true for testing since SecurityContext is defaulted by the core 7154 if sc == nil { 7155 return allErrs 7156 } 7157 7158 if sc.Privileged != nil { 7159 if *sc.Privileged && !capabilities.Get().AllowPrivileged { 7160 allErrs = append(allErrs, field.Forbidden(fldPath.Child("privileged"), "disallowed by cluster policy")) 7161 } 7162 } 7163 7164 if sc.RunAsUser != nil { 7165 for _, msg := range validation.IsValidUserID(*sc.RunAsUser) { 7166 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *sc.RunAsUser, msg)) 7167 } 7168 } 7169 7170 if sc.RunAsGroup != nil { 7171 for _, msg := range validation.IsValidGroupID(*sc.RunAsGroup) { 7172 allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *sc.RunAsGroup, msg)) 7173 } 7174 } 7175 7176 if sc.ProcMount != nil { 7177 if err := ValidateProcMountType(fldPath.Child("procMount"), *sc.ProcMount); err != nil { 7178 allErrs = append(allErrs, err) 7179 } 7180 if hostUsers && *sc.ProcMount == core.UnmaskedProcMount { 7181 allErrs = append(allErrs, field.Invalid(fldPath.Child("procMount"), sc.ProcMount, "`hostUsers` must be false to use `Unmasked`")) 7182 } 7183 7184 } 7185 allErrs = append(allErrs, validateSeccompProfileField(sc.SeccompProfile, fldPath.Child("seccompProfile"))...) 7186 if sc.AllowPrivilegeEscalation != nil && !*sc.AllowPrivilegeEscalation { 7187 if sc.Privileged != nil && *sc.Privileged { 7188 allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `privileged` to true")) 7189 } 7190 7191 if sc.Capabilities != nil { 7192 for _, cap := range sc.Capabilities.Add { 7193 if string(cap) == "CAP_SYS_ADMIN" { 7194 allErrs = append(allErrs, field.Invalid(fldPath, sc, "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN")) 7195 } 7196 } 7197 } 7198 } 7199 7200 allErrs = append(allErrs, validateWindowsSecurityContextOptions(sc.WindowsOptions, fldPath.Child("windowsOptions"))...) 7201 allErrs = append(allErrs, ValidateAppArmorProfileField(sc.AppArmorProfile, fldPath.Child("appArmorProfile"))...) 7202 7203 return allErrs 7204 } 7205 7206 // maxGMSACredentialSpecLength is the max length, in bytes, for the actual contents 7207 // of a GMSA cred spec. In general, those shouldn't be more than a few hundred bytes, 7208 // so we want to give plenty of room here while still providing an upper bound. 7209 // The runAsUserName field will be used to execute the given container's entrypoint, and 7210 // it can be formatted as "DOMAIN/USER", where the DOMAIN is optional, maxRunAsUserNameDomainLength 7211 // is the max character length for the user's DOMAIN, and maxRunAsUserNameUserLength 7212 // is the max character length for the USER itself. Both the DOMAIN and USER have their 7213 // own restrictions, and more information about them can be found here: 7214 // https://support.microsoft.com/en-us/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and 7215 // https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/bb726984(v=technet.10) 7216 const ( 7217 maxGMSACredentialSpecLengthInKiB = 64 7218 maxGMSACredentialSpecLength = maxGMSACredentialSpecLengthInKiB * 1024 7219 maxRunAsUserNameDomainLength = 256 7220 maxRunAsUserNameUserLength = 104 7221 ) 7222 7223 var ( 7224 // control characters are not permitted in the runAsUserName field. 7225 ctrlRegex = regexp.MustCompile(`[[:cntrl:]]+`) 7226 7227 // a valid NetBios Domain name cannot start with a dot, has at least 1 character, 7228 // at most 15 characters, and it cannot the characters: \ / : * ? " < > | 7229 validNetBiosRegex = regexp.MustCompile(`^[^\\/:\*\?"<>|\.][^\\/:\*\?"<>|]{0,14}$`) 7230 7231 // a valid DNS name contains only alphanumeric characters, dots, and dashes. 7232 dnsLabelFormat = `[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?` 7233 dnsSubdomainFormat = fmt.Sprintf(`^%s(?:\.%s)*$`, dnsLabelFormat, dnsLabelFormat) 7234 validWindowsUserDomainDNSRegex = regexp.MustCompile(dnsSubdomainFormat) 7235 7236 // a username is invalid if it contains the characters: " / \ [ ] : ; | = , + * ? < > @ 7237 // or it contains only dots or spaces. 7238 invalidUserNameCharsRegex = regexp.MustCompile(`["/\\:;|=,\+\*\?<>@\[\]]`) 7239 invalidUserNameDotsSpacesRegex = regexp.MustCompile(`^[\. ]+$`) 7240 ) 7241 7242 func validateWindowsSecurityContextOptions(windowsOptions *core.WindowsSecurityContextOptions, fieldPath *field.Path) field.ErrorList { 7243 allErrs := field.ErrorList{} 7244 7245 if windowsOptions == nil { 7246 return allErrs 7247 } 7248 7249 if windowsOptions.GMSACredentialSpecName != nil { 7250 // gmsaCredentialSpecName must be the name of a custom resource 7251 for _, msg := range validation.IsDNS1123Subdomain(*windowsOptions.GMSACredentialSpecName) { 7252 allErrs = append(allErrs, field.Invalid(fieldPath.Child("gmsaCredentialSpecName"), windowsOptions.GMSACredentialSpecName, msg)) 7253 } 7254 } 7255 7256 if windowsOptions.GMSACredentialSpec != nil { 7257 if l := len(*windowsOptions.GMSACredentialSpec); l == 0 { 7258 allErrs = append(allErrs, field.Invalid(fieldPath.Child("gmsaCredentialSpec"), windowsOptions.GMSACredentialSpec, "gmsaCredentialSpec cannot be an empty string")) 7259 } else if l > maxGMSACredentialSpecLength { 7260 errMsg := fmt.Sprintf("gmsaCredentialSpec size must be under %d KiB", maxGMSACredentialSpecLengthInKiB) 7261 allErrs = append(allErrs, field.Invalid(fieldPath.Child("gmsaCredentialSpec"), windowsOptions.GMSACredentialSpec, errMsg)) 7262 } 7263 } 7264 7265 if windowsOptions.RunAsUserName != nil { 7266 if l := len(*windowsOptions.RunAsUserName); l == 0 { 7267 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, "runAsUserName cannot be an empty string")) 7268 } else if ctrlRegex.MatchString(*windowsOptions.RunAsUserName) { 7269 errMsg := "runAsUserName cannot contain control characters" 7270 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7271 } else if parts := strings.Split(*windowsOptions.RunAsUserName, "\\"); len(parts) > 2 { 7272 errMsg := "runAsUserName cannot contain more than one backslash" 7273 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7274 } else { 7275 var ( 7276 hasDomain = false 7277 domain = "" 7278 user string 7279 ) 7280 if len(parts) == 1 { 7281 user = parts[0] 7282 } else { 7283 hasDomain = true 7284 domain = parts[0] 7285 user = parts[1] 7286 } 7287 7288 if len(domain) >= maxRunAsUserNameDomainLength { 7289 errMsg := fmt.Sprintf("runAsUserName's Domain length must be under %d characters", maxRunAsUserNameDomainLength) 7290 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7291 } 7292 7293 if hasDomain && !(validNetBiosRegex.MatchString(domain) || validWindowsUserDomainDNSRegex.MatchString(domain)) { 7294 errMsg := "runAsUserName's Domain doesn't match the NetBios nor the DNS format" 7295 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7296 } 7297 7298 if l := len(user); l == 0 { 7299 errMsg := "runAsUserName's User cannot be empty" 7300 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7301 } else if l > maxRunAsUserNameUserLength { 7302 errMsg := fmt.Sprintf("runAsUserName's User length must not be longer than %d characters", maxRunAsUserNameUserLength) 7303 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7304 } 7305 7306 if invalidUserNameDotsSpacesRegex.MatchString(user) { 7307 errMsg := `runAsUserName's User cannot contain only periods or spaces` 7308 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7309 } 7310 7311 if invalidUserNameCharsRegex.MatchString(user) { 7312 errMsg := `runAsUserName's User cannot contain the following characters: "/\:;|=,+*?<>@[]` 7313 allErrs = append(allErrs, field.Invalid(fieldPath.Child("runAsUserName"), windowsOptions.RunAsUserName, errMsg)) 7314 } 7315 } 7316 } 7317 7318 return allErrs 7319 } 7320 7321 func validateWindowsHostProcessPod(podSpec *core.PodSpec, fieldPath *field.Path) field.ErrorList { 7322 allErrs := field.ErrorList{} 7323 7324 // Keep track of container and hostProcess container count for validate 7325 containerCount := 0 7326 hostProcessContainerCount := 0 7327 7328 var podHostProcess *bool 7329 if podSpec.SecurityContext != nil && podSpec.SecurityContext.WindowsOptions != nil { 7330 podHostProcess = podSpec.SecurityContext.WindowsOptions.HostProcess 7331 } 7332 7333 hostNetwork := false 7334 if podSpec.SecurityContext != nil { 7335 hostNetwork = podSpec.SecurityContext.HostNetwork 7336 } 7337 7338 podshelper.VisitContainersWithPath(podSpec, fieldPath, func(c *core.Container, cFieldPath *field.Path) bool { 7339 containerCount++ 7340 7341 var containerHostProcess *bool = nil 7342 if c.SecurityContext != nil && c.SecurityContext.WindowsOptions != nil { 7343 containerHostProcess = c.SecurityContext.WindowsOptions.HostProcess 7344 } 7345 7346 if podHostProcess != nil && containerHostProcess != nil && *podHostProcess != *containerHostProcess { 7347 errMsg := fmt.Sprintf("pod hostProcess value must be identical if both are specified, was %v", *podHostProcess) 7348 allErrs = append(allErrs, field.Invalid(cFieldPath.Child("securityContext", "windowsOptions", "hostProcess"), *containerHostProcess, errMsg)) 7349 } 7350 7351 switch { 7352 case containerHostProcess != nil && *containerHostProcess: 7353 // Container explicitly sets hostProcess=true 7354 hostProcessContainerCount++ 7355 case containerHostProcess == nil && podHostProcess != nil && *podHostProcess: 7356 // Container inherits hostProcess=true from pod settings 7357 hostProcessContainerCount++ 7358 } 7359 7360 return true 7361 }) 7362 7363 if hostProcessContainerCount > 0 { 7364 // At present, if a Windows Pods contains any HostProcess containers than all containers must be 7365 // HostProcess containers (explicitly set or inherited). 7366 if hostProcessContainerCount != containerCount { 7367 errMsg := "If pod contains any hostProcess containers then all containers must be HostProcess containers" 7368 allErrs = append(allErrs, field.Invalid(fieldPath, "", errMsg)) 7369 } 7370 7371 // At present Windows Pods which contain HostProcess containers must also set HostNetwork. 7372 if !hostNetwork { 7373 errMsg := "hostNetwork must be true if pod contains any hostProcess containers" 7374 allErrs = append(allErrs, field.Invalid(fieldPath.Child("hostNetwork"), hostNetwork, errMsg)) 7375 } 7376 7377 if !capabilities.Get().AllowPrivileged { 7378 errMsg := "hostProcess containers are disallowed by cluster policy" 7379 allErrs = append(allErrs, field.Forbidden(fieldPath, errMsg)) 7380 } 7381 } 7382 7383 return allErrs 7384 } 7385 7386 // validateOS validates the OS field within pod spec 7387 func validateOS(podSpec *core.PodSpec, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 7388 allErrs := field.ErrorList{} 7389 os := podSpec.OS 7390 if os == nil { 7391 return allErrs 7392 } 7393 if len(os.Name) == 0 { 7394 return append(allErrs, field.Required(fldPath.Child("name"), "cannot be empty")) 7395 } 7396 if !validOS.Has(os.Name) { 7397 allErrs = append(allErrs, field.NotSupported(fldPath, os.Name, sets.List(validOS))) 7398 } 7399 return allErrs 7400 } 7401 7402 func ValidatePodLogOptions(opts *core.PodLogOptions) field.ErrorList { 7403 allErrs := field.ErrorList{} 7404 if opts.TailLines != nil && *opts.TailLines < 0 { 7405 allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg)) 7406 } 7407 if opts.LimitBytes != nil && *opts.LimitBytes < 1 { 7408 allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0")) 7409 } 7410 switch { 7411 case opts.SinceSeconds != nil && opts.SinceTime != nil: 7412 allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified")) 7413 case opts.SinceSeconds != nil: 7414 if *opts.SinceSeconds < 1 { 7415 allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0")) 7416 } 7417 } 7418 return allErrs 7419 } 7420 7421 var ( 7422 supportedLoadBalancerIPMode = sets.New(core.LoadBalancerIPModeVIP, core.LoadBalancerIPModeProxy) 7423 ) 7424 7425 // ValidateLoadBalancerStatus validates required fields on a LoadBalancerStatus 7426 func ValidateLoadBalancerStatus(status *core.LoadBalancerStatus, fldPath *field.Path, spec *core.ServiceSpec) field.ErrorList { 7427 allErrs := field.ErrorList{} 7428 ingrPath := fldPath.Child("ingress") 7429 if !utilfeature.DefaultFeatureGate.Enabled(features.AllowServiceLBStatusOnNonLB) && spec.Type != core.ServiceTypeLoadBalancer && len(status.Ingress) != 0 { 7430 allErrs = append(allErrs, field.Forbidden(ingrPath, "may only be used when `spec.type` is 'LoadBalancer'")) 7431 } else { 7432 for i, ingress := range status.Ingress { 7433 idxPath := ingrPath.Index(i) 7434 if len(ingress.IP) > 0 { 7435 allErrs = append(allErrs, validation.IsValidIP(idxPath.Child("ip"), ingress.IP)...) 7436 } 7437 7438 if utilfeature.DefaultFeatureGate.Enabled(features.LoadBalancerIPMode) && ingress.IPMode == nil { 7439 if len(ingress.IP) > 0 { 7440 allErrs = append(allErrs, field.Required(idxPath.Child("ipMode"), "must be specified when `ip` is set")) 7441 } 7442 } else if ingress.IPMode != nil && len(ingress.IP) == 0 { 7443 allErrs = append(allErrs, field.Forbidden(idxPath.Child("ipMode"), "may not be specified when `ip` is not set")) 7444 } else if ingress.IPMode != nil && !supportedLoadBalancerIPMode.Has(*ingress.IPMode) { 7445 allErrs = append(allErrs, field.NotSupported(idxPath.Child("ipMode"), ingress.IPMode, sets.List(supportedLoadBalancerIPMode))) 7446 } 7447 7448 if len(ingress.Hostname) > 0 { 7449 for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) { 7450 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg)) 7451 } 7452 if isIP := (netutils.ParseIPSloppy(ingress.Hostname) != nil); isIP { 7453 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address")) 7454 } 7455 } 7456 } 7457 } 7458 return allErrs 7459 } 7460 7461 // validateVolumeNodeAffinity tests that the PersistentVolume.NodeAffinity has valid data 7462 // returns: 7463 // - true if volumeNodeAffinity is set 7464 // - errorList if there are validation errors 7465 func validateVolumeNodeAffinity(nodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) (bool, field.ErrorList) { 7466 allErrs := field.ErrorList{} 7467 7468 if nodeAffinity == nil { 7469 return false, allErrs 7470 } 7471 7472 if nodeAffinity.Required != nil { 7473 allErrs = append(allErrs, ValidateNodeSelector(nodeAffinity.Required, fldPath.Child("required"))...) 7474 } else { 7475 allErrs = append(allErrs, field.Required(fldPath.Child("required"), "must specify required node constraints")) 7476 } 7477 7478 return true, allErrs 7479 } 7480 7481 func IsDecremented(update, old *int32) bool { 7482 if update == nil && old != nil { 7483 return true 7484 } 7485 if update == nil || old == nil { 7486 return false 7487 } 7488 return *update < *old 7489 } 7490 7491 // ValidateProcMountType tests that the argument is a valid ProcMountType. 7492 func ValidateProcMountType(fldPath *field.Path, procMountType core.ProcMountType) *field.Error { 7493 switch procMountType { 7494 case core.DefaultProcMount, core.UnmaskedProcMount: 7495 return nil 7496 default: 7497 return field.NotSupported(fldPath, procMountType, []core.ProcMountType{core.DefaultProcMount, core.UnmaskedProcMount}) 7498 } 7499 } 7500 7501 var ( 7502 supportedScheduleActions = sets.New(core.DoNotSchedule, core.ScheduleAnyway) 7503 ) 7504 7505 // validateTopologySpreadConstraints validates given TopologySpreadConstraints. 7506 func validateTopologySpreadConstraints(constraints []core.TopologySpreadConstraint, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { 7507 allErrs := field.ErrorList{} 7508 7509 for i, constraint := range constraints { 7510 subFldPath := fldPath.Index(i) 7511 if err := ValidateMaxSkew(subFldPath.Child("maxSkew"), constraint.MaxSkew); err != nil { 7512 allErrs = append(allErrs, err) 7513 } 7514 if err := ValidateTopologyKey(subFldPath.Child("topologyKey"), constraint.TopologyKey); err != nil { 7515 allErrs = append(allErrs, err) 7516 } 7517 if err := ValidateWhenUnsatisfiable(subFldPath.Child("whenUnsatisfiable"), constraint.WhenUnsatisfiable); err != nil { 7518 allErrs = append(allErrs, err) 7519 } 7520 // tuple {topologyKey, whenUnsatisfiable} denotes one kind of spread constraint 7521 if err := ValidateSpreadConstraintNotRepeat(subFldPath.Child("{topologyKey, whenUnsatisfiable}"), constraint, constraints[i+1:]); err != nil { 7522 allErrs = append(allErrs, err) 7523 } 7524 allErrs = append(allErrs, validateMinDomains(subFldPath.Child("minDomains"), constraint.MinDomains, constraint.WhenUnsatisfiable)...) 7525 if err := validateNodeInclusionPolicy(subFldPath.Child("nodeAffinityPolicy"), constraint.NodeAffinityPolicy); err != nil { 7526 allErrs = append(allErrs, err) 7527 } 7528 if err := validateNodeInclusionPolicy(subFldPath.Child("nodeTaintsPolicy"), constraint.NodeTaintsPolicy); err != nil { 7529 allErrs = append(allErrs, err) 7530 } 7531 allErrs = append(allErrs, validateMatchLabelKeysInTopologySpread(subFldPath.Child("matchLabelKeys"), constraint.MatchLabelKeys, constraint.LabelSelector)...) 7532 if !opts.AllowInvalidTopologySpreadConstraintLabelSelector { 7533 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(constraint.LabelSelector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, subFldPath.Child("labelSelector"))...) 7534 } 7535 } 7536 7537 return allErrs 7538 } 7539 7540 // ValidateMaxSkew tests that the argument is a valid MaxSkew. 7541 func ValidateMaxSkew(fldPath *field.Path, maxSkew int32) *field.Error { 7542 if maxSkew <= 0 { 7543 return field.Invalid(fldPath, maxSkew, isNotPositiveErrorMsg) 7544 } 7545 return nil 7546 } 7547 7548 // validateMinDomains tests that the argument is a valid MinDomains. 7549 func validateMinDomains(fldPath *field.Path, minDomains *int32, action core.UnsatisfiableConstraintAction) field.ErrorList { 7550 if minDomains == nil { 7551 return nil 7552 } 7553 var allErrs field.ErrorList 7554 if *minDomains <= 0 { 7555 allErrs = append(allErrs, field.Invalid(fldPath, minDomains, isNotPositiveErrorMsg)) 7556 } 7557 // When MinDomains is non-nil, whenUnsatisfiable must be DoNotSchedule. 7558 if action != core.DoNotSchedule { 7559 allErrs = append(allErrs, field.Invalid(fldPath, minDomains, fmt.Sprintf("can only use minDomains if whenUnsatisfiable=%s, not %s", core.DoNotSchedule, action))) 7560 } 7561 return allErrs 7562 } 7563 7564 // ValidateTopologyKey tests that the argument is a valid TopologyKey. 7565 func ValidateTopologyKey(fldPath *field.Path, topologyKey string) *field.Error { 7566 if len(topologyKey) == 0 { 7567 return field.Required(fldPath, "can not be empty") 7568 } 7569 return nil 7570 } 7571 7572 // ValidateWhenUnsatisfiable tests that the argument is a valid UnsatisfiableConstraintAction. 7573 func ValidateWhenUnsatisfiable(fldPath *field.Path, action core.UnsatisfiableConstraintAction) *field.Error { 7574 if !supportedScheduleActions.Has(action) { 7575 return field.NotSupported(fldPath, action, sets.List(supportedScheduleActions)) 7576 } 7577 return nil 7578 } 7579 7580 // ValidateSpreadConstraintNotRepeat tests that if `constraint` duplicates with `existingConstraintPairs` 7581 // on TopologyKey and WhenUnsatisfiable fields. 7582 func ValidateSpreadConstraintNotRepeat(fldPath *field.Path, constraint core.TopologySpreadConstraint, restingConstraints []core.TopologySpreadConstraint) *field.Error { 7583 for _, restingConstraint := range restingConstraints { 7584 if constraint.TopologyKey == restingConstraint.TopologyKey && 7585 constraint.WhenUnsatisfiable == restingConstraint.WhenUnsatisfiable { 7586 return field.Duplicate(fldPath, fmt.Sprintf("{%v, %v}", constraint.TopologyKey, constraint.WhenUnsatisfiable)) 7587 } 7588 } 7589 return nil 7590 } 7591 7592 var ( 7593 supportedPodTopologySpreadNodePolicies = sets.New(core.NodeInclusionPolicyIgnore, core.NodeInclusionPolicyHonor) 7594 ) 7595 7596 // validateNodeAffinityPolicy tests that the argument is a valid NodeInclusionPolicy. 7597 func validateNodeInclusionPolicy(fldPath *field.Path, policy *core.NodeInclusionPolicy) *field.Error { 7598 if policy == nil { 7599 return nil 7600 } 7601 7602 if !supportedPodTopologySpreadNodePolicies.Has(*policy) { 7603 return field.NotSupported(fldPath, policy, sets.List(supportedPodTopologySpreadNodePolicies)) 7604 } 7605 return nil 7606 } 7607 7608 // validateMatchLabelKeysAndMismatchLabelKeys checks if both matchLabelKeys and mismatchLabelKeys are valid. 7609 // - validate that all matchLabelKeys and mismatchLabelKeys are valid label names. 7610 // - validate that the user doens't specify the same key in both matchLabelKeys and labelSelector. 7611 // - validate that any matchLabelKeys are not duplicated with mismatchLabelKeys. 7612 func validateMatchLabelKeysAndMismatchLabelKeys(fldPath *field.Path, matchLabelKeys, mismatchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList { 7613 var allErrs field.ErrorList 7614 // 1. validate that all matchLabelKeys and mismatchLabelKeys are valid label names. 7615 allErrs = append(allErrs, validateLabelKeys(fldPath.Child("matchLabelKeys"), matchLabelKeys, labelSelector)...) 7616 allErrs = append(allErrs, validateLabelKeys(fldPath.Child("mismatchLabelKeys"), mismatchLabelKeys, labelSelector)...) 7617 7618 // 2. validate that the user doens't specify the same key in both matchLabelKeys and labelSelector. 7619 // It doesn't make sense to have the labelselector with the key specified in matchLabelKeys 7620 // because the matchLabelKeys will be `In` labelSelector which matches with only one value in the key 7621 // and we cannot make any further filtering with that key. 7622 // On the other hand, we may want to have labelSelector with the key specified in mismatchLabelKeys. 7623 // because the mismatchLabelKeys will be `NotIn` labelSelector 7624 // and we may want to filter Pods further with other labelSelector with that key. 7625 7626 // labelKeysMap is keyed by label key and valued by the index of label key in labelKeys. 7627 if labelSelector != nil { 7628 labelKeysMap := map[string]int{} 7629 for i, key := range matchLabelKeys { 7630 labelKeysMap[key] = i 7631 } 7632 labelSelectorKeys := sets.New[string]() 7633 for key := range labelSelector.MatchLabels { 7634 labelSelectorKeys.Insert(key) 7635 } 7636 for _, matchExpression := range labelSelector.MatchExpressions { 7637 key := matchExpression.Key 7638 if i, ok := labelKeysMap[key]; ok && labelSelectorKeys.Has(key) { 7639 // Before validateLabelKeysWithSelector is called, the labelSelector has already got the selector created from matchLabelKeys. 7640 // Here, we found the duplicate key in labelSelector and the key is specified in labelKeys. 7641 // Meaning that the same key is specified in both labelSelector and matchLabelKeys/mismatchLabelKeys. 7642 allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector")) 7643 } 7644 7645 labelSelectorKeys.Insert(key) 7646 } 7647 } 7648 7649 // 3. validate that any matchLabelKeys are not duplicated with mismatchLabelKeys. 7650 mismatchLabelKeysSet := sets.New(mismatchLabelKeys...) 7651 for i, k := range matchLabelKeys { 7652 if mismatchLabelKeysSet.Has(k) { 7653 allErrs = append(allErrs, field.Invalid(fldPath.Child("matchLabelKeys").Index(i), k, "exists in both matchLabelKeys and mismatchLabelKeys")) 7654 } 7655 } 7656 7657 return allErrs 7658 } 7659 7660 // validateMatchLabelKeysInTopologySpread tests that the elements are a valid label name and are not already included in labelSelector. 7661 func validateMatchLabelKeysInTopologySpread(fldPath *field.Path, matchLabelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList { 7662 if len(matchLabelKeys) == 0 { 7663 return nil 7664 } 7665 7666 var allErrs field.ErrorList 7667 labelSelectorKeys := sets.Set[string]{} 7668 7669 if labelSelector != nil { 7670 for key := range labelSelector.MatchLabels { 7671 labelSelectorKeys.Insert(key) 7672 } 7673 for _, matchExpression := range labelSelector.MatchExpressions { 7674 labelSelectorKeys.Insert(matchExpression.Key) 7675 } 7676 } else { 7677 allErrs = append(allErrs, field.Forbidden(fldPath, "must not be specified when labelSelector is not set")) 7678 } 7679 7680 for i, key := range matchLabelKeys { 7681 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(key, fldPath.Index(i))...) 7682 if labelSelectorKeys.Has(key) { 7683 allErrs = append(allErrs, field.Invalid(fldPath.Index(i), key, "exists in both matchLabelKeys and labelSelector")) 7684 } 7685 } 7686 7687 return allErrs 7688 } 7689 7690 // validateLabelKeys tests that the label keys are a valid label name. 7691 // It's intended to be used for matchLabelKeys or mismatchLabelKeys. 7692 func validateLabelKeys(fldPath *field.Path, labelKeys []string, labelSelector *metav1.LabelSelector) field.ErrorList { 7693 if len(labelKeys) == 0 { 7694 return nil 7695 } 7696 7697 if labelSelector == nil { 7698 return field.ErrorList{field.Forbidden(fldPath, "must not be specified when labelSelector is not set")} 7699 } 7700 7701 var allErrs field.ErrorList 7702 for i, key := range labelKeys { 7703 allErrs = append(allErrs, unversionedvalidation.ValidateLabelName(key, fldPath.Index(i))...) 7704 } 7705 7706 return allErrs 7707 } 7708 7709 // ValidateServiceClusterIPsRelatedFields validates .spec.ClusterIPs,, 7710 // .spec.IPFamilies, .spec.ipFamilyPolicy. This is exported because it is used 7711 // during IP init and allocation. 7712 func ValidateServiceClusterIPsRelatedFields(service *core.Service) field.ErrorList { 7713 // ClusterIP, ClusterIPs, IPFamilyPolicy and IPFamilies are validated prior (all must be unset) for ExternalName service 7714 if service.Spec.Type == core.ServiceTypeExternalName { 7715 return field.ErrorList{} 7716 } 7717 7718 allErrs := field.ErrorList{} 7719 hasInvalidIPs := false 7720 7721 specPath := field.NewPath("spec") 7722 clusterIPsField := specPath.Child("clusterIPs") 7723 ipFamiliesField := specPath.Child("ipFamilies") 7724 ipFamilyPolicyField := specPath.Child("ipFamilyPolicy") 7725 7726 // Make sure ClusterIP and ClusterIPs are synced. For most cases users can 7727 // just manage one or the other and we'll handle the rest (see PrepareFor* 7728 // in strategy). 7729 if len(service.Spec.ClusterIP) != 0 { 7730 // If ClusterIP is set, ClusterIPs[0] must match. 7731 if len(service.Spec.ClusterIPs) == 0 { 7732 allErrs = append(allErrs, field.Required(clusterIPsField, "")) 7733 } else if service.Spec.ClusterIPs[0] != service.Spec.ClusterIP { 7734 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "first value must match `clusterIP`")) 7735 } 7736 } else { // ClusterIP == "" 7737 // If ClusterIP is not set, ClusterIPs must also be unset. 7738 if len(service.Spec.ClusterIPs) != 0 { 7739 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "must be empty when `clusterIP` is not specified")) 7740 } 7741 } 7742 7743 // ipfamilies stand alone validation 7744 // must be either IPv4 or IPv6 7745 seen := sets.Set[core.IPFamily]{} 7746 for i, ipFamily := range service.Spec.IPFamilies { 7747 if !supportedServiceIPFamily.Has(ipFamily) { 7748 allErrs = append(allErrs, field.NotSupported(ipFamiliesField.Index(i), ipFamily, sets.List(supportedServiceIPFamily))) 7749 } 7750 // no duplicate check also ensures that ipfamilies is dualstacked, in any order 7751 if seen.Has(ipFamily) { 7752 allErrs = append(allErrs, field.Duplicate(ipFamiliesField.Index(i), ipFamily)) 7753 } 7754 seen.Insert(ipFamily) 7755 } 7756 7757 // IPFamilyPolicy stand alone validation 7758 // note: nil is ok, defaulted in alloc check registry/core/service/* 7759 if service.Spec.IPFamilyPolicy != nil { 7760 // must have a supported value 7761 if !supportedServiceIPFamilyPolicy.Has(*(service.Spec.IPFamilyPolicy)) { 7762 allErrs = append(allErrs, field.NotSupported(ipFamilyPolicyField, service.Spec.IPFamilyPolicy, sets.List(supportedServiceIPFamilyPolicy))) 7763 } 7764 } 7765 7766 // clusterIPs stand alone validation 7767 // valid ips with None and empty string handling 7768 // duplication check is done as part of DualStackvalidation below 7769 for i, clusterIP := range service.Spec.ClusterIPs { 7770 // valid at first location only. if and only if len(clusterIPs) == 1 7771 if i == 0 && clusterIP == core.ClusterIPNone { 7772 if len(service.Spec.ClusterIPs) > 1 { 7773 hasInvalidIPs = true 7774 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "'None' must be the first and only value")) 7775 } 7776 continue 7777 } 7778 7779 // is it valid ip? 7780 errorMessages := validation.IsValidIP(clusterIPsField.Index(i), clusterIP) 7781 hasInvalidIPs = (len(errorMessages) != 0) || hasInvalidIPs 7782 allErrs = append(allErrs, errorMessages...) 7783 } 7784 7785 // max two 7786 if len(service.Spec.ClusterIPs) > 2 { 7787 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "may only hold up to 2 values")) 7788 } 7789 7790 // at this stage if there is an invalid ip or misplaced none/empty string 7791 // it will skew the error messages (bad index || dualstackness of already bad ips). so we 7792 // stop here if there are errors in clusterIPs validation 7793 if hasInvalidIPs { 7794 return allErrs 7795 } 7796 7797 // must be dual stacked ips if they are more than one ip 7798 if len(service.Spec.ClusterIPs) > 1 /* meaning: it does not have a None or empty string */ { 7799 dualStack, err := netutils.IsDualStackIPStrings(service.Spec.ClusterIPs) 7800 if err != nil { // though we check for that earlier. safe > sorry 7801 allErrs = append(allErrs, field.InternalError(clusterIPsField, fmt.Errorf("failed to check for dual stack with error:%v", err))) 7802 } 7803 7804 // We only support one from each IP family (i.e. max two IPs in this list). 7805 if !dualStack { 7806 allErrs = append(allErrs, field.Invalid(clusterIPsField, service.Spec.ClusterIPs, "may specify no more than one IP for each IP family")) 7807 } 7808 } 7809 7810 // match clusterIPs to their families, if they were provided 7811 if !isHeadlessService(service) && len(service.Spec.ClusterIPs) > 0 && len(service.Spec.IPFamilies) > 0 { 7812 for i, ip := range service.Spec.ClusterIPs { 7813 if i > (len(service.Spec.IPFamilies) - 1) { 7814 break // no more families to check 7815 } 7816 7817 // 4=>6 7818 if service.Spec.IPFamilies[i] == core.IPv4Protocol && netutils.IsIPv6String(ip) { 7819 allErrs = append(allErrs, field.Invalid(clusterIPsField.Index(i), ip, fmt.Sprintf("expected an IPv4 value as indicated by `ipFamilies[%v]`", i))) 7820 } 7821 // 6=>4 7822 if service.Spec.IPFamilies[i] == core.IPv6Protocol && !netutils.IsIPv6String(ip) { 7823 allErrs = append(allErrs, field.Invalid(clusterIPsField.Index(i), ip, fmt.Sprintf("expected an IPv6 value as indicated by `ipFamilies[%v]`", i))) 7824 } 7825 } 7826 } 7827 7828 return allErrs 7829 } 7830 7831 // specific validation for clusterIPs in cases of user upgrading or downgrading to/from dualstack 7832 func validateUpgradeDowngradeClusterIPs(oldService, service *core.Service) field.ErrorList { 7833 allErrs := make(field.ErrorList, 0) 7834 7835 // bail out early for ExternalName 7836 if service.Spec.Type == core.ServiceTypeExternalName || oldService.Spec.Type == core.ServiceTypeExternalName { 7837 return allErrs 7838 } 7839 newIsHeadless := isHeadlessService(service) 7840 oldIsHeadless := isHeadlessService(oldService) 7841 7842 if oldIsHeadless && newIsHeadless { 7843 return allErrs 7844 } 7845 7846 switch { 7847 // no change in ClusterIP lengths 7848 // compare each 7849 case len(oldService.Spec.ClusterIPs) == len(service.Spec.ClusterIPs): 7850 for i, ip := range oldService.Spec.ClusterIPs { 7851 if ip != service.Spec.ClusterIPs[i] { 7852 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(i), service.Spec.ClusterIPs, "may not change once set")) 7853 } 7854 } 7855 7856 // something has been released (downgraded) 7857 case len(oldService.Spec.ClusterIPs) > len(service.Spec.ClusterIPs): 7858 // primary ClusterIP has been released 7859 if len(service.Spec.ClusterIPs) == 0 { 7860 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(0), service.Spec.ClusterIPs, "primary clusterIP can not be unset")) 7861 } 7862 7863 // test if primary clusterIP has changed 7864 if len(oldService.Spec.ClusterIPs) > 0 && 7865 len(service.Spec.ClusterIPs) > 0 && 7866 service.Spec.ClusterIPs[0] != oldService.Spec.ClusterIPs[0] { 7867 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(0), service.Spec.ClusterIPs, "may not change once set")) 7868 } 7869 7870 // test if secondary ClusterIP has been released. has this service been downgraded correctly? 7871 // user *must* set IPFamilyPolicy == SingleStack 7872 if len(service.Spec.ClusterIPs) == 1 { 7873 if service.Spec.IPFamilyPolicy == nil || *(service.Spec.IPFamilyPolicy) != core.IPFamilyPolicySingleStack { 7874 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "must be set to 'SingleStack' when releasing the secondary clusterIP")) 7875 } 7876 } 7877 case len(oldService.Spec.ClusterIPs) < len(service.Spec.ClusterIPs): 7878 // something has been added (upgraded) 7879 // test if primary clusterIP has changed 7880 if len(oldService.Spec.ClusterIPs) > 0 && 7881 service.Spec.ClusterIPs[0] != oldService.Spec.ClusterIPs[0] { 7882 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "clusterIPs").Index(0), service.Spec.ClusterIPs, "may not change once set")) 7883 } 7884 // we don't check for Policy == RequireDualStack here since, Validation/Creation func takes care of it 7885 } 7886 return allErrs 7887 } 7888 7889 // specific validation for ipFamilies in cases of user upgrading or downgrading to/from dualstack 7890 func validateUpgradeDowngradeIPFamilies(oldService, service *core.Service) field.ErrorList { 7891 allErrs := make(field.ErrorList, 0) 7892 // bail out early for ExternalName 7893 if service.Spec.Type == core.ServiceTypeExternalName || oldService.Spec.Type == core.ServiceTypeExternalName { 7894 return allErrs 7895 } 7896 7897 oldIsHeadless := isHeadlessService(oldService) 7898 newIsHeadless := isHeadlessService(service) 7899 7900 // if changed to/from headless, then bail out 7901 if newIsHeadless != oldIsHeadless { 7902 return allErrs 7903 } 7904 // headless can change families 7905 if newIsHeadless { 7906 return allErrs 7907 } 7908 7909 switch { 7910 case len(oldService.Spec.IPFamilies) == len(service.Spec.IPFamilies): 7911 // no change in ClusterIP lengths 7912 // compare each 7913 7914 for i, ip := range oldService.Spec.IPFamilies { 7915 if ip != service.Spec.IPFamilies[i] { 7916 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.IPFamilies, "may not change once set")) 7917 } 7918 } 7919 7920 case len(oldService.Spec.IPFamilies) > len(service.Spec.IPFamilies): 7921 // something has been released (downgraded) 7922 7923 // test if primary ipfamily has been released 7924 if len(service.Spec.ClusterIPs) == 0 { 7925 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.IPFamilies, "primary ipFamily can not be unset")) 7926 } 7927 7928 // test if primary ipFamily has changed 7929 if len(service.Spec.IPFamilies) > 0 && 7930 service.Spec.IPFamilies[0] != oldService.Spec.IPFamilies[0] { 7931 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.ClusterIPs, "may not change once set")) 7932 } 7933 7934 // test if secondary IPFamily has been released. has this service been downgraded correctly? 7935 // user *must* set IPFamilyPolicy == SingleStack 7936 if len(service.Spec.IPFamilies) == 1 { 7937 if service.Spec.IPFamilyPolicy == nil || *(service.Spec.IPFamilyPolicy) != core.IPFamilyPolicySingleStack { 7938 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilyPolicy"), service.Spec.IPFamilyPolicy, "must be set to 'SingleStack' when releasing the secondary ipFamily")) 7939 } 7940 } 7941 case len(oldService.Spec.IPFamilies) < len(service.Spec.IPFamilies): 7942 // something has been added (upgraded) 7943 7944 // test if primary ipFamily has changed 7945 if len(oldService.Spec.IPFamilies) > 0 && 7946 len(service.Spec.IPFamilies) > 0 && 7947 service.Spec.IPFamilies[0] != oldService.Spec.IPFamilies[0] { 7948 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "ipFamilies").Index(0), service.Spec.ClusterIPs, "may not change once set")) 7949 } 7950 // we don't check for Policy == RequireDualStack here since, Validation/Creation func takes care of it 7951 } 7952 return allErrs 7953 } 7954 7955 func isHeadlessService(service *core.Service) bool { 7956 return service != nil && 7957 len(service.Spec.ClusterIPs) == 1 && 7958 service.Spec.ClusterIPs[0] == core.ClusterIPNone 7959 } 7960 7961 // validateLoadBalancerClassField validation for loadBalancerClass 7962 func validateLoadBalancerClassField(oldService, service *core.Service) field.ErrorList { 7963 allErrs := make(field.ErrorList, 0) 7964 if oldService != nil { 7965 // validate update op 7966 if isTypeLoadBalancer(oldService) && isTypeLoadBalancer(service) { 7967 // old and new are both LoadBalancer 7968 if !sameLoadBalancerClass(oldService, service) { 7969 // can't change loadBalancerClass 7970 allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "loadBalancerClass"), service.Spec.LoadBalancerClass, "may not change once set")) 7971 } 7972 } 7973 } 7974 7975 if isTypeLoadBalancer(service) { 7976 // check LoadBalancerClass format 7977 if service.Spec.LoadBalancerClass != nil { 7978 allErrs = append(allErrs, ValidateQualifiedName(*service.Spec.LoadBalancerClass, field.NewPath("spec", "loadBalancerClass"))...) 7979 } 7980 } else { 7981 // check if LoadBalancerClass set for non LoadBalancer type of service 7982 if service.Spec.LoadBalancerClass != nil { 7983 allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "loadBalancerClass"), "may only be used when `type` is 'LoadBalancer'")) 7984 } 7985 } 7986 return allErrs 7987 } 7988 7989 // isTypeLoadBalancer tests service type is loadBalancer or not 7990 func isTypeLoadBalancer(service *core.Service) bool { 7991 return service.Spec.Type == core.ServiceTypeLoadBalancer 7992 } 7993 7994 // sameLoadBalancerClass check two services have the same loadBalancerClass or not 7995 func sameLoadBalancerClass(oldService, service *core.Service) bool { 7996 if oldService.Spec.LoadBalancerClass == nil && service.Spec.LoadBalancerClass == nil { 7997 return true 7998 } 7999 if oldService.Spec.LoadBalancerClass == nil || service.Spec.LoadBalancerClass == nil { 8000 return false 8001 } 8002 return *oldService.Spec.LoadBalancerClass == *service.Spec.LoadBalancerClass 8003 } 8004 8005 func ValidatePodAffinityTermSelector(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList { 8006 var allErrs field.ErrorList 8007 labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: allowInvalidLabelValueInSelector} 8008 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, labelSelectorValidationOptions, fldPath.Child("labelSelector"))...) 8009 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.NamespaceSelector, labelSelectorValidationOptions, fldPath.Child("namespaceSelector"))...) 8010 return allErrs 8011 } 8012 8013 var betaToGALabel = map[string]string{ 8014 v1.LabelFailureDomainBetaZone: v1.LabelTopologyZone, 8015 v1.LabelFailureDomainBetaRegion: v1.LabelTopologyRegion, 8016 kubeletapis.LabelOS: v1.LabelOSStable, 8017 kubeletapis.LabelArch: v1.LabelArchStable, 8018 v1.LabelInstanceType: v1.LabelInstanceTypeStable, 8019 } 8020 8021 var ( 8022 maskNodeSelectorLabelChangeEqualities conversion.Equalities 8023 initMaskNodeSelectorLabelChangeEqualities sync.Once 8024 ) 8025 8026 func getMaskNodeSelectorLabelChangeEqualities() conversion.Equalities { 8027 initMaskNodeSelectorLabelChangeEqualities.Do(func() { 8028 var eqs = apiequality.Semantic.Copy() 8029 err := eqs.AddFunc( 8030 func(newReq, oldReq core.NodeSelectorRequirement) bool { 8031 // allow newReq to change to a GA key 8032 if oldReq.Key != newReq.Key && betaToGALabel[oldReq.Key] == newReq.Key { 8033 oldReq.Key = newReq.Key // +k8s:verify-mutation:reason=clone 8034 } 8035 return apiequality.Semantic.DeepEqual(newReq, oldReq) 8036 }, 8037 ) 8038 if err != nil { 8039 panic(fmt.Errorf("failed to instantiate semantic equalities: %w", err)) 8040 } 8041 maskNodeSelectorLabelChangeEqualities = eqs 8042 }) 8043 return maskNodeSelectorLabelChangeEqualities 8044 } 8045 8046 func validatePvNodeAffinity(newPvNodeAffinity, oldPvNodeAffinity *core.VolumeNodeAffinity, fldPath *field.Path) field.ErrorList { 8047 var allErrs field.ErrorList 8048 if !getMaskNodeSelectorLabelChangeEqualities().DeepEqual(newPvNodeAffinity, oldPvNodeAffinity) { 8049 allErrs = append(allErrs, field.Invalid(fldPath, newPvNodeAffinity, fieldImmutableErrorMsg+", except for updating from beta label to GA")) 8050 } 8051 return allErrs 8052 } 8053 8054 func validateNodeSelectorMutation(fldPath *field.Path, newNodeSelector, oldNodeSelector map[string]string) field.ErrorList { 8055 var allErrs field.ErrorList 8056 8057 // Validate no existing node selectors were deleted or mutated. 8058 for k, v1 := range oldNodeSelector { 8059 if v2, ok := newNodeSelector[k]; !ok || v1 != v2 { 8060 allErrs = append(allErrs, field.Invalid(fldPath, newNodeSelector, "only additions to spec.nodeSelector are allowed (no mutations or deletions)")) 8061 return allErrs 8062 } 8063 } 8064 return allErrs 8065 } 8066 8067 func validateNodeAffinityMutation(nodeAffinityPath *field.Path, newNodeAffinity, oldNodeAffinity *core.NodeAffinity) field.ErrorList { 8068 var allErrs field.ErrorList 8069 // If old node affinity was nil, anything can be set. 8070 if oldNodeAffinity == nil || oldNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { 8071 return allErrs 8072 } 8073 8074 oldTerms := oldNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms 8075 var newTerms []core.NodeSelectorTerm 8076 if newNodeAffinity != nil && newNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil { 8077 newTerms = newNodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms 8078 } 8079 8080 // If there are no old terms, we can set the new terms to anything. 8081 // If there are old terms, we cannot add any new ones. 8082 if len(oldTerms) > 0 && len(oldTerms) != len(newTerms) { 8083 return append(allErrs, field.Invalid(nodeAffinityPath.Child("requiredDuringSchedulingIgnoredDuringExecution").Child("nodeSelectorTerms"), newTerms, "no additions/deletions to non-empty NodeSelectorTerms list are allowed")) 8084 } 8085 8086 // For requiredDuringSchedulingIgnoredDuringExecution, if old NodeSelectorTerms 8087 // was empty, anything can be set. If non-empty, only additions of NodeSelectorRequirements 8088 // to matchExpressions or fieldExpressions are allowed. 8089 for i := range oldTerms { 8090 if !validateNodeSelectorTermHasOnlyAdditions(newTerms[i], oldTerms[i]) { 8091 allErrs = append(allErrs, field.Invalid(nodeAffinityPath.Child("requiredDuringSchedulingIgnoredDuringExecution").Child("nodeSelectorTerms").Index(i), newTerms[i], "only additions are allowed (no mutations or deletions)")) 8092 } 8093 } 8094 return allErrs 8095 } 8096 8097 func validateNodeSelectorTermHasOnlyAdditions(newTerm, oldTerm core.NodeSelectorTerm) bool { 8098 if len(oldTerm.MatchExpressions) == 0 && len(oldTerm.MatchFields) == 0 { 8099 if len(newTerm.MatchExpressions) > 0 || len(newTerm.MatchFields) > 0 { 8100 return false 8101 } 8102 } 8103 8104 // Validate MatchExpressions only has additions (no deletions or mutations) 8105 if l := len(oldTerm.MatchExpressions); l > 0 { 8106 if len(newTerm.MatchExpressions) < l { 8107 return false 8108 } 8109 if !apiequality.Semantic.DeepEqual(newTerm.MatchExpressions[:l], oldTerm.MatchExpressions) { 8110 return false 8111 } 8112 } 8113 // Validate MatchFields only has additions (no deletions or mutations) 8114 if l := len(oldTerm.MatchFields); l > 0 { 8115 if len(newTerm.MatchFields) < l { 8116 return false 8117 } 8118 if !apiequality.Semantic.DeepEqual(newTerm.MatchFields[:l], oldTerm.MatchFields) { 8119 return false 8120 } 8121 } 8122 return true 8123 }