Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame] | 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 | "fmt" |
| 21 | "strings" |
| 22 | |
| 23 | apiequality "k8s.io/apimachinery/pkg/api/equality" |
| 24 | "k8s.io/apimachinery/pkg/api/meta" |
| 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 26 | v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" |
| 27 | "k8s.io/apimachinery/pkg/runtime/schema" |
| 28 | "k8s.io/apimachinery/pkg/util/sets" |
| 29 | "k8s.io/apimachinery/pkg/util/validation" |
| 30 | "k8s.io/apimachinery/pkg/util/validation/field" |
| 31 | ) |
| 32 | |
| 33 | const FieldImmutableErrorMsg string = `field is immutable` |
| 34 | |
| 35 | const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB |
| 36 | |
| 37 | // BannedOwners is a black list of object that are not allowed to be owners. |
| 38 | var BannedOwners = map[schema.GroupVersionKind]struct{}{ |
| 39 | {Group: "", Version: "v1", Kind: "Event"}: {}, |
| 40 | } |
| 41 | |
| 42 | // ValidateClusterName can be used to check whether the given cluster name is valid. |
| 43 | var ValidateClusterName = NameIsDNS1035Label |
| 44 | |
| 45 | // ValidateAnnotations validates that a set of annotations are correctly defined. |
| 46 | func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { |
| 47 | allErrs := field.ErrorList{} |
| 48 | var totalSize int64 |
| 49 | for k, v := range annotations { |
| 50 | for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) { |
| 51 | allErrs = append(allErrs, field.Invalid(fldPath, k, msg)) |
| 52 | } |
| 53 | totalSize += (int64)(len(k)) + (int64)(len(v)) |
| 54 | } |
| 55 | if totalSize > (int64)(totalAnnotationSizeLimitB) { |
| 56 | allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB)) |
| 57 | } |
| 58 | return allErrs |
| 59 | } |
| 60 | |
| 61 | func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList { |
| 62 | allErrs := field.ErrorList{} |
| 63 | gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind) |
| 64 | // gvk.Group is empty for the legacy group. |
| 65 | if len(gvk.Version) == 0 { |
| 66 | allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty")) |
| 67 | } |
| 68 | if len(gvk.Kind) == 0 { |
| 69 | allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty")) |
| 70 | } |
| 71 | if len(ownerReference.Name) == 0 { |
| 72 | allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty")) |
| 73 | } |
| 74 | if len(ownerReference.UID) == 0 { |
| 75 | allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty")) |
| 76 | } |
| 77 | if _, ok := BannedOwners[gvk]; ok { |
| 78 | allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk))) |
| 79 | } |
| 80 | return allErrs |
| 81 | } |
| 82 | |
| 83 | func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList { |
| 84 | allErrs := field.ErrorList{} |
| 85 | controllerName := "" |
| 86 | for _, ref := range ownerReferences { |
| 87 | allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...) |
| 88 | if ref.Controller != nil && *ref.Controller { |
| 89 | if controllerName != "" { |
| 90 | allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences, |
| 91 | fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", controllerName, ref.Name))) |
| 92 | } else { |
| 93 | controllerName = ref.Name |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | return allErrs |
| 98 | } |
| 99 | |
| 100 | // Validate finalizer names |
| 101 | func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList { |
| 102 | allErrs := field.ErrorList{} |
| 103 | for _, msg := range validation.IsQualifiedName(stringValue) { |
| 104 | allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg)) |
| 105 | } |
| 106 | |
| 107 | return allErrs |
| 108 | } |
| 109 | |
| 110 | func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList { |
| 111 | allErrs := field.ErrorList{} |
| 112 | extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...)) |
| 113 | if len(extra) != 0 { |
| 114 | allErrs = append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("no new finalizers can be added if the object is being deleted, found new finalizers %#v", extra.List()))) |
| 115 | } |
| 116 | return allErrs |
| 117 | } |
| 118 | |
| 119 | func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList { |
| 120 | allErrs := field.ErrorList{} |
| 121 | if !apiequality.Semantic.DeepEqual(oldVal, newVal) { |
| 122 | allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg)) |
| 123 | } |
| 124 | return allErrs |
| 125 | } |
| 126 | |
| 127 | // ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already |
| 128 | // been performed. |
| 129 | // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. |
| 130 | func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { |
| 131 | metadata, err := meta.Accessor(objMeta) |
| 132 | if err != nil { |
| 133 | allErrs := field.ErrorList{} |
| 134 | allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error())) |
| 135 | return allErrs |
| 136 | } |
| 137 | return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath) |
| 138 | } |
| 139 | |
| 140 | // ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already |
| 141 | // been performed. |
| 142 | // It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before. |
| 143 | func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList { |
| 144 | allErrs := field.ErrorList{} |
| 145 | |
| 146 | if len(meta.GetGenerateName()) != 0 { |
| 147 | for _, msg := range nameFn(meta.GetGenerateName(), true) { |
| 148 | allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg)) |
| 149 | } |
| 150 | } |
| 151 | // If the generated name validates, but the calculated value does not, it's a problem with generation, and we |
| 152 | // report it here. This may confuse users, but indicates a programming bug and still must be validated. |
| 153 | // If there are multiple fields out of which one is required then add an or as a separator |
| 154 | if len(meta.GetName()) == 0 { |
| 155 | allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required")) |
| 156 | } else { |
| 157 | for _, msg := range nameFn(meta.GetName(), false) { |
| 158 | allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg)) |
| 159 | } |
| 160 | } |
| 161 | if requiresNamespace { |
| 162 | if len(meta.GetNamespace()) == 0 { |
| 163 | allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "")) |
| 164 | } else { |
| 165 | for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) { |
| 166 | allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg)) |
| 167 | } |
| 168 | } |
| 169 | } else { |
| 170 | if len(meta.GetNamespace()) != 0 { |
| 171 | allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type")) |
| 172 | } |
| 173 | } |
| 174 | if len(meta.GetClusterName()) != 0 { |
| 175 | for _, msg := range ValidateClusterName(meta.GetClusterName(), false) { |
| 176 | allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.GetClusterName(), msg)) |
| 177 | } |
| 178 | } |
| 179 | allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...) |
| 180 | allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...) |
| 181 | allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...) |
| 182 | allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) |
| 183 | allErrs = append(allErrs, ValidateInitializers(meta.GetInitializers(), fldPath.Child("initializers"))...) |
| 184 | allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...) |
| 185 | return allErrs |
| 186 | } |
| 187 | |
| 188 | func ValidateInitializers(initializers *metav1.Initializers, fldPath *field.Path) field.ErrorList { |
| 189 | var allErrs field.ErrorList |
| 190 | if initializers == nil { |
| 191 | return allErrs |
| 192 | } |
| 193 | for i, initializer := range initializers.Pending { |
| 194 | allErrs = append(allErrs, validation.IsFullyQualifiedName(fldPath.Child("pending").Index(i).Child("name"), initializer.Name)...) |
| 195 | } |
| 196 | allErrs = append(allErrs, validateInitializersResult(initializers.Result, fldPath.Child("result"))...) |
| 197 | return allErrs |
| 198 | } |
| 199 | |
| 200 | func validateInitializersResult(result *metav1.Status, fldPath *field.Path) field.ErrorList { |
| 201 | var allErrs field.ErrorList |
| 202 | if result == nil { |
| 203 | return allErrs |
| 204 | } |
| 205 | switch result.Status { |
| 206 | case metav1.StatusFailure: |
| 207 | default: |
| 208 | allErrs = append(allErrs, field.Invalid(fldPath.Child("status"), result.Status, "must be 'Failure'")) |
| 209 | } |
| 210 | return allErrs |
| 211 | } |
| 212 | |
| 213 | // ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers. |
| 214 | func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList { |
| 215 | allErrs := field.ErrorList{} |
| 216 | hasFinalizerOrphanDependents := false |
| 217 | hasFinalizerDeleteDependents := false |
| 218 | for _, finalizer := range finalizers { |
| 219 | allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...) |
| 220 | if finalizer == metav1.FinalizerOrphanDependents { |
| 221 | hasFinalizerOrphanDependents = true |
| 222 | } |
| 223 | if finalizer == metav1.FinalizerDeleteDependents { |
| 224 | hasFinalizerDeleteDependents = true |
| 225 | } |
| 226 | } |
| 227 | if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents { |
| 228 | allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents))) |
| 229 | } |
| 230 | return allErrs |
| 231 | } |
| 232 | |
| 233 | // ValidateObjectMetaUpdate validates an object's metadata when updated |
| 234 | func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { |
| 235 | newMetadata, err := meta.Accessor(newMeta) |
| 236 | if err != nil { |
| 237 | allErrs := field.ErrorList{} |
| 238 | allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error())) |
| 239 | return allErrs |
| 240 | } |
| 241 | oldMetadata, err := meta.Accessor(oldMeta) |
| 242 | if err != nil { |
| 243 | allErrs := field.ErrorList{} |
| 244 | allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error())) |
| 245 | return allErrs |
| 246 | } |
| 247 | return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath) |
| 248 | } |
| 249 | |
| 250 | func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList { |
| 251 | var allErrs field.ErrorList |
| 252 | |
| 253 | // Finalizers cannot be added if the object is already being deleted. |
| 254 | if oldMeta.GetDeletionTimestamp() != nil { |
| 255 | allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...) |
| 256 | } |
| 257 | |
| 258 | // Reject updates that don't specify a resource version |
| 259 | if len(newMeta.GetResourceVersion()) == 0 { |
| 260 | allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update")) |
| 261 | } |
| 262 | |
| 263 | // Generation shouldn't be decremented |
| 264 | if newMeta.GetGeneration() < oldMeta.GetGeneration() { |
| 265 | allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented")) |
| 266 | } |
| 267 | |
| 268 | allErrs = append(allErrs, ValidateInitializersUpdate(newMeta.GetInitializers(), oldMeta.GetInitializers(), fldPath.Child("initializers"))...) |
| 269 | |
| 270 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...) |
| 271 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...) |
| 272 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...) |
| 273 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp"))...) |
| 274 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp"))...) |
| 275 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds"))...) |
| 276 | allErrs = append(allErrs, ValidateImmutableField(newMeta.GetClusterName(), oldMeta.GetClusterName(), fldPath.Child("clusterName"))...) |
| 277 | |
| 278 | allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...) |
| 279 | allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...) |
| 280 | allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...) |
| 281 | |
| 282 | return allErrs |
| 283 | } |
| 284 | |
| 285 | // ValidateInitializersUpdate checks the update of the metadata initializers field |
| 286 | func ValidateInitializersUpdate(newInit, oldInit *metav1.Initializers, fldPath *field.Path) field.ErrorList { |
| 287 | var allErrs field.ErrorList |
| 288 | switch { |
| 289 | case oldInit == nil && newInit != nil: |
| 290 | // Initializers may not be set on new objects |
| 291 | allErrs = append(allErrs, field.Invalid(fldPath, nil, "field is immutable once initialization has completed")) |
| 292 | case oldInit != nil && newInit == nil: |
| 293 | // this is a valid transition and means initialization was successful |
| 294 | case oldInit != nil && newInit != nil: |
| 295 | // validate changes to initializers |
| 296 | switch { |
| 297 | case oldInit.Result == nil && newInit.Result != nil: |
| 298 | // setting a result is allowed |
| 299 | allErrs = append(allErrs, validateInitializersResult(newInit.Result, fldPath.Child("result"))...) |
| 300 | case oldInit.Result != nil: |
| 301 | // setting Result implies permanent failure, and all future updates will be prevented |
| 302 | allErrs = append(allErrs, ValidateImmutableField(newInit.Result, oldInit.Result, fldPath.Child("result"))...) |
| 303 | default: |
| 304 | // leaving the result nil is allowed |
| 305 | } |
| 306 | } |
| 307 | return allErrs |
| 308 | } |