| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package validation |
| |
| import ( |
| "fmt" |
| "net" |
| "runtime" |
| "strconv" |
| "strings" |
| |
| utilnet "k8s.io/apimachinery/pkg/util/net" |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/apimachinery/pkg/util/validation/field" |
| utilfeature "k8s.io/apiserver/pkg/util/feature" |
| componentbaseconfig "k8s.io/component-base/config" |
| logsapi "k8s.io/component-base/logs/api/v1" |
| "k8s.io/component-base/metrics" |
| apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" |
| "k8s.io/kubernetes/pkg/features" |
| kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config" |
| netutils "k8s.io/utils/net" |
| ) |
| |
| // Validate validates the configuration of kube-proxy |
| func Validate(config *kubeproxyconfig.KubeProxyConfiguration) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| newPath := field.NewPath("KubeProxyConfiguration") |
| |
| effectiveFeatures := utilfeature.DefaultFeatureGate.DeepCopy() |
| if err := effectiveFeatures.SetFromMap(config.FeatureGates); err != nil { |
| allErrs = append(allErrs, field.Invalid(newPath.Child("featureGates"), config.FeatureGates, err.Error())) |
| } |
| |
| allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...) |
| switch config.Mode { |
| case kubeproxyconfig.ProxyModeIPVS: |
| allErrs = append(allErrs, validateKubeProxyIPVSConfiguration(config.IPVS, newPath.Child("KubeProxyIPVSConfiguration"))...) |
| case kubeproxyconfig.ProxyModeNFTables: |
| allErrs = append(allErrs, validateKubeProxyNFTablesConfiguration(config.NFTables, newPath.Child("KubeProxyNFTablesConfiguration"))...) |
| } |
| allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...) |
| allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...) |
| allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...) |
| |
| if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) { |
| allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]")) |
| } |
| |
| if config.ConfigSyncPeriod.Duration <= 0 { |
| allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0")) |
| } |
| |
| if netutils.ParseIPSloppy(config.BindAddress) == nil { |
| allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address")) |
| } |
| |
| if config.HealthzBindAddress != "" { |
| allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...) |
| } |
| allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...) |
| |
| if config.ClusterCIDR != "" { |
| cidrs := strings.Split(config.ClusterCIDR, ",") |
| switch { |
| case len(cidrs) > 2: |
| 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)")) |
| // if DualStack and two cidrs validate if there is at least one of each IP family |
| case len(cidrs) == 2: |
| isDual, err := netutils.IsDualStackCIDRStrings(cidrs) |
| if err != nil || !isDual { |
| 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)")) |
| } |
| // if we are here means that len(cidrs) == 1, we need to validate it |
| default: |
| if _, _, err := netutils.ParseCIDRSloppy(config.ClusterCIDR); err != nil { |
| 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)")) |
| } |
| } |
| } |
| |
| if _, err := utilnet.ParsePortRange(config.PortRange); err != nil { |
| allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)")) |
| } |
| |
| allErrs = append(allErrs, validateKubeProxyNodePortAddress(config.NodePortAddresses, newPath.Child("NodePortAddresses"))...) |
| allErrs = append(allErrs, validateShowHiddenMetricsVersion(config.ShowHiddenMetricsForVersion, newPath.Child("ShowHiddenMetricsForVersion"))...) |
| |
| allErrs = append(allErrs, validateDetectLocalMode(config.DetectLocalMode, newPath.Child("DetectLocalMode"))...) |
| if config.DetectLocalMode == kubeproxyconfig.LocalModeBridgeInterface { |
| allErrs = append(allErrs, validateInterface(config.DetectLocal.BridgeInterface, newPath.Child("InterfaceName"))...) |
| } |
| if config.DetectLocalMode == kubeproxyconfig.LocalModeInterfaceNamePrefix { |
| allErrs = append(allErrs, validateInterface(config.DetectLocal.InterfaceNamePrefix, newPath.Child("InterfacePrefix"))...) |
| } |
| allErrs = append(allErrs, logsapi.Validate(&config.Logging, effectiveFeatures, newPath.Child("logging"))...) |
| |
| return allErrs |
| } |
| |
| func validateKubeProxyIPTablesConfiguration(config kubeproxyconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]")) |
| } |
| |
| if config.SyncPeriod.Duration <= 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) |
| } |
| |
| if config.MinSyncPeriod.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) |
| } |
| |
| if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) |
| } |
| |
| return allErrs |
| } |
| |
| func validateKubeProxyIPVSConfiguration(config kubeproxyconfig.KubeProxyIPVSConfiguration, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if config.SyncPeriod.Duration <= 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) |
| } |
| |
| if config.MinSyncPeriod.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) |
| } |
| |
| if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) |
| } |
| |
| allErrs = append(allErrs, validateIPVSTimeout(config, fldPath)...) |
| allErrs = append(allErrs, validateIPVSExcludeCIDRs(config.ExcludeCIDRs, fldPath.Child("ExcludeCidrs"))...) |
| |
| return allErrs |
| } |
| |
| func validateKubeProxyNFTablesConfiguration(config kubeproxyconfig.KubeProxyNFTablesConfiguration, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]")) |
| } |
| |
| if config.SyncPeriod.Duration <= 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0")) |
| } |
| |
| if config.MinSyncPeriod.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0")) |
| } |
| |
| if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String()))) |
| } |
| |
| return allErrs |
| } |
| |
| func validateKubeProxyConntrackConfiguration(config kubeproxyconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if config.MaxPerCore != nil && *config.MaxPerCore < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0")) |
| } |
| |
| if config.Min != nil && *config.Min < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0")) |
| } |
| |
| // config.TCPEstablishedTimeout has a default value, so can't be nil. |
| if config.TCPEstablishedTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than or equal to 0")) |
| } |
| |
| // config.TCPCloseWaitTimeout has a default value, so can't be nil. |
| if config.TCPCloseWaitTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than or equal to 0")) |
| } |
| |
| if config.UDPTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("UDPTimeout"), config.UDPTimeout, "must be greater than or equal to 0")) |
| } |
| |
| if config.UDPStreamTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("UDPStreamTimeout"), config.UDPStreamTimeout, "must be greater than or equal to 0")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateProxyMode(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { |
| if runtime.GOOS == "windows" { |
| return validateProxyModeWindows(mode, fldPath) |
| } |
| |
| return validateProxyModeLinux(mode, fldPath) |
| } |
| |
| func validateProxyModeLinux(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { |
| validModes := sets.New[string]( |
| string(kubeproxyconfig.ProxyModeIPTables), |
| string(kubeproxyconfig.ProxyModeIPVS), |
| ) |
| |
| if utilfeature.DefaultFeatureGate.Enabled(features.NFTablesProxyMode) { |
| validModes.Insert(string(kubeproxyconfig.ProxyModeNFTables)) |
| } |
| |
| if mode == "" || validModes.Has(string(mode)) { |
| return nil |
| } |
| |
| errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy [currently iptables])", strings.Join(sets.List(validModes), ", ")) |
| return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)} |
| } |
| |
| func validateProxyModeWindows(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList { |
| validModes := sets.New[string]( |
| string(kubeproxyconfig.ProxyModeKernelspace), |
| ) |
| |
| if mode == "" || validModes.Has(string(mode)) { |
| return nil |
| } |
| |
| errMsg := fmt.Sprintf("must be %s or blank (blank means the most-available proxy [currently 'kernelspace'])", strings.Join(sets.List(validModes), ", ")) |
| return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)} |
| } |
| |
| func validateDetectLocalMode(mode kubeproxyconfig.LocalMode, fldPath *field.Path) field.ErrorList { |
| validModes := []string{ |
| string(kubeproxyconfig.LocalModeClusterCIDR), |
| string(kubeproxyconfig.LocalModeNodeCIDR), |
| string(kubeproxyconfig.LocalModeBridgeInterface), |
| string(kubeproxyconfig.LocalModeInterfaceNamePrefix), |
| "", |
| } |
| |
| if sets.New(validModes...).Has(string(mode)) { |
| return nil |
| } |
| |
| return field.ErrorList{field.NotSupported(fldPath, string(mode), validModes)} |
| } |
| |
| func validateClientConnectionConfiguration(config componentbaseconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...) |
| return allErrs |
| } |
| |
| func validateHostPort(input string, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| hostIP, port, err := net.SplitHostPort(input) |
| if err != nil { |
| allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port")) |
| return allErrs |
| } |
| |
| if ip := netutils.ParseIPSloppy(hostIP); ip == nil { |
| allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP")) |
| } |
| |
| if p, err := strconv.Atoi(port); err != nil { |
| allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) |
| } else if p < 1 || p > 65535 { |
| allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateKubeProxyNodePortAddress(nodePortAddresses []string, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| for i := range nodePortAddresses { |
| if _, _, err := netutils.ParseCIDRSloppy(nodePortAddresses[i]); err != nil { |
| allErrs = append(allErrs, field.Invalid(fldPath.Index(i), nodePortAddresses[i], "must be a valid CIDR")) |
| } |
| } |
| |
| return allErrs |
| } |
| |
| func validateIPVSTimeout(config kubeproxyconfig.KubeProxyIPVSConfiguration, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| if config.TCPTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPTimeout"), config.TCPTimeout, "must be greater than or equal to 0")) |
| } |
| |
| if config.TCPFinTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPFinTimeout"), config.TCPFinTimeout, "must be greater than or equal to 0")) |
| } |
| |
| if config.UDPTimeout.Duration < 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath.Child("UDPTimeout"), config.UDPTimeout, "must be greater than or equal to 0")) |
| } |
| |
| return allErrs |
| } |
| |
| func validateIPVSExcludeCIDRs(excludeCIDRs []string, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| |
| for i := range excludeCIDRs { |
| if _, _, err := netutils.ParseCIDRSloppy(excludeCIDRs[i]); err != nil { |
| allErrs = append(allErrs, field.Invalid(fldPath.Index(i), excludeCIDRs[i], "must be a valid CIDR")) |
| } |
| } |
| return allErrs |
| } |
| |
| func validateShowHiddenMetricsVersion(version string, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| errs := metrics.ValidateShowHiddenMetricsVersion(version) |
| for _, e := range errs { |
| allErrs = append(allErrs, field.Invalid(fldPath, version, e.Error())) |
| } |
| |
| return allErrs |
| } |
| |
| func validateInterface(iface string, fldPath *field.Path) field.ErrorList { |
| allErrs := field.ErrorList{} |
| if len(iface) == 0 { |
| allErrs = append(allErrs, field.Invalid(fldPath, iface, "must not be empty")) |
| } |
| return allErrs |
| } |