mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2026-02-24 23:03:51 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c0761149d | ||
|
|
124647a05c | ||
|
|
42948d91ba | ||
|
|
f48ad5eb54 | ||
|
|
cf62def9fd | ||
|
|
a6a1ce945f | ||
|
|
20e7f0a433 | ||
|
|
af0eadc792 | ||
|
|
549adf23bd | ||
|
|
6b9f117f87 | ||
|
|
086bb629db | ||
|
|
e6f2b4d4fe | ||
|
|
34f8b8e980 | ||
|
|
86d49b1b1a | ||
|
|
aee2081d73 | ||
|
|
7c7d477d78 | ||
|
|
dddea28713 | ||
|
|
0933fa420b | ||
|
|
d2fa9d35b6 | ||
|
|
4e17e3b3d5 | ||
|
|
45cf25e007 | ||
|
|
96c469669c | ||
|
|
50cce99daf | ||
|
|
dee64c30ae | ||
|
|
0bbc867396 | ||
|
|
767e8eb835 | ||
|
|
9c07527069 | ||
|
|
c39516581b | ||
|
|
09ca739dc0 | ||
|
|
16fbf084e9 | ||
|
|
b5f4876138 | ||
|
|
ffeb33defd | ||
|
|
cf5f025593 | ||
|
|
2b4047a3c1 | ||
|
|
7bb66dd2da | ||
|
|
9c563b0987 | ||
|
|
29122b82ad | ||
|
|
43c1470c0e | ||
|
|
82c92e0078 | ||
|
|
1c58dfefbb | ||
|
|
b339a753b5 | ||
|
|
f88de572f6 |
@@ -105,3 +105,6 @@ These operations are:
|
||||
- `lte`: tests if the flag value is less than or equal to the compared value.
|
||||
- `has`: tests if the flag value contains the compared value.
|
||||
- `nothave`: tests if the flag value does not contain the compared value.
|
||||
|
||||
# Roadmap
|
||||
The tests are up-to-date with the CIS Benchmark 1.1.0, which refers to Kubernetes 1.7. Going forward we should release updates to kube-bench to reflect new releases of the Benchmark, which in turn we can anticipate being made for each new Kubernetes release.
|
||||
|
||||
@@ -29,7 +29,7 @@ installation:
|
||||
conf:
|
||||
apiserver: /etc/kubernetes/apiserver
|
||||
scheduler: /etc/kubernetes/scheduler
|
||||
controller-manager: /etc/kubernetes/apiserver
|
||||
controller-manager: /etc/kubernetes/controller-manager
|
||||
node:
|
||||
bin:
|
||||
kubelet: kubelet
|
||||
@@ -73,16 +73,16 @@ installation:
|
||||
scheduler: hyperkube scheduler
|
||||
controller-manager: hyperkube controller-manager
|
||||
conf:
|
||||
apiserver: /etc/kubernetes/apiserver
|
||||
scheduler: /etc/kubernetes/scheduler
|
||||
controller-manager: /etc/kubernetes/apiserver
|
||||
apiserver: /etc/kubernetes/manifests/kube-apiserver.yaml
|
||||
scheduler: /etc/kubernetes/manifests/kube-scheduler.yaml
|
||||
controller-manager: /etc/kubernetes/manifests/kube-controller-manager.yaml
|
||||
node:
|
||||
bin:
|
||||
kubelet: hyperkube kubelet
|
||||
proxy: hyperkube proxy
|
||||
conf:
|
||||
kubelet: /etc/kubernetes/kubelet
|
||||
proxy: /etc/kubernetes/proxy
|
||||
proxy: /etc/kubernetes/addons/kube-proxy-daemonset.yaml
|
||||
federated:
|
||||
bin:
|
||||
apiserver: hyperkube federation-apiserver
|
||||
|
||||
200
cfg/master.yaml
200
cfg/master.yaml
@@ -479,19 +479,14 @@ groups:
|
||||
parameter to \"--experimental-encryption-provider-config=</path/to/EncryptionConfig/File>\""
|
||||
scored: true
|
||||
|
||||
# TODO: provide flag to WARN of manual tasks which we can't automate.
|
||||
- id: 1.1.35
|
||||
text: "Ensure that the encryption provider is set to aescbc (Scored)"
|
||||
audit: "ps -ef | grep $apiserverbin | grep -v grep"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "requires manual intervention"
|
||||
set: true
|
||||
type: "manual"
|
||||
remediation: "Follow the Kubernetes documentation and configure a EncryptionConfig file. In this file,
|
||||
choose aescbc as the encryption provider"
|
||||
scored: true
|
||||
|
||||
|
||||
- id: 1.2
|
||||
text: "Scheduler"
|
||||
checks:
|
||||
@@ -573,7 +568,13 @@ groups:
|
||||
KUBE_CONTROLLER_MANAGER_ARGS parameter to include --root-ca-file=<file>"
|
||||
scored: true
|
||||
|
||||
# TODO: 1.3.6 is manual, provide way to WARN
|
||||
- id: 1.3.6
|
||||
text: "Apply Security Context to Your Pods and Containers (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Edit the /etc/kubernetes/controller-manager file on the master node and set the
|
||||
KUBE_CONTROLLER_MANAGER_ARGS parameter to a value to include
|
||||
\"--feature-gates=RotateKubeletServerCertificate=true\""
|
||||
scored: false
|
||||
|
||||
- id: 1.3.7
|
||||
text: " Ensure that the RotateKubeletServerCertificate argument is set to true (Scored)"
|
||||
@@ -595,10 +596,25 @@ groups:
|
||||
checks:
|
||||
- id: 1.4.1
|
||||
text: "Ensure that the apiserver file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $apiserverconf; then stat -c %a $apiserverconf; fi"
|
||||
# audit: "/bin/bash -c 'if test -e $apiserverconf; then stat -c %a $apiserverconf; fi'"
|
||||
audit: "/bin/sh -c 'if test -e $apiserverconf; then stat -c %a $apiserverconf; 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 master node.
|
||||
\nFor example, chmod 644 $apiserverconf"
|
||||
@@ -606,10 +622,13 @@ groups:
|
||||
|
||||
- id: 1.4.2
|
||||
text: "Ensure that the apiserver file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $apiserverconf; then stat -c %U:%G $apiserverconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $apiserverconf; then stat -c %U:%G $apiserverconf; 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 master node.
|
||||
\nFor example, chown root:root $apiserverconf"
|
||||
@@ -617,10 +636,24 @@ groups:
|
||||
|
||||
- id: 1.4.3
|
||||
text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $config; then stat -c %a $config; fi"
|
||||
audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; 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 master node.
|
||||
\nFor example, chmod 644 $config"
|
||||
@@ -628,10 +661,13 @@ groups:
|
||||
|
||||
- id: 1.4.4
|
||||
text: "Ensure that the config file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $config; then stat -c %U:%G $config; fi"
|
||||
audit: "/bin/sh -c 'if test -e $config; then stat -c %U:%G $config; 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 master node.
|
||||
\nFor example, chown root:root $config"
|
||||
@@ -639,10 +675,24 @@ groups:
|
||||
|
||||
- id: 1.4.5
|
||||
text: "Ensure that the scheduler file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $schedulerconf; then stat -c %a $schedulerconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $schedulerconf; then stat -c %a $schedulerconf; 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 master node.
|
||||
\nFor example, chmod 644 $schedulerconf"
|
||||
@@ -650,10 +700,13 @@ groups:
|
||||
|
||||
- id: 1.4.6
|
||||
text: "Ensure that the scheduler file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $schedulerconf; then stat -c %U:%G $schedulerconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $schedulerconf; then stat -c %U:%G $schedulerconf; 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 master node.
|
||||
\nFor example, chown root:root $schedulerconf"
|
||||
@@ -661,10 +714,24 @@ groups:
|
||||
|
||||
- id: 1.4.7
|
||||
text: "Ensure that the etcd.conf file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $etcdconf; then stat -c %a $etcdconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $etcdconf; then stat -c %a $etcdconf; 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 master node.
|
||||
\nFor example, chmod 644 $etcdconf"
|
||||
@@ -672,10 +739,13 @@ groups:
|
||||
|
||||
- id: 1.4.8
|
||||
text: "Ensure that the etcd.conf file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $etcdconf; then stat -c %U:%G $etcdconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $etcdconf; then stat -c %U:%G $etcdconf; 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 master node.
|
||||
\nFor example, chown root:root $etcdconf"
|
||||
@@ -683,10 +753,24 @@ groups:
|
||||
|
||||
- id: 1.4.9
|
||||
text: "Ensure that the flanneld file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $flanneldconf; then stat -c %a $flanneldconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $flanneldconf; then stat -c %a $flanneldconf; 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 master node.
|
||||
\nFor example, chmod 644 $flanneldconf"
|
||||
@@ -694,10 +778,13 @@ groups:
|
||||
|
||||
- id: 1.4.10
|
||||
text: "Ensure that the flanneld file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $flanneldconf; then stat -c %U:%G $flanneldconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $flanneldconf; then stat -c %U:%G $flanneldconf; 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 master node.
|
||||
\nFor example, chown root:root $flanneldconf"
|
||||
@@ -709,6 +796,9 @@ groups:
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "700"
|
||||
compare:
|
||||
op: eq
|
||||
value: "700"
|
||||
set: true
|
||||
remediation: "On the etcd server node, get the etcd data directory, passed as an argument --data-dir ,
|
||||
from the below command:\n
|
||||
@@ -717,6 +807,20 @@ groups:
|
||||
chmod 700 /var/lib/etcd/default.etcd"
|
||||
scored: true
|
||||
|
||||
- id: 1.4.12
|
||||
text: "Ensure that the etcd data directory ownership is set to etcd:etcd (Scored)"
|
||||
audit: "ps -ef | grep $etcdbin | grep -v grep | grep -o data-dir=.* | cut -d= -f2 | xargs stat -c %U:%G"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "etcd:etcd"
|
||||
set: true
|
||||
remediation: "On the etcd server node, get the etcd data directory, passed as an argument --data-dir ,
|
||||
from the below command:\n
|
||||
ps -ef | grep etcd\n
|
||||
Run the below command (based on the etcd data directory found above). For example,\n
|
||||
chown etcd:etcd /var/lib/etcd/default.etcd"
|
||||
scored: true
|
||||
|
||||
- id: 1.5
|
||||
text: "etcd"
|
||||
checks:
|
||||
@@ -859,3 +963,65 @@ groups:
|
||||
remediation: "Follow the etcd documentation and create a dedicated certificate authority setup for the
|
||||
etcd service."
|
||||
scored: false
|
||||
|
||||
- id: 1.6
|
||||
text: "General Security Primitives"
|
||||
checks:
|
||||
- id: 1.6.1
|
||||
text: "Ensure that the cluster-admin role is only used where required (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Remove any unneeded clusterrolebindings: kubectl delete clusterrolebinding [name]"
|
||||
scored: false
|
||||
|
||||
- id: 1.6.2
|
||||
text: "Create Pod Security Policies for your cluster (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Follow the documentation and create and enforce Pod Security Policies for your cluster.
|
||||
Additionally, you could refer the \"CIS Security Benchmark for Docker\" and follow the
|
||||
suggested Pod Security Policies for your environment."
|
||||
scored: false
|
||||
|
||||
- id: 1.6.3
|
||||
text: "Create administrative boundaries between resources using namespaces (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Follow the documentation and create namespaces for objects in your deployment as you
|
||||
need them."
|
||||
scored: false
|
||||
|
||||
- id: 1.6.4
|
||||
text: "Create network segmentation using Network Policies (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Follow the documentation and create NetworkPolicy objects as you need them."
|
||||
scored: false
|
||||
|
||||
- id: 1.6.5
|
||||
text: "Ensure that the seccomp profile is set to docker/default in your pod definitions (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Seccomp is an alpha feature currently. By default, all alpha features are disabled. So, you
|
||||
would need to enable alpha features in the apiserver by passing \"--feature-
|
||||
gates=AllAlpha=true\" argument.\n
|
||||
Edit the $apiserverconf file on the master node and set the KUBE_API_ARGS
|
||||
parameter to \"--feature-gates=AllAlpha=true\"
|
||||
KUBE_API_ARGS=\"--feature-gates=AllAlpha=true\""
|
||||
scored: false
|
||||
|
||||
- id: 1.6.6
|
||||
text: "Apply Security Context to Your Pods and Containers (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Follow the Kubernetes documentation and apply security contexts to your pods. For a
|
||||
suggested list of security contexts, you may refer to the CIS Security Benchmark for Docker
|
||||
Containers."
|
||||
scored: false
|
||||
|
||||
- id: 1.6.7
|
||||
text: "Configure Image Provenance using ImagePolicyWebhook admission controller (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Follow the Kubernetes documentation and setup image provenance."
|
||||
scored: false
|
||||
|
||||
- id: 1.6.8
|
||||
text: "Configure Network policies as appropriate (Not Scored)"
|
||||
type: "manual"
|
||||
remediation: "Follow the Kubernetes documentation and setup network policies as appropriate."
|
||||
scored: false
|
||||
|
||||
|
||||
@@ -221,10 +221,24 @@ groups:
|
||||
checks:
|
||||
- id: 2.2.1
|
||||
text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $config; then stat -c %a $config; fi"
|
||||
audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; 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.
|
||||
\nFor example, chmod 644 $config"
|
||||
@@ -232,10 +246,13 @@ groups:
|
||||
|
||||
- id: 2.2.2
|
||||
text: "Ensure that the config file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $config; then stat -c %U:%G $config; fi"
|
||||
audit: "/bin/sh -c 'if test -e $config; then stat -c %U:%G $config; 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.
|
||||
\nFor example, chown root:root $config"
|
||||
@@ -243,10 +260,24 @@ groups:
|
||||
|
||||
- id: 2.2.3
|
||||
text: "Ensure that the kubelet file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $kubeletconf; then stat -c %a $kubeletconf; fi"
|
||||
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 below command (based on the file location on your system) on the each worker node.
|
||||
\nFor example, chmod 644 $kubeletconf"
|
||||
@@ -254,7 +285,7 @@ groups:
|
||||
|
||||
- id: 2.2.4
|
||||
text: "Ensure that the kubelet file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $kubeletconf; then stat -c %U:%G $kubeletconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $kubeletconf; then stat -c %U:%G $kubeletconf; fi'"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "root:root"
|
||||
@@ -265,10 +296,24 @@ groups:
|
||||
|
||||
- id: 2.2.5
|
||||
text: "Ensure that the proxy file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "if test -e $proxyconf; then stat -c %a $proxyconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $proxyconf; then stat -c %a $proxyconf; 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.
|
||||
\nFor example, chmod 644 $proxyconf"
|
||||
@@ -276,7 +321,7 @@ groups:
|
||||
|
||||
- id: 2.2.6
|
||||
text: "Ensure that the proxy file ownership is set to root:root (Scored)"
|
||||
audit: "if test -e $proxyconf; then stat -c %U:%G $proxyconf; fi"
|
||||
audit: "/bin/sh -c 'if test -e $proxyconf; then stat -c %U:%G $proxyconf; fi'"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "root:root"
|
||||
@@ -285,23 +330,35 @@ groups:
|
||||
\nFor example, chown root:root $proxyconf"
|
||||
scored: true
|
||||
|
||||
# TODO: provide flag to WARN about manual checks.
|
||||
- id: 2.2.7
|
||||
text: "Ensure that the certificate authorities file permissions are set to
|
||||
644 or more restrictive (Scored)"
|
||||
audit: "if test -e $ca-file; then stat -c %a $ca-file; fi"
|
||||
audit: "/bin/sh -c 'if test -e $ca-file; then stat -c %a $ca-file; 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 to modify the file permissions of the --client-ca-file
|
||||
\nchmod 644 <filename>"
|
||||
scored: true
|
||||
|
||||
# TODO: provide flag to WARN about manual checks.
|
||||
- id: 2.2.8
|
||||
text: "Ensure that the client certificate authorities file ownership is set to root:root"
|
||||
audit: "if test -e $ca-file; then stat -c %U:%G $ca-file; fi"
|
||||
audit: "/bin/sh -c 'if test -e $ca-file; then stat -c %U:%G $ca-file; fi'"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "notexist:notexist"
|
||||
|
||||
@@ -20,7 +20,10 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// NodeType indicates the type of node (master, node, federated).
|
||||
@@ -60,6 +63,7 @@ type Check struct {
|
||||
ID string `yaml:"id" json:"id"`
|
||||
Text string
|
||||
Audit string `json:"omit"`
|
||||
Type string `json:"type"`
|
||||
Commands []*exec.Cmd `json:"omit"`
|
||||
Tests *tests `json:"omit"`
|
||||
Set bool `json:"omit"`
|
||||
@@ -69,14 +73,19 @@ type Check struct {
|
||||
|
||||
// Run executes the audit commands specified in a check and outputs
|
||||
// the results.
|
||||
func (c *Check) Run(verbose bool) {
|
||||
func (c *Check) Run() {
|
||||
// If check type is manual, force result to WARN.
|
||||
if c.Type == "manual" {
|
||||
c.State = WARN
|
||||
return
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
var errmsgs string
|
||||
|
||||
// Check if command exists or exit with WARN.
|
||||
for _, cmd := range c.Commands {
|
||||
_, err := exec.LookPath(cmd.Path)
|
||||
if err != nil {
|
||||
if !isShellCommand(cmd.Path) {
|
||||
c.State = WARN
|
||||
return
|
||||
}
|
||||
@@ -111,7 +120,6 @@ func (c *Check) Run(verbose bool) {
|
||||
cs[i].Args,
|
||||
),
|
||||
)
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -148,9 +156,7 @@ func (c *Check) Run(verbose bool) {
|
||||
i++
|
||||
}
|
||||
|
||||
if verbose && errmsgs != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", errmsgs)
|
||||
}
|
||||
glog.V(2).Info("%s\n", errmsgs)
|
||||
|
||||
res := c.Tests.execute(out.String())
|
||||
if res {
|
||||
@@ -160,18 +166,44 @@ func (c *Check) Run(verbose bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// textToCommand transforms a text representation of commands to be
|
||||
// textToCommand transforms an input text representation of commands to be
|
||||
// run into a slice of commands.
|
||||
// TODO: Make this more robust.
|
||||
func textToCommand(s string) []*exec.Cmd {
|
||||
cmds := []*exec.Cmd{}
|
||||
|
||||
cp := strings.Split(s, "|")
|
||||
// fmt.Println("check.toCommand:", cp)
|
||||
|
||||
for _, v := range cp {
|
||||
v = strings.Trim(v, " ")
|
||||
cs := strings.Split(v, " ")
|
||||
|
||||
// TODO:
|
||||
// GOAL: To split input text into arguments for exec.Cmd.
|
||||
//
|
||||
// CHALLENGE: The input text may contain quoted strings that
|
||||
// must be passed as a unit to exec.Cmd.
|
||||
// eg. bash -c 'foo bar'
|
||||
// 'foo bar' must be passed as unit to exec.Cmd if not the command
|
||||
// will fail when it is executed.
|
||||
// eg. exec.Cmd("bash", "-c", "foo bar")
|
||||
//
|
||||
// PROBLEM: Current solution assumes the grouped string will always
|
||||
// be at the end of the input text.
|
||||
re := regexp.MustCompile(`^(.*)(['"].*['"])$`)
|
||||
grps := re.FindStringSubmatch(v)
|
||||
|
||||
var cs []string
|
||||
if len(grps) > 0 {
|
||||
s := strings.Trim(grps[1], " ")
|
||||
cs = strings.Split(s, " ")
|
||||
|
||||
s1 := grps[len(grps)-1]
|
||||
s1 = strings.Trim(s1, "'\"")
|
||||
|
||||
cs = append(cs, s1)
|
||||
} else {
|
||||
cs = strings.Split(v, " ")
|
||||
}
|
||||
|
||||
cmd := exec.Command(cs[0], cs[1:]...)
|
||||
cmds = append(cmds, cmd)
|
||||
@@ -179,3 +211,18 @@ func textToCommand(s string) []*exec.Cmd {
|
||||
|
||||
return cmds
|
||||
}
|
||||
|
||||
func isShellCommand(s string) bool {
|
||||
cmd := exec.Command("/bin/sh", "-c", "command -v "+s)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if strings.Contains(string(out), s) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func NewControls(t NodeType, in []byte) (*Controls, error) {
|
||||
}
|
||||
|
||||
// RunGroup runs all checks in a group.
|
||||
func (controls *Controls) RunGroup(verbose bool, gids ...string) Summary {
|
||||
func (controls *Controls) RunGroup(gids ...string) Summary {
|
||||
g := []*Group{}
|
||||
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0
|
||||
|
||||
@@ -82,7 +82,7 @@ func (controls *Controls) RunGroup(verbose bool, gids ...string) Summary {
|
||||
for _, gid := range gids {
|
||||
if gid == group.ID {
|
||||
for _, check := range group.Checks {
|
||||
check.Run(verbose)
|
||||
check.Run()
|
||||
summarize(controls, check)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (controls *Controls) RunGroup(verbose bool, gids ...string) Summary {
|
||||
}
|
||||
|
||||
// RunChecks runs the checks with the supplied IDs.
|
||||
func (controls *Controls) RunChecks(verbose bool, ids ...string) Summary {
|
||||
func (controls *Controls) RunChecks(ids ...string) Summary {
|
||||
g := []*Group{}
|
||||
m := make(map[string]*Group)
|
||||
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0
|
||||
@@ -110,7 +110,7 @@ func (controls *Controls) RunChecks(verbose bool, ids ...string) Summary {
|
||||
for _, check := range group.Checks {
|
||||
for _, id := range ids {
|
||||
if id == check.ID {
|
||||
check.Run(verbose)
|
||||
check.Run()
|
||||
summarize(controls, check)
|
||||
|
||||
// Check if we have already added this checks group.
|
||||
|
||||
115
check/data
115
check/data
@@ -7,59 +7,42 @@ groups:
|
||||
- id: 1.1
|
||||
text: "Kube-apiserver"
|
||||
checks:
|
||||
- id: 1.1.1
|
||||
text: "Ensure that the --allow-privileged argument is set (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 0
|
||||
text: "flag is set"
|
||||
tests:
|
||||
test_items:
|
||||
-
|
||||
flag: "--allow-privileged"
|
||||
- flag: "--allow-privileged"
|
||||
set: true
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.2
|
||||
text: "Ensure that the --basic-auth argument is not set (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 1
|
||||
text: "flag is not set"
|
||||
tests:
|
||||
test_item:
|
||||
-
|
||||
flag: "--basic-auth"
|
||||
- flag: "--basic-auth"
|
||||
set: false
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.3
|
||||
text: "Ensure that the --insecure-port argument is set to 0 (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 2
|
||||
text: "flag value is set to some value"
|
||||
tests:
|
||||
test_items:
|
||||
-
|
||||
flag: "--insecure-port"
|
||||
- flag: "--insecure-port"
|
||||
compare:
|
||||
op: eq
|
||||
value: 0
|
||||
set: true
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.4
|
||||
text: "Ensure that the --audit-log-maxage argument is set to 30 or appropriate (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 3
|
||||
text: "flag value is greater than or equal some number"
|
||||
tests:
|
||||
test_items:
|
||||
-
|
||||
flag: "--audit-log-maxage"
|
||||
- flag: "--audit-log-maxage"
|
||||
compare:
|
||||
op: gte
|
||||
value: 30
|
||||
set: true
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.5
|
||||
text: "Ensure that the --max-backlog argument is set to 30 or less (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 4
|
||||
text: "flag value is less than some number"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "--max-backlog"
|
||||
@@ -67,26 +50,19 @@ groups:
|
||||
op: lt
|
||||
value: 30
|
||||
set: true
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.6
|
||||
text: "Ensure admission control does not include AlwaysAdmit (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 5
|
||||
text: "flag value does not have some value"
|
||||
tests:
|
||||
test_items:
|
||||
-
|
||||
flag: "--admission-control"
|
||||
- flag: "--admission-control"
|
||||
compare:
|
||||
op: nothave
|
||||
value: AlwaysAdmit
|
||||
set: true
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.7
|
||||
text: "Ensure that the --kubelet-client-certificate and --kubelet-clientkey arguments are set as appropriate (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 6
|
||||
text: "test AND binary operation"
|
||||
tests:
|
||||
bin_op: and
|
||||
test_items:
|
||||
@@ -94,17 +70,13 @@ groups:
|
||||
set: true
|
||||
- flag: "--kubelet-clientkey"
|
||||
set: true
|
||||
remediation: "Edit the /etc/kubernetes/config file on the master node and set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
|
||||
scored: true
|
||||
|
||||
- id: 1.1.8
|
||||
text: "Ensure that the --secure-port argument is not set to 0 (Scored)"
|
||||
audit: "ps -ef | grep kube-apiserver | grep -v grep"
|
||||
- id: 7
|
||||
text: "test OR binary operation"
|
||||
tests:
|
||||
bin_op: or
|
||||
test_items:
|
||||
-
|
||||
flag: "--secure-port"
|
||||
- flag: "--secure-port"
|
||||
compare:
|
||||
op: eq
|
||||
value: 0
|
||||
@@ -112,28 +84,35 @@ groups:
|
||||
-
|
||||
flag: "--secure-port"
|
||||
set: false
|
||||
remediation: "Edit the /etc/kubernetes/apiserver file on the master node and either remove the -secure-port argument from the KUBE_API_ARGS parameter or set it to a different desired port."
|
||||
scored: true
|
||||
|
||||
- id: 1.4.1
|
||||
text: "Ensure that the apiserver file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "stat -c %a /etc/kubernetes/apiserver"
|
||||
- id: 8
|
||||
text: "test flag with arbitrary text"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "644"
|
||||
set: true
|
||||
remediation: "Run the below command (based on the file location on your system) on the master node. For example, chmod 644 /etc/kubernetes/apiserver"
|
||||
scored: true
|
||||
|
||||
- id: 2.1.14
|
||||
text: "Ensure that the apiserver file permissions are set to 644 or more restrictive (Scored)"
|
||||
audit: "ps -ef | grep kubelet | grep -v grep"
|
||||
tests:
|
||||
test_items:
|
||||
- flag: "KubeletClient"
|
||||
compare:
|
||||
op: eq
|
||||
value: true
|
||||
value: "644"
|
||||
set: true
|
||||
|
||||
- id: 9
|
||||
text: "test permissions"
|
||||
audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; 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 master node. For example, chmod 644 /etc/kubernetes/apiserver"
|
||||
scored: true
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
|
||||
type testItem struct {
|
||||
Flag string
|
||||
Output string
|
||||
Value string
|
||||
Set bool
|
||||
Compare compare
|
||||
@@ -57,14 +58,22 @@ func (t *testItem) execute(s string) (result bool) {
|
||||
isset := match
|
||||
|
||||
if isset && t.Compare.Op != "" {
|
||||
pttn := t.Flag + `=([^\s,]*) *`
|
||||
// Expects flags in the form;
|
||||
// --flag=somevalue
|
||||
// --flag
|
||||
// somevalue
|
||||
pttn := `(` + t.Flag + `)(=)*([^\s,]*) *`
|
||||
flagRe := regexp.MustCompile(pttn)
|
||||
vals := flagRe.FindStringSubmatch(s)
|
||||
|
||||
if len(vals) > 0 {
|
||||
flagVal = vals[1]
|
||||
if vals[3] != "" {
|
||||
flagVal = vals[3]
|
||||
} else {
|
||||
flagVal = vals[1]
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "expected value for %s but none found\n", t.Flag)
|
||||
fmt.Fprintf(os.Stderr, "invalid flag in testitem definition")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ package check
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -30,79 +32,74 @@ func init() {
|
||||
if err != nil {
|
||||
panic("Failed reading test data: " + err.Error())
|
||||
}
|
||||
controls, err = NewControls(MASTER, in)
|
||||
|
||||
// substitute variables in data file
|
||||
user := os.Getenv("USER")
|
||||
s := strings.Replace(string(in), "$user", user, -1)
|
||||
|
||||
controls, err = NewControls(MASTER, []byte(s))
|
||||
// controls, err = NewControls(MASTER, in)
|
||||
if err != nil {
|
||||
panic("Failed creating test controls: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestExecute(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
*tests
|
||||
testfor string
|
||||
str string
|
||||
*Check
|
||||
str string
|
||||
}{
|
||||
{
|
||||
controls.Groups[0].Checks[0].Tests,
|
||||
"flag set",
|
||||
controls.Groups[0].Checks[0],
|
||||
"2:45 ../kubernetes/kube-apiserver --allow-privileged=false --option1=20,30,40",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[1].Tests,
|
||||
"flag not set",
|
||||
controls.Groups[0].Checks[1],
|
||||
"2:45 ../kubernetes/kube-apiserver --allow-privileged=false",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[2].Tests,
|
||||
"flag and value set",
|
||||
controls.Groups[0].Checks[2],
|
||||
"niinai 13617 2635 99 19:26 pts/20 00:03:08 ./kube-apiserver --insecure-port=0 --anonymous-auth",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[3].Tests,
|
||||
"flag value greater than value",
|
||||
controls.Groups[0].Checks[3],
|
||||
"2:45 ../kubernetes/kube-apiserver --secure-port=0 --audit-log-maxage=40 --option",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[4].Tests,
|
||||
"flag value less than value",
|
||||
controls.Groups[0].Checks[4],
|
||||
"2:45 ../kubernetes/kube-apiserver --max-backlog=20 --secure-port=0 --audit-log-maxage=40 --option",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[5].Tests,
|
||||
"flag value does not have",
|
||||
controls.Groups[0].Checks[5],
|
||||
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[6].Tests,
|
||||
"AND multiple tests, all testitems pass",
|
||||
controls.Groups[0].Checks[6],
|
||||
"2:45 .. --kubelet-clientkey=foo --kubelet-client-certificate=bar --admission-control=Webhook,RBAC",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[7].Tests,
|
||||
"OR multiple tests",
|
||||
controls.Groups[0].Checks[7],
|
||||
"2:45 .. --secure-port=0 --kubelet-client-certificate=bar --admission-control=Webhook,RBAC",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[8].Tests,
|
||||
"text",
|
||||
controls.Groups[0].Checks[8],
|
||||
"644",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[9].Tests,
|
||||
"flag value is comma-separated",
|
||||
"2:35 ../kubelet --features-gates=KubeletClient=true,KubeletServer=true",
|
||||
controls.Groups[0].Checks[9],
|
||||
"640",
|
||||
},
|
||||
{
|
||||
controls.Groups[0].Checks[9].Tests,
|
||||
"flag value is comma-separated",
|
||||
"2:35 ../kubelet --features-gates=KubeletServer=true,KubeletClient=true",
|
||||
controls.Groups[0].Checks[9],
|
||||
"600",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
res := c.tests.execute(c.str)
|
||||
res := c.Tests.execute(c.str)
|
||||
if !res {
|
||||
t.Errorf("%s, expected:%v, got:%v\n", c.testfor, true, res)
|
||||
t.Errorf("%s, expected:%v, got:%v\n", c.Text, true, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
107
cmd/common.go
107
cmd/common.go
@@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/kube-bench/check"
|
||||
"github.com/spf13/viper"
|
||||
@@ -46,7 +45,8 @@ var (
|
||||
errmsgs string
|
||||
|
||||
// TODO: Consider specifying this in config file.
|
||||
kubeVersion = "1.7.0"
|
||||
kubeMajorVersion = "1"
|
||||
kubeMinorVersion = "7"
|
||||
)
|
||||
|
||||
func runChecks(t check.NodeType) {
|
||||
@@ -59,7 +59,7 @@ func runChecks(t check.NodeType) {
|
||||
schedulerBin = viper.GetString("installation." + installation + ".master.bin.scheduler")
|
||||
schedulerConf = viper.GetString("installation." + installation + ".master.conf.scheduler")
|
||||
controllerManagerBin = viper.GetString("installation." + installation + ".master.bin.controller-manager")
|
||||
controllerManagerConf = viper.GetString("installation." + installation + ".master.conf.controler-manager")
|
||||
controllerManagerConf = viper.GetString("installation." + installation + ".master.conf.controller-manager")
|
||||
config = viper.GetString("installation." + installation + ".config")
|
||||
|
||||
etcdBin = viper.GetString("etcd.bin")
|
||||
@@ -78,7 +78,8 @@ func runChecks(t check.NodeType) {
|
||||
fedControllerManagerBin = viper.GetString("installation." + installation + ".federated.bin.controller-manager")
|
||||
|
||||
// Run kubernetes installation validation checks.
|
||||
warns := verifyNodeType(t)
|
||||
verifyKubeVersion(kubeMajorVersion, kubeMinorVersion)
|
||||
verifyNodeType(t)
|
||||
|
||||
switch t {
|
||||
case check.MASTER:
|
||||
@@ -91,92 +92,94 @@ func runChecks(t check.NodeType) {
|
||||
|
||||
in, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error opening %s controls file: %v\n", t, err)
|
||||
os.Exit(1)
|
||||
exitWithError(fmt.Errorf("error opening %s controls file: %v", t, err))
|
||||
}
|
||||
|
||||
// Variable substitutions. Replace all occurrences of variables in controls files.
|
||||
s := strings.Replace(string(in), "$apiserverbin", apiserverBin, -1)
|
||||
s = strings.Replace(s, "$apiserverconf", apiserverConf, -1)
|
||||
s = strings.Replace(s, "$schedulerbin", schedulerBin, -1)
|
||||
s = strings.Replace(s, "$schedulerconf", schedulerConf, -1)
|
||||
s = strings.Replace(s, "$controllermanagerbin", controllerManagerBin, -1)
|
||||
s = strings.Replace(s, "$controllermanagerconf", controllerManagerConf, -1)
|
||||
s = strings.Replace(s, "$controllermanagerconf", controllerManagerConf, -1)
|
||||
s = strings.Replace(s, "$config", config, -1)
|
||||
s := multiWordReplace(string(in), "$apiserverbin", apiserverBin)
|
||||
s = multiWordReplace(s, "$apiserverconf", apiserverConf)
|
||||
s = multiWordReplace(s, "$schedulerbin", schedulerBin)
|
||||
s = multiWordReplace(s, "$schedulerconf", schedulerConf)
|
||||
s = multiWordReplace(s, "$controllermanagerbin", controllerManagerBin)
|
||||
s = multiWordReplace(s, "$controllermanagerconf", controllerManagerConf)
|
||||
s = multiWordReplace(s, "$config", config)
|
||||
|
||||
s = strings.Replace(s, "$etcdbin", etcdBin, -1)
|
||||
s = strings.Replace(s, "$etcdconf", etcdConf, -1)
|
||||
s = strings.Replace(s, "$flanneldbin", flanneldBin, -1)
|
||||
s = strings.Replace(s, "$flanneldconf", flanneldConf, -1)
|
||||
s = multiWordReplace(s, "$etcdbin", etcdBin)
|
||||
s = multiWordReplace(s, "$etcdconf", etcdConf)
|
||||
s = multiWordReplace(s, "$flanneldbin", flanneldBin)
|
||||
s = multiWordReplace(s, "$flanneldconf", flanneldConf)
|
||||
|
||||
s = strings.Replace(s, "$kubeletbin", kubeletBin, -1)
|
||||
s = strings.Replace(s, "$kubeletconf", kubeletConf, -1)
|
||||
s = strings.Replace(s, "$proxybin", proxyBin, -1)
|
||||
s = strings.Replace(s, "$proxyconf", proxyConf, -1)
|
||||
s = multiWordReplace(s, "$kubeletbin", kubeletBin)
|
||||
s = multiWordReplace(s, "$kubeletconf", kubeletConf)
|
||||
s = multiWordReplace(s, "$proxybin", proxyBin)
|
||||
s = multiWordReplace(s, "$proxyconf", proxyConf)
|
||||
|
||||
s = strings.Replace(s, "$fedapiserverbin", fedApiserverBin, -1)
|
||||
s = strings.Replace(s, "$fedcontrollermanagerbin", fedControllerManagerBin, -1)
|
||||
s = multiWordReplace(s, "$fedapiserverbin", fedApiserverBin)
|
||||
s = multiWordReplace(s, "$fedcontrollermanagerbin", fedControllerManagerBin)
|
||||
|
||||
controls, err := check.NewControls(t, []byte(s))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error setting up %s controls: %v\n", t, err)
|
||||
os.Exit(1)
|
||||
exitWithError(fmt.Errorf("error setting up %s controls: %v", t, err))
|
||||
}
|
||||
|
||||
if groupList != "" && checkList == "" {
|
||||
ids := cleanIDs(groupList)
|
||||
summary = controls.RunGroup(verbose, ids...)
|
||||
summary = controls.RunGroup(ids...)
|
||||
} else if checkList != "" && groupList == "" {
|
||||
ids := cleanIDs(checkList)
|
||||
summary = controls.RunChecks(verbose, ids...)
|
||||
summary = controls.RunChecks(ids...)
|
||||
} else if checkList != "" && groupList != "" {
|
||||
fmt.Fprintf(os.Stderr, "group option and check option can't be used together\n")
|
||||
os.Exit(1)
|
||||
exitWithError(fmt.Errorf("group option and check option can't be used together"))
|
||||
} else {
|
||||
summary = controls.RunGroup(verbose)
|
||||
summary = controls.RunGroup()
|
||||
}
|
||||
|
||||
// if we successfully ran some tests and it's json format, ignore the warnings
|
||||
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0) && jsonFmt {
|
||||
out, err := controls.JSON()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to output in JSON format: %v\n", err)
|
||||
os.Exit(1)
|
||||
exitWithError(fmt.Errorf("failed to output in JSON format: %v", err))
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
} else {
|
||||
prettyPrint(warns, controls, summary)
|
||||
prettyPrint(controls, summary)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyNodeType checks the executables and config files are as expected
|
||||
// for the specified tests (master, node or federated).
|
||||
func verifyNodeType(t check.NodeType) []string {
|
||||
var w []string
|
||||
// Always clear out error messages.
|
||||
errmsgs = ""
|
||||
func verifyNodeType(t check.NodeType) {
|
||||
var bins []string
|
||||
var confs []string
|
||||
|
||||
switch t {
|
||||
case check.MASTER:
|
||||
w = append(w, verifyBin(apiserverBin, schedulerBin, controllerManagerBin)...)
|
||||
w = append(w, verifyConf(apiserverConf, schedulerConf, controllerManagerConf)...)
|
||||
w = append(w, verifyKubeVersion(apiserverBin)...)
|
||||
bins = []string{apiserverBin, schedulerBin, controllerManagerBin}
|
||||
confs = []string{apiserverConf, schedulerConf, controllerManagerConf}
|
||||
case check.NODE:
|
||||
w = append(w, verifyBin(kubeletBin, proxyBin)...)
|
||||
w = append(w, verifyConf(kubeletConf, proxyConf)...)
|
||||
w = append(w, verifyKubeVersion(kubeletBin)...)
|
||||
bins = []string{kubeletBin, proxyBin}
|
||||
confs = []string{kubeletConf, proxyConf}
|
||||
case check.FEDERATED:
|
||||
w = append(w, verifyBin(fedApiserverBin, fedControllerManagerBin)...)
|
||||
w = append(w, verifyKubeVersion(fedApiserverBin)...)
|
||||
bins = []string{fedApiserverBin, fedControllerManagerBin}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", errmsgs)
|
||||
for _, bin := range bins {
|
||||
if !verifyBin(bin, ps) {
|
||||
printlnWarn(fmt.Sprintf("%s is not running", bin))
|
||||
}
|
||||
}
|
||||
|
||||
return w
|
||||
for _, conf := range confs {
|
||||
_, err := os.Stat(conf)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
printlnWarn(fmt.Sprintf("Missing kubernetes config file: %s", conf))
|
||||
} else {
|
||||
exitWithError(fmt.Errorf("error looking for file %s: %v", conf, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// colorPrint outputs the state in a specific colour, along with a message string
|
||||
@@ -186,13 +189,9 @@ func colorPrint(state check.State, s string) {
|
||||
}
|
||||
|
||||
// prettyPrint outputs the results to stdout in human-readable format
|
||||
func prettyPrint(warnings []string, r *check.Controls, summary check.Summary) {
|
||||
func prettyPrint(r *check.Controls, summary check.Summary) {
|
||||
colorPrint(check.INFO, fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
|
||||
|
||||
for _, w := range warnings {
|
||||
colorPrint(check.WARN, w)
|
||||
}
|
||||
|
||||
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Text))
|
||||
for _, g := range r.Groups {
|
||||
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text))
|
||||
|
||||
14
cmd/root.go
14
cmd/root.go
@@ -15,6 +15,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -34,11 +35,12 @@ var (
|
||||
nodeFile string
|
||||
federatedFile string
|
||||
|
||||
loud bool
|
||||
|
||||
kubeConfDir string
|
||||
etcdConfDir string
|
||||
flanneldConfDir string
|
||||
|
||||
verbose bool
|
||||
installation string
|
||||
)
|
||||
|
||||
@@ -52,6 +54,9 @@ var RootCmd = &cobra.Command{
|
||||
// Execute adds all child commands to the root command sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
goflag.Set("logtostderr", "true")
|
||||
goflag.CommandLine.Parse([]string{})
|
||||
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
@@ -83,7 +88,11 @@ func init() {
|
||||
`Run all the checks under this comma-delimited list of groups. Example --group="1.1"`,
|
||||
)
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./cfg/config.yaml)")
|
||||
RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output (default false)")
|
||||
|
||||
goflag.CommandLine.VisitAll(func(goflag *goflag.Flag) {
|
||||
RootCmd.PersistentFlags().AddGoFlag(goflag)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
@@ -103,5 +112,4 @@ func initConfig() {
|
||||
colorPrint(check.FAIL, fmt.Sprintf("Failed to read config file: %v\n", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
180
cmd/util.go
180
cmd/util.go
@@ -4,10 +4,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/kube-bench/check"
|
||||
"github.com/fatih/color"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -20,11 +22,35 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func handleError(err error, context string) (errmsg string) {
|
||||
func printlnWarn(msg string) {
|
||||
fmt.Fprintf(os.Stderr, "[%s] %s\n",
|
||||
colors[check.WARN].Sprintf("%s", check.WARN),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
func sprintlnWarn(msg string) string {
|
||||
return fmt.Sprintf("[%s] %s",
|
||||
colors[check.WARN].Sprintf("%s", check.WARN),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
func exitWithError(err error) {
|
||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func continueWithError(err error, msg string) string {
|
||||
if err != nil {
|
||||
errmsg = fmt.Sprintf("%s, error: %s\n", context, err)
|
||||
glog.V(1).Info(err)
|
||||
}
|
||||
return
|
||||
|
||||
if msg != "" {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func cleanIDs(list string) []string {
|
||||
@@ -38,76 +64,98 @@ func cleanIDs(list string) []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
func verifyConf(confPath ...string) []string {
|
||||
var w []string
|
||||
for _, c := range confPath {
|
||||
if _, err := os.Stat(c); err != nil && os.IsNotExist(err) {
|
||||
w = append(w, fmt.Sprintf("config file %s does not exist\n", c))
|
||||
}
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
func verifyBin(binPath ...string) []string {
|
||||
var w []string
|
||||
var binList string
|
||||
|
||||
// Construct proc name for ps(1)
|
||||
for _, b := range binPath {
|
||||
binList += b + ","
|
||||
_, err := exec.LookPath(b)
|
||||
errmsgs += handleError(
|
||||
err,
|
||||
fmt.Sprintf("%s: command not found in path", b),
|
||||
)
|
||||
}
|
||||
|
||||
binList = strings.Trim(binList, ",")
|
||||
|
||||
// Run ps command
|
||||
cmd := exec.Command("ps", "-C", binList, "-o", "cmd", "--no-headers")
|
||||
// ps execs out to the ps command; it's separated into a function so we can write tests
|
||||
func ps(proc string) string {
|
||||
cmd := exec.Command("ps", "-C", proc, "-o", "cmd", "--no-headers")
|
||||
out, err := cmd.Output()
|
||||
errmsgs += handleError(
|
||||
err,
|
||||
fmt.Sprintf("failed to run: %s", cmd.Args),
|
||||
)
|
||||
|
||||
// Actual verification
|
||||
for _, b := range binPath {
|
||||
matched := strings.Contains(string(out), b)
|
||||
|
||||
if !matched {
|
||||
w = append(w, fmt.Sprintf("%s is not running\n", b))
|
||||
}
|
||||
if err != nil {
|
||||
continueWithError(fmt.Errorf("%s: %s", cmd.Args, err), "")
|
||||
}
|
||||
|
||||
return w
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func verifyKubeVersion(b string) []string {
|
||||
// verifyBin checks that the binary specified is running
|
||||
func verifyBin(bin string, psFunc func(string) string) bool {
|
||||
|
||||
// Strip any quotes
|
||||
bin = strings.Trim(bin, "'\"")
|
||||
|
||||
// bin could consist of more than one word
|
||||
// We'll search for running processes with the first word, and then check the whole
|
||||
// proc as supplied is included in the results
|
||||
proc := strings.Fields(bin)[0]
|
||||
out := psFunc(proc)
|
||||
|
||||
return strings.Contains(out, bin)
|
||||
}
|
||||
|
||||
func verifyKubeVersion(major string, minor string) {
|
||||
// These executables might not be on the user's path.
|
||||
// TODO! Check the version number using kubectl, which is more likely to be on the path.
|
||||
var w []string
|
||||
|
||||
_, err := exec.LookPath(b)
|
||||
errmsgs += handleError(
|
||||
err,
|
||||
fmt.Sprintf("%s: command not found on path - version check skipped", b),
|
||||
)
|
||||
|
||||
// Check version
|
||||
cmd := exec.Command(b, "--version")
|
||||
out, err := cmd.Output()
|
||||
errmsgs += handleError(
|
||||
err,
|
||||
fmt.Sprintf("failed to run:%s", cmd.Args),
|
||||
)
|
||||
|
||||
matched := strings.Contains(string(out), kubeVersion)
|
||||
if !matched {
|
||||
w = append(w, fmt.Sprintf("%s unsupported version\n", b))
|
||||
_, err := exec.LookPath("kubectl")
|
||||
if err != nil {
|
||||
continueWithError(err, sprintlnWarn("Kubernetes version check skipped"))
|
||||
return
|
||||
}
|
||||
|
||||
return w
|
||||
cmd := exec.Command("kubectl", "version")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
s := fmt.Sprintf("Kubernetes version check skipped with error %v", err)
|
||||
continueWithError(err, sprintlnWarn(s))
|
||||
if len(out) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
msg := checkVersion("Client", string(out), major, minor)
|
||||
if msg != "" {
|
||||
continueWithError(fmt.Errorf(msg), msg)
|
||||
}
|
||||
|
||||
msg = checkVersion("Server", string(out), major, minor)
|
||||
if msg != "" {
|
||||
continueWithError(fmt.Errorf(msg), msg)
|
||||
}
|
||||
}
|
||||
|
||||
var regexVersionMajor = regexp.MustCompile("Major:\"([0-9]+)\"")
|
||||
var regexVersionMinor = regexp.MustCompile("Minor:\"([0-9]+)\"")
|
||||
|
||||
func checkVersion(x string, s string, expMajor string, expMinor string) string {
|
||||
regexVersion, err := regexp.Compile(x + " Version: version.Info{(.*)}")
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Error checking Kubernetes version: %v", err)
|
||||
}
|
||||
|
||||
ss := regexVersion.FindString(s)
|
||||
major := versionMatch(regexVersionMajor, ss)
|
||||
minor := versionMatch(regexVersionMinor, ss)
|
||||
if major == "" || minor == "" {
|
||||
return fmt.Sprintf("Couldn't find %s version from kubectl output '%s'", x, s)
|
||||
}
|
||||
|
||||
if major != expMajor || minor != expMinor {
|
||||
return fmt.Sprintf("Unexpected %s version %s.%s", x, major, minor)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func versionMatch(r *regexp.Regexp, s string) string {
|
||||
match := r.FindStringSubmatch(s)
|
||||
if len(match) < 2 {
|
||||
return ""
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
func multiWordReplace(s string, subname string, sub string) string {
|
||||
f := strings.Fields(sub)
|
||||
if len(f) > 1 {
|
||||
sub = "'" + sub + "'"
|
||||
}
|
||||
|
||||
return strings.Replace(s, subname, sub, -1)
|
||||
}
|
||||
|
||||
131
cmd/util_test.go
Normal file
131
cmd/util_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright © 2017 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 (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckVersion(t *testing.T) {
|
||||
kubeoutput := `Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-06-30T09:51:01Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"}
|
||||
Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-07-26T00:12:31Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}`
|
||||
cases := []struct {
|
||||
t string
|
||||
s string
|
||||
major string
|
||||
minor string
|
||||
exp string
|
||||
}{
|
||||
{t: "Client", s: kubeoutput, major: "1", minor: "7"},
|
||||
{t: "Server", s: kubeoutput, major: "1", minor: "7"},
|
||||
{t: "Client", s: kubeoutput, major: "1", minor: "6", exp: "Unexpected Client version 1.7"},
|
||||
{t: "Client", s: kubeoutput, major: "2", minor: "0", exp: "Unexpected Client version 1.7"},
|
||||
{t: "Server", s: "something unexpected", major: "2", minor: "0", exp: "Couldn't find Server version from kubectl output 'something unexpected'"},
|
||||
}
|
||||
|
||||
for id, c := range cases {
|
||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
||||
m := checkVersion(c.t, c.s, c.major, c.minor)
|
||||
if m != c.exp {
|
||||
t.Fatalf("Got: %s, expected: %s", m, c.exp)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestVersionMatch(t *testing.T) {
|
||||
minor := regexVersionMinor
|
||||
major := regexVersionMajor
|
||||
client := `Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-06-30T09:51:01Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"}`
|
||||
server := `Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-07-26T00:12:31Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}`
|
||||
|
||||
cases := []struct {
|
||||
r *regexp.Regexp
|
||||
s string
|
||||
exp string
|
||||
}{
|
||||
{r: major, s: server, exp: "1"},
|
||||
{r: minor, s: server, exp: "7"},
|
||||
{r: major, s: client, exp: "1"},
|
||||
{r: minor, s: client, exp: "7"},
|
||||
{r: major, s: "Some unexpected string"},
|
||||
{r: minor}, // Checking that we don't fall over if the string is empty
|
||||
}
|
||||
|
||||
for id, c := range cases {
|
||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
||||
m := versionMatch(c.r, c.s)
|
||||
if m != c.exp {
|
||||
t.Fatalf("Got %s expected %s", m, c.exp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var g string
|
||||
|
||||
func fakeps(proc string) string {
|
||||
return g
|
||||
}
|
||||
func TestVerifyBin(t *testing.T) {
|
||||
cases := []struct {
|
||||
proc string
|
||||
psOut string
|
||||
exp bool
|
||||
}{
|
||||
{proc: "single", psOut: "single", exp: true},
|
||||
{proc: "single", psOut: "", exp: false},
|
||||
{proc: "two words", psOut: "two words", exp: true},
|
||||
{proc: "two words", psOut: "", exp: false},
|
||||
{proc: "cmd", psOut: "cmd param1 param2", exp: true},
|
||||
{proc: "cmd param", psOut: "cmd param1 param2", exp: true},
|
||||
{proc: "cmd param", psOut: "cmd", exp: false},
|
||||
}
|
||||
|
||||
for id, c := range cases {
|
||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
||||
g = c.psOut
|
||||
v := verifyBin(c.proc, fakeps)
|
||||
if v != c.exp {
|
||||
t.Fatalf("Expected %v got %v", c.exp, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiWordReplace(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
sub string
|
||||
subname string
|
||||
output string
|
||||
}{
|
||||
{input: "Here's a file with no substitutions", sub: "blah", subname: "blah", output: "Here's a file with no substitutions"},
|
||||
{input: "Here's a file with a substitution", sub: "blah", subname: "substitution", output: "Here's a file with a blah"},
|
||||
{input: "Here's a file with multi-word substitutions", sub: "multi word", subname: "multi-word", output: "Here's a file with 'multi word' substitutions"},
|
||||
{input: "Here's a file with several several substitutions several", sub: "blah", subname: "several", output: "Here's a file with blah blah substitutions blah"},
|
||||
}
|
||||
for id, c := range cases {
|
||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
||||
s := multiWordReplace(c.input, c.subname, c.sub)
|
||||
if s != c.output {
|
||||
t.Fatalf("Expected %s got %s", c.output, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user