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