validation.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package validation
  14. import (
  15. "fmt"
  16. "net"
  17. "runtime"
  18. "strconv"
  19. "strings"
  20. utilnet "k8s.io/apimachinery/pkg/util/net"
  21. "k8s.io/apimachinery/pkg/util/sets"
  22. "k8s.io/apimachinery/pkg/util/validation/field"
  23. utilfeature "k8s.io/apiserver/pkg/util/feature"
  24. componentbaseconfig "k8s.io/component-base/config"
  25. "k8s.io/component-base/metrics"
  26. apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
  27. kubefeatures "k8s.io/kubernetes/pkg/features"
  28. kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
  29. netutils "k8s.io/utils/net"
  30. )
  31. // Validate validates the configuration of kube-proxy
  32. func Validate(config *kubeproxyconfig.KubeProxyConfiguration) field.ErrorList {
  33. allErrs := field.ErrorList{}
  34. newPath := field.NewPath("KubeProxyConfiguration")
  35. effectiveFeatures := utilfeature.DefaultFeatureGate.DeepCopy()
  36. if err := effectiveFeatures.SetFromMap(config.FeatureGates); err != nil {
  37. allErrs = append(allErrs, field.Invalid(newPath.Child("featureGates"), config.FeatureGates, err.Error()))
  38. }
  39. allErrs = append(allErrs, validateKubeProxyIPTablesConfiguration(config.IPTables, newPath.Child("KubeProxyIPTablesConfiguration"))...)
  40. if config.Mode == kubeproxyconfig.ProxyModeIPVS {
  41. allErrs = append(allErrs, validateKubeProxyIPVSConfiguration(config.IPVS, newPath.Child("KubeProxyIPVSConfiguration"))...)
  42. }
  43. allErrs = append(allErrs, validateKubeProxyConntrackConfiguration(config.Conntrack, newPath.Child("KubeProxyConntrackConfiguration"))...)
  44. allErrs = append(allErrs, validateProxyMode(config.Mode, newPath.Child("Mode"))...)
  45. allErrs = append(allErrs, validateClientConnectionConfiguration(config.ClientConnection, newPath.Child("ClientConnection"))...)
  46. if config.OOMScoreAdj != nil && (*config.OOMScoreAdj < -1000 || *config.OOMScoreAdj > 1000) {
  47. allErrs = append(allErrs, field.Invalid(newPath.Child("OOMScoreAdj"), *config.OOMScoreAdj, "must be within the range [-1000, 1000]"))
  48. }
  49. if config.UDPIdleTimeout.Duration <= 0 {
  50. allErrs = append(allErrs, field.Invalid(newPath.Child("UDPIdleTimeout"), config.UDPIdleTimeout, "must be greater than 0"))
  51. }
  52. if config.ConfigSyncPeriod.Duration <= 0 {
  53. allErrs = append(allErrs, field.Invalid(newPath.Child("ConfigSyncPeriod"), config.ConfigSyncPeriod, "must be greater than 0"))
  54. }
  55. if net.ParseIP(config.BindAddress) == nil {
  56. allErrs = append(allErrs, field.Invalid(newPath.Child("BindAddress"), config.BindAddress, "not a valid textual representation of an IP address"))
  57. }
  58. if config.HealthzBindAddress != "" {
  59. allErrs = append(allErrs, validateHostPort(config.HealthzBindAddress, newPath.Child("HealthzBindAddress"))...)
  60. }
  61. allErrs = append(allErrs, validateHostPort(config.MetricsBindAddress, newPath.Child("MetricsBindAddress"))...)
  62. if config.ClusterCIDR != "" {
  63. cidrs := strings.Split(config.ClusterCIDR, ",")
  64. dualStackEnabled := effectiveFeatures.Enabled(kubefeatures.IPv6DualStack)
  65. switch {
  66. // if DualStack only valid one cidr or two cidrs with one of each IP family
  67. case dualStackEnabled && len(cidrs) > 2:
  68. 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)"))
  69. // if DualStack and two cidrs validate if there is at least one of each IP family
  70. case dualStackEnabled && len(cidrs) == 2:
  71. isDual, err := netutils.IsDualStackCIDRStrings(cidrs)
  72. if err != nil || !isDual {
  73. 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)"))
  74. }
  75. // if not DualStack only one CIDR allowed
  76. case !dualStackEnabled && len(cidrs) > 1:
  77. allErrs = append(allErrs, field.Invalid(newPath.Child("ClusterCIDR"), config.ClusterCIDR, "only one CIDR allowed (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)"))
  78. // if we are here means that len(cidrs) == 1, we need to validate it
  79. default:
  80. if _, _, err := net.ParseCIDR(config.ClusterCIDR); err != nil {
  81. 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)"))
  82. }
  83. }
  84. }
  85. if _, err := utilnet.ParsePortRange(config.PortRange); err != nil {
  86. allErrs = append(allErrs, field.Invalid(newPath.Child("PortRange"), config.PortRange, "must be a valid port range (e.g. 300-2000)"))
  87. }
  88. allErrs = append(allErrs, validateKubeProxyNodePortAddress(config.NodePortAddresses, newPath.Child("NodePortAddresses"))...)
  89. allErrs = append(allErrs, validateShowHiddenMetricsVersion(config.ShowHiddenMetricsForVersion, newPath.Child("ShowHiddenMetricsForVersion"))...)
  90. return allErrs
  91. }
  92. func validateKubeProxyIPTablesConfiguration(config kubeproxyconfig.KubeProxyIPTablesConfiguration, fldPath *field.Path) field.ErrorList {
  93. allErrs := field.ErrorList{}
  94. if config.MasqueradeBit != nil && (*config.MasqueradeBit < 0 || *config.MasqueradeBit > 31) {
  95. allErrs = append(allErrs, field.Invalid(fldPath.Child("MasqueradeBit"), config.MasqueradeBit, "must be within the range [0, 31]"))
  96. }
  97. if config.SyncPeriod.Duration <= 0 {
  98. allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0"))
  99. }
  100. if config.MinSyncPeriod.Duration < 0 {
  101. allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0"))
  102. }
  103. if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration {
  104. allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String())))
  105. }
  106. return allErrs
  107. }
  108. func validateKubeProxyIPVSConfiguration(config kubeproxyconfig.KubeProxyIPVSConfiguration, fldPath *field.Path) field.ErrorList {
  109. allErrs := field.ErrorList{}
  110. if config.SyncPeriod.Duration <= 0 {
  111. allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.SyncPeriod, "must be greater than 0"))
  112. }
  113. if config.MinSyncPeriod.Duration < 0 {
  114. allErrs = append(allErrs, field.Invalid(fldPath.Child("MinSyncPeriod"), config.MinSyncPeriod, "must be greater than or equal to 0"))
  115. }
  116. if config.MinSyncPeriod.Duration > config.SyncPeriod.Duration {
  117. allErrs = append(allErrs, field.Invalid(fldPath.Child("SyncPeriod"), config.MinSyncPeriod, fmt.Sprintf("must be greater than or equal to %s", fldPath.Child("MinSyncPeriod").String())))
  118. }
  119. allErrs = append(allErrs, validateIPVSSchedulerMethod(kubeproxyconfig.IPVSSchedulerMethod(config.Scheduler), fldPath.Child("Scheduler"))...)
  120. allErrs = append(allErrs, validateIPVSExcludeCIDRs(config.ExcludeCIDRs, fldPath.Child("ExcludeCidrs"))...)
  121. return allErrs
  122. }
  123. func validateKubeProxyConntrackConfiguration(config kubeproxyconfig.KubeProxyConntrackConfiguration, fldPath *field.Path) field.ErrorList {
  124. allErrs := field.ErrorList{}
  125. if config.MaxPerCore != nil && *config.MaxPerCore < 0 {
  126. allErrs = append(allErrs, field.Invalid(fldPath.Child("MaxPerCore"), config.MaxPerCore, "must be greater than or equal to 0"))
  127. }
  128. if config.Min != nil && *config.Min < 0 {
  129. allErrs = append(allErrs, field.Invalid(fldPath.Child("Min"), config.Min, "must be greater than or equal to 0"))
  130. }
  131. if config.TCPEstablishedTimeout.Duration < 0 {
  132. allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPEstablishedTimeout"), config.TCPEstablishedTimeout, "must be greater than or equal to 0"))
  133. }
  134. if config.TCPCloseWaitTimeout.Duration < 0 {
  135. allErrs = append(allErrs, field.Invalid(fldPath.Child("TCPCloseWaitTimeout"), config.TCPCloseWaitTimeout, "must be greater than or equal to 0"))
  136. }
  137. return allErrs
  138. }
  139. func validateProxyMode(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList {
  140. if runtime.GOOS == "windows" {
  141. return validateProxyModeWindows(mode, fldPath)
  142. }
  143. return validateProxyModeLinux(mode, fldPath)
  144. }
  145. func validateProxyModeLinux(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList {
  146. validModes := sets.NewString(
  147. string(kubeproxyconfig.ProxyModeUserspace),
  148. string(kubeproxyconfig.ProxyModeIPTables),
  149. string(kubeproxyconfig.ProxyModeIPVS),
  150. )
  151. if mode == "" || validModes.Has(string(mode)) {
  152. return nil
  153. }
  154. errMsg := fmt.Sprintf("must be %s or blank (blank means the best-available proxy [currently iptables])", strings.Join(validModes.List(), ","))
  155. return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)}
  156. }
  157. func validateProxyModeWindows(mode kubeproxyconfig.ProxyMode, fldPath *field.Path) field.ErrorList {
  158. validModes := sets.NewString(
  159. string(kubeproxyconfig.ProxyModeUserspace),
  160. string(kubeproxyconfig.ProxyModeKernelspace),
  161. )
  162. if mode == "" || validModes.Has(string(mode)) {
  163. return nil
  164. }
  165. errMsg := fmt.Sprintf("must be %s or blank (blank means the most-available proxy [currently userspace])", strings.Join(validModes.List(), ","))
  166. return field.ErrorList{field.Invalid(fldPath.Child("ProxyMode"), string(mode), errMsg)}
  167. }
  168. func validateClientConnectionConfiguration(config componentbaseconfig.ClientConnectionConfiguration, fldPath *field.Path) field.ErrorList {
  169. allErrs := field.ErrorList{}
  170. allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(config.Burst), fldPath.Child("Burst"))...)
  171. return allErrs
  172. }
  173. func validateHostPort(input string, fldPath *field.Path) field.ErrorList {
  174. allErrs := field.ErrorList{}
  175. hostIP, port, err := net.SplitHostPort(input)
  176. if err != nil {
  177. allErrs = append(allErrs, field.Invalid(fldPath, input, "must be IP:port"))
  178. return allErrs
  179. }
  180. if ip := net.ParseIP(hostIP); ip == nil {
  181. allErrs = append(allErrs, field.Invalid(fldPath, hostIP, "must be a valid IP"))
  182. }
  183. if p, err := strconv.Atoi(port); err != nil {
  184. allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port"))
  185. } else if p < 1 || p > 65535 {
  186. allErrs = append(allErrs, field.Invalid(fldPath, port, "must be a valid port"))
  187. }
  188. return allErrs
  189. }
  190. func validateIPVSSchedulerMethod(scheduler kubeproxyconfig.IPVSSchedulerMethod, fldPath *field.Path) field.ErrorList {
  191. supportedMethod := []kubeproxyconfig.IPVSSchedulerMethod{
  192. kubeproxyconfig.RoundRobin,
  193. kubeproxyconfig.WeightedRoundRobin,
  194. kubeproxyconfig.LeastConnection,
  195. kubeproxyconfig.WeightedLeastConnection,
  196. kubeproxyconfig.LocalityBasedLeastConnection,
  197. kubeproxyconfig.LocalityBasedLeastConnectionWithReplication,
  198. kubeproxyconfig.SourceHashing,
  199. kubeproxyconfig.DestinationHashing,
  200. kubeproxyconfig.ShortestExpectedDelay,
  201. kubeproxyconfig.NeverQueue,
  202. "",
  203. }
  204. allErrs := field.ErrorList{}
  205. var found bool
  206. for i := range supportedMethod {
  207. if scheduler == supportedMethod[i] {
  208. found = true
  209. break
  210. }
  211. }
  212. // Not found
  213. if !found {
  214. errMsg := fmt.Sprintf("must be in %v, blank means the default algorithm method (currently rr)", supportedMethod)
  215. allErrs = append(allErrs, field.Invalid(fldPath.Child("Scheduler"), string(scheduler), errMsg))
  216. }
  217. return allErrs
  218. }
  219. func validateKubeProxyNodePortAddress(nodePortAddresses []string, fldPath *field.Path) field.ErrorList {
  220. allErrs := field.ErrorList{}
  221. for i := range nodePortAddresses {
  222. if _, _, err := net.ParseCIDR(nodePortAddresses[i]); err != nil {
  223. allErrs = append(allErrs, field.Invalid(fldPath, nodePortAddresses, "must be a valid IP block"))
  224. break
  225. }
  226. }
  227. return allErrs
  228. }
  229. func validateIPVSExcludeCIDRs(excludeCIDRs []string, fldPath *field.Path) field.ErrorList {
  230. allErrs := field.ErrorList{}
  231. for i := range excludeCIDRs {
  232. if _, _, err := net.ParseCIDR(excludeCIDRs[i]); err != nil {
  233. allErrs = append(allErrs, field.Invalid(fldPath, excludeCIDRs, "must be a valid IP block"))
  234. }
  235. }
  236. return allErrs
  237. }
  238. func validateShowHiddenMetricsVersion(version string, fldPath *field.Path) field.ErrorList {
  239. allErrs := field.ErrorList{}
  240. errs := metrics.ValidateShowHiddenMetricsVersion(version)
  241. for _, e := range errs {
  242. allErrs = append(allErrs, field.Invalid(fldPath, version, e.Error()))
  243. }
  244. return allErrs
  245. }