Compare commits

...

50 Commits

Author SHA1 Message Date
Liz Rice
442447851e Merge pull request #259 from aquasecurity/no-master-binaries
Don't assume master if 0 master binaries specified
2019-05-05 16:02:40 +01:00
Liz Rice
1f67c45fd6 Merge branch 'master' into no-master-binaries 2019-05-05 15:56:53 +01:00
Liz Rice
7d9089d376 Merge pull request #273 from danielpacak/issue-172-filter-cis-checks
Add flags to further filter CIS checks to run
2019-05-05 15:55:39 +01:00
Liz Rice
aebd35a5ab Update copyright date 2019-05-02 18:15:31 -07:00
Liz Rice
8c8ae7ce76 Update copyright date 2019-05-02 18:15:05 -07:00
Liz Rice
0d57a9dff3 Update copyright date 2019-05-02 18:13:25 -07:00
Daniel Pacak
5fb133cd02 Adjust the semantics of scored and unscored flags 2019-05-01 22:52:56 +02:00
Daniel Pacak
306e1960af Add flags to further filter CIS checks to run 2019-05-01 22:52:56 +02:00
Liz Rice
fc536b239b Merge pull request #275 from aquasecurity/fix-270
Fix failing check 1.5.2 in version 1.11
2019-05-01 08:11:21 -07:00
Abubakr-Sadik Nii Nai Davis
fbbf6b37c7 Change test_items in 1.11 master.yaml check 1.5.2 to fix issue with
check failing even when --client-cert-auth is set.
2019-04-30 16:51:10 +00:00
Liz Rice
e5b6603da5 Merge branch 'master' into no-master-binaries 2019-04-24 10:02:32 +01:00
Liz Rice
6d9a3b4888 Merge pull request #260 from aquasecurity/json-config
Json & YAML config, continued
2019-04-24 09:59:10 +01:00
Liz Rice
a800ac6ccc Merge branch 'master' into json-config 2019-04-24 09:29:18 +01:00
Liz Rice
331d64b294 Merge pull request #267 from aquasecurity/lizrice-patch-1
Add OCP info into the README
2019-04-23 17:15:46 +02:00
Liz Rice
ceb44583dd Tidy up a couple of things 2019-04-23 16:07:27 +01:00
Liz Rice
91c6ef2155 Merge branch 'master' into json-config 2019-04-23 13:51:30 +02:00
Liz Rice
f9d0f4acc1 Add OCP info into the README 2019-04-23 11:59:54 +01:00
Liz Rice
ab2001e393 Merge pull request #261 from aquasecurity/yoavrotems-patch-3
update files
2019-04-23 12:54:39 +02:00
Liz Rice
7e8dfbc6ea Fix invalid YAML 2019-04-23 11:41:48 +01:00
Liz Rice
b4419e810f Tiny typo 2019-04-23 11:01:38 +01:00
Liz Rice
d05d71553f Tiny typo 2019-04-23 10:57:15 +01:00
yoavrotems
e70f50b2b5 update files 2019-04-16 06:01:51 +00:00
Liz Rice
a613f6f028 Document job for EKS 2019-04-11 19:00:17 +01:00
Liz Rice
fa60fb68fd Add job for EKS 2019-04-11 18:45:16 +01:00
Liz Rice
27dc75fefa No need for unused master config file.
Better comments in config file
2019-04-11 18:36:30 +01:00
Liz Rice
de623220e1 No need to load config just to check if components are running.
This also allows for there to be no master.yaml file, for environments where such a thing doesn’t need to exist
2019-04-11 18:34:22 +01:00
Liz Rice
248942e2fa No need to load config just to check if components are running.
This also allows for there to be no master.yaml file, for environments where such a thing doesn’t need to exist
2019-04-11 18:31:26 +01:00
Liz Rice
596dae03d9 Don't assume master if 0 master binaries specified 2019-04-11 17:19:50 +01:00
Liz Rice
01179963ce Don't assume master if 0 master binaries specified 2019-04-11 17:15:50 +01:00
Liz Rice
902a10f1c7 Just have one path for both json and yaml 2019-04-11 17:09:33 +01:00
Liz Rice
9b034024a7 Complete merge where test numbers changes 2019-04-11 10:21:19 +01:00
Liz Rice
c887794807 Merge branch 'master' into feature/json-config 2019-04-11 10:03:07 +01:00
Liz Rice
d30786da4a Merge pull request #258 from aquasecurity/fix-241
Add ":" as a valid flag-value separator for tests
2019-04-11 09:37:39 +01:00
Liz Rice
c03e958311 Merge branch 'master' into fix-241 2019-04-11 09:34:02 +01:00
Liz Rice
241972c659 Merge pull request #249 from aquasecurity/document-output
Document output states
2019-04-11 09:18:34 +01:00
Liz Rice
d93ed0acca Merge branch 'master' into fix-241 2019-04-11 09:05:18 +01:00
Liz Rice
b5f3299e92 Merge branch 'master' into document-output 2019-04-11 09:04:04 +01:00
Liz Rice
588d75d20d Merge pull request #251 from aquasecurity/version-mapping
Add CIS & Kubernetes version mapping to README
2019-04-11 09:03:44 +01:00
Abubakr-Sadik Nii Nai Davis
4b8a7ffbe1 Add ":" as a valid flag-value separator for tests
This is useful for checking values in YAML (possibly JSON) kubernetes config files.
2019-04-10 22:47:26 +00:00
Liz Rice
651b72f7d1 Merge branch 'master' into document-output 2019-04-10 08:45:55 +01:00
Liz Rice
0c40532e76 Merge branch 'master' into version-mapping 2019-04-10 08:31:04 +01:00
Liz Rice
54502c5f75 Merge pull request #247 from aquasecurity/yoavrotems-patch-2
Update master.yaml
2019-03-27 14:24:03 +00:00
Liz Rice
df556c2f42 Add CIS & Kubernetes version mapping to README 2019-03-27 14:21:22 +00:00
Liz Rice
488f5221ef Document output states
Also describe how tests can be omitted by editing the YAML
2019-03-26 10:37:17 +00:00
Liz Rice
b1ce0a9a75 Merge branch 'master' into yoavrotems-patch-2 2019-03-26 09:51:03 +00:00
Liz Rice
0f86bfc060 Merge pull request #246 from aquasecurity/yoavrotems-patch-1
Update master.yaml
2019-03-26 09:41:40 +00:00
yoavrotems
d059196b71 Update master.yaml
Fix 1.1.23 to check *if* --service-account-lookup argument is set and if so then if it's equal to true
2019-03-25 14:41:06 +02:00
yoavrotems
a85e5a7759 Update master.yaml
Fix title of 1.4.21 from 644 to 600 according to cis benchmark
2019-03-25 14:33:52 +02:00
Florent Delannoy
abfc38d672 Update documentation after review 2019-03-21 15:05:20 +00:00
Florent Delannoy
4d3144ca21 Support JSON and YAML configuration
Support new configuration options besides --flags:
- JSON file through `jsonpath`
- YAML file through `yamlpath`

These new options are fully backwards-compatible with the existing
tests.

Added a new profile, 1.11-json, that expects a JSON kubelet
configuration file and scores accordingly. This profile is compatible
with EKS.
2019-03-21 12:13:31 +00:00
22 changed files with 3186 additions and 2040 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ vendor
dist
.vscode/
hack/kind.test.yaml
.idea/

49
Gopkg.lock generated
View File

