1/* 2Copyright 2019 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 22 corev1 "k8s.io/api/core/v1" 23 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 24 metavalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 25 "k8s.io/apimachinery/pkg/util/sets" 26 "k8s.io/apimachinery/pkg/util/validation" 27 "k8s.io/apimachinery/pkg/util/validation/field" 28 api "k8s.io/kubernetes/pkg/apis/core" 29 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 30 "k8s.io/kubernetes/pkg/apis/discovery" 31) 32 33var ( 34 supportedAddressTypes = sets.NewString( 35 string(discovery.AddressTypeIPv4), 36 string(discovery.AddressTypeIPv6), 37 string(discovery.AddressTypeFQDN), 38 ) 39 supportedPortProtocols = sets.NewString( 40 string(api.ProtocolTCP), 41 string(api.ProtocolUDP), 42 string(api.ProtocolSCTP), 43 ) 44 maxTopologyLabels = 16 45 maxAddresses = 100 46 maxPorts = 20000 47 maxEndpoints = 1000 48 maxZoneHints = 8 49) 50 51// ValidateEndpointSliceName can be used to check whether the given endpoint 52// slice name is valid. Prefix indicates this name will be used as part of 53// generation, in which case trailing dashes are allowed. 54var ValidateEndpointSliceName = apimachineryvalidation.NameIsDNSSubdomain 55 56// ValidateEndpointSlice validates an EndpointSlice. 57func ValidateEndpointSlice(endpointSlice *discovery.EndpointSlice) field.ErrorList { 58 allErrs := apivalidation.ValidateObjectMeta(&endpointSlice.ObjectMeta, true, ValidateEndpointSliceName, field.NewPath("metadata")) 59 allErrs = append(allErrs, validateAddressType(endpointSlice.AddressType)...) 60 allErrs = append(allErrs, validateEndpoints(endpointSlice.Endpoints, endpointSlice.AddressType, field.NewPath("endpoints"))...) 61 allErrs = append(allErrs, validatePorts(endpointSlice.Ports, field.NewPath("ports"))...) 62 63 return allErrs 64} 65 66// ValidateEndpointSliceCreate validates an EndpointSlice when it is created. 67func ValidateEndpointSliceCreate(endpointSlice *discovery.EndpointSlice) field.ErrorList { 68 return ValidateEndpointSlice(endpointSlice) 69} 70 71// ValidateEndpointSliceUpdate validates an EndpointSlice when it is updated. 72func ValidateEndpointSliceUpdate(newEndpointSlice, oldEndpointSlice *discovery.EndpointSlice) field.ErrorList { 73 allErrs := ValidateEndpointSlice(newEndpointSlice) 74 allErrs = append(allErrs, apivalidation.ValidateImmutableField(newEndpointSlice.AddressType, oldEndpointSlice.AddressType, field.NewPath("addressType"))...) 75 76 return allErrs 77} 78 79func validateEndpoints(endpoints []discovery.Endpoint, addrType discovery.AddressType, fldPath *field.Path) field.ErrorList { 80 allErrs := field.ErrorList{} 81 82 if len(endpoints) > maxEndpoints { 83 allErrs = append(allErrs, field.TooMany(fldPath, len(endpoints), maxEndpoints)) 84 return allErrs 85 } 86 87 for i, endpoint := range endpoints { 88 idxPath := fldPath.Index(i) 89 addressPath := idxPath.Child("addresses") 90 91 if len(endpoint.Addresses) == 0 { 92 allErrs = append(allErrs, field.Required(addressPath, "must contain at least 1 address")) 93 } else if len(endpoint.Addresses) > maxAddresses { 94 allErrs = append(allErrs, field.TooMany(addressPath, len(endpoint.Addresses), maxAddresses)) 95 } 96 97 for i, address := range endpoint.Addresses { 98 // This validates known address types, unknown types fall through 99 // and do not get validated. 100 switch addrType { 101 case discovery.AddressTypeIPv4: 102 allErrs = append(allErrs, validation.IsValidIPv4Address(addressPath.Index(i), address)...) 103 allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...) 104 case discovery.AddressTypeIPv6: 105 allErrs = append(allErrs, validation.IsValidIPv6Address(addressPath.Index(i), address)...) 106 allErrs = append(allErrs, apivalidation.ValidateNonSpecialIP(address, addressPath.Index(i))...) 107 case discovery.AddressTypeFQDN: 108 allErrs = append(allErrs, validation.IsFullyQualifiedDomainName(addressPath.Index(i), address)...) 109 } 110 } 111 112 if endpoint.NodeName != nil { 113 nnPath := idxPath.Child("nodeName") 114 for _, msg := range apivalidation.ValidateNodeName(*endpoint.NodeName, false) { 115 allErrs = append(allErrs, field.Invalid(nnPath, *endpoint.NodeName, msg)) 116 } 117 } 118 119 topologyPath := idxPath.Child("deprecatedTopology") 120 if len(endpoint.DeprecatedTopology) > maxTopologyLabels { 121 allErrs = append(allErrs, field.TooMany(topologyPath, len(endpoint.DeprecatedTopology), maxTopologyLabels)) 122 } 123 allErrs = append(allErrs, metavalidation.ValidateLabels(endpoint.DeprecatedTopology, topologyPath)...) 124 if _, found := endpoint.DeprecatedTopology[corev1.LabelTopologyZone]; found { 125 allErrs = append(allErrs, field.InternalError(topologyPath.Key(corev1.LabelTopologyZone), fmt.Errorf("reserved key was not removed in conversion"))) 126 } 127 128 if endpoint.Hostname != nil { 129 allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(*endpoint.Hostname, idxPath.Child("hostname"))...) 130 } 131 132 if endpoint.Hints != nil { 133 allErrs = append(allErrs, validateHints(endpoint.Hints, idxPath.Child("hints"))...) 134 } 135 } 136 137 return allErrs 138} 139 140func validatePorts(endpointPorts []discovery.EndpointPort, fldPath *field.Path) field.ErrorList { 141 allErrs := field.ErrorList{} 142 143 if len(endpointPorts) > maxPorts { 144 allErrs = append(allErrs, field.TooMany(fldPath, len(endpointPorts), maxPorts)) 145 return allErrs 146 } 147 148 portNames := sets.String{} 149 for i, endpointPort := range endpointPorts { 150 idxPath := fldPath.Index(i) 151 152 if len(*endpointPort.Name) > 0 { 153 allErrs = append(allErrs, apivalidation.ValidateDNS1123Label(*endpointPort.Name, idxPath.Child("name"))...) 154 } 155 156 if portNames.Has(*endpointPort.Name) { 157 allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), endpointPort.Name)) 158 } else { 159 portNames.Insert(*endpointPort.Name) 160 } 161 162 if endpointPort.Protocol == nil { 163 allErrs = append(allErrs, field.Required(idxPath.Child("protocol"), "")) 164 } else if !supportedPortProtocols.Has(string(*endpointPort.Protocol)) { 165 allErrs = append(allErrs, field.NotSupported(idxPath.Child("protocol"), *endpointPort.Protocol, supportedPortProtocols.List())) 166 } 167 168 if endpointPort.AppProtocol != nil { 169 allErrs = append(allErrs, apivalidation.ValidateQualifiedName(*endpointPort.AppProtocol, idxPath.Child("appProtocol"))...) 170 } 171 } 172 173 return allErrs 174} 175 176func validateAddressType(addressType discovery.AddressType) field.ErrorList { 177 allErrs := field.ErrorList{} 178 179 if addressType == "" { 180 allErrs = append(allErrs, field.Required(field.NewPath("addressType"), "")) 181 } else if !supportedAddressTypes.Has(string(addressType)) { 182 allErrs = append(allErrs, field.NotSupported(field.NewPath("addressType"), addressType, supportedAddressTypes.List())) 183 } 184 185 return allErrs 186} 187 188func validateHints(endpointHints *discovery.EndpointHints, fldPath *field.Path) field.ErrorList { 189 allErrs := field.ErrorList{} 190 191 fzPath := fldPath.Child("forZones") 192 if len(endpointHints.ForZones) > maxZoneHints { 193 allErrs = append(allErrs, field.TooMany(fzPath, len(endpointHints.ForZones), maxZoneHints)) 194 return allErrs 195 } 196 197 zoneNames := sets.String{} 198 for i, forZone := range endpointHints.ForZones { 199 zonePath := fzPath.Index(i).Child("name") 200 if zoneNames.Has(forZone.Name) { 201 allErrs = append(allErrs, field.Duplicate(zonePath, forZone.Name)) 202 } else { 203 zoneNames.Insert(forZone.Name) 204 } 205 206 for _, msg := range validation.IsValidLabelValue(forZone.Name) { 207 allErrs = append(allErrs, field.Invalid(zonePath, forZone.Name, msg)) 208 } 209 } 210 211 return allErrs 212} 213