1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package validation
18
19import (
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// FieldImmutableErrorMsg is a error message for field is immutable.
34const FieldImmutableErrorMsg string = `field is immutable`
35
36const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
37
38// BannedOwners is a black list of object that are not allowed to be owners.
39var BannedOwners = map[schema.GroupVersionKind]struct{}{
40	{Group: "", Version: "v1", Kind: "Event"}: {},
41}
42
43// ValidateClusterName can be used to check whether the given cluster name is valid.
44var ValidateClusterName = NameIsDNS1035Label
45
46// ValidateAnnotations validates that a set of annotations are correctly defined.
47func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList {
48	allErrs := field.ErrorList{}
49	var totalSize int64
50	for k, v := range annotations {
51		for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
52			allErrs = append(allErrs, field.Invalid(fldPath, k, msg))
53		}
54		totalSize += (int64)(len(k)) + (int64)(len(v))
55	}
56	if totalSize > (int64)(totalAnnotationSizeLimitB) {
57		allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB))
58	}
59	return allErrs
60}
61
62func validateOwnerReference(ownerReference metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
63	allErrs := field.ErrorList{}
64	gvk := schema.FromAPIVersionAndKind(ownerReference.APIVersion, ownerReference.Kind)
65	// gvk.Group is empty for the legacy group.
66	if len(gvk.Version) == 0 {
67		allErrs = append(allErrs, field.Invalid(fldPath.Child("apiVersion"), ownerReference.APIVersion, "version must not be empty"))
68	}
69	if len(gvk.Kind) == 0 {
70		allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), ownerReference.Kind, "kind must not be empty"))
71	}
72	if len(ownerReference.Name) == 0 {
73		allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), ownerReference.Name, "name must not be empty"))
74	}
75	if len(ownerReference.UID) == 0 {
76		allErrs = append(allErrs, field.Invalid(fldPath.Child("uid"), ownerReference.UID, "uid must not be empty"))
77	}
78	if _, ok := BannedOwners[gvk]; ok {
79		allErrs = append(allErrs, field.Invalid(fldPath, ownerReference, fmt.Sprintf("%s is disallowed from being an owner", gvk)))
80	}
81	return allErrs
82}
83
84// ValidateOwnerReferences validates that a set of owner references are correctly defined.
85func ValidateOwnerReferences(ownerReferences []metav1.OwnerReference, fldPath *field.Path) field.ErrorList {
86	allErrs := field.ErrorList{}
87	controllerName := ""
88	for _, ref := range ownerReferences {
89		allErrs = append(allErrs, validateOwnerReference(ref, fldPath)...)
90		if ref.Controller != nil && *ref.Controller {
91			if controllerName != "" {
92				allErrs = append(allErrs, field.Invalid(fldPath, ownerReferences,
93					fmt.Sprintf("Only one reference can have Controller set to true. Found \"true\" in references for %v and %v", controllerName, ref.Name)))
94			} else {
95				controllerName = ref.Name
96			}
97		}
98	}
99	return allErrs
100}
101
102// ValidateFinalizerName validates finalizer names.
103func ValidateFinalizerName(stringValue string, fldPath *field.Path) field.ErrorList {
104	allErrs := field.ErrorList{}
105	for _, msg := range validation.IsQualifiedName(stringValue) {
106		allErrs = append(allErrs, field.Invalid(fldPath, stringValue, msg))
107	}
108
109	return allErrs
110}
111
112// ValidateNoNewFinalizers validates the new finalizers has no new finalizers compare to old finalizers.
113func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fldPath *field.Path) field.ErrorList {
114	allErrs := field.ErrorList{}
115	extra := sets.NewString(newFinalizers...).Difference(sets.NewString(oldFinalizers...))
116	if len(extra) != 0 {
117		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())))
118	}
119	return allErrs
120}
121
122// ValidateImmutableField validates the new value and the old value are deeply equal.
123func ValidateImmutableField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
124	allErrs := field.ErrorList{}
125	if !apiequality.Semantic.DeepEqual(oldVal, newVal) {
126		allErrs = append(allErrs, field.Invalid(fldPath, newVal, FieldImmutableErrorMsg))
127	}
128	return allErrs
129}
130
131// ValidateObjectMeta validates an object's metadata on creation. It expects that name generation has already
132// been performed.
133// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
134func ValidateObjectMeta(objMeta *metav1.ObjectMeta, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
135	metadata, err := meta.Accessor(objMeta)
136	if err != nil {
137		allErrs := field.ErrorList{}
138		allErrs = append(allErrs, field.Invalid(fldPath, objMeta, err.Error()))
139		return allErrs
140	}
141	return ValidateObjectMetaAccessor(metadata, requiresNamespace, nameFn, fldPath)
142}
143
144// ValidateObjectMetaAccessor validates an object's metadata on creation. It expects that name generation has already
145// been performed.
146// It doesn't return an error for rootscoped resources with namespace, because namespace should already be cleared before.
147func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, nameFn ValidateNameFunc, fldPath *field.Path) field.ErrorList {
148	allErrs := field.ErrorList{}
149
150	if len(meta.GetGenerateName()) != 0 {
151		for _, msg := range nameFn(meta.GetGenerateName(), true) {
152			allErrs = append(allErrs, field.Invalid(fldPath.Child("generateName"), meta.GetGenerateName(), msg))
153		}
154	}
155	// If the generated name validates, but the calculated value does not, it's a problem with generation, and we
156	// report it here. This may confuse users, but indicates a programming bug and still must be validated.
157	// If there are multiple fields out of which one is required then add an or as a separator
158	if len(meta.GetName()) == 0 {
159		allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name or generateName is required"))
160	} else {
161		for _, msg := range nameFn(meta.GetName(), false) {
162			allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), meta.GetName(), msg))
163		}
164	}
165	if requiresNamespace {
166		if len(meta.GetNamespace()) == 0 {
167			allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), ""))
168		} else {
169			for _, msg := range ValidateNamespaceName(meta.GetNamespace(), false) {
170				allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), meta.GetNamespace(), msg))
171			}
172		}
173	} else {
174		if len(meta.GetNamespace()) != 0 {
175			allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "not allowed on this type"))
176		}
177	}
178	if len(meta.GetClusterName()) != 0 {
179		for _, msg := range ValidateClusterName(meta.GetClusterName(), false) {
180			allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.GetClusterName(), msg))
181		}
182	}
183	for _, entry := range meta.GetManagedFields() {
184		allErrs = append(allErrs, v1validation.ValidateFieldManager(entry.Manager, fldPath.Child("fieldManager"))...)
185	}
186	allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...)
187	allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...)
188	allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...)
189	allErrs = append(allErrs, ValidateOwnerReferences(meta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
190	allErrs = append(allErrs, ValidateFinalizers(meta.GetFinalizers(), fldPath.Child("finalizers"))...)
191	allErrs = append(allErrs, v1validation.ValidateManagedFields(meta.GetManagedFields(), fldPath.Child("managedFields"))...)
192	return allErrs
193}
194
195// ValidateFinalizers tests if the finalizers name are valid, and if there are conflicting finalizers.
196func ValidateFinalizers(finalizers []string, fldPath *field.Path) field.ErrorList {
197	allErrs := field.ErrorList{}
198	hasFinalizerOrphanDependents := false
199	hasFinalizerDeleteDependents := false
200	for _, finalizer := range finalizers {
201		allErrs = append(allErrs, ValidateFinalizerName(finalizer, fldPath)...)
202		if finalizer == metav1.FinalizerOrphanDependents {
203			hasFinalizerOrphanDependents = true
204		}
205		if finalizer == metav1.FinalizerDeleteDependents {
206			hasFinalizerDeleteDependents = true
207		}
208	}
209	if hasFinalizerDeleteDependents && hasFinalizerOrphanDependents {
210		allErrs = append(allErrs, field.Invalid(fldPath, finalizers, fmt.Sprintf("finalizer %s and %s cannot be both set", metav1.FinalizerOrphanDependents, metav1.FinalizerDeleteDependents)))
211	}
212	return allErrs
213}
214
215// ValidateObjectMetaUpdate validates an object's metadata when updated.
216func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
217	newMetadata, err := meta.Accessor(newMeta)
218	if err != nil {
219		allErrs := field.ErrorList{}
220		allErrs = append(allErrs, field.Invalid(fldPath, newMeta, err.Error()))
221		return allErrs
222	}
223	oldMetadata, err := meta.Accessor(oldMeta)
224	if err != nil {
225		allErrs := field.ErrorList{}
226		allErrs = append(allErrs, field.Invalid(fldPath, oldMeta, err.Error()))
227		return allErrs
228	}
229	return ValidateObjectMetaAccessorUpdate(newMetadata, oldMetadata, fldPath)
230}
231
232// ValidateObjectMetaAccessorUpdate validates an object's metadata when updated.
233func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *field.Path) field.ErrorList {
234	var allErrs field.ErrorList
235
236	// Finalizers cannot be added if the object is already being deleted.
237	if oldMeta.GetDeletionTimestamp() != nil {
238		allErrs = append(allErrs, ValidateNoNewFinalizers(newMeta.GetFinalizers(), oldMeta.GetFinalizers(), fldPath.Child("finalizers"))...)
239	}
240
241	// Reject updates that don't specify a resource version
242	if len(newMeta.GetResourceVersion()) == 0 {
243		allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newMeta.GetResourceVersion(), "must be specified for an update"))
244	}
245
246	// Generation shouldn't be decremented
247	if newMeta.GetGeneration() < oldMeta.GetGeneration() {
248		allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented"))
249	}
250
251	for _, entry := range newMeta.GetManagedFields() {
252		allErrs = append(allErrs, v1validation.ValidateFieldManager(entry.Manager, fldPath.Child("fieldManager"))...)
253	}
254	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...)
255	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...)
256	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...)
257	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetCreationTimestamp(), oldMeta.GetCreationTimestamp(), fldPath.Child("creationTimestamp"))...)
258	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionTimestamp(), oldMeta.GetDeletionTimestamp(), fldPath.Child("deletionTimestamp"))...)
259	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetDeletionGracePeriodSeconds(), oldMeta.GetDeletionGracePeriodSeconds(), fldPath.Child("deletionGracePeriodSeconds"))...)
260	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetClusterName(), oldMeta.GetClusterName(), fldPath.Child("clusterName"))...)
261
262	allErrs = append(allErrs, v1validation.ValidateLabels(newMeta.GetLabels(), fldPath.Child("labels"))...)
263	allErrs = append(allErrs, ValidateAnnotations(newMeta.GetAnnotations(), fldPath.Child("annotations"))...)
264	allErrs = append(allErrs, ValidateOwnerReferences(newMeta.GetOwnerReferences(), fldPath.Child("ownerReferences"))...)
265	allErrs = append(allErrs, v1validation.ValidateManagedFields(newMeta.GetManagedFields(), fldPath.Child("managedFields"))...)
266
267	return allErrs
268}
269