@@ -1,6 +1,14 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd"
name = "github.com/fatih/color"
@@ -113,6 +121,14 @@
pruneopts = "UT"
revision = "0131db6d737cfbbfb678f8b7d92e55e27ce46224"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:1fccaaeae58b2a2f1af4dbf7eee92ff14f222e161d143bfd20082ef664f91216"
name = "github.com/spf13/afero"
@@ -161,6 +177,25 @@
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
version = "v1.0.0"
[[projects]]
digest = "1:ac83cf90d08b63ad5f7e020ef480d319ae890c208f8524622a2f3136e2686b02"
name = "github.com/stretchr/objx"
packages = ["."]
pruneopts = "UT"
revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c"
version = "v0.1.1"
[[projects]]
digest = "1:0bcc464dabcfad5393daf87c3f8142911d0f6c52569b837e91a1c15e890265f3"
name = "github.com/stretchr/testify"
packages = [
"assert",
"mock",
]
pruneopts = "UT"
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
digest = "1:c9c0ba9ea00233c41b91e441cfd490f34b129bbfebcb1858979623bd8de07f72"
name = "golang.org/x/sys"
@@ -189,6 +224,17 @@
pruneopts = "UT"
revision = "c95af922eae69f190717a0b7148960af8c55a072"
[[projects]]
digest = "1:e8e3acc03397f71fad44385631e665c639a8d55bd187bcfa6e70b695e3705edd"
name = "k8s.io/client-go"
packages = [
"third_party/forked/golang/template",
"util/jsonpath",
]
pruneopts = "UT"
revision = "e64494209f554a6723674bd494d69445fb76a1d4"
version = "v10.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
@@ -199,7 +245,10 @@
"github.com/jinzhu/gorm/dialects/postgres",
"github.com/spf13/cobra",
"github.com/spf13/viper",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/mock",
"gopkg.in/yaml.v2",
"k8s.io/client-go/util/jsonpath",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -18,6 +18,10 @@
name = "github.com/spf13/viper"
version = "1.0.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.3.0"
[prune]
go-tests = true
unused-packages = true

View File

