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