1
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
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
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
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
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
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