@@ -13,7 +13,19 @@ 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.
There is also preliminary support for Red Hat's Openshift Hardening Guide for 3.10 and 3.11. Please note that kube-bench does not automatically detect Openshift - see below.
## Installation
@@ -37,14 +49,14 @@ You can even use your own configs by mounting them over the default ones in `/op
docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro -t -v path/to/my-config.yaml:/opt/kube-bench/cfg/config.yaml aquasec/kube-bench:latest [master|node]
```
> Note: the tests require either the kubelet or kubectl binary in the path in order to know the Kubernetes version. You can pass `-v $(which kubectl):/usr/bin/kubectl` to the above invocations to resolve this.
> Note: the tests require either the kubelet or kubectl binary in the path in order to auto-detect the Kubernetes version. You can pass `-v $(which kubectl):/usr/bin/kubectl` to the above invocations to resolve this.
### Running in a kubernetes cluster
You can run kube-bench inside a pod, but it will need access to the host's PID namespace in order to check the running processes, as well as access to some directories on the host where config files and other files are stored.
Master nodes are automatically detected by kube-bench and will run master checks when possible.
The detection is done by verifying that mandatory components for master are running. (see [config file](#configuration).
The detection is done by verifying that mandatory components for master, as defined in the config files, are running (see [Configuration](#configuration)).
The supplied `job.yaml` file can be applied to run the tests as a job. For example:
@@ -62,7 +74,7 @@ NAME READY STATUS RESTARTS AGE
kube-bench-j76s9 0/1 Completed 0 11s
# The results are held in the pod's logs
k logs kube-bench-j76s9
kubectl logs kube-bench-j76s9
[INFO] 1 Master Node Security Configuration
[INFO] 1.1 API Server
...
@@ -74,6 +86,15 @@ To run the tests on the master node, the pod needs to be scheduled on that node.
The default labels applied to master nodes has changed since Kubernetes 1.11, so if you are using an older version you may need to modify the nodeSelector and tolerations to run the job on the master node.
### Running in an EKS cluster
There is a `job-eks.yaml` file for running the kube-bench node checks on an EKS cluster. **Note that you must update the image reference in `job-eks.yaml`.** Typically you will push the container image for kube-bench to ECR and refer to it there in the YAML file.
There are two significant differences on EKS:
* It uses [config files in JSON format](https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/)
* It's not possible to schedule jobs onto the master node, so master checks can't be performed
### Installing from a container
This command copies the kube-bench binary and configuration files to your host from the Docker container:
@@ -102,6 +123,9 @@ go build -o kube-bench .
./kube-bench
```
## Running on OpenShift
kube-bench includes a set of test files for Red Hat's OpenShift hardening guide for OCP 3.10 and 3.11. To run this you will need to specify `--version ocp-3.10` when you run the `kube-bench` command (either directly or through YAML). This config version is valid for OCP 3.10 and 3.11.
## Configuration
@@ -114,6 +138,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 +177,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.
@@ -159,6 +204,19 @@ tests:
value:
...
```
You can also define jsonpath and yamlpath tests using the following syntax:
```
tests:
- path:
set:
compare:
op:
value:
...
```
Tests have various `operations` which are used to compare the output of audit commands for success.
These operations are:
@@ -188,4 +246,4 @@ Next you'll have to build the kube-bench docker image using `make build-docker`,
Finally we can use the `make kind-run` target to run the current version of kube-bench in the cluster and follow the logs of pods created. (Ctrl+C to exit)
Everytime you want to test a change, you'll need to rebuild the docker image and push it to cluster before running it again. ( `make build-docker kind-push kind-run` )
Everytime you want to test a change, you'll need to rebuild the docker image and push it to cluster before running it again. ( `make build-docker kind-push kind-run` )

20
cfg/1.11-json/config.yaml Normal file
View File

@@ -0,0 +1,20 @@
---
# Config file for systems such as EKS where config is in JSON files
# Master nodes are controlled by EKS and not user-accessible
node:
kubernetes:
confs:
- "/var/lib/kubelet/kubeconfig"
kubeconfig:
- "/var/lib/kubelet/kubeconfig"
kubelet:
bins:
- "hyperkube kubelet"
- "kubelet"
defaultconf: "/etc/kubernetes/kubelet/kubelet-config.json"
defaultsvc: "/etc/systemd/system/kubelet.service"
defaultkubeconfig: "/var/lib/kubelet/kubeconfig"
proxy:
defaultkubeconfig: "/var/lib/kubelet/kubeconfig"

508
cfg/1.11-json/node.yaml Normal file
View File

@@ -0,0 +1,508 @@
---
controls:
version: 1.11
id: 2
text: "Worker Node Security Configuration"
type: "node"
groups:
- id: 2.1
text: "Kubelet"
checks:
- id: 2.1.1
text: "Ensure that the --allow-privileged argument is set to false (Scored)"
audit: "ps -fC $kubeletbin"
tests:
test_items:
- flag: "--allow-privileged"
compare:
op: eq
value: false
set: true
remediation: |
Edit the kubelet service file $kubeletsvc
on each worker node and set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
--allow-privileged=false
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.2
text: "Ensure that the --anonymous-auth argument is set to false (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.authentication.anonymous.enabled}"
compare:
op: eq
value: false
set: true
remediation: |
If using a Kubelet config file, edit the file to set authentication: anonymous: enabled to
false .
If using executable arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
--anonymous-auth=false
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.3
text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.authorization.mode}"
compare:
op: noteq
value: "AlwaysAllow"
set: true
remediation: |
If using a Kubelet config file, edit the file to set authorization: mode to Webhook.
If using executable arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_AUTHZ_ARGS variable.
--authorization-mode=Webhook
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.4
text: "Ensure that the --client-ca-file argument is set as appropriate (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.authentication.x509.clientCAFile}"
set: true
remediation: |
If using a Kubelet config file, edit the file to set authentication: x509: clientCAFile to
the location of the client CA file.
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_AUTHZ_ARGS variable.
--client-ca-file=<path/to/client-ca-file>
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.5
text: "Ensure that the --read-only-port argument is set to 0 (Scored)"
audit: "cat $kubeletconf"
tests:
bin_op: or
test_items:
- path: "{.readOnlyPort}"
set: false
- path: "{.readOnlyPort}"
compare:
op: eq
value: "0"
set: true
remediation: |
If using a Kubelet config file, edit the file to set readOnlyPort to 0 .
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
--read-only-port=0
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.6
text: "Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Scored)"
audit: "cat $kubeletconf"
tests:
bin_op: or
test_items:
- path: "{.streamingConnectionIdleTimeout}"
set: false
- path: "{.streamingConnectionIdleTimeout}"
compare:
op: noteq
value: 0
set: true
remediation: |
If using a Kubelet config file, edit the file to set streamingConnectionIdleTimeout to a
value other than 0.
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
--streaming-connection-idle-timeout=5m
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.7
text: "Ensure that the --protect-kernel-defaults argument is set to true (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.protectKernelDefaults}"
compare:
op: eq
value: true
set: true
remediation: |
If using a Kubelet config file, edit the file to set protectKernelDefaults: true .
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
--protect-kernel-defaults=true
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.8
text: "Ensure that the --make-iptables-util-chains argument is set to true (Scored)"
audit: "cat $kubeletconf"
tests:
bin_op: or
test_items:
- path: "{.makeIPTablesUtilChains}"
set: false
- path: "{.makeIPTablesUtilChains}"
compare:
op: eq
value: true
set: true
remediation: |
If using a Kubelet config file, edit the file to set makeIPTablesUtilChains: true .
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
remove the --make-iptables-util-chains argument from the
KUBELET_SYSTEM_PODS_ARGS variable.
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.9
text: "Ensure that the --hostname-override argument is not set (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.hostnameOverride}"
set: false
remediation: |
Edit the kubelet service file $kubeletsvc
on each worker node and remove the --hostname-override argument from the
KUBELET_SYSTEM_PODS_ARGS variable.
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.10
text: "Ensure that the --event-qps argument is set to 0 (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.eventRecordQPS}"
compare:
op: eq
value: 0
set: true
remediation: |
If using a Kubelet config file, edit the file to set eventRecordQPS: 0 .
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
--event-qps=0
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.11
text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Scored)"
audit: "cat $kubeletconf"
tests:
bin_op: and
test_items:
- path: "{.tlsCertFile}"
set: true
- path: "{.tlsPrivateKeyFile}"
set: true
remediation: |
If using a Kubelet config file, edit the file to set tlsCertFile to the location of the certificate
file to use to identify this Kubelet, and tlsPrivateKeyFile to the location of the
corresponding private key file.
If using command line arguments, edit the kubelet service file
$kubeletsvc on each worker node and
set the below parameters in KUBELET_CERTIFICATE_ARGS variable.
--tls-cert-file=<path/to/tls-certificate-file>
file=<path/to/tls-key-file>
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.12
text: "Ensure that the --cadvisor-port argument is set to 0 (Scored)"
audit: "cat $kubeletconf"
tests:
bin_op: or
test_items:
- path: "{.cadvisorPort}"
compare:
op: eq
value: 0
set: true
- path: "{.cadvisorPort}"
set: false
remediation: |
Edit the kubelet service file $kubeletsvc
on each worker node and set the below parameter in KUBELET_CADVISOR_ARGS variable.
--cadvisor-port=0
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.13
text: "Ensure that the --rotate-certificates argument is not set to false (Scored)"
audit: "cat $kubeletconf"
tests:
bin_op: or
test_items:
- path: "{.rotateCertificates}"
set: false
- path: "{.rotateCertificates}"
compare:
op: noteq
value: "false"
set: true
remediation: |
If using a Kubelet config file, edit the file to add the line rotateCertificates: true.
If using command line arguments, edit the kubelet service file $kubeletsvc
on each worker node and add --rotate-certificates=true argument to the KUBELET_CERTIFICATE_ARGS variable.
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.14
text: "Ensure that the RotateKubeletServerCertificate argument is set to true (Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.featureGates.RotateKubeletServerCertificate}"
compare:
op: eq
value: true
set: true
remediation: |
Edit the kubelet service file $kubeletsvc
on each worker node and set the below parameter in KUBELET_CERTIFICATE_ARGS variable.
--feature-gates=RotateKubeletServerCertificate=true
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service
scored: true
- id: 2.1.15
text: "Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers (Not Scored)"
audit: "cat $kubeletconf"
tests:
test_items:
- path: "{.tlsCipherSuites}"
compare:
op: eq
value: "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"
set: true
remediation: |
If using a Kubelet config file, edit the file to set TLSCipherSuites: to 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
If using executable arguments, edit the kubelet service file $kubeletconf on each worker node and set the below parameter.
--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
scored: false
- id: 2.2
text: "Configuration Files"
checks:
- id: 2.2.1
text: "Ensure that the kubelet.conf file permissions are set to 644 or
more restrictive (Scored)"
audit: "/bin/sh -c 'if test -e $kubeletkubeconfig; then stat -c %a $kubeletkubeconfig; fi'"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command (based on the file location on your system) on the each worker
node. For example,
chmod 644 $kubeletkubeconfig
scored: true
- id: 2.2.2
text: "Ensure that the kubelet.conf file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $kubeletkubeconfig; then stat -c %U:%G $kubeletkubeconfig; fi'"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command (based on the file location on your system) on the each worker
node. For example,
chown root:root $kubeletkubeconfig
scored: true
- id: 2.2.3
text: "Ensure that the kubelet service file permissions are set to 644 or
more restrictive (Scored)"
audit: "/bin/sh -c 'if test -e $kubeletsvc; then stat -c %a $kubeletsvc; fi'"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: 644
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command (based on the file location on your system) on the each worker
node. For example,
chmod 755 $kubeletsvc
scored: true
- id: 2.2.4
text: "Ensure that the kubelet service file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $kubeletsvc; then stat -c %U:%G $kubeletsvc; fi'"
tests:
test_items:
- flag: "root:root"
set: true
remediation: |
Run the below command (based on the file location on your system) on the each worker
node. For example,
chown root:root $kubeletsvc
scored: true
- id: 2.2.5
text: "Ensure that the proxy kubeconfig file permissions are set to 644 or more restrictive (Scored)"
audit: "/bin/sh -c 'if test -e $proxykubeconfig; then stat -c %a $proxykubeconfig; fi'"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command (based on the file location on your system) on the each worker
node. For example,
chmod 644 $proxykubeconfig
scored: true
- id: 2.2.6
text: "Ensure that the proxy kubeconfig file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $proxykubeconfig; then stat -c %U:%G $proxykubeconfig; fi'"
tests:
test_items:
- flag: "root:root"
set: true
remediation: |
Run the below command (based on the file location on your system) on the each worker
node. For example,
chown root:root $proxykubeconfig
scored: true
- id: 2.2.7
text: "Ensure that the certificate authorities file permissions are set to
644 or more restrictive (Scored)"
type: manual
remediation: |
Run the following command to modify the file permissions of the --client-ca-file
chmod 644 <filename>
scored: true
- id: 2.2.8
text: "Ensure that the client certificate authorities file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $ca-file; then stat -c %U:%G $ca-file; fi'"
type: manual
remediation: |
Run the following command to modify the ownership of the --client-ca-file .
chown root:root <filename>
scored: true
- id: 2.2.9
text: "Ensure that the kubelet configuration file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $kubeletconf; then stat -c %U:%G $kubeletconf; fi'"
tests:
test_items:
- flag: "root:root"
set: true
remediation: |
Run the following command (using the config file location identied in the Audit step)
chown root:root $kubeletconf
scored: true
- id: 2.2.10
text: "Ensure that the kubelet configuration file has permissions set to 644 or more restrictive (Scored)"
audit: "/bin/sh -c 'if test -e $kubeletconf; then stat -c %a $kubeletconf; fi'"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the following command (using the config file location identied in the Audit step)
chmod 644 $kubeletconf
scored: true

View File

@@ -1201,8 +1201,8 @@ groups:
test_items:
- flag: "--client-cert-auth"
compare:
op: eq
value: true
op: noteq
value: false
set: true
remediation: |
Edit the etcd pod specification file $etcdconf on the master

View File

@@ -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:

File diff suppressed because it is too large Load Diff

View File

@@ -1,376 +1,376 @@
---
controls:
id: 2
text: "Worker Node Security Configuration"
type: "node"
groups:
- id: 2.1
text: "Kubelet"
checks:
- id: 2.1.1
text: "Ensure that the --allow-privileged argument is set to false (Scored)"
type: "skip"
scored: true
- id: 2.1.2
text: "Ensure that the --anonymous-auth argument is set to false (Scored)"
type: "skip"
scored: true
- id: 2.1.3
text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)"
audit: "grep -A1 authorization-mode /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "authorization-mode"
set: false
- flag: "authorization-mode: Webhook"
compare:
op: has
value: "Webhook"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and remove authorization-mode under
kubeletArguments in /etc/origin/node/node-config.yaml or set it to "Webhook".
scored: true
- id: 2.1.4
text: "Ensure that the --client-ca-file argument is set as appropriate (Scored)"
audit: "grep -A1 client-ca-file /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "client-ca-file"
set: false
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and remove any configuration returned by the following:
grep -A1 client-ca-file /etc/origin/node/node-config.yaml
Reset to the OpenShift default.
See https://github.com/openshift/openshift-ansible/blob/release-3.10/roles/openshift_node_group/templates/node-config.yaml.j2#L65
The config file does not have this defined in kubeletArgument, but in PodManifestConfig.
scored: true
- id: 2.1.5
text: "Ensure that the --read-only-port argument is set to 0 (Scored)"
audit: "grep -A1 read-only-port /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "read-only-port"
set: false
- flag: "read-only-port: 0"
compare:
op: has
value: "0"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and removed so that the OpenShift default is applied.
scored: true
- id: 2.1.6
text: "Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Scored)"
audit: "grep -A1 streaming-connection-idle-timeout /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "streaming-connection-idle-timeout"
set: false
- flag: "0"
set: false
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and set the streaming-connection-timeout
value like the following in node-config.yaml.
kubeletArguments:
 streaming-connection-idle-timeout:
   - "5m"
scored: true
- id: 2.1.7
text: "Ensure that the --protect-kernel-defaults argument is set to true (Scored)"
type: "skip"
scored: true
- id: 2.1.8
text: "Ensure that the --make-iptables-util-chains argument is set to true (Scored)"
audit: "grep -A1 make-iptables-util-chains /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "make-iptables-util-chains"
set: false
- flag: "make-iptables-util-chains: true"
compare:
op: has
value: "true"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and reset make-iptables-util-chains to the OpenShift
default value of true.
scored: true
id: 2.1.9
text: "Ensure that the --keep-terminated-pod-volumeskeep-terminated-pod-volumes argument is set to false (Scored)"
audit: "grep -A1 keep-terminated-pod-volumes /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "keep-terminated-pod-volumes: false"
compare:
op: has
value: "false"
set: true
remediation: |
Reset to the OpenShift defaults
scored: true
- id: 2.1.10
text: "Ensure that the --hostname-override argument is not set (Scored)"
type: "skip"
scored: true
- id: 2.1.11
text: "Ensure that the --event-qps argument is set to 0 (Scored)"
audit: "grep -A1 event-qps /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "event-qps"
set: false
- flag: "event-qps: 0"
compare:
op: has
value: "0"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml set the event-qps argument to 0 in
the kubeletArguments section of.
scored: true
- id: 2.1.12
text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Scored)"
audit: "grep -A1 cert-dir /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "/etc/origin/node/certificates"
compare:
op: has
value: "/etc/origin/node/certificates"
set: true
remediation: |
Reset to the OpenShift default values.
scored: true
- id: 2.1.13
text: "Ensure that the --cadvisor-port argument is set to 0 (Scored)"
audit: "grep -A1 cadvisor-port /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "cadvisor-port"
set: false
- flag: "cadvisor-port: 0"
compare:
op: has
value: "0"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and remove the cadvisor-port flag
if it is set in the kubeletArguments section.
scored: true
- id: 2.1.14
text: "Ensure that the RotateKubeletClientCertificate argument is not set to false (Scored)"
audit: "grep -B1 RotateKubeletClientCertificate=true /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "RotateKubeletClientCertificate=true"
compare:
op: has
value: "true"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and set RotateKubeletClientCertificate to true.
scored: true
- id: 2.1.15
text: "Ensure that the RotateKubeletServerCertificate argument is set to true (Scored)"
audit: "grep -B1 RotateKubeletServerCertificate=true /etc/origin/node/node-config.yaml"
test:
test_items:
- flag: "RotateKubeletServerCertificate=true"
compare:
op: has
value: "true"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and set RotateKubeletServerCertificate to true.
scored: true
- id: 2.2
text: "Configuration Files"
checks:
- id: 2.2.1
text: "Ensure that the kubelet.conf file permissions are set to 644 or more restrictive (Scored)"
audit: "stat -c %a /etc/origin/node/node.kubeconfig"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/origin/node/node.kubeconfig
scored: true
- id: 2.2.2
text: "Ensure that the kubelet.conf file ownership is set to root:root (Scored)"
audit: "stat -c %U:%G /etc/origin/node/node.kubeconfig"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/origin/node/node.kubeconfig
scored: true
- id: 2.2.3
text: "Ensure that the kubelet service file permissions are set to 644 or more restrictive (Scored)"
audit: "stat -c %a /etc/systemd/system/atomic-openshift-node.service"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/systemd/system/atomic-openshift-node.service
scored: true
- id: 2.2.4
text: "Ensure that the kubelet service file ownership is set to root:root (Scored)"
audit: "stat -c %U:%G /etc/systemd/system/atomic-openshift-node.service"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/systemd/system/atomic-openshift-node.service
scored: true
- id: 2.2.5
text: "Ensure that the proxy kubeconfig file permissions are set to 644 or more restrictive (Scored)"
audit: "stat -c %a /etc/origin/node/node.kubeconfig"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/origin/node/node.kubeconfig
scored: true
- id: 2.2.6
text: "Ensure that the proxy kubeconfig file ownership is set to root:root (Scored)"
audit: "stat -c %U:%G /etc/origin/node/node.kubeconfig"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/origin/node/node.kubeconfig
scored: true
- id: 2.2.7
text: "Ensure that the certificate authorities file permissions are set to 644 or more restrictive (Scored)"
audit: "stat -c %a /etc/origin/node/client-ca.crt"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/origin/node/client-ca.crt
scored: true
- id: 2.2.8
text: "Ensure that the client certificate authorities file ownership is set to root:root (Scored)"
audit: "stat -c %U:%G /etc/origin/node/client-ca.crt"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/origin/node/client-ca.crt
scored: true
---
controls:
id: 2
text: "Worker Node Security Configuration"
type: "node"
groups:
- id: 7
text: "Kubelet"
checks:
- id: 7.1
text: "Use Security Context Constraints to manage privileged containers as needed"
type: "skip"
scored: true
- id: 7.2
text: "Ensure anonymous-auth is not disabled"
type: "skip"
scored: true
- id: 7.3
text: "Verify that the --authorization-mode argument is set to WebHook"
audit: "grep -A1 authorization-mode /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "authorization-mode"
set: false
- flag: "authorization-mode: Webhook"
compare:
op: has
value: "Webhook"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and remove authorization-mode under
kubeletArguments in /etc/origin/node/node-config.yaml or set it to "Webhook".
scored: true
- id: 7.4
text: "Verify the OpenShift default for the client-ca-file argument"
audit: "grep -A1 client-ca-file /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "client-ca-file"
set: false
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and remove any configuration returned by the following:
grep -A1 client-ca-file /etc/origin/node/node-config.yaml
Reset to the OpenShift default.
See https://github.com/openshift/openshift-ansible/blob/release-3.10/roles/openshift_node_group/templates/node-config.yaml.j2#L65
The config file does not have this defined in kubeletArgument, but in PodManifestConfig.
scored: true
- id: 7.5
text: "Verify the OpenShift default setting for the read-only-port argument"
audit: "grep -A1 read-only-port /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "read-only-port"
set: false
- flag: "read-only-port: 0"
compare:
op: has
value: "0"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and removed so that the OpenShift default is applied.
scored: true
- id: 7.6
text: "Adjust the streaming-connection-idle-timeout argument"
audit: "grep -A1 streaming-connection-idle-timeout /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "streaming-connection-idle-timeout"
set: false
- flag: "5m"
set: false
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and set the streaming-connection-timeout
value like the following in node-config.yaml.
kubeletArguments:
 streaming-connection-idle-timeout:
   - "5m"
