commit 154a140f74549129872100c5e542f86f68f4a95e Author: Amir Jerbi Date: Fri May 26 09:25:29 2017 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ebc744 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +kubernetes-bench-security +*.swp diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/cfg/config.yaml b/cfg/config.yaml new file mode 100644 index 0000000..85f1e96 --- /dev/null +++ b/cfg/config.yaml @@ -0,0 +1,17 @@ +--- +## Controls Files. +# These are YAML files that hold all the details for running checks. +# +## Uncomment to use different control file paths. +# masterControls: $HOME/.cis_kubernetes/master.yaml +# nodeControls: $HOME/.cis_kubernetes/node.yaml +# federatedControls: $HOME/.cis_kubernetes/federated.yaml + +## Configuration Directories. +# Specifies the directories to look for configuration files +# for the kubernetes components. +# +## Uncomment to use different paths. +# kubeConfDir: /etc/kubernetes +# etcdConfDir: /etc/etcd +# flanneldConfDir: /etc/sysconfig diff --git a/cfg/federated.yaml b/cfg/federated.yaml new file mode 100644 index 0000000..ddbb6bb --- /dev/null +++ b/cfg/federated.yaml @@ -0,0 +1,284 @@ +--- +controls: +id: 3 +text: "Federated Deployments" +type: "federated" +groups: +- id: 3.1 + text: "Federation API Server" + checks: + - id: 3.1.1 + text: "Ensure that the --anonymous-auth argument is set to false (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--anonymous-auth" + compare: + op: eq + value: false + set: true + remediation: "Edit the deployment specs and set --anonymous-auth=false.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.2 + text: "Ensure that the --basic-auth-file argument is not set (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--basic-auth-file" + set: false + remediation: "Follow the documentation and configure alternate mechanisms for authentication. + Then, edit the deployment specs and remove \"--basic-auth-file=\".\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.3 + text: "Ensure that the --insecure-allow-any-token argument is not set (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--insecure-allow-any-token" + set: false + remediation: "Edit the deployment specs and remove --insecure-allow-any-token.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.4 + text: "Ensure that the --insecure-bind-address argument is not set (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--insecure-bind-address" + set: false + remediation: "Edit the deployment specs and remove --insecure-bind-address.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.5 + text: "Ensure that the --insecure-port argument is set to 0 (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--insecure-port" + compare: + op: eq + value: 0 + set: true + remediation: "Edit the deployment specs and set --insecure-port=0.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.6 + text: "Ensure that the --secure-port argument is not set to 0 (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + bin_op: or + test_items: + - flag: "--secure-port" + compare: + op: gt + value: 0 + set: true + - flag: "--secure-port" + set: false + remediation: "Edit the deployment specs and set the --secure-port argument to the desired port.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.7 + text: "Ensure that the --profiling argument is set to false (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--profiling" + compare: + op: eq + value: false + set: true + remediation: "Edit the deployment specs and set \"--profiling=false\".\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + score: true + + - id: 3.1.8 + text: "Ensure that the admission control policy is not set to AlwaysAdmit (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: nothave + value: AlwaysAdmit + set: true + remediation: "Edit the deployment specs and set --admission-control argument to a value that does + not include AlwaysAdmit.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.9 + text: "Ensure that the admission control policy is set to NamespaceLifecycle (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "admission-control" + compare: + op: has + value: "NamespaceLifecycle" + set: true + remediation: "Edit the deployment specs and set --admission-control argument to a value that includes NamespaceLifecycle.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.10 + text: "Ensure that the --audit-log-path argument is set as appropriate (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-path" + set: true + remediation: "Edit the deployment specs and set --audit-log-path argument as appropriate.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.11 + text: "Ensure that the --audit-log-maxage argument is set to 30 or as appropriate (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-maxage" + compare: + op: gte + value: 30 + set: true + remediation: "Edit the deployment specs and set --audit-log-maxage to 30 or as appropriate.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.12 + text: "Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-maxbackup" + compare: + op: gte + value: 10 + set: true + remediation: "Edit the deployment specs and set --audit-log-maxbackup to 10 or as appropriate.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.13 + text: "Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-maxsize" + compare: + op: gte + value: 100 + set: true + remediation: "Edit the deployment specs and set --audit-log-maxsize=100 to 100 or as appropriate.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.14 + text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--authorization-mode" + compare: + op: nothave + value: "AlwaysAllow" + set: true + remediation: "Edit the deployment specs and set --authorization-mode argument to a value other than AlwaysAllow.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.15 + text: "Ensure that the --token-auth-file parameter is not set (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--token-auth-file" + set: false + remediation: "Follow the documentation and configure alternate mechanisms for authentication. + Then, edit the deployment specs and remove the --token-auth-file= argument.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.16 + text: "Ensure that the --service-account-lookup argument is set to true (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--service-account-lookup" + compare: + op: eq + value: true + set: true + remediation: "Edit the deployment specs and set \"--service-account-lookup=true\".\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.17 + text: "Ensure that the --service-account-key-file argument is set as appropriate (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + test_items: + - flag: "--service-account-key-file" + set: true + remediation: "Edit the deployment specs and set --service-account-key-file argument as appropriate.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.18 + text: "Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate (Scored" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + bin_op: and + test_items: + - flag: "--etcd-certfile" + set: true + - flag: "--etcd-keyfile" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection between the + federation apiserver and etcd. Then, edit the deployment specs and set \"--etcd- + certfile=\" and \"--etcd- + keyfile=\" arguments.\n + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + + - id: 3.1.19 + text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Scored)" + audit: "ps -ef | grep federation-apiserver | grep -v grep" + tests: + bin_op: and + test_items: + - flag: "--tls-cert-file" + set: true + - flag: "--tls-private-key-file" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection on the federation + apiserver. Then, edit the deployment specs and set \"--tls-cert-file=\" and \"--tls-private-key-file=\" : + kubectl edit deployments federation-apiserver-deployment --namespace=federation-system" + scored: true + +- id: 3.2 + text: "Federation Controller Manager" + checks: + - id: 3.2.1 + text: "Ensure that the --profiling argument is set to false (Scored)" + audit: "ps -ef | grep federation-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--profiling" + compare: + op: eq + value: false + set: true + remediation: "Edit the deployment specs and set \"--profiling=false\".\n + kubectl edit deployments federation-controller-manager-deployment --namespace=federation-system" + scored: true diff --git a/cfg/master.yaml b/cfg/master.yaml new file mode 100644 index 0000000..f334e65 --- /dev/null +++ b/cfg/master.yaml @@ -0,0 +1,794 @@ +--- +controls: +id: 1 +text: "Master Node Security Configuration" +type: "master" +groups: +- id: 1.1 + text: "API Server" + checks: + - id: 1.1.1 + text: "Ensure that the --allow-privileged argument is set to false (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "allow-privileged" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/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 --anonymous-auth argument is set to false (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--anonymous-auth" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set + the KUBE_API_ARGS parameter to \"--anonymous-auth=false\"" + scored: true + + - id: 1.1.3 + text: "Ensure that the --basic-auth-file argument is not set (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--basic-auth-file" + set: false + remediation: "Follow the documentation and configure alternate mechanisms for + authentication. Then, edit the $kubeConfDir/apiserver file on the master + node and remove the \"--basic-auth-file=\" argument from the + KUBE_API_ARGS parameter." + scored: true + + - id: 1.1.4 + text: "Ensure that the --insecure-allow-any-token argument is not set (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--insecure-allow-any-token" + set: false + remediation: "Edit the $kubeConfDir/apiserver file on the master node and remove + the --insecure-allow-any-token argument from the KUBE_API_ARGS parameter." + scored: true + + - id: 1.1.5 + text: "Ensure that the --kubelet-https argument is set to true (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--kubelet-https" + compare: + op: eq + value: true + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and remove + the --kubelet-https argument from the KUBE_API_ARGS parameter." + scored: true + + - id: 1.1.6 + text: "Ensure that the --insecure-bind-address argument is not set (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--insecure-bind-address" + set: false + remediation: "Edit the $kubeConfDir/apiserver file on the master node and remove + the --insecure-bind-address argument from the KUBE_API_ADDRESS parameter." + scored: true + + - id: 1.1.7 + text: "Ensure that the --insecure-port argument is set to 0 (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--insecure-port" + compare: + op: eq + value: 0 + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set + --insecure-port=0 in the KUBE_API_PORT parameter." + 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" + tests: + bin_op: or + test_items: + - flag: "--secure-port" + compare: + op: gt + value: 0 + set: true + - flag: "--secure-port" + set: false + remediation: "Edit the $kubeConfDir/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.1.9 + text: "Ensure that the --profiling argument is set to false (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--profiling" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--profiling=false\"" + scored: true + + - id: 1.1.10 + text: "Ensure that the --repair-malformed-updates argument is set to false (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--repair-malformed-updates" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--repair-malformed-updates=false\"" + scored: true + + - id: 1.1.11 + text: "Ensure that the admission control policy is not set to AlwaysAdmit (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: nothave + value: AlwaysAdmit + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_ADMISSION_CONTROL parameter to a value that does not include AlwaysAdmit" + scored: true + + - id: 1.1.12 + text: "Ensure that the admission control policy is set to AlwaysPullImages (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: has + value: "AlwaysPullImages" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_ADMISSION_CONTROL parameter to \"--admission-control=...,AlwaysPullImages,...\"" + scored: true + + - id: 1.1.13 + text: "Ensure that the admission control policy is set to DenyEscalatingExec (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: has + value: "DenyEscalatingExec" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_ADMISSION_CONTROL parameter to \"--admission-control=...,DenyEscalatingExec,...\"" + scored: true + + - id: 1.1.14 + text: "Ensure that the admission control policy is set to SecurityContextDeny (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: has + value: "SecurityContextDeny" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_ADMISSION_CONTROL parameter to \"--admission-control=...,SecurityContextDeny,...\"" + scored: true + + - id: 1.1.15 + text: "Ensure that the admission control policy is set to NamespaceLifecycle (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "admission-control" + compare: + op: has + value: "NamespaceLifecycle" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_ADMISSION_CONTROL parameter to \"--admission-control=NamespaceLifecycle,...\"" + scored: true + + - id: 1.1.16 + text: "Ensure that the --audit-log-path argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-path" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--audit-log-path=\"" + scored: true + + - id: 1.1.17 + text: "Ensure that the --audit-log-maxage argument is set to 30 or as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-maxage" + compare: + op: gte + value: 30 + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--audit-log-maxage=30\"" + scored: true + + - id: 1.1.18 + text: "Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-maxbackup" + compare: + op: gte + value: 10 + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--audit-log-maxbackup=10\"" + scored: true + + - id: 1.1.19 + text: "Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--audit-log-maxsize" + compare: + op: gte + value: 100 + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--audit-log-maxsize=100\"" + scored: true + + - id: 1.1.20 + text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--authorization-mode" + compare: + op: nothave + value: "AlwaysAllow" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to values other than \"--authorization-mode=AlwaysAllow\"" + scored: true + + - id: 1.1.21 + text: "Ensure that the --token-auth-file parameter is not set (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--token-auth-file" + set: false + remediation: "Follow the documentation and configure alternate mechanisms for authentication. + Then, edit the $kubeConfDir/apiserver file on the master node and remove the + \"--tokenauth-file=\" argument from the KUBE_API_ARGS parameter." + scored: true + + - id: 1.1.22 + text: "Ensure that the --kubelet-certificate-authority argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--kubelet-certificate-authority" + set: true + remediation: "Follow the Kubernetes documentation and setup the TLS connection between + the apiserver and kubelets. Then, edit the $kubeConfDir/apiserver file on the + master node and set the KUBE_API_ARGS parameter to + \"--kubelet-certificate-authority=\"" + scored: true + + - id: 1.1.23 + 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" + tests: + bin_op: and + test_items: + - flag: "--kubelet-client-certificate" + set: true + - flag: "--kubelet-client-key" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection between the apiserver + and kubelets. Then, edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to \"--kubelet-clientcertificate=\" + and \"--kubelet-clientkey=\"" + scored: true + + - id: 1.1.24 + text: "Ensure that the --service-account-lookup argument is set to true (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--service-account-lookup" + compare: + op: eq + value: true + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the KUBE_API_ARGS parameter + to \"--service-account-lookup=true\"" + scored: true + + - id: 1.1.25 + text: "Ensure that the admission control policy is set to PodSecurityPolicy (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: has + value: "PodSecurityPolicy" + set: true + remediation: "Follow the documentation and create Pod Security Policy objects as per your environment. + Then, edit the $kubeConfDir/apiserver file on the master node and set the KUBE_ADMISSION_CONTROL + parameter to \"--admission-control=...,PodSecurityPolicy,...\"" + scored: true + + - id: 1.1.26 + text: "Ensure that the --service-account-key-file argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--service-account-key-file" + set: true + remediation: "Edit the $kubeConfDir/apiserver file on the master node and set the KUBE_API_ARGS + parameter to \"--service-account-key-file=\"" + scored: true + + - id: 1.1.27 + text: "Ensure that the --etcd-certfile and --etcd-keyfile arguments are set as appropriate (Scored" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + bin_op: and + test_items: + - flag: "--etcd-certfile" + set: true + - flag: "--etcd-keyfile" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection between the apiserver + and etcd. Then, edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to include \"--etcd-certfile=\" + and \"--etcd-keyfile=\"" + scored: true + + - id: 1.1.28 + text: "Ensure that the admission control policy is set to ServiceAccount (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--admission-control" + compare: + op: has + value: "ServiceAccount" + set: true + remediation: "Follow the documentation and create ServiceAccount objects as per your environment. + Then, edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_ADMISSION_CONTROL parameter to \"--admissioncontrol=...,ServiceAccount,...\"" + scored: true + + - id: 1.1.29 + text: "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + bin_op: and + test_items: + - flag: "--tls-cert-file" + set: true + - flag: "--tls-private-key-file" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection on the apiserver. + Then, edit the $kubeConfDir/apiserver file on the master node and set the KUBE_API_ARGS parameter to + include \"--tls-cert-file=\" and + \"--tls-private-key-file=\"" + scored: true + + - id: 1.1.30 + text: "Ensure that the --client-ca-file argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--client-ca-file" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection on the apiserver. + Then, edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to include \"--client-ca-file=\"" + scored: true + + - id: 1.1.31 + text: "Ensure that the --etcd-cafile argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-apiserver | grep -v grep" + tests: + test_items: + - flag: "--etcd-cafile" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection between the apiserver + and etcd. Then, edit the $kubeConfDir/apiserver file on the master node and set the + KUBE_API_ARGS parameter to include \"--etcd-cafile=\"" + scored: true + +- id: 1.2 + text: "Scheduler" + checks: + - id: 1.2.1 + text: "Ensure that the --profiling argument is set to false (Scored)" + audit: "ps -ef | grep kube-scheduler | grep -v grep" + tests: + test_items: + - flag: "--profiling" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/scheduler file on the master node and set the KUBE_SCHEDULER_ARGS + parameter to \"--profiling=false\"" + scored: true + +- id: 1.3 + text: "Controller Manager" + checks: + - id: 1.3.1 + text: "Ensure that the --terminated-pod-gc-threshold argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--terminated-pod-gc-threshold" + set: true + remediation: "Edit the $kubeConfDir/controller-manager file on the master node and set the + KUBE_CONTROLLER_MANAGER_ARGS parameter to \"--terminated-pod-gcthreshold=\"" + scored: true + + - id: 1.3.2 + text: "Ensure that the --profiling argument is set to false (Scored)" + audit: "ps -ef | grep kube-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--profiling" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/controller-manager file on the master node and set the + KUBE_CONTROLLER_MANAGER_ARGS parameter to \"--profiling=false\"" + scored: true + + - id: 1.3.3 + text: "Ensure that the --insecure-experimental-approve-all-kubelet-csrs-for-group argument is not set (Scored)" + audit: "ps -ef | grep kube-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--insecure-experimental-approve-all-kubelet-csrs-for-group" + set: false + remediation: "Edit the $kubeConfDir/controller-manager file on the master node and remove + the -insecure-experimental-approve-all-kubelet-csrs-for-group argument from the + KUBE_CONTROLLER_MANAGER_ARGS parameter" + scored: true + + - id: 1.3.4 + text: "Ensure that the --use-service-account-credentials argument is set" + audit: "ps -ef | grep kube-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--use-service-account-credentials" + compare: + op: eq + value: true + set: true + remediation: "Edit the $kubeConfDir/controller-manager file on the master node and set the + KUBE_CONTROLLER_MANAGER_ARGS parameter to --use-service-account-credentials=true" + scored: true + + - id: 1.3.5 + text: "Ensure that the --service-account-private-key-file argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--service-account-private-key-file" + set: true + remediation: "Edit the $kubeConfDir/controller-manager file on the master node and set the + KUBE_CONTROLLER_MANAGER_ARGS parameter to --service-account-private-keyfile=" + scored: true + + - id: 1.3.6 + text: "Ensure that the --root-ca-file argument is set as appropriate (Scored)" + audit: "ps -ef | grep kube-controller-manager | grep -v grep" + tests: + test_items: + - flag: "--root-ca-file" + set: true + remediation: "Edit the $kubeConfDir/controller-manager file on the master node and set the + KUBE_CONTROLLER_MANAGER_ARGS parameter to include --root-ca-file=" + scored: true + +- id: 1.4 + text: "Configure Files" + checks: + - id: 1.4.1 + text: "Ensure that the apiserver file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $kubeConfDir/apiserver" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the master node. + \nFor example, chmod 644 $kubeConfDir/apiserver" + scored: true + + - id: 1.4.2 + text: "Ensure that the apiserver file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $kubeConfDir/apiserver" + tests: + test_items: + - flag: "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 $kubeConfDir/apiserver" + scored: true + + - id: 1.4.3 + text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $kubeConfDir/config" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the master node. + \nFor example, chmod 644 $kubeConfDir/config" + scored: true + + - id: 1.4.4 + text: "Ensure that the config file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $kubeConfDir/config" + tests: + test_items: + - flag: "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 $kubeConfDir/config" + scored: true + + - id: 1.4.5 + text: "Ensure that the scheduler file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $kubeConfDir/scheduler" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the master node. + \nFor example, chmod 644 $kubeConfDir/scheduler" + scored: true + + - id: 1.4.6 + text: "Ensure that the scheduler file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $kubeConfDir/scheduler" + tests: + test_items: + - flag: "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 $kubeConfDir/scheduler" + scored: true + + - id: 1.4.7 + text: "Ensure that the etcd.conf file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $etcdConfDir/etcd.conf" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the master node. + \nFor example, chmod 644 $etcdConfDir/etcd.conf" + scored: true + + - id: 1.4.8 + text: "Ensure that the etcd.conf file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $etcdConfDir/etcd.conf" + tests: + test_items: + - flag: "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 $etcdConfDir/etcd.conf" + scored: true + + - id: 1.4.9 + text: "Ensure that the flanneld file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a /etc/sysconfig/flanneld" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the master node. + \nFor example, chmod 644 /etc/sysconfig/flanneld" + scored: true + + - id: 1.4.10 + text: "Ensure that the flanneld file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G /etc/sysconfig/flanneld" + tests: + test_items: + - flag: "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 /etc/sysconfig/flanneld" + scored: true + + # TODO: Resolve issue get pipeline to work. + # - id: 1.4.11 + # - text: "Ensure that the etcd data directory permissions are set to 700 or more restrictive (Scored)" + # - audit: "ps -ef | grep etcd | grep -v grep | sed 's,.*--data-dir=\\(.*\\)\\s.*,\\1,' | xargs stat -c %a" + # - tests: + # - test_items: + # - - flag: "700" + # - set: true + # - remediation: "" + # - scored: true + +- id: 1.5 + text: "etcd" + checks: + - id: 1.5.1 + text: "Ensure that the --cert-file and --key-file arguments are set as appropriate (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--cert-file" + set: true + - flag: "--key-file" + set: true + remediation: "Follow the etcd service documentation and configure TLS encryption." + scored: true + + - id: 1.5.2 + text: "Ensure that the --client-cert-auth argument is set to true (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--client-cert-auth" + compare: + op: eq + value: true + set: true + remediation: "Edit the etcd envrironment file (for example, $etcdConfDir/etcd.conf) on the + etcd server node and set the ETCD_CLIENT_CERT_AUTH parameter to \"true\". + Edit the etcd startup file (for example, /etc/systemd/system/multiuser.target.wants/etcd.service) + and configure the startup parameter for --clientcert-auth and set it to \"${ETCD_CLIENT_CERT_AUTH}\"" + scored: true + + - id: 1.5.3 + text: "Ensure that the --auto-tls argument is not set to true (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + bin_op: or + test_items: + - flag: "--auto-tls" + set: false + - flag: "--auto-tls" + compare: + op: neq + value: true + remediation: "Edit the etcd environment file (for example, $etcdConfDir/etcd.conf) on the etcd server + node and comment out the ETCD_AUTO_TLS parameter. Edit the etcd startup file (for example, + /etc/systemd/system/multiuser.target.wants/etcd.service) and remove the startup parameter + for --auto-tls." + scored: true + + - id: 1.5.4 + text: "Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--peer-cert-file" + set: true + - flag: "--peer-key-file" + set: true + remediation: "Note: This recommendation is applicable only for etcd clusters. If you are using only + one etcd server in your environment then this recommendation is not applicable. + Follow the etcd service documentation and configure peer TLS encryption as appropriate for + your etcd cluster." + scored: true + + - id: 1.5.5 + text: "Ensure that the --peer-client-cert-auth argument is set to true (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--peer-client-cert-auth" + compare: + op: eq + value: true + set: true + remediation: "Note: This recommendation is applicable only for etcd clusters. If you are using only + one etcd server in your environment then this recommendation is not applicable. + Edit the etcd environment file (for example, $etcdConfDir/etcd.conf) on the etcd server node + and set the ETCD_PEER_CLIENT_CERT_AUTH parameter to \"true\". Edit the etcd startup file + (for example, /etc/systemd/system/multiuser.target.wants/etcd.service) and configure the + startup parameter for --peer-client-cert-auth and set it to \"${ETCD_PEER_CLIENT_CERT_AUTH}\"" + scored: true + + - id: 1.5.6 + text: "Ensure that the --peer-auto-tls argument is not set to true (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + bin_op: or + test_items: + - flag: "--peer-auto-tls" + set: false + - flag: "--peer-auto-tls" + compare: + op: eq + value: false + set: true + remediation: "Note: This recommendation is applicable only for etcd clusters. + If you are using only one etcd server in your environment then this recommendation is + not applicable. Edit the etcd environment file (for example, $etcdConfDir/etcd.conf) + on the etcd server node and comment out the ETCD_PEER_AUTO_TLS parameter. + Edit the etcd startup file (for example, /etc/systemd/system/multiuser.target.wants/etcd.service) + and remove the startup parameter for --peer-auto-tls." + scored: true + + - id: 1.5.7 + text: "Ensure that the --wal-dir argument is set as appropriate (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--wal-dir" + set: true + remediation: "Edit the etcd environment file (for example, $etcdConfDir/etcd.conf) on the etcd server node + and set the ETCD_WAL_DIR parameter as appropriate. Edit the etcd startup file (for example, + /etc/systemd/system/multiuser.target.wants/etcd.service) and configure the startup parameter for + --wal-dir and set it to \"${ETCD_WAL_DIR}\"" + scored: true + + - id: 1.5.8 + text: "Ensure that the --max-wals argument is set to 0 (Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--max-wals" + compare: + op: eq + value: 0 + set: true + remediation: "Edit the etcd environment file (for example, $etcdConfDir/etcd.conf) on the etcd server node + and set the ETCD_MAX_WALS parameter to 0. Edit the etcd startup file (for example, + /etc/systemd/system/multiuser.target.wants/etcd.service) and configure the startup parameter + for --max-wals and set it to \"${ETCD_MAX_WALS}\"." + scored: true + + - id: 1.5.9 + text: "Ensure that a unique Certificate Authority is used for etcd (Not Scored)" + audit: "ps -ef | grep etcd | grep -v grep" + tests: + test_items: + - flag: "--trusted-ca-file" + set: true + remediation: "Follow the etcd documentation and create a dedicated certificate authority setup for the + etcd service." + scored: false diff --git a/cfg/node.yaml b/cfg/node.yaml new file mode 100644 index 0000000..31465af --- /dev/null +++ b/cfg/node.yaml @@ -0,0 +1,258 @@ +--- +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)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--allow-privileged" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/config file on each node and set the KUBE_ALLOW_PRIV + parameter to \"--allow-privileged=false\"" + scored: true + + - id: 2.1.2 + text: "Ensure that the --anonymous-auth argument is set to false (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--anonymous-auth" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/kubelet file on the master node and set the + KUBELET_ARGS parameter to \"--anonymous-auth=false\"" + scored: true + + - id: 2.1.3 + text: "Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--authorization-mode" + compare: + op: nothave + value: "AlwaysAllow" + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the + KUBELET_ARGS parameter to \"--authorization-mode=Webhook\"" + scored: true + + - id: 2.1.4 + text: "Ensure that the --client-ca-file argument is set as appropriate (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--client-ca-file" + set: true + remediation: "Follow the Kubernetes documentation and setup the TLS connection between + the apiserver and kubelets. Then, edit the $kubeConfDir/kubelet file on each node + and set the KUBELET_ARGS parameter to \"--client-ca-file=\"" + scored: true + + - id: 2.1.5 + text: "Ensure that the --read-only-port argument is set to 0 (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--read-only-port" + compare: + op: eq + value: 0 + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_ARGS + parameter to \"--read-only-port=0\"" + scored: true + + - id: 2.1.6 + text: "Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--streaming-connection-idle-timeout" + compare: + op: gt + value: 0 + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_ARGS + parameter to \"--streaming-connection-idle-timeout=\"" + scored: true + + - id: 2.1.7 + text: "Ensure that the --protect-kernel-defaults argument is set to true (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--protect-kernel-defaults" + compare: + op: eq + value: true + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_ARGS + parameter to \"--protect-kernel-defaults=true\"" + scored: true + + - id: 2.1.8 + text: "Ensure that the --make-iptables-util-chains argument is set to true (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + bin_op: or + test_items: + - flag: "--make-iptables-util-chains" + compare: + op: eq + value: true + set: true + - flag: "--make-iptables-util-chains" + set: false + remediation: "Edit the $kubeConfDir/kubelet file on each node and remove the + --make-iptables-util-chains argument from the KUBELET_ARGS parameter." + scored: true + + - id: 2.1.9 + text: "Ensure that the --keep-terminated-pod-volumes argument is set to false (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--keep-terminated-pod-volumes" + compare: + op: eq + value: false + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_ARGS + parameter to \"--keep-terminated-pod-volumes=false\"" + scored: true + + - id: 2.1.10 + text: "Ensure that the --hostname-override argument is not set (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--hostname-override" + set: false + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_HOSTNAME + parameter to \"\"" + scored: true + + - id: 2.1.11 + text: "Ensure that the --event-qps argument is set to 0 (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--event-qps" + compare: + op: eq + value: 0 + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_ARGS + parameter to \"--event-qps=0\"" + 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: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--tls-cert-file" + set: true + - flag: "--tls-private-key-file" + set: true + remediation: "Follow the Kubernetes documentation and set up the TLS connection on the Kubelet. + Then, edit the $kubeConfDir/kubelet file on the master node and set the KUBELET_ARGS + parameter to include \"--tls-cert-file=\" and + \"--tls-private-key-file=\"" + scored: true + + - id: 2.1.13 + text: "Ensure that the --cadvisor-port argument is set to 0 (Scored)" + audit: "ps -ef | grep kubelet | grep -v grep" + tests: + test_items: + - flag: "--cadvisor-port" + compare: + op: eq + value: 0 + set: true + remediation: "Edit the $kubeConfDir/kubelet file on each node and set the KUBELET_ARGS parameter + to \"--cadvisor-port=0\"" + scored: true + +- id: 2.2 + text: "Configuration Files" + checks: + - id: 2.2.1 + text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $kubeConfDir/config" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the each worker node. + \nFor example, chmod 644 $kubeConfDir/config" + scored: true + + - id: 2.2.2 + text: "Ensure that the config file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $kubeConfDir/config" + 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. + \nFor example, chown root:root $kubeConfDir/config" + scored: true + + - id: 2.2.3 + text: "Ensure that the kubelet file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $kubeConfDir/kubelet" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the each worker node. + \nFor example, chmod 644 $kubeConfDir/kubelet" + scored: true + + - id: 2.2.4 + text: "Ensure that the kubelet file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $kubeConfDir/kubelet" + 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. + \nFor example, chown root:root $kubeConfDir/kubelet" + scored: true + + - id: 2.2.5 + text: "Ensure that the proxy file permissions are set to 644 or more restrictive (Scored)" + audit: "stat -c %a $kubeConfDir/proxy" + tests: + test_items: + - flag: "644" + set: true + remediation: "Run the below command (based on the file location on your system) on the each worker node. + \nFor example, chmod 644 $kubeConfDir/proxy" + scored: true + + - id: 2.2.6 + text: "Ensure that the proxy file ownership is set to root:root (Scored)" + audit: "stat -c %U:%G $kubeConfDir/proxy" + 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. + \nFor example, chown root:root $kubeConfDir/proxy" + scored: true diff --git a/check/README.md b/check/README.md new file mode 100644 index 0000000..cdfe4b3 --- /dev/null +++ b/check/README.md @@ -0,0 +1,55 @@ +# Checks +Checks are recommendations from the Center for Internet Security for Kubernetes 1.6+ installations. + +## YAML Representation +In this application these recommendations are represented as YAML documents. +An example is as listed below: +``` +--- +controls: +id: 1 +text: "Master Checks" +type: "master" +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" + tests: + - 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 +``` + +Recommendations (called `checks` in this document) can run on Kubernetes Master, Node or Federated API Servers. +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. + +## 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. + +The syntax for tests: +``` +tests: +- flag: + set: + compare: + op: + value: +... +``` +Tests have various `operations` which are used to compare the output of audit commands for success. +These operations are: + +- `eq`: tests if the flag value is equal to the compared value. +- `noteq`: tests if the flag value is unequal to the compared value. +- `gt`: tests if the flag value is greater than the compared value. +- `gte`: tests if the flag value is greater than or equal to the compared value. +- `lt`: tests if the flag value is less than the compared value. +- `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. + diff --git a/check/check.go b/check/check.go new file mode 100644 index 0000000..ef0be6b --- /dev/null +++ b/check/check.go @@ -0,0 +1,144 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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 ( + "bufio" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/joncalhoun/pipe" +) + +// NodeType indicates the type of node (master, node, federated). +type NodeType string + +// State is the state of a control check. +type State string + +const ( + // PASS check passed. + PASS State = "PASS" + // FAIL check failed. + FAIL = "FAIL" + // WARN could not carry out check. + WARN = "WARN" + + // MASTER a master node + MASTER NodeType = "master" + // NODE a node + NODE NodeType = "node" + // FEDERATED a federated deployment. + FEDERATED NodeType = "federated" +) + +// Check contains information about a recommendation in the +// CIS Kubernetes 1.6+ document. +type Check struct { + ID string `yaml:"id" json:"id"` + Text string + Audit string `json:"omit"` + Commands []*exec.Cmd `json:"omit"` + Tests *tests `json:"omit"` + Set bool `json:"omit"` + Remediation string + State +} + +// Run executes the audit commands specified in a check and outputs +// the results. +func (c *Check) Run() { + var out string + + // Check if command exists or exit with WARN. + for _, cmd := range c.Commands { + _, err := exec.LookPath(cmd.Path) + if err != nil { + c.State = WARN + return + } + } + + // Run commands. + if len(c.Commands) == 0 { + // Likely a warning message. + c.State = WARN + return + } + + p, err := pipe.New(c.Commands...) + if err != nil { + fmt.Fprintf(os.Stderr, "init: error creating command pipeline %s\n", err) + os.Exit(1) + } + + pr, pw := io.Pipe() + p.Stdout = pw + defer pw.Close() + + if err := p.Start(); err != nil { + fmt.Fprintf(os.Stderr, "start: error running audit command %s\n", err) + os.Exit(1) + } + + // Read output of command chain into string for check. + go func() { + defer pr.Close() + scanner := bufio.NewScanner(pr) + for scanner.Scan() { + out += scanner.Text() + } + + if err := scanner.Err(); err != nil { + fmt.Fprintf(os.Stderr, "error accumulating output %s\n", err) + os.Exit(1) + } + }() + + if err := p.Wait(); err != nil { + fmt.Fprintf(os.Stderr, "wait: error running audit command %s\n", err) + os.Exit(1) + } + + res := c.Tests.execute(out) + if res { + c.State = PASS + } else { + c.State = FAIL + } +} + +// textToCommand transforms a 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, " ") + + cmd := exec.Command(cs[0], cs[1:]...) + cmds = append(cmds, cmd) + } + + return cmds +} diff --git a/check/controls.go b/check/controls.go new file mode 100644 index 0000000..4e7cde5 --- /dev/null +++ b/check/controls.go @@ -0,0 +1,183 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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 ( + "encoding/json" + "fmt" + "os" + + yaml "gopkg.in/yaml.v2" +) + +// Controls holds all controls to check for master nodes. +type Controls struct { + ID string `yaml:"id"` + Text string + Type NodeType + Groups []*Group + Summary +} + +// Group is a collection of similar checks. +type Group struct { + ID string `yaml:"id"` + Text string + Checks []*Check +} + +// Summary is a summary of the results of control checks run. +type Summary struct { + Pass int + Fail int + Warn int +} + +// NewControls instantiates a new master Controls object. +func NewControls(t NodeType, in []byte) *Controls { + var err error + c := new(Controls) + + err = yaml.Unmarshal(in, c) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + + if t != c.Type { + fmt.Fprintf(os.Stderr, "non-%s controls file specified\n", t) + os.Exit(1) + } + + // Prepare audit commands + for _, group := range c.Groups { + for _, check := range group.Checks { + check.Commands = textToCommand(check.Audit) + } + } + + return c +} + +// 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 = 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() + summarize(controls, 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{} + m := make(map[string]*Group) + controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 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() + 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) + } + + } + } + } + } + + controls.Groups = g + return controls.Summary +} + +// JSON encodes the results of last run to JSON. +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 { + case PASS: + controls.Summary.Pass++ + case FAIL: + controls.Summary.Fail++ + case WARN: + controls.Summary.Warn++ + } +} diff --git a/check/data b/check/data new file mode 100644 index 0000000..b1ce6ff --- /dev/null +++ b/check/data @@ -0,0 +1,126 @@ +--- +controls: +id: 1 +text: "Master Checks" +type: "master" +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" + tests: + test_items: + - + 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" + tests: + test_item: + - + 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" + tests: + test_items: + - + 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" + tests: + test_items: + - + 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" + tests: + test_items: + - flag: "--max-backlog" + compare: + 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" + tests: + test_items: + - + 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" + tests: + bin_op: and + test_items: + - flag: "--kubelet-client-certificate" + 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" + tests: + bin_op: or + test_items: + - + flag: "--secure-port" + compare: + op: eq + value: 0 + set: true + - + 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" + 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 diff --git a/check/test.go b/check/test.go new file mode 100644 index 0000000..479c947 --- /dev/null +++ b/check/test.go @@ -0,0 +1,158 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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 ( + "fmt" + "os" + "regexp" + "strconv" + "strings" +) + +// test: +// flag: OPTION +// set: (true|false) +// compare: +// op: (eq|gt|gte|lt|lte|has) +// value: val + +type binOp string + +const ( + and binOp = "and" + or = "or" +) + +type testItem struct { + Flag string + Value string + Set bool + Compare compare +} + +type compare struct { + Op string + Value string +} + +func (t *testItem) execute(s string) (result bool) { + result = false + match := strings.Contains(s, t.Flag) + + if t.Set { + var flagVal string + isset := match + + if isset && t.Compare.Op != "" { + pttn := t.Flag + `=(\S*) *` + flagRe := regexp.MustCompile(pttn) + vals := flagRe.FindStringSubmatch(s) + + if len(vals) > 0 { + flagVal = vals[1] + } else { + fmt.Fprintf(os.Stderr, "expected value for %s but none found\n", t.Flag) + os.Exit(1) + } + + switch t.Compare.Op { + case "eq": + result = flagVal == t.Compare.Value + + case "noteq": + result = !(flagVal == t.Compare.Value) + + case "gt": + a, b := toNumeric(flagVal, t.Compare.Value) + result = a > b + + case "gte": + a, b := toNumeric(flagVal, t.Compare.Value) + result = a >= b + + case "lt": + a, b := toNumeric(flagVal, t.Compare.Value) + result = a < b + + case "lte": + a, b := toNumeric(flagVal, t.Compare.Value) + result = a <= b + + case "has": + result = strings.Contains(flagVal, t.Compare.Value) + + case "nothave": + result = !strings.Contains(flagVal, t.Compare.Value) + } + } else { + result = isset + } + + } else { + notset := !match + result = notset + } + + return +} + +type tests struct { + TestItems []*testItem `yaml:"test_items"` + BinOp binOp `yaml:"bin_op"` +} + +func (ts *tests) execute(s string) (result bool) { + res := make([]bool, len(ts.TestItems)) + + for i, t := range ts.TestItems { + res[i] = t.execute(s) + } + + // If no binary operation is specified, default to AND + switch ts.BinOp { + default: + fmt.Fprintf(os.Stderr, "unknown binary operator for tests %s\n", ts.BinOp) + os.Exit(1) + case and, "": + result = true + for i := range res { + result = result && res[i] + } + case or: + result = false + for i := range res { + result = result || res[i] + } + } + + return +} + +func toNumeric(a, b string) (c, d int) { + var err error + c, err = strconv.Atoi(a) + if err != nil { + fmt.Fprintf(os.Stderr, "error converting %s: %s\n", a, err) + os.Exit(1) + } + d, err = strconv.Atoi(b) + if err != nil { + fmt.Fprintf(os.Stderr, "error converting %s: %s\n", b, err) + os.Exit(1) + } + + return c, d +} diff --git a/check/test_test.go b/check/test_test.go new file mode 100644 index 0000000..8e495cc --- /dev/null +++ b/check/test_test.go @@ -0,0 +1,95 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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" + "testing" +) + +var ( + in []byte + controls *Controls +) + +func init() { + var err error + in, err = ioutil.ReadFile("data") + if err != nil { + panic("Failed reading test data: " + err.Error()) + } + controls = NewControls(MASTER, in) +} + +func TestTestExecute(t *testing.T) { + cases := []struct { + *tests + testfor string + str string + }{ + { + controls.Groups[0].Checks[0].Tests, + "flag set", + "2:45 ../kubernetes/kube-apiserver --allow-privileged=false --option1=20,30,40", + }, + { + controls.Groups[0].Checks[1].Tests, + "flag not set", + "2:45 ../kubernetes/kube-apiserver --allow-privileged=false", + }, + { + controls.Groups[0].Checks[2].Tests, + "flag and value set", + "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", + "2:45 ../kubernetes/kube-apiserver --secure-port=0 --audit-log-maxage=40 --option", + }, + { + controls.Groups[0].Checks[4].Tests, + "flag value less than value", + "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", + "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", + "2:45 .. --kubelet-clientkey=foo --kubelet-client-certificate=bar --admission-control=Webhook,RBAC", + }, + { + controls.Groups[0].Checks[7].Tests, + "OR multiple tests", + "2:45 .. --secure-port=0 --kubelet-client-certificate=bar --admission-control=Webhook,RBAC", + }, + { + controls.Groups[0].Checks[8].Tests, + "text", + "644", + }, + } + + for _, c := range cases { + res := c.tests.execute(c.str) + if !res { + t.Errorf("%s, expected:%v, got:%v\n", c.testfor, true, res) + } + } +} diff --git a/cmd/common.go b/cmd/common.go new file mode 100644 index 0000000..8124225 --- /dev/null +++ b/cmd/common.go @@ -0,0 +1,225 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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 ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "regexp" + "strings" + + "github.com/aquasecurity/kubernetes-bench-security/check" + "github.com/fatih/color" + "github.com/spf13/viper" +) + +var ( + kubeMasterBin = []string{"kube-apiserver", "kube-scheduler", "kube-controller-manager"} + kubeMasterConf = []string{} + + kubeNodeBin = []string{"kubelet"} + kubeNodeConf = []string{} + + kubeFederatedBin = []string{"federation-apiserver", "federation-controller-manager"} + kubeFederatedConf = []string{} + + // TODO: Consider specifying this in config file. + kubeVersion = "Kubernetes v1.6" + + // Used for variable substitution + symbols = map[string]string{} +) + +func runChecks(t check.NodeType) { + var summary check.Summary + var file string + + // Set up for config file check. + kubeMasterConf = append(kubeMasterConf, viper.Get("kubeConfDir").(string)+"/apiserver") + kubeMasterConf = append(kubeMasterConf, viper.Get("kubeConfDir").(string)+"/scheduler") + kubeMasterConf = append(kubeMasterConf, viper.Get("kubeConfDir").(string)+"/controller-manager") + kubeMasterConf = append(kubeMasterConf, viper.Get("kubeConfDir").(string)+"/config") + kubeMasterConf = append(kubeMasterConf, viper.Get("etcdConfDir").(string)+"/etcd.conf") + kubeMasterConf = append(kubeMasterConf, viper.Get("flanneldConfDir").(string)+"/flanneld") + kubeNodeConf = append(kubeNodeConf, viper.Get("kubeConfDir").(string)+"/kubelet") + kubeNodeConf = append(kubeNodeConf, viper.Get("kubeConfDir").(string)+"/proxy") + + verifyNodeType(t) + + switch t { + case check.MASTER: + file = masterFile + case check.NODE: + file = nodeFile + case check.FEDERATED: + file = federatedFile + } + + in, err := ioutil.ReadFile(file) + if err != nil { + fmt.Fprintf(os.Stderr, "error opening %s controls file: %s\n", t, err) + os.Exit(1) + } + + // Variable substitutions. Replace all occurrences of variables in controls file. + s := strings.Replace(string(in), "$kubeConfDir", viper.Get("kubeConfDir").(string), -1) + s = strings.Replace(s, "$etcdConfDir", viper.Get("etcdConfDir").(string), -1) + s = strings.Replace(s, "$flanneldConfDir", viper.Get("etcdConfDir").(string), -1) + + controls := check.NewControls(t, []byte(s)) + + if groupList != "" && checkList == "" { + // log.Println("group: set, checks: not set") + ids := cleanIDs(groupList) + summary = controls.RunGroup(ids...) + + } else if checkList != "" && groupList == "" { + // log.Println("group: not set, checks: set") + ids := cleanIDs(checkList) + summary = controls.RunChecks(ids...) + + } else if checkList != "" && groupList != "" { + // log.Println("group: set, checks: set") + fmt.Fprintf(os.Stderr, "group option and check option can't be used together\n") + os.Exit(1) + + } else { + summary = controls.RunGroup() + } + + if jsonFmt { + out, err := controls.JSON() + if err != nil { + } + + fmt.Println(string(out)) + } else { + prettyPrint(controls, summary) + } +} + +func cleanIDs(list string) []string { + list = strings.Trim(list, ",") + ids := strings.Split(list, ",") + + for _, id := range ids { + id = strings.Trim(id, " ") + } + + return ids +} + +func verifyNodeType(t check.NodeType) { + var binPath []string + var confPath []string + var out []byte + + switch t { + case check.MASTER: + binPath = kubeMasterBin + confPath = kubeMasterConf + case check.NODE: + binPath = kubeNodeBin + confPath = kubeNodeConf + case check.FEDERATED: + binPath = kubeFederatedBin + confPath = kubeFederatedConf + } + + for _, b := range binPath { + _, err := exec.LookPath(b) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: command not found\n", b) + os.Exit(1) + } + + // Check version + cmd := exec.Command(b, "--version") + out, _ = cmd.Output() + if matched, _ := regexp.MatchString(kubeVersion, string(out)); !matched { + fmt.Fprintf(os.Stderr, + "%s unsupported version, expected v%s, got %s\n", + b, + kubeVersion, + string(out), + ) + os.Exit(1) + } + + // Check if running + cmd = exec.Command("ps", "-ef") + out, _ = cmd.Output() + if matched, _ := regexp.MatchString(".*"+b, string(out)); !matched { + fmt.Fprintf(os.Stderr, "%s is not running\n", b) + os.Exit(1) + } + } + + for _, c := range confPath { + if _, err := os.Stat(c); os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "config file %s does not exist\n", c) + os.Exit(1) + } + } +} + +func prettyPrint(r *check.Controls, summary check.Summary) { + colors := map[check.State]*color.Color{ + check.PASS: color.New(color.FgGreen), + check.FAIL: color.New(color.FgRed), + check.WARN: color.New(color.FgYellow), + } + + // Print checks and results. + fmt.Printf("[INFO] %s %s\n", r.ID, r.Text) + for _, g := range r.Groups { + fmt.Printf("[INFO] %s %s\n", g.ID, g.Text) + for _, c := range g.Checks { + colors[c.State].Printf("[%s] ", c.State) + fmt.Printf("%s %s\n", c.ID, c.Text) + } + } + fmt.Println() + + // Print remediations. + if summary.Fail > 0 || summary.Warn > 0 { + colors[check.WARN].Printf("== Remediations ==\n") + for _, g := range r.Groups { + for _, c := range g.Checks { + if c.State != check.PASS { + fmt.Printf("%s %s\n", c.ID, c.Remediation) + } + } + } + fmt.Println() + } + + // Print summary setting output color to highest severity. + var res check.State + if summary.Fail > 0 { + res = check.FAIL + } else if summary.Warn > 0 { + res = check.WARN + } else { + res = check.PASS + } + + colors[res].Printf("== Summary ==\n") + fmt.Printf("%d checks PASS\n%d checks FAIL\n%d checks WARN\n", + summary.Pass, summary.Fail, summary.Warn, + ) +} diff --git a/cmd/federated.go b/cmd/federated.go new file mode 100644 index 0000000..6e6f48f --- /dev/null +++ b/cmd/federated.go @@ -0,0 +1,41 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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/kubernetes-bench-security/check" + "github.com/spf13/cobra" +) + +// nodeCmd represents the node command +var federatedCmd = &cobra.Command{ + Use: "federated", + Short: "Checks for Kubernetes federated deployment.", + Long: `Checks for Kubernetes federated deployment.`, + Run: func(cmd *cobra.Command, args []string) { + runChecks(check.FEDERATED) + }, +} + +func init() { + federatedCmd.PersistentFlags().StringVarP(&federatedFile, + "file", + "f", + cfgDir+"/federated.yaml", + "Alternative YAML file for federated checks", + ) + + RootCmd.AddCommand(federatedCmd) +} diff --git a/cmd/master.go b/cmd/master.go new file mode 100644 index 0000000..3c28aec --- /dev/null +++ b/cmd/master.go @@ -0,0 +1,41 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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/kubernetes-bench-security/check" + "github.com/spf13/cobra" +) + +// masterCmd represents the master command +var masterCmd = &cobra.Command{ + Use: "master", + Short: "Checks for Kubernetes master node.", + Long: `Checks for Kubernetes master node.`, + Run: func(cmd *cobra.Command, args []string) { + runChecks(check.MASTER) + }, +} + +func init() { + masterCmd.PersistentFlags().StringVarP(&masterFile, + "file", + "f", + cfgDir+"/master.yaml", + "Alternative YAML file for master checks", + ) + + RootCmd.AddCommand(masterCmd) +} diff --git a/cmd/node.go b/cmd/node.go new file mode 100644 index 0000000..31b9071 --- /dev/null +++ b/cmd/node.go @@ -0,0 +1,41 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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/kubernetes-bench-security/check" + "github.com/spf13/cobra" +) + +// nodeCmd represents the node command +var nodeCmd = &cobra.Command{ + Use: "node", + Short: "Checks for Kubernetes node.", + Long: `Checks for Kubernetes node.`, + Run: func(cmd *cobra.Command, args []string) { + runChecks(check.NODE) + }, +} + +func init() { + nodeCmd.PersistentFlags().StringVarP(&nodeFile, + "file", + "f", + cfgDir+"/node.yaml", + "Alternative YAML file for node checks", + ) + + RootCmd.AddCommand(nodeCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..bae9dba --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,106 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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 ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + cfgDir = os.Getenv("HOME") + "/.cis_kubernetes" + cfgFile string + + jsonFmt bool + checkList string + groupList string + masterFile string + nodeFile string + federatedFile string + + kubeConfDir string + etcdConfDir string + flannelConfDir string +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "cis_kubernetes", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, +} + +// 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() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Output results as JSON") + RootCmd.PersistentFlags().StringVarP(&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, + "group", + "g", + "", + `Run all the checks under this comma-delimited list of groups. Example --group="1.1"`, + ) + +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + + viper.SetConfigName("config") // name of config file (without extension) + viper.AddConfigPath(cfgDir) // adding home directory as first search path + + viper.SetEnvPrefix("CISK8S") + viper.AutomaticEnv() // read in environment variables that match + + // Set defaults + viper.SetDefault("kubeConfDir", "/etc/kubernetes") + viper.SetDefault("etcdConfDir", "/etc/etcd") + viper.SetDefault("flanneldConfDir", "/etc/sysconfig") + + viper.SetDefault("masterFile", cfgDir+"/master.yaml") + viper.SetDefault("nodeFile", cfgDir+"/node.yaml") + viper.SetDefault("federatedFile", cfgDir+"/federated.yaml") + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..17d7aaf --- /dev/null +++ b/install.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +cfgdir="$HOME/.cis_kubernetes" + +echo "create cis_kubernetes configuration directory" +mkdir $cfgdir + +echo "copy cis_kubernetes configuration file" +cp cfg/config.yaml $cfgdir + +echo "copy controls files to configuration directory" +cp cfg/{master,node,federated}.yaml $cfgdir diff --git a/main.go b/main.go new file mode 100644 index 0000000..0766c6b --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// 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 main + +import "github.com/aquasecurity/kubernetes-bench-security/cmd" + +func main() { + cmd.Execute() +}