1/*
2Copyright 2015 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	"regexp"
22	"unicode"
23
24	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25	"k8s.io/apimachinery/pkg/types"
26	"k8s.io/apimachinery/pkg/util/sets"
27	"k8s.io/apimachinery/pkg/util/validation"
28	"k8s.io/apimachinery/pkg/util/validation/field"
29)
30
31func ValidateLabelSelector(ps *metav1.LabelSelector, fldPath *field.Path) field.ErrorList {
32	allErrs := field.ErrorList{}
33	if ps == nil {
34		return allErrs
35	}
36	allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
37	for i, expr := range ps.MatchExpressions {
38		allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, fldPath.Child("matchExpressions").Index(i))...)
39	}
40	return allErrs
41}
42
43func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, fldPath *field.Path) field.ErrorList {
44	allErrs := field.ErrorList{}
45	switch sr.Operator {
46	case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn:
47		if len(sr.Values) == 0 {
48			allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'"))
49		}
50	case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist:
51		if len(sr.Values) > 0 {
52			allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'"))
53		}
54	default:
55		allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
56	}
57	allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
58	return allErrs
59}
60
61// ValidateLabelName validates that the label name is correctly defined.
62func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList {
63	allErrs := field.ErrorList{}
64	for _, msg := range validation.IsQualifiedName(labelName) {
65		allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg))
66	}
67	return allErrs
68}
69
70// ValidateLabels validates that a set of labels are correctly defined.
71func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList {
72	allErrs := field.ErrorList{}
73	for k, v := range labels {
74		allErrs = append(allErrs, ValidateLabelName(k, fldPath)...)
75		for _, msg := range validation.IsValidLabelValue(v) {
76			allErrs = append(allErrs, field.Invalid(fldPath, v, msg))
77		}
78	}
79	return allErrs
80}
81
82func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
83	allErrs := field.ErrorList{}
84	//lint:file-ignore SA1019 Keep validation for deprecated OrphanDependents option until it's being removed
85	if options.OrphanDependents != nil && options.PropagationPolicy != nil {
86		allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set"))
87	}
88	if options.PropagationPolicy != nil &&
89		*options.PropagationPolicy != metav1.DeletePropagationForeground &&
90		*options.PropagationPolicy != metav1.DeletePropagationBackground &&
91		*options.PropagationPolicy != metav1.DeletePropagationOrphan {
92		allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"}))
93	}
94	allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
95	return allErrs
96}
97
98func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList {
99	return append(
100		ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager")),
101		ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...,
102	)
103}
104
105func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
106	return append(
107		ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager")),
108		ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...,
109	)
110}
111
112func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
113	allErrs := field.ErrorList{}
114	if patchType != types.ApplyPatchType {
115		if options.Force != nil {
116			allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
117		}
118	} else {
119		if options.FieldManager == "" {
120			// This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers.
121			allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch"))
122		}
123	}
124	allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
125	allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
126	return allErrs
127}
128
129var FieldManagerMaxLength = 128
130
131// ValidateFieldManager valides that the fieldManager is the proper length and
132// only has printable characters.
133func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList {
134	allErrs := field.ErrorList{}
135	// the field can not be set as a `*string`, so a empty string ("") is
136	// considered as not set and is defaulted by the rest of the process
137	// (unless apply is used, in which case it is required).
138	if len(fieldManager) > FieldManagerMaxLength {
139		allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength))
140	}
141	// Verify that all characters are printable.
142	for i, r := range fieldManager {
143		if !unicode.IsPrint(r) {
144			allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i)))
145		}
146	}
147
148	return allErrs
149}
150
151var allowedDryRunValues = sets.NewString(metav1.DryRunAll)
152
153// ValidateDryRun validates that a dryRun query param only contains allowed values.
154func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList {
155	allErrs := field.ErrorList{}
156	if !allowedDryRunValues.HasAll(dryRun...) {
157		allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List()))
158	}
159	return allErrs
160}
161
162const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized`
163
164// ValidateTableOptions returns any invalid flags on TableOptions.
165func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList {
166	var allErrs field.ErrorList
167	switch opts.IncludeObject {
168	case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "":
169	default:
170		allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty"))
171	}
172	return allErrs
173}
174
175const MaxSubresourceNameLength = 256
176
177func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path) field.ErrorList {
178	var allErrs field.ErrorList
179	for i, fields := range fieldsList {
180		fldPath := fldPath.Index(i)
181		switch fields.Operation {
182		case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate:
183		default:
184			allErrs = append(allErrs, field.Invalid(fldPath.Child("operation"), fields.Operation, "must be `Apply` or `Update`"))
185		}
186		if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" {
187			allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`"))
188		}
189		allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...)
190
191		if len(fields.Subresource) > MaxSubresourceNameLength {
192			allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), fields.Subresource, MaxSubresourceNameLength))
193		}
194	}
195	return allErrs
196}
197
198func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList {
199	var allErrs field.ErrorList
200
201	conditionTypeToFirstIndex := map[string]int{}
202	for i, condition := range conditions {
203		if _, ok := conditionTypeToFirstIndex[condition.Type]; ok {
204			allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type))
205		} else {
206			conditionTypeToFirstIndex[condition.Type] = i
207		}
208
209		allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...)
210	}
211
212	return allErrs
213}
214
215// validConditionStatuses is used internally to check validity and provide a good message
216var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown))
217
218const (
219	maxReasonLen  = 1 * 1024
220	maxMessageLen = 32 * 1024
221)
222
223func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList {
224	var allErrs field.ErrorList
225
226	// type is set and is a valid format
227	allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...)
228
229	// status is set and is an accepted value
230	if !validConditionStatuses.Has(string(condition.Status)) {
231		allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List()))
232	}
233
234	if condition.ObservedGeneration < 0 {
235		allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero"))
236	}
237
238	if condition.LastTransitionTime.IsZero() {
239		allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set"))
240	}
241
242	if len(condition.Reason) == 0 {
243		allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set"))
244	} else {
245		for _, currErr := range isValidConditionReason(condition.Reason) {
246			allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr))
247		}
248		if len(condition.Reason) > maxReasonLen {
249			allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, maxReasonLen))
250		}
251	}
252
253	if len(condition.Message) > maxMessageLen {
254		allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen))
255	}
256
257	return allErrs
258}
259
260const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?"
261const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'"
262
263var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$")
264
265// isValidConditionReason tests for a string that conforms to rules for condition reasons. This checks the format, but not the length.
266func isValidConditionReason(value string) []string {
267	if !conditionReasonRegexp.MatchString(value) {
268		return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")}
269	}
270	return nil
271}
272