scored: true
- id: 7.7
text: "Verify the OpenShift defaults for the protect-kernel-defaults argument"
type: "skip"
scored: true
- id: 7.8
text: "Verify the OpenShift default value of true for the make-iptables-util-chains argument"
audit: "grep -A1 make-iptables-util-chains /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "make-iptables-util-chains"
set: false
- flag: "make-iptables-util-chains: true"
compare:
op: has
value: "true"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and reset make-iptables-util-chains to the OpenShift
default value of true.
scored: true
- id: 7.9
text: "Verify that the --keep-terminated-pod-volumes argument is set to false"
audit: "grep -A1 keep-terminated-pod-volumes /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "keep-terminated-pod-volumes: false"
compare:
op: has
value: "false"
set: true
remediation: |
Reset to the OpenShift defaults
scored: true
- id: 7.10
text: "Verify the OpenShift defaults for the hostname-override argument"
type: "skip"
scored: true
- id: 7.11
text: "Set the --event-qps argument to 0"
audit: "grep -A1 event-qps /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "event-qps"
set: false
- flag: "event-qps: 0"
compare:
op: has
value: "0"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml set the event-qps argument to 0 in
the kubeletArguments section of.
scored: true
- id: 7.12
text: "Verify the OpenShift cert-dir flag for HTTPS traffic"
audit: "grep -A1 cert-dir /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "/etc/origin/node/certificates"
compare:
op: has
value: "/etc/origin/node/certificates"
set: true
remediation: |
Reset to the OpenShift default values.
scored: true
- id: 7.13
text: "Verify the OpenShift default of 0 for the cadvisor-port argument"
audit: "grep -A1 cadvisor-port /etc/origin/node/node-config.yaml"
tests:
bin_op: or
test_items:
- flag: "cadvisor-port"
set: false
- flag: "cadvisor-port: 0"
compare:
op: has
value: "0"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and remove the cadvisor-port flag
if it is set in the kubeletArguments section.
scored: true
- id: 7.14
text: "Verify that the RotateKubeletClientCertificate argument is set to true"
audit: "grep -B1 RotateKubeletClientCertificate=true /etc/origin/node/node-config.yaml"
tests:
test_items:
- flag: "RotateKubeletClientCertificate=true"
compare:
op: has
value: "true"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and set RotateKubeletClientCertificate to true.
scored: true
- id: 7.15
text: "Verify that the RotateKubeletServerCertificate argument is set to true"
audit: "grep -B1 RotateKubeletServerCertificate=true /etc/origin/node/node-config.yaml"
test:
test_items:
- flag: "RotateKubeletServerCertificate=true"
compare:
op: has
value: "true"
set: true
remediation: |
Edit the Openshift node config file /etc/origin/node/node-config.yaml and set RotateKubeletServerCertificate to true.
scored: true
- id: 8
text: "Configuration Files"
checks:
- id: 8.1
text: "Verify the OpenShift default permissions for the kubelet.conf file"
audit: "stat -c %a /etc/origin/node/node.kubeconfig"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/origin/node/node.kubeconfig
scored: true
- id: 8.2
text: "Verify the kubeconfig file ownership of root:root"
audit: "stat -c %U:%G /etc/origin/node/node.kubeconfig"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/origin/node/node.kubeconfig
scored: true
- id: 8.3
text: "Verify the kubelet service file permissions of 644"
audit: "stat -c %a /etc/systemd/system/atomic-openshift-node.service"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/systemd/system/atomic-openshift-node.service
scored: true
- id: 8.4
text: "Verify the kubelet service file ownership of root:root"
audit: "stat -c %U:%G /etc/systemd/system/atomic-openshift-node.service"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/systemd/system/atomic-openshift-node.service
scored: true
- id: 8.5
text: "Verify the OpenShift default permissions for the proxy kubeconfig file"
audit: "stat -c %a /etc/origin/node/node.kubeconfig"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/origin/node/node.kubeconfig
scored: true
- id: 8.6
text: "Verify the proxy kubeconfig file ownership of root:root"
audit: "stat -c %U:%G /etc/origin/node/node.kubeconfig"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/origin/node/node.kubeconfig
scored: true
- id: 8.7
text: "Verify the OpenShift default permissions for the certificate authorities file."
audit: "stat -c %a /etc/origin/node/client-ca.crt"
tests:
bin_op: or
test_items:
- flag: "644"
compare:
op: eq
value: "644"
set: true
- flag: "640"
compare:
op: eq
value: "640"
set: true
- flag: "600"
compare:
op: eq
value: "600"
set: true
remediation: |
Run the below command on each worker node.
chmod 644 /etc/origin/node/client-ca.crt
scored: true
- id: 8.8
text: "Verify the client certificate authorities file ownership of root:root"
audit: "stat -c %U:%G /etc/origin/node/client-ca.crt"
tests:
test_items:
- flag: "root:root"
compare:
op: eq
value: root:root
set: true
remediation: |
Run the below command on each worker node.
chown root:root /etc/origin/node/client-ca.crt
scored: true

