diff --git a/README.md b/README.md index a687e0e167ce2134018b13c0340fd62b6225b847..c9ae166366e276856531252f85fa16f4d570cff3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,17 @@ Tests are configured with YAML files, making this tool easy to update as test sp ## CIS Kubernetes Benchmark support -kube-bench supports the tests for multiple versions of Kubernetes (1.6, 1.7, 1.8, and 1.11) as defined in the CIS Benchmarks 1.0.0, 1.1.0, 1.2.0, and 1.3.0 respectively. It will determine the test set to run based on the Kubernetes version running on the machine. +kube-bench supports the tests for Kubernetes as defined in the CIS Benchmarks 1.0.0 to 1.4.0 respectively. + +| CIS Kubernetes Benchmark | kube-bench config | Kubernetes versions | +|---|---|---| +| 1.0.0| 1.6 | 1.6 | +| 1.1.0| 1.7 | 1.7 | +| 1.2.0| 1.8 | 1.8-1.10 | +| 1.3.0| 1.11 | 1.11-1.12 | +| 1.4.0| 1.13 | 1.13- | + +By default kube-bench will determine the test set to run based on the Kubernetes version running on the machine. ## Installation @@ -114,6 +124,13 @@ For each type of node (*master*, *node* or *federated*) there is a list of compo * **confs** - If one of the listed config files is found, this will be considered for the test. Tests can continue even if no config file is found. If no file is found at any of the listed locations, and a *defaultconf* location is given for the component, the test will give remediation advice using the *defaultconf* location. * **unitfiles** - From version 1.2.0 of the benchmark (tests for Kubernetes 1.8), the remediation instructions were updated to assume that kubelet configuration is defined in a service file, and this setting defines where to look for that configuration. +## Output + +There are three output states +- [PASS] and [FAIL] indicate that a test was run successfully, and it either passed or failed +- [WARN] means this test needs further attention, for example it is a test that needs to be run manually +- [INFO] is informational output that needs no further action. + ## Test config YAML representation The tests are represented as YAML documents (installed by default into ./cfg). @@ -146,6 +163,20 @@ Recommendations (called `checks` in this document) can run on Kubernetes Master, Checks are organized into `groups` which share similar controls (things to check for) and are grouped together in the section of the CIS Kubernetes document. These groups are further organized under `controls` which can be of the type `master`, `node` or `federated apiserver` to reflect the various Kubernetes node types. +### Omitting checks + +If you decide that a recommendation is not appropriate for your environment, you can choose to omit it by editing the test YAML file to give it the check type `skip` as in this example: + +```yaml + checks: + - id: 2.1.1 + text: "Ensure that the --allow-privileged argument is set to false (Scored)" + type: "skip" + scored: true +``` + +No tests will be run for this check and the output will be marked [INFO]. + ## Tests Tests are the items we actually look for to determine if a check is successful or not. Checks can have multiple tests, which must all be successful for the check to pass. diff --git a/cfg/1.13/master.yaml b/cfg/1.13/master.yaml index 9518b3505ed8597150304275cf0d4852c5f2c3d1..e2ac93acc56124c844f6cfe0f87f52a6fe29f52a 100644 --- a/cfg/1.13/master.yaml +++ b/cfg/1.13/master.yaml @@ -366,7 +366,10 @@ groups: text: "Ensure that the --service-account-lookup argument is set to true (Scored)" audit: "ps -ef | grep $apiserverbin | grep -v grep" tests: + bin_op: or test_items: + - flag: "--service-account-lookup" + set: false - flag: "--service-account-lookup" compare: op: eq @@ -1213,7 +1216,7 @@ groups: scored: true - id: 1.4.21 - text: "Ensure that the Kubernetes PKI certificate file permissions are set to 644 or more restrictive (Scored)" + text: "Ensure that the Kubernetes PKI certificate file permissions are set to 600 or more restrictive (Scored)" audit: "stat -c %n\ %a /etc/kubernetes/pki/*.key" type: "manual" tests: diff --git a/check/check.go b/check/check.go index 0813858f8da3bd783aa7bcbfea6577fc05047b5b..4ace74b7d3819c43745326c46ea859998ce864ef 100644 --- a/check/check.go +++ b/check/check.go @@ -166,6 +166,8 @@ func (c *Check) Run() { i++ } + glog.V(3).Info(out.String()) + finalOutput := c.Tests.execute(out.String()) if finalOutput != nil { c.ActualValue = finalOutput.actualResult diff --git a/check/data b/check/data index b3a4cfe76eeed0688bfd5158608ab42146194f2d..c474e6a7ef31e0e37ce6d8d30172a58b868e26ed 100644 --- a/check/data +++ b/check/data @@ -158,6 +158,16 @@ groups: set: true - id: 14 + text: "check that flag some-arg is set to some-val with ':' separator" + tests: + test_items: + - flag: "some-arg" + compare: + op: eq + value: some-val + set: true + + - id: 15 text: "jsonpath correct value on field" tests: test_items: @@ -177,7 +187,7 @@ groups: value: 15000 set: true - - id: 15 + - id: 16 text: "jsonpath correct case-sensitive value on string field" tests: test_items: @@ -197,7 +207,7 @@ groups: value: "WebHook,Something,RBAC" set: true - - id: 16 + - id: 17 text: "jsonpath correct value on boolean field" tests: test_items: @@ -217,14 +227,14 @@ groups: value: true set: true - - id: 17 + - id: 18 text: "jsonpath field absent" tests: test_items: - jsonpath: "{.notARealField}" set: false - - id: 18 + - id: 19 text: "jsonpath correct value on nested field" tests: test_items: @@ -234,7 +244,7 @@ groups: value: "false" set: true - - id: 19 + - id: 20 text: "yamlpath correct value on field" tests: test_items: @@ -244,14 +254,14 @@ groups: value: 14999 set: true - - id: 20 + - id: 21 text: "yamlpath field absent" tests: test_items: - yamlpath: "{.fieldThatIsUnset}" set: false - - id: 21 + - id: 22 text: "yamlpath correct value on nested field" tests: test_items: @@ -261,7 +271,7 @@ groups: value: "false" set: true - - id: 22 + - id: 23 text: "jsonpath on invalid json" tests: test_items: @@ -271,14 +281,14 @@ groups: value: "false" set: true - - id: 23 + - id: 24 text: "jsonpath with broken expression" tests: test_items: - jsonpath: "{.missingClosingBrace" set: true - - id: 24 + - id: 25 text: "yamlpath on invalid yaml" tests: test_items: diff --git a/check/test.go b/check/test.go index 6ac8d0a9e0347430983df35be41effb700f0ffd1..924e1c415d0c607c0df03f8eee62a61b4065bf86 100644 --- a/check/test.go +++ b/check/test.go @@ -123,10 +123,10 @@ func (t *testItem) execute(s string) *testOutput { if t.Flag != "" { // Expects flags in the form; // --flag=somevalue + // flag: somevalue // --flag // somevalue - //pttn := `(` + t.Flag + `)(=)*([^\s,]*) *` - pttn := `(` + t.Flag + `)(=)*([^\s]*) *` + pttn := `(` + t.Flag + `)(=|: *)*([^\s]*) *` flagRe := regexp.MustCompile(pttn) vals := flagRe.FindStringSubmatch(s) diff --git a/check/test_test.go b/check/test_test.go index 24ba757e63374ce9e205553dd72c29d939bda42a..2c15b15d84ce5cc629f3d623bec14f9911081ce7 100644 --- a/check/test_test.go +++ b/check/test_test.go @@ -111,28 +111,34 @@ func TestTestExecute(t *testing.T) { "2:45 ../kubernetes/kube-apiserver --option --admission-control=Something ---audit-log-maxage=40", }, { + // check for ':' as argument-value separator, with space between arg and val controls.Groups[0].Checks[14], - "{\"readOnlyPort\": 15000}", + "2:45 kube-apiserver some-arg: some-val --admission-control=Something ---audit-log-maxage=40", }, { + // check for ':' as argument-value separator, with no space between arg and val + controls.Groups[0].Checks[14], + "2:45 kube-apiserver some-arg:some-val --admission-control=Something ---audit-log-maxage=40", + }, + { controls.Groups[0].Checks[15], - "{\"stringValue\": \"WebHook,Something,RBAC\"}", + "{\"readOnlyPort\": 15000}", }, { controls.Groups[0].Checks[16], - "{\"trueValue\": true}", + "{\"stringValue\": \"WebHook,Something,RBAC\"}", }, { controls.Groups[0].Checks[17], - "{\"readOnlyPort\": 15000}", + "{\"trueValue\": true}", }, { controls.Groups[0].Checks[18], - "{\"authentication\": { \"anonymous\": {\"enabled\": false}}}", + "{\"readOnlyPort\": 15000}", }, { controls.Groups[0].Checks[19], - "readOnlyPort: 15000", + "{\"authentication\": { \"anonymous\": {\"enabled\": false}}}", }, { controls.Groups[0].Checks[20], @@ -140,6 +146,10 @@ func TestTestExecute(t *testing.T) { }, { controls.Groups[0].Checks[21], + "readOnlyPort: 15000", + }, + { + controls.Groups[0].Checks[22], "authentication:\n anonymous:\n enabled: false", }, }