...

Source file src/k8s.io/kubernetes/pkg/proxy/apis/config/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/proxy/apis/config/validation

     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  }
   354  

View as plain text