View File

@@ -36,11 +36,11 @@ const (
// PASS check passed.
PASS State = "PASS"
// FAIL check failed.
FAIL = "FAIL"
FAIL State = "FAIL"
// WARN could not carry out check.
WARN = "WARN"
WARN State = "WARN"
// INFO informational message
INFO = "INFO"
INFO State = "INFO"
// MASTER a master node
MASTER NodeType = "master"
@@ -74,20 +74,37 @@ type Check struct {
Scored bool `json:"scored"`
}
// Runner wraps the basic Run method.
type Runner interface {
// Run runs a given check and returns the execution state.
Run(c *Check) State
}
// NewRunner constructs a default Runner.
func NewRunner() Runner {
return &defaultRunner{}
}
type defaultRunner struct{}
func (r *defaultRunner) Run(c *Check) State {
return c.run()
}
// Run executes the audit commands specified in a check and outputs
// the results.
func (c *Check) Run() {
func (c *Check) run() State {
// If check type is skip, force result to INFO
if c.Type == "skip" {
c.State = INFO
return
return c.State
}
// If check type is manual or the check is not scored, force result to WARN
if c.Type == "manual" || !c.Scored {
c.State = WARN
return
return c.State
}
var out bytes.Buffer
@@ -97,7 +114,7 @@ func (c *Check) Run() {
for _, cmd := range c.Commands {
if !isShellCommand(cmd.Path) {
c.State = WARN
return
return c.State
}
}
@@ -106,7 +123,7 @@ func (c *Check) Run() {
if n == 0 {
// Likely a warning message.
c.State = WARN
return
return c.State
}
// Each command runs,
@@ -166,6 +183,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
@@ -186,6 +205,7 @@ func (c *Check) Run() {
if errmsgs != "" {
glog.V(2).Info(errmsgs)
}
return c.State
}
// textToCommand transforms an input text representation of commands to be

View File

@@ -1,3 +1,17 @@
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package check
import (
@@ -21,7 +35,7 @@ func TestCheck_Run(t *testing.T) {
for _, testCase := range testCases {
testCase.check.Run()
testCase.check.run()
if testCase.check.State != testCase.Expected {
t.Errorf("test failed, expected %s, actual %s\n", testCase.Expected, testCase.check.State)

View File

@@ -17,8 +17,8 @@ package check
import (
"encoding/json"
"fmt"
yaml "gopkg.in/yaml.v2"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
)
// Controls holds all controls to check for master nodes.
@@ -50,6 +50,9 @@ type Summary struct {
Info int `json:"total_info"`
}
// Predicate a predicate on the given Group and Check arguments.
type Predicate func(group *Group, check *Check) bool
// NewControls instantiates a new master Controls object.
func NewControls(t NodeType, in []byte) (*Controls, error) {
c := new(Controls)
@@ -73,76 +76,44 @@ func NewControls(t NodeType, in []byte) (*Controls, error) {
return c, nil
}
// RunGroup runs all checks in a group.
func (controls *Controls) RunGroup(gids ...string) Summary {
g := []*Group{}
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 0, 0, 0
// If no groupid is passed run all group checks.
if len(gids) == 0 {
gids = controls.getAllGroupIDs()
}
for _, group := range controls.Groups {
for _, gid := range gids {
if gid == group.ID {
for _, check := range group.Checks {
check.Run()
check.TestInfo = append(check.TestInfo, check.Remediation)
summarize(controls, check)
summarizeGroup(group, check)
}
g = append(g, group)
}
}
}
controls.Groups = g
return controls.Summary
}
// RunChecks runs the checks with the supplied IDs.
func (controls *Controls) RunChecks(ids ...string) Summary {
g := []*Group{}
// RunChecks runs the checks with the given Runner. Only checks for which the filter Predicate returns `true` will run.
func (controls *Controls) RunChecks(runner Runner, filter Predicate) Summary {
var g []*Group
m := make(map[string]*Group)
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 0, 0, 0
// If no groupid is passed run all group checks.
if len(ids) == 0 {
ids = controls.getAllCheckIDs()
}
for _, group := range controls.Groups {
for _, check := range group.Checks {
for _, id := range ids {
if id == check.ID {
check.Run()
check.TestInfo = append(check.TestInfo, check.Remediation)
summarize(controls, check)
// Check if we have already added this checks group.
if v, ok := m[group.ID]; !ok {
// Create a group with same info
w := &Group{
ID: group.ID,
Text: group.Text,
Checks: []*Check{},
}
// Add this check to the new group
w.Checks = append(w.Checks, check)
// Add to groups we have visited.
m[w.ID] = w
g = append(g, w)
} else {
v.Checks = append(v.Checks, check)
}
}
if !filter(group, check) {
continue
}
state := runner.Run(check)
check.TestInfo = append(check.TestInfo, check.Remediation)
// Check if we have already added this checks group.
if v, ok := m[group.ID]; !ok {
// Create a group with same info
w := &Group{
ID: group.ID,
Text: group.Text,
Checks: []*Check{},
}
// Add this check to the new group
w.Checks = append(w.Checks, check)
summarizeGroup(w, state)
// Add to groups we have visited.
m[w.ID] = w
g = append(g, w)
} else {
v.Checks = append(v.Checks, check)
summarizeGroup(v, state)
}
summarize(controls, state)
}
}
@@ -155,29 +126,8 @@ func (controls *Controls) JSON() ([]byte, error) {
return json.Marshal(controls)
}
func (controls *Controls) getAllGroupIDs() []string {
var ids []string
for _, group := range controls.Groups {
ids = append(ids, group.ID)
}
return ids
}
func (controls *Controls) getAllCheckIDs() []string {
var ids []string
for _, group := range controls.Groups {
for _, check := range group.Checks {
ids = append(ids, check.ID)
}
}
return ids
}
func summarize(controls *Controls, check *Check) {
switch check.State {
func summarize(controls *Controls, state State) {
switch state {
case PASS:
controls.Summary.Pass++
case FAIL:
@@ -186,11 +136,13 @@ func summarize(controls *Controls, check *Check) {
controls.Summary.Warn++
case INFO:
controls.Summary.Info++
default:
glog.Warningf("Unrecognized state %s", state)
}
}
func summarizeGroup(group *Group, check *Check) {
switch check.State {
func summarizeGroup(group *Group, state State) {
switch state {
case PASS:
group.Pass++
case FAIL:
@@ -199,5 +151,7 @@ func summarizeGroup(group *Group, check *Check) {
group.Warn++
case INFO:
group.Info++
default:
glog.Warningf("Unrecognized state %s", state)
}
}

View File

@@ -1,41 +1,151 @@
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package check
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
yaml "gopkg.in/yaml.v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"gopkg.in/yaml.v2"
)
const cfgDir = "../cfg/"
type mockRunner struct {
mock.Mock
}
func (m *mockRunner) Run(c *Check) State {
args := m.Called(c)
return args.Get(0).(State)
}
// validate that the files we're shipping are valid YAML
func TestYamlFiles(t *testing.T) {
// TODO: make this list dynamic
dirs := []string{"1.6/", "1.7/"}
for _, dir := range dirs {
dir = cfgDir + dir
files, err := ioutil.ReadDir(dir)
err := filepath.Walk(cfgDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
t.Fatalf("error reading %s directory: %v", dir, err)
t.Fatalf("failure accessing path %q: %v\n", path, err)
}
for _, file := range files {
fileName := file.Name()
in, err := ioutil.ReadFile(dir + fileName)
if !info.IsDir() {
t.Logf("reading file: %s", path)
in, err := ioutil.ReadFile(path)
if err != nil {
t.Fatalf("error opening file %s: %v", fileName, err)
t.Fatalf("error opening file %s: %v", path, err)
}
c := new(Controls)
err = yaml.Unmarshal(in, c)
if err != nil {
t.Fatalf("failed to load YAML from %s: %v", fileName, err)
if err == nil {
t.Logf("YAML file successfully unmarshalled: %s", path)
} else {
t.Fatalf("failed to load YAML from %s: %v", path, err)
}
}
return nil
})
if err != nil {
t.Fatalf("failure walking cfg dir: %v\n", err)
}
}
func TestNewControls(t *testing.T) {
t.Run("Should return error when node type is not specified", func(t *testing.T) {
// given
in := []byte(`
---
controls:
type: # not specified
groups:
`)
// when
_, err := NewControls(MASTER, in)
// then
assert.EqualError(t, err, "non-master controls file specified")
})
t.Run("Should return error when input YAML is invalid", func(t *testing.T) {
// given
in := []byte("BOOM")
// when
_, err := NewControls(MASTER, in)
// then
assert.EqualError(t, err, "failed to unmarshal YAML: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `BOOM` into check.Controls")
})
}
func TestControls_RunChecks(t *testing.T) {
t.Run("Should run checks matching the filter and update summaries", func(t *testing.T) {
// given
runner := new(mockRunner)
// and
in := []byte(`
---
type: "master"
groups:
- id: G1
checks:
- id: G1/C1
- id: G2
checks:
- id: G2/C1
`)
// and
controls, _ := NewControls(MASTER, in)
// and
runner.On("Run", controls.Groups[0].Checks[0]).Return(PASS)
runner.On("Run", controls.Groups[1].Checks[0]).Return(FAIL)
// and
var runAll Predicate = func(group *Group, c *Check) bool {
return true
}
// when
controls.RunChecks(runner, runAll)
// then
assert.Equal(t, 2, len(controls.Groups))
// and
G1 := controls.Groups[0]
assert.Equal(t, "G1", G1.ID)
assert.Equal(t, "G1/C1", G1.Checks[0].ID)
assertEqualGroupSummary(t, 1, 0, 0, 0, G1)
// and
G2 := controls.Groups[1]
assert.Equal(t, "G2", G2.ID)
assert.Equal(t, "G2/C1", G2.Checks[0].ID)
assertEqualGroupSummary(t, 0, 1, 0, 0, G2)
// and
assert.Equal(t, 1, controls.Summary.Pass)
assert.Equal(t, 1, controls.Summary.Fail)
assert.Equal(t, 0, controls.Summary.Info)
assert.Equal(t, 0, controls.Summary.Warn)
// and
runner.AssertExpectations(t)
})
}
func assertEqualGroupSummary(t *testing.T, pass, fail, info, warn int, actual *Group) {
t.Helper()
assert.Equal(t, pass, actual.Pass)
assert.Equal(t, fail, actual.Fail)
assert.Equal(t, info, actual.Info)
assert.Equal(t, warn, actual.Warn)
}

View File

@@ -157,4 +157,143 @@ groups:
value: Something
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:
- path: "{.readOnlyPort}"
compare:
op: eq
value: 15000
set: true
- path: "{.readOnlyPort}"
compare:
op: gte
value: 15000
set: true
- path: "{.readOnlyPort}"
compare:
op: lte
value: 15000
set: true
- id: 16
text: "jsonpath correct case-sensitive value on string field"
tests:
test_items:
- path: "{.stringValue}"
compare:
op: noteq
value: "None"
set: true
- path: "{.stringValue}"
compare:
op: noteq
value: "webhook,Something,RBAC"
set: true
- path: "{.stringValue}"
compare:
op: eq
value: "WebHook,Something,RBAC"
set: true
- id: 17
text: "jsonpath correct value on boolean field"
tests:
test_items:
- path: "{.trueValue}"
compare:
op: noteq
value: somethingElse
set: true
- path: "{.trueValue}"
compare:
op: noteq
value: false
set: true
- path: "{.trueValue}"
compare:
op: eq
value: true
set: true
- id: 18
text: "jsonpath field absent"
tests:
test_items:
- path: "{.notARealField}"
set: false
- id: 19
text: "jsonpath correct value on nested field"
tests:
test_items:
- path: "{.authentication.anonymous.enabled}"
compare:
op: eq
value: "false"
set: true
- id: 20
text: "yamlpath correct value on field"
tests:
test_items:
- path: "{.readOnlyPort}"
compare:
op: gt
value: 14999
set: true
- id: 21
text: "yamlpath field absent"
tests:
test_items:
- path: "{.fieldThatIsUnset}"
set: false
- id: 22
text: "yamlpath correct value on nested field"
tests:
test_items:
- path: "{.authentication.anonymous.enabled}"
compare:
op: eq
value: "false"
set: true
- id: 23
text: "path on invalid json"
tests:
test_items:
- path: "{.authentication.anonymous.enabled}"
compare:
op: eq
value: "false"
set: true
- id: 24
text: "path with broken expression"
tests:
test_items:
- path: "{.missingClosingBrace"
set: true
- id: 25
text: "yamlpath on invalid yaml"
tests:
test_items:
- path: "{.authentication.anonymous.enabled}"
compare:
op: eq
value: "false"
set: true

View File

@@ -15,11 +15,16 @@
package check
import (
"bytes"
"encoding/json"
"fmt"
"os"
"regexp"
"strconv"
"strings"
yaml "gopkg.in/yaml.v2"
"k8s.io/client-go/util/jsonpath"
)
// test:
@@ -38,6 +43,7 @@ const (
type testItem struct {
Flag string
Path string
Output string
Value string
Set bool
@@ -54,33 +60,79 @@ type testOutput struct {
actualResult string
}
func failTestItem(s string) *testOutput {
return &testOutput{testResult: false, actualResult: s}
}
func (t *testItem) execute(s string) *testOutput {
result := &testOutput{}
match := strings.Contains(s, t.Flag)
var match bool
var flagVal string
if t.Flag != "" {
// Flag comparison: check if the flag is present in the input
match = strings.Contains(s, t.Flag)
} else {
// Path != "" - we don't know whether it's YAML or JSON but
// we can just try one then the other
buf := new(bytes.Buffer)
var jsonInterface interface{}
if t.Path != "" {
err := json.Unmarshal([]byte(s), &jsonInterface)
if err != nil {
err := yaml.Unmarshal([]byte(s), &jsonInterface)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load YAML or JSON from provided input \"%s\": %v\n", s, err)
return failTestItem("failed to load YAML or JSON")
}
}
}
// Parse the jsonpath/yamlpath expression...
j := jsonpath.New("jsonpath")
j.AllowMissingKeys(true)
err := j.Parse(t.Path)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to parse path expression \"%s\": %v\n", t.Path, err)
return failTestItem("unable to parse path expression")
}
err = j.Execute(buf, jsonInterface)
if err != nil {
fmt.Fprintf(os.Stderr, "error executing path expression \"%s\": %v\n", t.Path, err)
return failTestItem("error executing path expression")
}
jsonpathResult := fmt.Sprintf("%s", buf)
match = (jsonpathResult != "")
flagVal = jsonpathResult
}
if t.Set {
var flagVal string
isset := match
if isset && t.Compare.Op != "" {
// Expects flags in the form;
// --flag=somevalue
// --flag
// somevalue
//pttn := `(` + t.Flag + `)(=)*([^\s,]*) *`
pttn := `(` + t.Flag + `)(=)*([^\s]*) *`
flagRe := regexp.MustCompile(pttn)
vals := flagRe.FindStringSubmatch(s)
if t.Flag != "" {
// Expects flags in the form;
// --flag=somevalue
// flag: somevalue
// --flag
// somevalue
pttn := `(` + t.Flag + `)(=|: *)*([^\s]*) *`
flagRe := regexp.MustCompile(pttn)
vals := flagRe.FindStringSubmatch(s)
if len(vals) > 0 {
if vals[3] != "" {
flagVal = vals[3]
if len(vals) > 0 {
if vals[3] != "" {
flagVal = vals[3]
} else {
flagVal = vals[1]
}
} else {
flagVal = vals[1]
fmt.Fprintf(os.Stderr, "invalid flag in testitem definition")
os.Exit(1)
}
} else {
fmt.Fprintf(os.Stderr, "invalid flag in testitem definition")
os.Exit(1)
}
result.actualResult = strings.ToLower(flagVal)

View File

@@ -110,6 +110,48 @@ func TestTestExecute(t *testing.T) {
controls.Groups[0].Checks[13],
"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],
"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],
"{\"readOnlyPort\": 15000}",
},
{
controls.Groups[0].Checks[16],
"{\"stringValue\": \"WebHook,Something,RBAC\"}",
},
{
controls.Groups[0].Checks[17],
"{\"trueValue\": true}",
},
{
controls.Groups[0].Checks[18],
"{\"readOnlyPort\": 15000}",
},
{
controls.Groups[0].Checks[19],
"{\"authentication\": { \"anonymous\": {\"enabled\": false}}}",
},
{
controls.Groups[0].Checks[20],
"readOnlyPort: 15000",
},
{
controls.Groups[0].Checks[21],
"readOnlyPort: 15000",
},
{
controls.Groups[0].Checks[22],
"authentication:\n anonymous:\n enabled: false",
},
}
for _, c := range cases {
@@ -119,3 +161,31 @@ func TestTestExecute(t *testing.T) {
}
}
}
func TestTestExecuteExceptions(t *testing.T) {
cases := []struct {
*Check
str string
}{
{
controls.Groups[0].Checks[23],
"this is not valid json {} at all",
},
{
controls.Groups[0].Checks[24],
"{\"key\": \"value\"}",
},
{
controls.Groups[0].Checks[25],
"broken } yaml\nenabled: true",
},
}
for _, c := range cases {
res := c.Tests.execute(c.str).testResult
if res {
t.Errorf("%s, expected:%v, got:%v\n", c.Text, false, res)
}
}
}

View File

@@ -25,9 +25,40 @@ import (
"github.com/spf13/viper"
)
var (
errmsgs string
)
// NewRunFilter constructs a Predicate based on FilterOpts which determines whether tested Checks should be run or not.
func NewRunFilter(opts FilterOpts) (check.Predicate, error) {
if opts.CheckList != "" && opts.GroupList != "" {
return nil, fmt.Errorf("group option and check option can't be used together")
}
var groupIDs map[string]bool
if opts.GroupList != "" {
groupIDs = cleanIDs(opts.GroupList)
}
var checkIDs map[string]bool
if opts.CheckList != "" {
checkIDs = cleanIDs(opts.CheckList)
}
return func(g *check.Group, c *check.Check) bool {
var test = true
if len(groupIDs) > 0 {
_, ok := groupIDs[g.ID]
test = test && ok
}
if len(checkIDs) > 0 {
_, ok := checkIDs[c.ID]
test = test && ok
}
test = test && (opts.Scored && c.Scored || opts.Unscored && !c.Scored)
return test
}, nil
}
func runChecks(nodetype check.NodeType) {
var summary check.Summary
@@ -40,7 +71,7 @@ func runChecks(nodetype check.NodeType) {
glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def))
// Get the set of exectuables and config files we care about on this type of node.
// Get the set of executables and config files we care about on this type of node.
typeConf := viper.Sub(string(nodetype))
binmap, err := getBinaries(typeConf)
@@ -65,18 +96,14 @@ func runChecks(nodetype check.NodeType) {
exitWithError(fmt.Errorf("error setting up %s controls: %v", nodetype, err))
}
if groupList != "" && checkList == "" {
ids := cleanIDs(groupList)
summary = controls.RunGroup(ids...)
} else if checkList != "" && groupList == "" {
ids := cleanIDs(checkList)
summary = controls.RunChecks(ids...)
} else if checkList != "" && groupList != "" {
exitWithError(fmt.Errorf("group option and check option can't be used together"))
} else {
summary = controls.RunGroup()
runner := check.NewRunner()
filter, err := NewRunFilter(filterOpts)
if err != nil {
exitWithError(fmt.Errorf("error setting up run filter: %v", err))
}
summary = controls.RunChecks(runner, filter)
// if we successfully ran some tests and it's json format, ignore the warnings
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0 || summary.Info > 0) && jsonFmt {
out, err := controls.JSON()
@@ -199,12 +226,17 @@ func loadConfig(nodetype check.NodeType) string {
// isMaster verify if master components are running on the node.
func isMaster() bool {
_ = loadConfig(check.MASTER)
glog.V(2).Info("Checking if the current node is running master components")
masterConf := viper.Sub(string(check.MASTER))
if _, err := getBinaries(masterConf); err != nil {
components, err := getBinaries(masterConf)
if err != nil {
glog.V(2).Info(err)
return false
}
if len(components) == 0 {
glog.V(2).Info("No master binaries specified")
return false
}
return true
}

112
cmd/common_test.go Normal file
View File

@@ -0,0 +1,112 @@
// Copyright © 2017-2019 Aqua Security Software Ltd. <info@aquasec.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"github.com/aquasecurity/kube-bench/check"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNewRunFilter(t *testing.T) {
type TestCase struct {
Name string
FilterOpts FilterOpts
Group *check.Group
Check *check.Check
Expected bool
}
testCases := []TestCase{
{
Name: "Should return true when scored flag is enabled and check is scored",
FilterOpts: FilterOpts{Scored: true, Unscored: false},
Group: &check.Group{},
Check: &check.Check{Scored: true},
Expected: true,
},
{
Name: "Should return false when scored flag is enabled and check is not scored",
FilterOpts: FilterOpts{Scored: true, Unscored: false},
Group: &check.Group{},
Check: &check.Check{Scored: false},
Expected: false,
},
{
Name: "Should return true when unscored flag is enabled and check is not scored",
FilterOpts: FilterOpts{Scored: false, Unscored: true},
Group: &check.Group{},
Check: &check.Check{Scored: false},
Expected: true,
},
{
Name: "Should return false when unscored flag is enabled and check is scored",
FilterOpts: FilterOpts{Scored: false, Unscored: true},
Group: &check.Group{},
Check: &check.Check{Scored: true},
Expected: false,
},
{
Name: "Should return true when group flag contains group's ID",
FilterOpts: FilterOpts{Scored: true, Unscored: true, GroupList: "G1,G2,G3"},
Group: &check.Group{ID: "G2"},
Check: &check.Check{},
Expected: true,
},
{
Name: "Should return false when group flag doesn't contain group's ID",
FilterOpts: FilterOpts{GroupList: "G1,G3"},
Group: &check.Group{ID: "G2"},
Check: &check.Check{},
Expected: false,
},
{
Name: "Should return true when check flag contains check's ID",
FilterOpts: FilterOpts{Scored: true, Unscored: true, CheckList: "C1,C2,C3"},
Group: &check.Group{},
Check: &check.Check{ID: "C2"},
Expected: true,
},
{
Name: "Should return false when check flag doesn't contain check's ID",
FilterOpts: FilterOpts{CheckList: "C1,C3"},
Group: &check.Group{},
Check: &check.Check{ID: "C2"},
Expected: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
filter, _ := NewRunFilter(testCase.FilterOpts)
assert.Equal(t, testCase.Expected, filter(testCase.Group, testCase.Check))
})
}
t.Run("Should return error when both group and check flags are used", func(t *testing.T) {
// given
opts := FilterOpts{GroupList: "G1", CheckList: "C1"}
// when
_, err := NewRunFilter(opts)
// then
assert.EqualError(t, err, "group option and check option can't be used together")
})
}

View File

@@ -25,6 +25,13 @@ import (
"github.com/spf13/viper"
)
type FilterOpts struct {
CheckList string
GroupList string
Scored bool
Unscored bool
}
var (
envVarsPrefix = "KUBE_BENCH"
defaultKubeVersion = "1.6"
@@ -33,14 +40,13 @@ var (
cfgDir string
jsonFmt bool
pgSQL bool
checkList string
groupList string
masterFile = "master.yaml"
nodeFile = "node.yaml"
federatedFile string
noResults bool
noSummary bool
noRemediations bool
filterOpts FilterOpts
)
// RootCmd represents the base command when called without any subcommands
@@ -79,16 +85,18 @@ func init() {
RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section")
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
RootCmd.PersistentFlags().BoolVar(&filterOpts.Scored, "scored", true, "Run the scored CIS checks")
RootCmd.PersistentFlags().BoolVar(&filterOpts.Unscored, "unscored", true, "Run the unscored CIS checks")
RootCmd.PersistentFlags().StringVarP(
&checkList,
&filterOpts.CheckList,
"check",
"c",
"",
`A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"`,
)
RootCmd.PersistentFlags().StringVarP(
&groupList,
&filterOpts.GroupList,
"group",
"g",
"",

View File

@@ -50,15 +50,18 @@ func continueWithError(err error, msg string) string {
return ""
}
func cleanIDs(list string) []string {
func cleanIDs(list string) map[string]bool {
list = strings.Trim(list, ",")
ids := strings.Split(list, ",")
set := make(map[string]bool)
for _, id := range ids {
id = strings.Trim(id, " ")
set[id] = true
}
return ids
return set
}
// ps execs out to the ps command; it's separated into a function so we can write tests

34
job-eks.yaml Normal file
View File

@@ -0,0 +1,34 @@
apiVersion: batch/v1
kind: Job
metadata:
name: kube-bench
spec:
template:
spec:
hostPID: true
containers:
- name: kube-bench
# Push the image to your ECR and then refer to it here
image: <ID.dkr.ecr.region.amazonaws.com/aquasec/kube-bench:ref>
command: ["kube-bench", "--version", "1.11-json"]
volumeMounts:
- name: var-lib-kubelet
mountPath: /var/lib/kubelet
- name: etc-systemd
mountPath: /etc/systemd
- name: etc-kubernetes
mountPath: /etc/kubernetes
restartPolicy: Never
volumes:
- name: var-lib-kubelet
hostPath:
path: "/var/lib/kubelet"
- name: etc-systemd
hostPath:
path: "/etc/systemd"
- name: etc-kubernetes
hostPath:
path: "/etc/kubernetes"
- name: usr-bin
hostPath:
path: "/usr/bin"