k8s: Validate API server request timeout
[integration.git] / test / security / k8s / src / check / validators / master / api.go
1 package master
2
3 import (
4         "strconv"
5         "strings"
6 )
7
8 const (
9         portDisabled = 0
10         portLowest   = 1
11         portHighest  = 65536
12
13         auditLogAge     = 30
14         auditLogBackups = 10
15         auditLogSize    = 100
16
17         strongCryptoCiphers = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM" +
18                 "_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM" +
19                 "_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM" +
20                 "_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256"
21
22         requestTimeout = 60
23 )
24
25 // IsBasicAuthFileAbsent validates there is no basic authentication file specified.
26 func IsBasicAuthFileAbsent(params []string) bool {
27         return isFlagAbsent("--basic-auth-file=", params)
28 }
29
30 // IsTokenAuthFileAbsent validates there is no token based authentication file specified.
31 func IsTokenAuthFileAbsent(params []string) bool {
32         return isFlagAbsent("--token-auth-file=", params)
33 }
34
35 // IsInsecureAllowAnyTokenAbsent validates insecure tokens are not accepted.
36 func IsInsecureAllowAnyTokenAbsent(params []string) bool {
37         return isFlagAbsent("--insecure-allow-any-token", params)
38 }
39
40 // isFlagAbsent checks absence of selected flag in parameters.
41 func isFlagAbsent(flag string, params []string) bool {
42         found := filterFlags(params, flag)
43         if len(found) != 0 {
44                 return false
45         }
46         return true
47 }
48
49 // IsAnonymousAuthDisabled validates there is single "--anonymous-auth" flag and it is set to "false".
50 func IsAnonymousAuthDisabled(params []string) bool {
51         return hasSingleFlagArgument("--anonymous-auth=", "false", params)
52 }
53
54 // IsInsecurePortUnbound validates there is single "--insecure-port" flag and it is set to "0" (disabled).
55 func IsInsecurePortUnbound(params []string) bool {
56         return hasSingleFlagArgument("--insecure-port=", strconv.Itoa(portDisabled), params)
57 }
58
59 // IsProfilingDisabled validates there is single "--profiling" flag and it is set to "false".
60 func IsProfilingDisabled(params []string) bool {
61         return hasSingleFlagArgument("--profiling=", "false", params)
62 }
63
64 // IsRepairMalformedUpdatesDisabled validates there is single "--repair-malformed-updates" flag and it is set to "false".
65 func IsRepairMalformedUpdatesDisabled(params []string) bool {
66         return hasSingleFlagArgument("--repair-malformed-updates=", "false", params)
67 }
68
69 // IsServiceAccountLookupEnabled validates there is single "--service-account-lookup" flag and it is set to "true".
70 func IsServiceAccountLookupEnabled(params []string) bool {
71         return hasSingleFlagArgument("--service-account-lookup=", "true", params)
72 }
73
74 // IsStrongCryptoCipherInUse validates there is single "--tls-cipher-suites=" flag and it is set to strong crypto ciphers.
75 func IsStrongCryptoCipherInUse(params []string) bool {
76         return hasSingleFlagArgument("--tls-cipher-suites=", strongCryptoCiphers, params)
77 }
78
79 // hasSingleFlagArgument checks whether selected flag was used once and has requested argument.
80 func hasSingleFlagArgument(flag string, argument string, params []string) bool {
81         found := filterFlags(params, flag)
82         if len(found) != 1 {
83                 return false
84         }
85
86         _, value := splitKV(found[0], "=")
87         if value != argument {
88                 return false
89         }
90         return true
91 }
92
93 // filterFlags returns all occurrences of selected flag.
94 func filterFlags(strs []string, flag string) []string {
95         var filtered []string
96         for _, str := range strs {
97                 if strings.HasPrefix(str, flag) {
98                         filtered = append(filtered, str)
99                 }
100         }
101         return filtered
102 }
103
104 // splitKV splits key and value (after first occurrence of separator).
105 func splitKV(s, sep string) (string, string) {
106         ret := strings.SplitN(s, sep, 2)
107         return ret[0], ret[1]
108 }
109
110 // IsKubeletHTTPSAbsentOrEnabled validates there is single "--kubelet-https" flag and it is set to "true".
111 func IsKubeletHTTPSAbsentOrEnabled(params []string) bool {
112         return isFlagAbsent("--kubelet-https=", params) ||
113                 hasSingleFlagArgument("--kubelet-https=", "true", params)
114 }
115
116 // IsInsecureBindAddressAbsentOrLoopback validates there is no insecure bind address or it is loopback address.
117 func IsInsecureBindAddressAbsentOrLoopback(params []string) bool {
118         return isFlagAbsent("--insecure-bind-address=", params) ||
119                 hasSingleFlagArgument("--insecure-bind-address=", "127.0.0.1", params)
120 }
121
122 // IsSecurePortAbsentOrValid validates there is no secure port set explicitly or it has legal value.
123 func IsSecurePortAbsentOrValid(params []string) bool {
124         return isFlagAbsent("--secure-port=", params) ||
125                 hasFlagValidPort("--secure-port=", params)
126 }
127
128 // hasFlagValidPort checks whether selected flag has valid port as an argument in given command.
129 func hasFlagValidPort(flag string, params []string) bool {
130         found := filterFlags(params, flag)
131         if len(found) != 1 {
132                 return false
133         }
134
135         _, value := splitKV(found[0], "=")
136         port, err := strconv.Atoi(value) // what about empty parameter?
137         if err != nil {
138                 return false
139         }
140         if port < portLowest || port > portHighest {
141                 return false
142         }
143         return true
144 }
145
146 // IsAlwaysAdmitAdmissionControlPluginExcluded validates AlwaysAdmit is excluded from admission control plugins.
147 func IsAlwaysAdmitAdmissionControlPluginExcluded(params []string) bool {
148         if isSingleFlagPresent("--enable-admission-plugins=", params) {
149                 return !hasFlagArgumentIncluded("--enable-admission-plugins=", "AlwaysAdmit", params)
150         }
151         if isSingleFlagPresent("--admission-control=", params) {
152                 return !hasFlagArgumentIncluded("--admission-control=", "AlwaysAdmit", params)
153         }
154         return false
155 }
156
157 // IsAlwaysPullImagesAdmissionControlPluginIncluded validates AlwaysPullImages is included in admission control plugins.
158 func IsAlwaysPullImagesAdmissionControlPluginIncluded(params []string) bool {
159         if isSingleFlagPresent("--enable-admission-plugins=", params) {
160                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "AlwaysPullImages", params)
161         }
162         if isSingleFlagPresent("--admission-control=", params) {
163                 return hasFlagArgumentIncluded("--admission-control=", "AlwaysPullImages", params)
164         }
165         return false
166 }
167
168 // IsDenyEscalatingExecAdmissionControlPluginIncluded validates DenyEscalatingExec is included in admission control plugins.
169 func IsDenyEscalatingExecAdmissionControlPluginIncluded(params []string) bool {
170         if isSingleFlagPresent("--enable-admission-plugins=", params) {
171                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "DenyEscalatingExec", params)
172         }
173         if isSingleFlagPresent("--admission-control=", params) {
174                 return hasFlagArgumentIncluded("--admission-control=", "DenyEscalatingExec", params)
175         }
176         return false
177 }
178
179 // IsSecurityContextDenyAdmissionControlPluginIncluded validates SecurityContextDeny is included in admission control plugins.
180 func IsSecurityContextDenyAdmissionControlPluginIncluded(params []string) bool {
181         if isSingleFlagPresent("--enable-admission-plugins=", params) {
182                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "SecurityContextDeny", params)
183         }
184         if isSingleFlagPresent("--admission-control=", params) {
185                 return hasFlagArgumentIncluded("--admission-control=", "SecurityContextDeny", params)
186         }
187         return false
188 }
189
190 // IsPodSecurityPolicyAdmissionControlPluginIncluded validates PodSecurityPolicy is included in admission control plugins.
191 func IsPodSecurityPolicyAdmissionControlPluginIncluded(params []string) bool {
192         if isSingleFlagPresent("--enable-admission-plugins=", params) {
193                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "PodSecurityPolicy", params)
194         }
195         if isSingleFlagPresent("--admission-control=", params) {
196                 return hasFlagArgumentIncluded("--admission-control=", "PodSecurityPolicy", params)
197         }
198         return false
199 }
200
201 // IsServiceAccountAdmissionControlPluginIncluded validates ServiceAccount is included in admission control plugins.
202 func IsServiceAccountAdmissionControlPluginIncluded(params []string) bool {
203         if isSingleFlagPresent("--enable-admission-plugins=", params) {
204                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "ServiceAccount", params)
205         }
206         if isSingleFlagPresent("--admission-control=", params) {
207                 return hasFlagArgumentIncluded("--admission-control=", "ServiceAccount", params)
208         }
209         return false
210 }
211
212 // IsNodeRestrictionAdmissionControlPluginIncluded validates NodeRestriction is included in admission control plugins.
213 func IsNodeRestrictionAdmissionControlPluginIncluded(params []string) bool {
214         if isSingleFlagPresent("--enable-admission-plugins=", params) {
215                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "NodeRestriction", params)
216         }
217         if isSingleFlagPresent("--admission-control=", params) {
218                 return hasFlagArgumentIncluded("--admission-control=", "NodeRestriction", params)
219         }
220         return false
221 }
222
223 // IsEventRateLimitAdmissionControlPluginIncluded validates EventRateLimit is included in admission control plugins.
224 func IsEventRateLimitAdmissionControlPluginIncluded(params []string) bool {
225         if isSingleFlagPresent("--enable-admission-plugins=", params) {
226                 return hasFlagArgumentIncluded("--enable-admission-plugins=", "EventRateLimit", params)
227         }
228         if isSingleFlagPresent("--admission-control=", params) {
229                 return hasFlagArgumentIncluded("--admission-control=", "EventRateLimit", params)
230         }
231         return false
232 }
233
234 // IsNamespaceLifecycleAdmissionControlPluginNotExcluded validates NamespaceLifecycle is excluded from admission control plugins.
235 func IsNamespaceLifecycleAdmissionControlPluginNotExcluded(params []string) bool {
236         if isSingleFlagPresent("--disable-admission-plugins=", params) {
237                 return !hasFlagArgumentIncluded("--disable-admission-plugins=", "NamespaceLifecycle", params)
238         }
239         return true
240 }
241
242 // isSingleFlagPresent checks presence of selected flag and whether it was used once.
243 func isSingleFlagPresent(flag string, params []string) bool {
244         found := filterFlags(params, flag)
245         if len(found) != 1 {
246                 return false
247         }
248         return true
249 }
250
251 // hasFlagArgumentIncluded checks whether selected flag includes requested argument.
252 func hasFlagArgumentIncluded(flag string, argument string, params []string) bool {
253         found := filterFlags(params, flag)
254         if len(found) != 1 {
255                 return false
256         }
257
258         _, values := splitKV(found[0], "=")
259         for _, v := range strings.Split(values, ",") {
260                 if v == argument {
261                         return true
262                 }
263         }
264         return false
265 }
266
267 // IsAlwaysAllowAuthorizationModeExcluded validates AlwaysAllow is excluded from authorization modes.
268 func IsAlwaysAllowAuthorizationModeExcluded(params []string) bool {
269         return isSingleFlagPresent("--authorization-mode=", params) &&
270                 !hasFlagArgumentIncluded("--authorization-mode=", "AlwaysAllow", params)
271 }
272
273 // IsNodeAuthorizationModeIncluded validates Node is included in authorization modes.
274 func IsNodeAuthorizationModeIncluded(params []string) bool {
275         return hasFlagArgumentIncluded("--authorization-mode=", "Node", params)
276 }
277
278 // IsAuditLogPathSet validates there is single "--audit-log-path" flag and has non-empty argument.
279 func IsAuditLogPathSet(params []string) bool {
280         return hasSingleFlagNonemptyArgument("--audit-log-path=", params)
281 }
282
283 // IsKubeletCertificateAuthoritySet validates there is single "--kubelet-certificate-authority" flag and has non-empty argument.
284 func IsKubeletCertificateAuthoritySet(params []string) bool {
285         return hasSingleFlagNonemptyArgument("--kubelet-certificate-authority", params)
286 }
287
288 // IsClientCertificateAuthoritySet validates there is single "--client-ca-file" flag and has non-empty argument.
289 func IsClientCertificateAuthoritySet(params []string) bool {
290         return hasSingleFlagNonemptyArgument("--client-ca-file", params)
291 }
292
293 // IsEtcdCertificateAuthoritySet validates there is single "--etcd-cafile" flag and has non-empty argument.
294 func IsEtcdCertificateAuthoritySet(params []string) bool {
295         return hasSingleFlagNonemptyArgument("--etcd-cafile", params)
296 }
297
298 // IsServiceAccountKeySet validates there is single "--service-account-key-file" flag and has non-empty argument.
299 func IsServiceAccountKeySet(params []string) bool {
300         return hasSingleFlagNonemptyArgument("--service-account-key-file", params)
301 }
302
303 // IsKubeletClientCertificateAndKeySet validates there are single "--kubelet-client-certificate" and "--kubelet-client-key" flags and have non-empty arguments.
304 func IsKubeletClientCertificateAndKeySet(params []string) bool {
305         return hasSingleFlagNonemptyArgument("--kubelet-client-certificate", params) &&
306                 hasSingleFlagNonemptyArgument("--kubelet-client-key", params)
307 }
308
309 // IsEtcdCertificateAndKeySet validates there are single "--etcd-certfile" and "--etcd-keyfile" flags and have non-empty arguments.
310 func IsEtcdCertificateAndKeySet(params []string) bool {
311         return hasSingleFlagNonemptyArgument("--etcd-certfile", params) &&
312                 hasSingleFlagNonemptyArgument("--etcd-keyfile", params)
313 }
314
315 // IsTLSCertificateAndKeySet validates there are single "--tls-cert-file" and "--tls-private-key-file" flags and have non-empty arguments.
316 func IsTLSCertificateAndKeySet(params []string) bool {
317         return hasSingleFlagNonemptyArgument("--tls-cert-file", params) &&
318                 hasSingleFlagNonemptyArgument("--tls-private-key-file", params)
319 }
320
321 // hasSingleFlagNonemptyArgument checks whether selected flag was used once and has non-empty argument.
322 func hasSingleFlagNonemptyArgument(flag string, params []string) bool {
323         found := filterFlags(params, flag)
324         if len(found) != 1 {
325                 return false
326         }
327
328         _, value := splitKV(found[0], "=")
329         if value == "" {
330                 return false
331         }
332         return true
333 }
334
335 // IsAuditLogMaxAgeValid validates audit log age is set and it has recommended value.
336 func IsAuditLogMaxAgeValid(params []string) bool {
337         return hasSingleFlagRecommendedNumericArgument("--audit-log-maxage", auditLogAge, params)
338 }
339
340 // IsAuditLogMaxBackupValid validates audit log age is set and it has recommended value.
341 func IsAuditLogMaxBackupValid(params []string) bool {
342         return hasSingleFlagRecommendedNumericArgument("--audit-log-maxbackup", auditLogBackups, params)
343 }
344
345 // IsAuditLogMaxSizeValid validates audit log age is set and it has recommended value.
346 func IsAuditLogMaxSizeValid(params []string) bool {
347         return hasSingleFlagRecommendedNumericArgument("--audit-log-maxsize", auditLogSize, params)
348 }
349
350 // hasSingleFlagRecommendedNumericArgument checks whether selected flag was used once and has
351 // an argument that is greater or equal than the recommended value for given command.
352 func hasSingleFlagRecommendedNumericArgument(flag string, recommendation int, params []string) bool {
353         found := filterFlags(params, flag)
354         if len(found) != 1 {
355                 return false
356         }
357
358         _, value := splitKV(found[0], "=")
359         arg, err := strconv.Atoi(value) // what about empty parameter?
360         if err != nil {
361                 return false
362         }
363         if arg < recommendation {
364                 return false
365         }
366         return true
367 }
368
369 // IsRequestTimeoutValid validates request timeout is set and it has recommended value.
370 func IsRequestTimeoutValid(params []string) bool {
371         return isFlagAbsent("--request-timeout", params) ||
372                 hasSingleFlagValidTimeout("--request-timeout", requestTimeout, 2*requestTimeout, params)
373 }
374
375 // hasSingleFlagValidTimeout checks whether selected flag has valid timeout as an argument in given command.
376 func hasSingleFlagValidTimeout(flag string, min int, max int, params []string) bool {
377         found := filterFlags(params, flag)
378         if len(found) != 1 {
379                 return false
380         }
381
382         _, value := splitKV(found[0], "=")
383         timeout, err := strconv.Atoi(value) // what about empty parameter?
384         if err != nil {
385                 return false
386         }
387         if timeout < min || timeout > max {
388                 return false
389         }
390         return true
391 }