k8s.io/kubernetes@v1.29.3/pkg/proxy/apis/config/validation/validation.go (about) 1 /* 2 Copyright 2017 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 "net" 22 "runtime" 23 "strconv" 24 "strings" 25 26 utilnet "k8s.io/apimachinery/pkg/util/net" 27 "k8s.io/apimachinery/pkg/util/sets" 28 "k8s.io/apimachinery/pkg/util/validation/field" 29 utilfeature "k8s.io/apiserver/pkg/util/feature" 30 componentbaseconfig "k8s.io/component-base/config" 31 logsapi "k8s.io/component-base/logs/api/v1" 32 "k8s.io/component-base/metrics" 33 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 34 "k8s.io/kubernetes/pkg/features" 35 kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" 36 netutils "k8s.io/utils/net" 37 ) 38 39 // Validate validates the configuration of kube-proxy 40 func Validate(config *kubeproxyconfig.KubeProxyConfiguration) field.ErrorList { 41 allErrs := field.ErrorList{} 42 43 newPath := field.NewPath("KubeProxyConfiguration") 44 45 effectiveFeatures := utilfeature.DefaultFeatureGate.DeepCopy() 46 if err := effectiveFeatures.SetFromMap(config.FeatureGates); err != nil { 47 allErrs = append(allErrs, field.Invalid(newPath.Child("featureGates"), config.FeatureGates, err.Error())) 48 } 49 50 allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...) 51 switch config.Mode { 52 case kubeproxyconfig.ProxyModeIPVS: 53 allErrs = append(allErrs, validateKubeProxyIPVSConfiguration(config.IPVS, newPath.Child("KubeProxyIPVSConfiguration"))...) 54 case kubeproxyconfig.ProxyModeNFTables: 55 allErrs = append(allErrs, validateKubeProxyNFTablesConfiguration(config.NFTables, newPath.Child("KubeProxyNFTablesConfiguration"))...) 56 } 57 allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...) 58 allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...) 59 allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...) 60 61 if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) { 62 allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]")) 63 } 64 65 if config.ConfigSyncPeriod.Duration <= 0 { 66 allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0")) 67 } 68 69 if netutils.ParseIPSloppy(config.BindAddress) == nil { 70 allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address")) 71 } 72 73 if config.HealthzBindAddress != "" { 74 allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...) 75 } 76 allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...) 77 78 if config.ClusterCIDR != "" { 79 cidrs := strings.Split(config.ClusterCIDR, ",") 80 switch { 81 case len(cidrs) > 2: 82 allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)")) 83 // if DualStack and two cidrs validate if there is at least one of each IP family 84 case len(cidrs) == 2: 85 isDual, err := netutils.IsDualStackCIDRStrings(cidrs) 86 if err != nil || !isDual { 87 allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)")) 88 } 89 // if we are here means that len(cidrs) == 1, we need to validate it 90 default: 91 if _, _, err := netutils.ParseCIDRSloppy(config.ClusterCIDR); err != nil { 92 allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")) 93 } 94 } 95 } 96 97 if _, err := utilnet.ParsePortRange(config.PortRange); err != nil { 98 allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)")) 99 } 100 101 allErrs = append(allErrs, validateKubeProxyNodePortAddress(config.NodePortAddresses, newPath.Child("NodePortAddresses"))...) 102 allErrs = append(allErrs, validateShowHiddenMetricsVersion(config.ShowHiddenMetricsForVersion, newPath.Child("ShowHiddenMetricsForVersion"))...) 103 104 allErrs = append(allErrs, validateDetectLocalMode(config.DetectLocalMode, newPath.Child("DetectLocalMode"))...) 105 if config.DetectLocalMode == kubeproxyconfig.LocalModeBridgeInterface { 106 allErrs = append(allErrs, validateInterface(config.DetectLocal.BridgeInterface, newPath.Child("InterfaceName"))...) 107 } 108 if config.DetectLocalMode == kubeproxyconfig.LocalModeInterfaceNamePrefix { 109 allErrs = append(allErrs, validateInterface(config.DetectLocal.InterfaceNamePrefix, newPath.Child("InterfacePrefix"))...) 110 } 111 allErrs = append(allErrs, logsapi.Validate(&config.Logging, effectiveFeatures, newPath.Child("logging"))...) 112 113 return allErrs 114 } 115 116 func validateKubeProxyIPTablesConfiguration(config kubeproxyconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList { 117 allErrs := field.ErrorList{} 118 119 if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) { 120 allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]")) 121 } 122 123 if config.SyncPeriod.Duration <= 0 { 124 allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) 125 } 126 127 if config.MinSyncPeriod.Duration < 0 { 128 allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) 129 } 130 131 if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { 132 allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) 133 } 134 135 return allErrs 136 } 137 138 func validateKubeProxyIPVSConfiguration(config kubeproxyconfig.KubeProxyIPVSConfiguration, fldPath *field.Path) field.ErrorList { 139 allErrs := field.ErrorList{} 140 141 if config.SyncPeriod.Duration <= 0 { 142 allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) 143 } 144 145 if config.MinSyncPeriod.Duration < 0 { 146 allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) 147 } 148 149 if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { 150 allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) 151 } 152 153 allErrs = append(allErrs, validateIPVSTimeout(config, fldPath)...) 154 allErrs = append(allErrs, validateIPVSExcludeCIDRs(config.ExcludeCIDRs, fldPath.Child("ExcludeCidrs"))...) 155 156 return allErrs 157 } 158 159 func validateKubeProxyNFTablesConfiguration(config kubeproxyconfig.KubeProxyNFTablesConfiguration, fldPath *field.Path) field.ErrorList { 160 allErrs := field.ErrorList{} 161 162 if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) { 163 allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]")) 164 } 165 166 if config.SyncPeriod.Duration <= 0 { 167 allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) 168 } 169 170 if config.MinSyncPeriod.Duration < 0 { 171 allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) 172 } 173 174 if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { 175 allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) 176 } 177 178 return allErrs 179 } 180 181 func validateKubeProxyConntrackConfiguration(config kubeproxyconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList { 182 allErrs := field.ErrorList{} 183 184 if config.MaxPerCore != nil && *config.MaxPerCore < 0 { 185 allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0")) 186 } 187 188 if config.Min != nil && *config.Min < 0 { 189 allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0")) 190 } 191 192 // config.TCPEstablishedTimeout has a default value, so can't be nil. 193 if config.TCPEstablishedTimeout.Duration < 0 { 194 allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than or equal to 0")) 195 } 196 197 // config.TCPCloseWaitTimeout has a default value, so can't be nil. 198 if config.TCPCloseWaitTimeout.Duration < 0 { 199 allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than or equal to 0")) 200 } 201 202 if config.UDPTimeout.Duration < 0 { 203 allErrs = append(allErrs, field.Invalid(fldPath.Child("UDPTimeout"), config.UDPTimeout, "must be greater than or equal to 0")) 204 } 205 206 if config.UDPStreamTimeout.Duration < 0 { 207 allErrs = append(allErrs, field.Invalid(fldPath.Child("UDPStreamTimeout"), config.UDPStreamTimeout, "must be greater than or equal to 0")) 208 } 209 210 return allErrs 211 } 212 213 func validateProxyMode(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { 214 if runtime.GOOS == "windows" { 215 return validateProxyModeWindows(mode, fldPath) 216 } 217 218 return validateProxyModeLinux(mode, fldPath) 219 } 220 221 func validateProxyModeLinux(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { 222 validModes := sets.New[string]( 223 string(kubeproxyconfig.ProxyModeIPTables), 224 string(kubeproxyconfig.ProxyModeIPVS), 225 ) 226 227 if utilfeature.DefaultFeatureGate.Enabled(features.NFTablesProxyMode) { 228 validModes.Insert(string(kubeproxyconfig.ProxyModeNFTables)) 229 } 230 231 if mode == "" || validModes.Has(string(mode)) { 232 return nil 233 } 234 235 errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy [currently iptables])", strings.Join(sets.List(validModes), ", ")) 236 return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)} 237 } 238 239 func validateProxyModeWindows(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { 240 validModes := sets.New[string]( 241 string(kubeproxyconfig.ProxyModeKernelspace), 242 ) 243 244 if mode == "" || validModes.Has(string(mode)) { 245 return nil 246 } 247 248 errMsg := fmt.Sprintf("must be %s or blank (blank means the most-available proxy [currently 'kernelspace'])", strings.Join(sets.List(validModes), ", ")) 249 return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)} 250 } 251 252 func validateDetectLocalMode(mode kubeproxyconfig.LocalMode, fldPath *field.Path) field.ErrorList { 253 validModes := []string{ 254 string(kubeproxyconfig.LocalModeClusterCIDR), 255 string(kubeproxyconfig.LocalModeNodeCIDR), 256 string(kubeproxyconfig.LocalModeBridgeInterface), 257 string(kubeproxyconfig.LocalModeInterfaceNamePrefix), 258 "", 259 } 260 261 if sets.New(validModes...).Has(string(mode)) { 262 return nil 263 } 264 265 return field.ErrorList{field.NotSupported(fldPath, string(mode), validModes)} 266 } 267 268 func validateClientConnectionConfiguration(config componentbaseconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList { 269 allErrs := field.ErrorList{} 270 allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...) 271 return allErrs 272 } 273 274 func validateHostPort(input string, fldPath *field.Path) field.ErrorList { 275 allErrs := field.ErrorList{} 276 277 hostIP, port, err := net.SplitHostPort(input) 278 if err != nil { 279 allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port")) 280 return allErrs 281 } 282 283 if ip := netutils.ParseIPSloppy(hostIP); ip == nil { 284 allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP")) 285 } 286 287 if p, err := strconv.Atoi(port); err != nil { 288 allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) 289 } else if p < 1 || p > 65535 { 290 allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) 291 } 292 293 return allErrs 294 } 295 296 func validateKubeProxyNodePortAddress(nodePortAddresses []string, fldPath *field.Path) field.ErrorList { 297 allErrs := field.ErrorList{} 298 299 for i := range nodePortAddresses { 300 if _, _, err := netutils.ParseCIDRSloppy(nodePortAddresses[i]); err != nil { 301 allErrs = append(allErrs, field.Invalid(fldPath.Index(i), nodePortAddresses[i], "must be a valid CIDR")) 302 } 303 } 304 305 return allErrs 306 } 307 308 func validateIPVSTimeout(config kubeproxyconfig.KubeProxyIPVSConfiguration, fldPath *field.Path) field.ErrorList { 309 allErrs := field.ErrorList{} 310 311 if config.TCPTimeout.Duration < 0 { 312 allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPTimeout"), config.TCPTimeout, "must be greater than or equal to 0")) 313 } 314 315 if config.TCPFinTimeout.Duration < 0 { 316 allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPFinTimeout"), config.TCPFinTimeout, "must be greater than or equal to 0")) 317 } 318 319 if config.UDPTimeout.Duration < 0 { 320 allErrs = append(allErrs, field.Invalid(fldPath.Child("UDPTimeout"), config.UDPTimeout, "must be greater than or equal to 0")) 321 } 322 323 return allErrs 324 } 325 326 func validateIPVSExcludeCIDRs(excludeCIDRs []string, fldPath *field.Path) field.ErrorList { 327 allErrs := field.ErrorList{} 328 329 for i := range excludeCIDRs { 330 if _, _, err := netutils.ParseCIDRSloppy(excludeCIDRs[i]); err != nil { 331 allErrs = append(allErrs, field.Invalid(fldPath.Index(i), excludeCIDRs[i], "must be a valid CIDR")) 332 } 333 } 334 return allErrs 335 } 336 337 func validateShowHiddenMetricsVersion(version string, fldPath *field.Path) field.ErrorList { 338 allErrs := field.ErrorList{} 339 errs := metrics.ValidateShowHiddenMetricsVersion(version) 340 for _, e := range errs { 341 allErrs = append(allErrs, field.Invalid(fldPath, version, e.Error())) 342 } 343 344 return allErrs 345 } 346 347 func validateInterface(iface string, fldPath *field.Path) field.ErrorList { 348 allErrs := field.ErrorList{} 349 if len(iface) == 0 { 350 allErrs = append(allErrs, field.Invalid(fldPath, iface, "must not be empty")) 351 } 352 return allErrs 353 }