diff options
Diffstat (limited to 'test/security/k8s/src/check/validators/master/api')
3 files changed, 922 insertions, 0 deletions
diff --git a/test/security/k8s/src/check/validators/master/api/api.go b/test/security/k8s/src/check/validators/master/api/api.go new file mode 100644 index 000000000..1ca920e1a --- /dev/null +++ b/test/security/k8s/src/check/validators/master/api/api.go @@ -0,0 +1,391 @@ +package api + +import ( + "strconv" + "strings" +) + +const ( + portDisabled = 0 + portLowest = 1 + portHighest = 65536 + + auditLogAge = 30 + auditLogBackups = 10 + auditLogSize = 100 + + strongCryptoCiphers = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM" + + "_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM" + + "_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM" + + "_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256" + + requestTimeout = 60 +) + +// IsBasicAuthFileAbsent validates there is no basic authentication file specified. +func IsBasicAuthFileAbsent(params []string) bool { + return isFlagAbsent("--basic-auth-file=", params) +} + +// IsTokenAuthFileAbsent validates there is no token based authentication file specified. +func IsTokenAuthFileAbsent(params []string) bool { + return isFlagAbsent("--token-auth-file=", params) +} + +// IsInsecureAllowAnyTokenAbsent validates insecure tokens are not accepted. +func IsInsecureAllowAnyTokenAbsent(params []string) bool { + return isFlagAbsent("--insecure-allow-any-token", params) +} + +// isFlagAbsent checks absence of selected flag in parameters. +func isFlagAbsent(flag string, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 0 { + return false + } + return true +} + +// IsAnonymousAuthDisabled validates there is single "--anonymous-auth" flag and it is set to "false". +func IsAnonymousAuthDisabled(params []string) bool { + return hasSingleFlagArgument("--anonymous-auth=", "false", params) +} + +// IsInsecurePortUnbound validates there is single "--insecure-port" flag and it is set to "0" (disabled). +func IsInsecurePortUnbound(params []string) bool { + return hasSingleFlagArgument("--insecure-port=", strconv.Itoa(portDisabled), params) +} + +// IsProfilingDisabled validates there is single "--profiling" flag and it is set to "false". +func IsProfilingDisabled(params []string) bool { + return hasSingleFlagArgument("--profiling=", "false", params) +} + +// IsRepairMalformedUpdatesDisabled validates there is single "--repair-malformed-updates" flag and it is set to "false". +func IsRepairMalformedUpdatesDisabled(params []string) bool { + return hasSingleFlagArgument("--repair-malformed-updates=", "false", params) +} + +// IsServiceAccountLookupEnabled validates there is single "--service-account-lookup" flag and it is set to "true". +func IsServiceAccountLookupEnabled(params []string) bool { + return hasSingleFlagArgument("--service-account-lookup=", "true", params) +} + +// IsStrongCryptoCipherInUse validates there is single "--tls-cipher-suites=" flag and it is set to strong crypto ciphers. +func IsStrongCryptoCipherInUse(params []string) bool { + return hasSingleFlagArgument("--tls-cipher-suites=", strongCryptoCiphers, params) +} + +// hasSingleFlagArgument checks whether selected flag was used once and has requested argument. +func hasSingleFlagArgument(flag string, argument string, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + + _, value := splitKV(found[0], "=") + if value != argument { + return false + } + return true +} + +// filterFlags returns all occurrences of selected flag. +func filterFlags(strs []string, flag string) []string { + var filtered []string + for _, str := range strs { + if strings.HasPrefix(str, flag) { + filtered = append(filtered, str) + } + } + return filtered +} + +// splitKV splits key and value (after first occurrence of separator). +func splitKV(s, sep string) (string, string) { + ret := strings.SplitN(s, sep, 2) + return ret[0], ret[1] +} + +// IsKubeletHTTPSAbsentOrEnabled validates there is single "--kubelet-https" flag and it is set to "true". +func IsKubeletHTTPSAbsentOrEnabled(params []string) bool { + return isFlagAbsent("--kubelet-https=", params) || + hasSingleFlagArgument("--kubelet-https=", "true", params) +} + +// IsInsecureBindAddressAbsentOrLoopback validates there is no insecure bind address or it is loopback address. +func IsInsecureBindAddressAbsentOrLoopback(params []string) bool { + return isFlagAbsent("--insecure-bind-address=", params) || + hasSingleFlagArgument("--insecure-bind-address=", "127.0.0.1", params) +} + +// IsSecurePortAbsentOrValid validates there is no secure port set explicitly or it has legal value. +func IsSecurePortAbsentOrValid(params []string) bool { + return isFlagAbsent("--secure-port=", params) || + hasFlagValidPort("--secure-port=", params) +} + +// hasFlagValidPort checks whether selected flag has valid port as an argument in given command. +func hasFlagValidPort(flag string, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + + _, value := splitKV(found[0], "=") + port, err := strconv.Atoi(value) // what about empty parameter? + if err != nil { + return false + } + if port < portLowest || port > portHighest { + return false + } + return true +} + +// IsAlwaysAdmitAdmissionControlPluginExcluded validates AlwaysAdmit is excluded from admission control plugins. +func IsAlwaysAdmitAdmissionControlPluginExcluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return !hasFlagArgumentIncluded("--enable-admission-plugins=", "AlwaysAdmit", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return !hasFlagArgumentIncluded("--admission-control=", "AlwaysAdmit", params) + } + return false +} + +// IsAlwaysPullImagesAdmissionControlPluginIncluded validates AlwaysPullImages is included in admission control plugins. +func IsAlwaysPullImagesAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "AlwaysPullImages", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "AlwaysPullImages", params) + } + return false +} + +// IsDenyEscalatingExecAdmissionControlPluginIncluded validates DenyEscalatingExec is included in admission control plugins. +func IsDenyEscalatingExecAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "DenyEscalatingExec", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "DenyEscalatingExec", params) + } + return false +} + +// IsSecurityContextDenyAdmissionControlPluginIncluded validates SecurityContextDeny is included in admission control plugins. +func IsSecurityContextDenyAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "SecurityContextDeny", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "SecurityContextDeny", params) + } + return false +} + +// IsPodSecurityPolicyAdmissionControlPluginIncluded validates PodSecurityPolicy is included in admission control plugins. +func IsPodSecurityPolicyAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "PodSecurityPolicy", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "PodSecurityPolicy", params) + } + return false +} + +// IsServiceAccountAdmissionControlPluginIncluded validates ServiceAccount is included in admission control plugins. +func IsServiceAccountAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "ServiceAccount", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "ServiceAccount", params) + } + return false +} + +// IsNodeRestrictionAdmissionControlPluginIncluded validates NodeRestriction is included in admission control plugins. +func IsNodeRestrictionAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "NodeRestriction", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "NodeRestriction", params) + } + return false +} + +// IsEventRateLimitAdmissionControlPluginIncluded validates EventRateLimit is included in admission control plugins. +func IsEventRateLimitAdmissionControlPluginIncluded(params []string) bool { + if isSingleFlagPresent("--enable-admission-plugins=", params) { + return hasFlagArgumentIncluded("--enable-admission-plugins=", "EventRateLimit", params) + } + if isSingleFlagPresent("--admission-control=", params) { + return hasFlagArgumentIncluded("--admission-control=", "EventRateLimit", params) + } + return false +} + +// IsNamespaceLifecycleAdmissionControlPluginNotExcluded validates NamespaceLifecycle is excluded from admission control plugins. +func IsNamespaceLifecycleAdmissionControlPluginNotExcluded(params []string) bool { + if isSingleFlagPresent("--disable-admission-plugins=", params) { + return !hasFlagArgumentIncluded("--disable-admission-plugins=", "NamespaceLifecycle", params) + } + return true +} + +// isSingleFlagPresent checks presence of selected flag and whether it was used once. +func isSingleFlagPresent(flag string, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + return true +} + +// hasFlagArgumentIncluded checks whether selected flag includes requested argument. +func hasFlagArgumentIncluded(flag string, argument string, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + + _, values := splitKV(found[0], "=") + for _, v := range strings.Split(values, ",") { + if v == argument { + return true + } + } + return false +} + +// IsAlwaysAllowAuthorizationModeExcluded validates AlwaysAllow is excluded from authorization modes. +func IsAlwaysAllowAuthorizationModeExcluded(params []string) bool { + return isSingleFlagPresent("--authorization-mode=", params) && + !hasFlagArgumentIncluded("--authorization-mode=", "AlwaysAllow", params) +} + +// IsNodeAuthorizationModeIncluded validates Node is included in authorization modes. +func IsNodeAuthorizationModeIncluded(params []string) bool { + return hasFlagArgumentIncluded("--authorization-mode=", "Node", params) +} + +// IsAuditLogPathSet validates there is single "--audit-log-path" flag and has non-empty argument. +func IsAuditLogPathSet(params []string) bool { + return hasSingleFlagNonemptyArgument("--audit-log-path=", params) +} + +// IsKubeletCertificateAuthoritySet validates there is single "--kubelet-certificate-authority" flag and has non-empty argument. +func IsKubeletCertificateAuthoritySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--kubelet-certificate-authority", params) +} + +// IsClientCertificateAuthoritySet validates there is single "--client-ca-file" flag and has non-empty argument. +func IsClientCertificateAuthoritySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--client-ca-file", params) +} + +// IsEtcdCertificateAuthoritySet validates there is single "--etcd-cafile" flag and has non-empty argument. +func IsEtcdCertificateAuthoritySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--etcd-cafile", params) +} + +// IsServiceAccountKeySet validates there is single "--service-account-key-file" flag and has non-empty argument. +func IsServiceAccountKeySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--service-account-key-file", params) +} + +// IsKubeletClientCertificateAndKeySet validates there are single "--kubelet-client-certificate" and "--kubelet-client-key" flags and have non-empty arguments. +func IsKubeletClientCertificateAndKeySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--kubelet-client-certificate", params) && + hasSingleFlagNonemptyArgument("--kubelet-client-key", params) +} + +// IsEtcdCertificateAndKeySet validates there are single "--etcd-certfile" and "--etcd-keyfile" flags and have non-empty arguments. +func IsEtcdCertificateAndKeySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--etcd-certfile", params) && + hasSingleFlagNonemptyArgument("--etcd-keyfile", params) +} + +// IsTLSCertificateAndKeySet validates there are single "--tls-cert-file" and "--tls-private-key-file" flags and have non-empty arguments. +func IsTLSCertificateAndKeySet(params []string) bool { + return hasSingleFlagNonemptyArgument("--tls-cert-file", params) && + hasSingleFlagNonemptyArgument("--tls-private-key-file", params) +} + +// hasSingleFlagNonemptyArgument checks whether selected flag was used once and has non-empty argument. +func hasSingleFlagNonemptyArgument(flag string, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + + _, value := splitKV(found[0], "=") + if value == "" { + return false + } + return true +} + +// IsAuditLogMaxAgeValid validates audit log age is set and it has recommended value. +func IsAuditLogMaxAgeValid(params []string) bool { + return hasSingleFlagRecommendedNumericArgument("--audit-log-maxage", auditLogAge, params) +} + +// IsAuditLogMaxBackupValid validates audit log age is set and it has recommended value. +func IsAuditLogMaxBackupValid(params []string) bool { + return hasSingleFlagRecommendedNumericArgument("--audit-log-maxbackup", auditLogBackups, params) +} + +// IsAuditLogMaxSizeValid validates audit log age is set and it has recommended value. +func IsAuditLogMaxSizeValid(params []string) bool { + return hasSingleFlagRecommendedNumericArgument("--audit-log-maxsize", auditLogSize, params) +} + +// hasSingleFlagRecommendedNumericArgument checks whether selected flag was used once and has +// an argument that is greater or equal than the recommended value for given command. +func hasSingleFlagRecommendedNumericArgument(flag string, recommendation int, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + + _, value := splitKV(found[0], "=") + arg, err := strconv.Atoi(value) // what about empty parameter? + if err != nil { + return false + } + if arg < recommendation { + return false + } + return true +} + +// IsRequestTimeoutValid validates request timeout is set and it has recommended value. +func IsRequestTimeoutValid(params []string) bool { + return isFlagAbsent("--request-timeout", params) || + hasSingleFlagValidTimeout("--request-timeout", requestTimeout, 2*requestTimeout, params) +} + +// hasSingleFlagValidTimeout checks whether selected flag has valid timeout as an argument in given command. +func hasSingleFlagValidTimeout(flag string, min int, max int, params []string) bool { + found := filterFlags(params, flag) + if len(found) != 1 { + return false + } + + _, value := splitKV(found[0], "=") + timeout, err := strconv.Atoi(value) // what about empty parameter? + if err != nil { + return false + } + if timeout < min || timeout > max { + return false + } + return true +} diff --git a/test/security/k8s/src/check/validators/master/api/api_suite_test.go b/test/security/k8s/src/check/validators/master/api/api_suite_test.go new file mode 100644 index 000000000..28066f963 --- /dev/null +++ b/test/security/k8s/src/check/validators/master/api/api_suite_test.go @@ -0,0 +1,13 @@ +package api_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestApi(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Api Suite") +} diff --git a/test/security/k8s/src/check/validators/master/api/api_test.go b/test/security/k8s/src/check/validators/master/api/api_test.go new file mode 100644 index 000000000..4ba5070a8 --- /dev/null +++ b/test/security/k8s/src/check/validators/master/api/api_test.go @@ -0,0 +1,518 @@ +package api_test + +import ( + . "check/validators/master/api" + + . "github.com/onsi/ginkgo/extensions/table" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Api", func() { + var ( + // kubeApiServerCISCompliant uses secure defaults or follows CIS guidelines explicitly. + kubeApiServerCISCompliant = []string{ + "--anonymous-auth=false", + "--insecure-port=0", + "--profiling=false", + "--repair-malformed-updates=false", + "--service-account-lookup=true", + "--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount," + + "TaintNodesByCondition,Priority,DefaultTolerationSeconds,DefaultStorageClass," + + "PersistentVolumeClaimResize,MutatingAdmissionWebhook,ValidatingAdmissionWebhook," + + "ResourceQuota,AlwaysPullImages,DenyEscalatingExec,SecurityContextDeny," + + "PodSecurityPolicy,NodeRestriction,EventRateLimit", + "--authorization-mode=Node,RBAC", + "--audit-log-path=/var/log/apiserver/audit.log", + "--audit-log-maxage=30", + "--audit-log-maxbackup=10", + "--audit-log-maxsize=100", + "--kubelet-certificate-authority=TrustedCA", + "--client-ca-file=/etc/kubernetes/ssl/ca.pem", + "--etcd-cafile=/etc/kubernetes/etcd/ca.pem", + "--service-account-key-file=/etc/kubernetes/ssl/kube-service-account-token-key.pem", + "--kubelet-client-certificate=/etc/kubernetes/ssl/cert.pem", + "--kubelet-client-key=/etc/kubernetes/ssl/key.pem", + "--etcd-certfile=/etc/kubernetes/etcd/cert.pem", + "--etcd-keyfile=/etc/kubernetes/etcd/key.pem", + "--tls-cert-file=/etc/kubernetes/ssl/cert.pem", + "--tls-private-key-file=/etc/kubernetes/ssl/key.pem", + "--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," + + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305," + + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305," + + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384," + + "TLS_RSA_WITH_AES_128_GCM_SHA256", + } + + // kubeApiServerCasablanca was obtained from virtual environment for testing + // (introduced in Change-Id: I57f9f3caac0e8b391e9ed480f6bebba98e006882). + kubeApiServerCasablanca = []string{ + "--storage-backend=etcd2", + "--storage-media-type=application/json", + "--service-cluster-ip-range=10.43.0.0/16", + "--etcd-servers=https://etcd.kubernetes.rancher.internal:2379", + "--insecure-bind-address=0.0.0.0", + "--insecure-port=0", + "--cloud-provider=rancher", + "--allow-privileged=true", + "--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount," + + "PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota", + "--client-ca-file=/etc/kubernetes/ssl/ca.pem", + "--tls-cert-file=/etc/kubernetes/ssl/cert.pem", + "--tls-private-key-file=/etc/kubernetes/ssl/key.pem", + "--kubelet-client-certificate=/etc/kubernetes/ssl/cert.pem", + "--kubelet-client-key=/etc/kubernetes/ssl/key.pem", + "--runtime-config=batch/v2alpha1", + "--anonymous-auth=false", + "--authentication-token-webhook-config-file=/etc/kubernetes/authconfig", + "--runtime-config=authentication.k8s.io/v1beta1=true", + "--external-hostname=kubernetes.kubernetes.rancher.internal", + "--etcd-cafile=/etc/kubernetes/etcd/ca.pem", + "--etcd-certfile=/etc/kubernetes/etcd/cert.pem", + "--etcd-keyfile=/etc/kubernetes/etcd/key.pem", + "--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," + + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305," + + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384," + + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + } + + // kubeApiServerCasablanca was obtained from virtual environment for testing + // (introduced in Change-Id: I54ada5fade3b984dedd1715f20579e3ce901faa3). + kubeApiServerDublin = []string{ + "--requestheader-group-headers=X-Remote-Group", + "--proxy-client-cert-file=/etc/kubernetes/ssl/kube-apiserver-proxy-client.pem", + "--bind-address=0.0.0.0", + "--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256," + + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305," + + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384," + + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "--cloud-provider=", + "--etcd-cafile=/etc/kubernetes/ssl/kube-ca.pem", + "--etcd-servers=https://172.17.0.100:2379", + "--tls-cert-file=/etc/kubernetes/ssl/kube-apiserver.pem", + "--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount," + + "DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook," + + "ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction,PersistentVolumeLabel", + "--insecure-port=0", + "--secure-port=6443", + "--storage-backend=etcd3", + "--kubelet-client-key=/etc/kubernetes/ssl/kube-apiserver-key.pem", + "--requestheader-client-ca-file=/etc/kubernetes/ssl/kube-apiserver-requestheader-ca.pem", + "--service-account-key-file=/etc/kubernetes/ssl/kube-service-account-token-key.pem", + "--service-node-port-range=30000-32767", + "--tls-private-key-file=/etc/kubernetes/ssl/kube-apiserver-key.pem", + "--requestheader-username-headers=X-Remote-User", + "--repair-malformed-updates=false", + "--kubelet-client-certificate=/etc/kubernetes/ssl/kube-apiserver.pem", + "--service-cluster-ip-range=10.43.0.0/16", + "--advertise-address=172.17.0.100", + "--profiling=false", + "--requestheader-extra-headers-prefix=X-Remote-Extra-", + "--etcd-certfile=/etc/kubernetes/ssl/kube-node.pem", + "--anonymous-auth=false", + "--etcd-keyfile=/etc/kubernetes/ssl/kube-node-key.pem", + "--etcd-prefix=/registry", + "--client-ca-file=/etc/kubernetes/ssl/kube-ca.pem", + "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname", + "--requestheader-allowed-names=kube-apiserver-proxy-client", + "--service-account-lookup=true", + "--proxy-client-key-file=/etc/kubernetes/ssl/kube-apiserver-proxy-client-key.pem", + "--authorization-mode=Node,RBAC", + "--allow-privileged=true", + } + ) + + Describe("Boolean flags", func() { + DescribeTable("Accepting any token", + func(params []string, expected bool) { + Expect(IsInsecureAllowAnyTokenAbsent(params)).To(Equal(expected)) + }, + Entry("Is not absent on insecure cluster", []string{"--insecure-allow-any-token"}, false), + Entry("Should be absent on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be absent on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Anonymous requests", + func(params []string, expected bool) { + Expect(IsAnonymousAuthDisabled(params)).To(Equal(expected)) + }, + Entry("Is not set on insecure cluster", []string{}, false), + Entry("Should be set to false on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be set to false on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be set to false on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Profiling", + func(params []string, expected bool) { + Expect(IsProfilingDisabled(params)).To(Equal(expected)) + }, + Entry("Is not set on insecure cluster", []string{}, false), + Entry("Is explicitly enabled on insecure cluster", []string{"--profiling=true"}, false), + Entry("Is not set on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be set to false on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be set to false on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("HTTPS for kubelet", + func(params []string, expected bool) { + Expect(IsKubeletHTTPSAbsentOrEnabled(params)).To(Equal(expected)) + }, + Entry("Is explicitly disabled on insecure cluster", []string{"--kubelet-https=false"}, false), + Entry("Should be absent or set to true on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent or set to true on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be absent or set to true on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Repairing malformed updates", + func(params []string, expected bool) { + Expect(IsRepairMalformedUpdatesDisabled(params)).To(Equal(expected)) + }, + Entry("Is not set on insecure cluster", []string{}, false), + Entry("Is explicitly enabled on insecure cluster", []string{"--repair-malformed-updates=true"}, false), + Entry("Is not set on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be set to false on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be set to false on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Service account lookup", + func(params []string, expected bool) { + Expect(IsServiceAccountLookupEnabled(params)).To(Equal(expected)) + }, + Entry("Is not set on insecure cluster", []string{}, false), + Entry("Is explicitly disabled on insecure cluster", []string{"--service-account-lookup=false"}, false), + Entry("Is not set on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be set to true on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be set to true on Dublin cluster", kubeApiServerDublin, true), + ) + }) + + Describe("File path flags", func() { + DescribeTable("Basic authentication file", + func(params []string, expected bool) { + Expect(IsBasicAuthFileAbsent(params)).To(Equal(expected)) + }, + Entry("Is not absent on insecure cluster", []string{"--basic-auth-file=/path/to/file"}, false), + Entry("Should be absent on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be absent on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Token authentication file", + func(params []string, expected bool) { + Expect(IsTokenAuthFileAbsent(params)).To(Equal(expected)) + }, + Entry("Is not absent on insecure cluster", []string{"--token-auth-file=/path/to/file"}, false), + Entry("Should be absent on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be absent on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Audit log path", + func(params []string, expected bool) { + Expect(IsAuditLogPathSet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--audit-log-path="}, false), + Entry("Is absent on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is absent on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("Kubelet certificate authority", + func(params []string, expected bool) { + Expect(IsKubeletCertificateAuthoritySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--kubelet-certificate-authority="}, false), + Entry("Is absent on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is absent on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("Client certificate authority", + func(params []string, expected bool) { + Expect(IsClientCertificateAuthoritySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--client-ca-file="}, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Etcd certificate authority", + func(params []string, expected bool) { + Expect(IsEtcdCertificateAuthoritySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"-etcd-cafile="}, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Service account key", + func(params []string, expected bool) { + Expect(IsServiceAccountKeySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--service-account-key-file="}, false), + Entry("Is absent on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Kubelet client certificate and key", + func(params []string, expected bool) { + Expect(IsKubeletClientCertificateAndKeySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--kubelet-client-certificate= --kubelet-client-key="}, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Etcd certificate and key", + func(params []string, expected bool) { + Expect(IsEtcdCertificateAndKeySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--etcd-certfile= --etcd-keyfile="}, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("TLS certificate and key", + func(params []string, expected bool) { + Expect(IsTLSCertificateAndKeySet(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--tls-cert-file= --tls-private-key-file="}, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + }) + + Describe("Address and port flags", func() { + DescribeTable("Bind address", + func(params []string, expected bool) { + Expect(IsInsecureBindAddressAbsentOrLoopback(params)).To(Equal(expected)) + }, + Entry("Is not absent on insecure cluster", []string{"--insecure-bind-address=1.2.3.4"}, false), + Entry("Is not absent nor set to loopback on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be absent or set to loopback on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent or set to loopback on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Bind port", + func(params []string, expected bool) { + Expect(IsInsecurePortUnbound(params)).To(Equal(expected)) + }, + Entry("Is not set on insecure cluster", []string{}, false), + Entry("Is explicitly enabled on insecure cluster", []string{"--insecure-port=1234"}, false), + Entry("Should be set to 0 on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be set to 0 on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be set to 0 on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Secure bind port", + func(params []string, expected bool) { + Expect(IsSecurePortAbsentOrValid(params)).To(Equal(expected)) + }, + Entry("Is explicitly disabled on insecure cluster", []string{"--secure-port=0"}, false), + Entry("Should be absent or set to valid port on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent or set to valid port on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be absent or set to valid port on Dublin cluster", kubeApiServerDublin, true), + ) + }) + + Describe("Numeric flags", func() { + DescribeTable("Audit log age", + func(params []string, expected bool) { + Expect(IsAuditLogMaxAgeValid(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--audit-log-maxage="}, false), + Entry("Is insufficient on insecure cluster", []string{"--audit-log-maxage=5"}, false), + Entry("Is absent on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is absent on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be set appropriately on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("Audit log backups", + func(params []string, expected bool) { + Expect(IsAuditLogMaxBackupValid(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--audit-log-maxbackup="}, false), + Entry("Is insufficient on insecure cluster", []string{"--audit-log-maxbackup=2"}, false), + Entry("Is absent on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is absent on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be set appropriately on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("Audit log size", + func(params []string, expected bool) { + Expect(IsAuditLogMaxSizeValid(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--audit-log-maxsize="}, false), + Entry("Is insufficient on insecure cluster", []string{"--audit-log-maxsize=5"}, false), + Entry("Is absent on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is absent on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be set appropriately on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("Request timeout", + func(params []string, expected bool) { + Expect(IsRequestTimeoutValid(params)).To(Equal(expected)) + }, + Entry("Is empty on insecure cluster", []string{"--request-timeout="}, false), + Entry("Is too high on insecure cluster", []string{"--request-timeout=600"}, false), + Entry("Should be set only if needed on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be set only if needed on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be set only if needed on Dublin cluster", kubeApiServerDublin, true), + ) + }) + + Describe("Argument list flags", func() { + DescribeTable("AlwaysAdmit admission control plugin", + func(params []string, expected bool) { + Expect(IsAlwaysAdmitAdmissionControlPluginExcluded(params)).To(Equal(expected)) + }, + Entry("Is not absent on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar,AlwaysAdmit,Baz,Quuz"}, false), + Entry("Is not absent on insecure deprecated cluster", []string{"--admission-control=Foo,Bar,AlwaysAdmit,Baz,Quuz"}, false), + Entry("Should be absent on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be absent on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("AlwaysPullImages admission control plugin", + func(params []string, expected bool) { + Expect(IsAlwaysPullImagesAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Is not present on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is not present on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("DenyEscalatingExec admission control plugin", + func(params []string, expected bool) { + Expect(IsDenyEscalatingExecAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Is not present on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is not present on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("SecurityContextDeny admission control plugin", + func(params []string, expected bool) { + Expect(IsSecurityContextDenyAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Is not present on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is not present on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("PodSecurityPolicy admission control plugin", + func(params []string, expected bool) { + Expect(IsPodSecurityPolicyAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Is not present on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is not present on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("ServiceAccount admission control plugin", + func(params []string, expected bool) { + Expect(IsServiceAccountAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("NodeRestriction admission control plugin", + func(params []string, expected bool) { + Expect(IsNodeRestrictionAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Is not present on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be present on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("EventRateLimit admission control plugin", + func(params []string, expected bool) { + Expect(IsEventRateLimitAdmissionControlPluginIncluded(params)).To(Equal(expected)) + }, + Entry("Is not present on insecure cluster", []string{"--enable-admission-plugins=Foo,Bar"}, false), + Entry("Is not present on insecure deprecated cluster", []string{"--admission-control=Foo,Bar"}, false), + Entry("Is not present on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is not present on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + + DescribeTable("NamespaceLifecycle admission control plugin", + func(params []string, expected bool) { + Expect(IsNamespaceLifecycleAdmissionControlPluginNotExcluded(params)).To(Equal(expected)) + }, + Entry("Is explicitly disabled on insecure cluster", []string{"--disable-admission-plugins=Foo,Bar,NamespaceLifecycle,Baz,Quuz"}, false), + Entry("Should not be disabled on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should not be disabled on Casablanca cluster", kubeApiServerCasablanca, true), + Entry("Should not be disabled on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("AlwaysAllow authorization mode", + func(params []string, expected bool) { + Expect(IsAlwaysAllowAuthorizationModeExcluded(params)).To(Equal(expected)) + }, + Entry("Is not explicitly disabled on insecure cluster", []string{}, false), + Entry("Is not absent on insecure cluster", []string{"--authorization-mode=Foo,Bar,AlwaysAllow,Baz,Quuz"}, false), + Entry("Is not explicitly disabled on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should be absent on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should be absent on Dublin cluster", kubeApiServerDublin, true), + ) + + DescribeTable("Node authorization mode", + func(params []string, expected bool) { + Expect(IsNodeAuthorizationModeIncluded(params)).To(Equal(expected)) + }, + Entry("Is not explicitly enabled on insecure cluster", []string{}, false), + Entry("Is not present on insecure cluster", []string{"--authorization-mode=Foo,Bar"}, false), + Entry("Is not explicitly enabled on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Should present on CIS-compliant cluster", kubeApiServerCISCompliant, true), + Entry("Should present on Dublin cluster", kubeApiServerDublin, true), + ) + }) + + Describe("Flags requiring strict equality", func() { + DescribeTable("Strong Cryptographic Ciphers", + func(params []string, expected bool) { + Expect(IsStrongCryptoCipherInUse(params)).To(Equal(expected)) + }, + Entry("Is absent on insecure cluster", []string{}, false), + Entry("Is empty on insecure cluster", []string{"--tls-cipher-suites="}, false), + Entry("Is incomplete on insecure cluster", []string{"--tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"}, false), + Entry("Is incomplete on Casablanca cluster", kubeApiServerCasablanca, false), + Entry("Is incomplete on Dublin cluster", kubeApiServerDublin, false), + Entry("Should be complete on CIS-compliant cluster", kubeApiServerCISCompliant, true), + ) + }) +}) |