├── .dockerignore ├── .github └── workflows │ ├── issues.yml │ └── slsa.yml ├── .gitignore ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── Dockerfile ├── LICENSE.md ├── MAINTAINERS ├── README.md ├── Vagrantfile ├── docker-bench-security.sh ├── docker-compose.yml ├── functions ├── functions_lib.sh ├── helper_lib.sh └── output_lib.sh ├── img └── benchmark_log.png └── tests ├── 1_host_configuration.sh ├── 2_docker_daemon_configuration.sh ├── 3_docker_daemon_configuration_files.sh ├── 4_container_images.sh ├── 5_container_runtime.sh ├── 6_docker_security_operations.sh ├── 7_docker_swarm_configuration.sh ├── 8_docker_enterprise_configuration.sh └── 99_community_checks.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !docker-bench-security.sh 3 | !functions/ 4 | !tests/ 5 | !log/ 6 | log/* 7 | -------------------------------------------------------------------------------- /.github/workflows/issues.yml: -------------------------------------------------------------------------------- 1 | name: Issue assignment 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | auto-assign: 12 | permissions: 13 | issues: write 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: 'auto-assign issue' 17 | uses: pozil/auto-assign-issue@74b9f64cc1a08f99358061073e243a4c3d7dd5c4 # v1.11.0 18 | with: 19 | assignees: konstruktoid 20 | -------------------------------------------------------------------------------- /.github/workflows/slsa.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: SLSA 3 | on: 4 | push: 5 | release: 6 | permissions: 7 | contents: write 8 | types: [published, released] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | outputs: 16 | hashes: ${{ steps.hash.outputs.hashes }} 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Harden Runner 20 | uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 21 | with: 22 | egress-policy: audit 23 | 24 | - run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV 25 | shell: bash 26 | 27 | - name: Checkout repository 28 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 29 | 30 | - name: Build artifacts 31 | run: | 32 | find *.sh functions/* tests/* Dockerfile Vagrantfile -exec sha256sum {} \; > ${{ env.REPOSITORY_NAME }}.sha256 33 | 34 | - name: Generate hashes 35 | shell: bash 36 | id: hash 37 | run: | 38 | echo "hashes=$(sha256sum ${{ env.REPOSITORY_NAME }}.sha256 | base64 -w0)" >> "$GITHUB_OUTPUT" 39 | 40 | - name: Upload ${{ env.REPOSITORY_NAME }}.sha256 41 | uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 42 | with: 43 | name: ${{ env.REPOSITORY_NAME }}.sha256 44 | path: ${{ env.REPOSITORY_NAME }}.sha256 45 | if-no-files-found: error 46 | retention-days: 5 47 | 48 | provenance: 49 | needs: [build] 50 | permissions: 51 | actions: read 52 | id-token: write 53 | contents: write 54 | uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0 55 | with: 56 | base64-subjects: "${{ needs.build.outputs.hashes }}" 57 | upload-assets: ${{ startsWith(github.ref, 'refs/tags/') }} 58 | 59 | release: 60 | permissions: 61 | actions: read 62 | id-token: write 63 | contents: write 64 | needs: [build, provenance] 65 | runs-on: ubuntu-latest 66 | if: startsWith(github.ref, 'refs/tags/') 67 | steps: 68 | - run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV 69 | shell: bash 70 | 71 | - name: Download ${{ env.REPOSITORY_NAME }}.sha256 72 | uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 73 | with: 74 | name: ${{ env.REPOSITORY_NAME }}.sha256 75 | 76 | - name: Upload asset 77 | uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4 78 | with: 79 | files: | 80 | ${{ env.REPOSITORY_NAME }}.sha256 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log/* 2 | *.swp* 3 | .vagrant/ 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Docker Bench for Security 2 | 3 | Want to hack on Docker Bench? Awesome! Here are instructions to get you 4 | started. 5 | 6 | The Docker Bench for Security is a part of the [Docker](https://www.docker.com) 7 | project, and follows the same rules and principles. If you're already familiar 8 | with the way Docker does things, you'll feel right at home. 9 | 10 | Otherwise, go read 11 | [Contribute to the Moby Project](https://github.com/moby/moby/blob/master/CONTRIBUTING.md). 12 | 13 | ## Development Environment Setup 14 | 15 | ### Start hacking 16 | 17 | You can build the container that wraps the docker-bench for security: 18 | 19 | ```sh 20 | git clone git@github.com:docker/docker-bench-security.git 21 | cd docker-bench-security 22 | docker build -t docker-bench-security . 23 | ``` 24 | 25 | Or you can simply run the shell script locally: 26 | 27 | ```sh 28 | git clone git@github.com:docker/docker-bench-security.git 29 | cd docker-bench-security 30 | sudo sh docker-bench-security.sh 31 | ``` 32 | 33 | The Docker Bench has the main script called `docker-bench-security.sh`. 34 | This is the main script that checks for all the dependencies, deals with 35 | command line arguments and loads all the tests. 36 | 37 | The tests are split into the following files: 38 | 39 | ```sh 40 | tests/ 41 | ├── 1_host_configuration.sh 42 | ├── 2_docker_daemon_configuration.sh 43 | ├── 3_docker_daemon_configuration_files.sh 44 | ├── 4_container_images.sh 45 | ├── 5_container_runtime.sh 46 | ├── 6_docker_security_operations.sh 47 | ├── 7_docker_swarm_configuration.sh 48 | ├── 8_docker_enterprise_configuration.sh 49 | └── 99_community_checks.sh 50 | ``` 51 | 52 | To modify the Docker Bench for Security you should first clone the repository, 53 | make your changes, check your code with `shellcheck`, or similar tools, and 54 | then sign off on your commits. After that feel free to send us a pull request 55 | with the changes. 56 | 57 | While this tool was inspired by the [CIS Docker 1.11.0 benchmark](https://www.cisecurity.org/benchmark/docker/) 58 | and its successors, feel free to add new tests. 59 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | The following people, listed in alphabetical order, have contributed to docker-bench-security: 2 | 3 | * alberto 4 | * Andreas Stieger 5 | * Anthony Roger 6 | * Aurélien Gasser 7 | * binary 8 | * Boris Gorbylev 9 | * Cheng-Li Jerry Ma 10 | * Csaba Palfi 11 | * Daniele Marcocci 12 | * Dhawal Patel 13 | * Diogo Monica 14 | * Diogo Mónica 15 | * Ernst de Haan 16 | * HuKeping 17 | * Ivan Angelov 18 | * J0WI 19 | * jammasterj89 20 | * Jessica Frazelle 21 | * Joachim Lusiardi 22 | * Joachim Lusiardi 23 | * Joachim Lusiardi 24 | * Joe Williams 25 | * Julien Garcia Gonzalez 26 | * Jürgen Hermann 27 | * kakakakakku 28 | * Karol Babioch 29 | * Kevin Lim 30 | * kevinll 31 | * Liron Levin 32 | * liron-l 33 | * LorensK 34 | * lusitania 35 | * Maik Ellerbrock 36 | * Mark Stemm 37 | * Matt Fellows 38 | * Michael Crosby 39 | * Michael Stahn 40 | * Mike Ritter 41 | * Mr. Secure 42 | * MrSecure 43 | * Nigel Brown 44 | * Paul Czarkowski 45 | * Paul Morgan 46 | * Pete Sellars 47 | * Peter 48 | * Ravi Kumar Vadapalli 49 | * Scott McCarty 50 | * Sebastiaan van Stijn 51 | * telepresencebot2 52 | * Thomas Sjögren 53 | * Tom Partington 54 | * Werner Buck 55 | * will Farrell 56 | * Zvi "Viz" Effron 57 | 58 | This list was generated Tue Nov 5 09:45:35 UTC 2019. 59 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.18@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c016293fc851978 2 | 3 | LABEL \ 4 | org.label-schema.name="docker-bench-security" \ 5 | org.label-schema.url="https://dockerbench.com" \ 6 | org.label-schema.vcs-url="https://github.com/docker/docker-bench-security.git" 7 | 8 | RUN apk add --no-cache iproute2 \ 9 | docker-cli \ 10 | dumb-init \ 11 | jq 12 | 13 | COPY . /usr/local/bin/ 14 | 15 | HEALTHCHECK CMD exit 0 16 | 17 | WORKDIR /usr/local/bin 18 | 19 | ENTRYPOINT [ "/usr/bin/dumb-init", "/bin/sh", "docker-bench-security.sh" ] 20 | CMD [""] 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Docker, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | # Docker Bench for Security maintainers file 2 | # 3 | # This file describes who runs the docker/docker-bench-security project and how. 4 | # This is a living document - if you see something out of date or missing, speak up! 5 | # 6 | # It is structured to be consumable by both humans and programs. 7 | # To extract its contents programmatically, use any TOML-compliant parser. 8 | # 9 | # This file is compiled into the MAINTAINERS file in docker/opensource. 10 | # 11 | [Org] 12 | [Org."Core maintainers"] 13 | people = [ 14 | "diogomonica", 15 | "konstruktoid", 16 | ] 17 | 18 | [people] 19 | 20 | # A reference list of all people associated with the project. 21 | # All other sections should refer to people by their canonical key 22 | # in the people section. 23 | 24 | # ADD YOURSELF HERE IN ALPHABETICAL ORDER 25 | 26 | [people.diogomonica] 27 | Name = "Dr. Diogo Mónica" 28 | Email = "diogo@docker.com" 29 | GitHub = "diogomonica" 30 | 31 | [people.konstruktoid] 32 | Name = "Thomas Sjögren" 33 | Email = "thomas.sjogren@protonmail.com" 34 | GitHub = "konstruktoid" 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker Bench for Security 2 | 3 | ![Docker Bench for Security running](img/benchmark_log.png) 4 | 5 | The Docker Bench for Security is a script that checks for dozens of common best-practices around deploying Docker containers in production. The tests are all automated, and are based on the [CIS Docker Benchmark v1.6.0](https://www.cisecurity.org/benchmark/docker/). 6 | 7 | We are making this available as an open-source utility so the Docker community can have an easy way to self-assess their hosts and Docker containers against this benchmark. 8 | 9 | Release | CIS | 10 | :---:|:---:| 11 | 1.6.0|1.6.0| 12 | 1.5.0|1.5.0| 13 | 1.3.6|1.4.0| 14 | 1.3.5|1.2.0| 15 | 1.3.3|1.1.0| 16 | 1.3.0|1.13.0| 17 | 18 | ## Running Docker Bench for Security 19 | 20 | ### Run from your base host 21 | 22 | You can simply run this script from your base host by running: 23 | 24 | ```sh 25 | git clone https://github.com/docker/docker-bench-security.git 26 | cd docker-bench-security 27 | sudo sh docker-bench-security.sh 28 | ``` 29 | 30 | > Note: [`jq`](https://jqlang.github.io/jq/) is an optional but recommended dependency. 31 | 32 | ### Run with Docker 33 | 34 | #### Building Docker image 35 | 36 | You have two options if you wish to build and run this container yourself: 37 | 38 | 1. Use Docker Build: 39 | 40 | ```sh 41 | git clone https://github.com/docker/docker-bench-security.git 42 | cd docker-bench-security 43 | docker build --no-cache -t docker-bench-security . 44 | ``` 45 | 46 | Followed by an appropriate `docker run` command as stated below. 47 | 48 | 2. Use Docker Compose: 49 | 50 | ```sh 51 | git clone https://github.com/docker/docker-bench-security.git 52 | cd docker-bench-security 53 | docker-compose run --rm docker-bench-security 54 | ``` 55 | 56 | _Please note that the `docker/docker-bench-security` image is out-of-date and and a manual build is required. See [#405](https://github.com/docker/docker-bench-security/issues/405) for more information._ 57 | 58 | Note that this container is being run with a *lot* of privilege -- sharing the host's filesystem, pid and network namespaces, due to portions of the benchmark applying to the running host. 59 | 60 | ### Using the container 61 | 62 | ```sh 63 | docker run --rm --net host --pid host --userns host --cap-add audit_control \ 64 | -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ 65 | -v /etc:/etc:ro \ 66 | -v /usr/bin/containerd:/usr/bin/containerd:ro \ 67 | -v /usr/bin/runc:/usr/bin/runc:ro \ 68 | -v /usr/lib/systemd:/usr/lib/systemd:ro \ 69 | -v /var/lib:/var/lib:ro \ 70 | -v /var/run/docker.sock:/var/run/docker.sock:ro \ 71 | --label docker_bench_security \ 72 | docker-bench-security 73 | ``` 74 | 75 | Don't forget to adjust the shared volumes according to your operating system. 76 | Some examples are: 77 | 78 | 1. On Ubuntu the `docker.service` and `docker.secret` files are located in 79 | `/lib/systemd/system` folder by default. 80 | 81 | ```sh 82 | docker run --rm --net host --pid host --userns host --cap-add audit_control \ 83 | -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ 84 | -v /etc:/etc:ro \ 85 | -v /lib/systemd/system:/lib/systemd/system:ro \ 86 | -v /usr/bin/containerd:/usr/bin/containerd:ro \ 87 | -v /usr/bin/runc:/usr/bin/runc:ro \ 88 | -v /usr/lib/systemd:/usr/lib/systemd:ro \ 89 | -v /var/lib:/var/lib:ro \ 90 | -v /var/run/docker.sock:/var/run/docker.sock:ro \ 91 | --label docker_bench_security \ 92 | docker-bench-security 93 | ``` 94 | 95 | 2. The /etc/hostname file is missing on macOS, so it will need to be created first. Also, `Docker Desktop` on macOS doesn't have `/usr/lib/systemd` or the above Docker 96 | binaries. 97 | 98 | ```sh 99 | sudo touch /etc/hostname 100 | 101 | docker run --rm --net host --pid host --userns host --cap-add audit_control \ 102 | -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ 103 | -v /etc:/etc \ 104 | -v /var/lib:/var/lib:ro \ 105 | -v /var/run/docker.sock:/var/run/docker.sock:ro \ 106 | --label docker_bench_security \ 107 | docker-bench-security 108 | ``` 109 | 110 | ### Note 111 | 112 | Docker bench requires Docker 1.13.0 or later in order to run. 113 | 114 | Note that when distributions don't contain `auditctl`, the audit tests will check `/etc/audit/audit.rules` to see if a rule is present instead. 115 | 116 | ### Docker Bench for Security options 117 | 118 | ```sh 119 | -b optional Do not print colors 120 | -h optional Print this help message 121 | -l FILE optional Log output in FILE, inside container if run using docker 122 | -u USERS optional Comma delimited list of trusted docker user(s) 123 | -c CHECK optional Comma delimited list of specific check(s) id 124 | -e CHECK optional Comma delimited list of specific check(s) id to exclude 125 | -i INCLUDE optional Comma delimited list of patterns within a container or image name to check 126 | -x EXCLUDE optional Comma delimited list of patterns within a container or image name to exclude from check 127 | -t LABEL optional Comma delimited list of labels within a container or image to check 128 | -n LIMIT optional In JSON output, when reporting lists of items (containers, images, etc.), limit the number of reported items to LIMIT. Default 0 (no limit). 129 | -p PRINT optional Disable the printing of remediation measures. Default: print remediation measures. 130 | ``` 131 | 132 | By default the Docker Bench for Security script will run all available CIS tests and produce 133 | logs in the log folder from current directory, named `docker-bench-security.log.json` and 134 | `docker-bench-security.log`. 135 | 136 | If the docker container is used then the log files will be created inside the container in location `/usr/local/bin/log/`. If you wish to access them from the host after the container has been run you will need to mount a volume for storing them in. 137 | 138 | The CIS based checks are named `check_
_`, e.g. `check_2_6` and community contributed checks are named `check_c_`. 139 | 140 | `sh docker-bench-security.sh -c check_2_2` will only run check `2.2 Ensure the logging level is set to 'info'`. 141 | 142 | `sh docker-bench-security.sh -e check_2_2` will run all available checks except `2.2 Ensure the logging level is set to 'info'`. 143 | 144 | `sh docker-bench-security.sh -e docker_enterprise_configuration` will run all available checks except the docker_enterprise_configuration group 145 | 146 | `sh docker-bench-security.sh -e docker_enterprise_configuration,check_2_2` will run all available checks except the docker_enterprise_configuration group and `2.2 Ensure the logging level is set to 'info'` 147 | 148 | `sh docker-bench-security.sh -c container_images,container_runtime` will run just the container_images and container_runtime checks 149 | 150 | `sh docker-bench-security.sh -c container_images -e check_4_5` will run just the container_images checks except `4.5 Ensure Content trust for Docker is Enabled` 151 | 152 | Note that when submitting checks, provide information why it is a reasonable test to add and please include some kind of official documentation verifying that information. 153 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vbguest.installer_options = { allow_kernel_upgrade: true } 3 | config.vm.provider "virtualbox" do |v| 4 | v.memory = 2048 5 | v.cpus = 2 6 | v.customize ["modifyvm", :id, "--uart1", "0x3F8", "4"] 7 | v.customize ["modifyvm", :id, "--uartmode1", "file", File::NULL] 8 | end 9 | 10 | config.vm.define "jammy" do |jammy| 11 | jammy.ssh.extra_args = ["-o","ConnectTimeout=600"] 12 | jammy.ssh.insert_key = true 13 | jammy.vm.boot_timeout = 600 14 | jammy.vm.box = "ubuntu/jammy64" 15 | jammy.vm.hostname = "jammy" 16 | jammy.vm.provision "shell", 17 | inline: "apt-get update && curl -sSL get.docker.com | sh && addgroup vagrant docker" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docker-bench-security.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -------------------------------------------------------------------------------------------- 3 | # Docker Bench for Security 4 | # 5 | # Docker, Inc. (c) 2015-2022 6 | # 7 | # Checks for dozens of common best-practices around deploying Docker containers in production. 8 | # -------------------------------------------------------------------------------------------- 9 | 10 | version='1.6.0' 11 | 12 | LIBEXEC="." # Distributions can change this to /usr/libexec or similar. 13 | 14 | # Load dependencies 15 | . $LIBEXEC/functions/functions_lib.sh 16 | . $LIBEXEC/functions/helper_lib.sh 17 | 18 | # Setup the paths 19 | this_path=$(abspath "$0") ## Path of this file including filename 20 | myname=$(basename "${this_path%.*}") ## file name of this script. 21 | 22 | readonly version 23 | readonly this_path 24 | readonly myname 25 | 26 | export PATH="$PATH:/bin:/sbin:/usr/bin:/usr/local/bin:/usr/sbin/" 27 | 28 | # Check for required program(s) 29 | req_programs 'awk docker grep sed stat tail tee tr wc xargs' 30 | 31 | # Ensure we can connect to docker daemon 32 | if ! docker ps -q >/dev/null 2>&1; then 33 | printf "Error connecting to docker daemon (does docker ps work?)\n" 34 | exit 1 35 | fi 36 | 37 | usage () { 38 | cat < 67 | Full documentation: 68 | Released under the Apache-2.0 License. 69 | EOF 70 | } 71 | 72 | # Default values 73 | if [ ! -d log ]; then 74 | mkdir log 75 | fi 76 | 77 | logger="log/${myname}.log" 78 | limit=0 79 | printremediation="0" 80 | globalRemediation="" 81 | 82 | # Get the flags 83 | # If you add an option here, please 84 | # remember to update usage() above. 85 | while getopts bhl:u:c:e:i:x:t:n:p args 86 | do 87 | case $args in 88 | b) nocolor="nocolor";; 89 | h) usage; exit 0 ;; 90 | l) logger="$OPTARG" ;; 91 | u) dockertrustusers="$OPTARG" ;; 92 | c) check="$OPTARG" ;; 93 | e) checkexclude="$OPTARG" ;; 94 | i) include="$OPTARG" ;; 95 | x) exclude="$OPTARG" ;; 96 | t) labels="$OPTARG" ;; 97 | n) limit="$OPTARG" ;; 98 | p) printremediation="1" ;; 99 | *) usage; exit 1 ;; 100 | esac 101 | done 102 | 103 | # Load output formating 104 | . $LIBEXEC/functions/output_lib.sh 105 | 106 | yell_info 107 | 108 | # Warn if not root 109 | if [ "$(id -u)" != "0" ]; then 110 | warn "$(yell 'Some tests might require root to run')\n" 111 | sleep 3 112 | fi 113 | 114 | # Total Score 115 | # Warn Scored -1, Pass Scored +1, Not Score -0 116 | 117 | totalChecks=0 118 | currentScore=0 119 | 120 | logit "Initializing $(date +%Y-%m-%dT%H:%M:%S%:z)\n" 121 | beginjson "$version" "$(date +%s)" 122 | 123 | # Load all the tests from tests/ and run them 124 | main () { 125 | logit "\n${bldylw}Section A - Check results${txtrst}" 126 | 127 | # Get configuration location 128 | get_docker_configuration_file 129 | 130 | # If there is a container with label docker_bench_security, memorize it: 131 | benchcont="nil" 132 | for c in $(docker ps | sed '1d' | awk '{print $NF}'); do 133 | if docker inspect --format '{{ .Config.Labels }}' "$c" | \ 134 | grep -e 'docker.bench.security' >/dev/null 2>&1; then 135 | benchcont="$c" 136 | fi 137 | done 138 | 139 | # Get the image id of the docker_bench_security_image, memorize it: 140 | benchimagecont="nil" 141 | for c in $(docker images | sed '1d' | awk '{print $3}'); do 142 | if docker inspect --format '{{ .Config.Labels }}' "$c" | \ 143 | grep -e 'docker.bench.security' >/dev/null 2>&1; then 144 | benchimagecont="$c" 145 | fi 146 | done 147 | 148 | # Format LABELS 149 | for label in $(echo "$labels" | sed 's/,/ /g'); do 150 | LABELS="$LABELS --filter label=$label" 151 | done 152 | 153 | if [ -n "$include" ]; then 154 | pattern=$(echo "$include" | sed 's/,/|/g') 155 | containers=$(docker ps $LABELS| sed '1d' | awk '{print $NF}' | grep -v "$benchcont" | grep -E "$pattern") 156 | images=$(docker images $LABELS| sed '1d' | grep -E "$pattern" | awk '{print $3}' | grep -v "$benchimagecont") 157 | elif [ -n "$exclude" ]; then 158 | pattern=$(echo "$exclude" | sed 's/,/|/g') 159 | containers=$(docker ps $LABELS| sed '1d' | awk '{print $NF}' | grep -v "$benchcont" | grep -Ev "$pattern") 160 | images=$(docker images $LABELS| sed '1d' | grep -Ev "$pattern" | awk '{print $3}' | grep -v "$benchimagecont") 161 | else 162 | containers=$(docker ps $LABELS| sed '1d' | awk '{print $NF}' | grep -v "$benchcont") 163 | images=$(docker images -q $LABELS| grep -v "$benchcont") 164 | fi 165 | 166 | for test in $LIBEXEC/tests/*.sh; do 167 | . "$test" 168 | done 169 | 170 | if [ -z "$check" ] && [ ! "$checkexclude" ]; then 171 | # No options just run 172 | cis 173 | elif [ -z "$check" ]; then 174 | # No check defined but excludes defined set to calls in cis() function 175 | check=$(sed -ne "/cis() {/,/}/{/{/d; /}/d; p;}" functions/functions_lib.sh) 176 | fi 177 | 178 | for c in $(echo "$check" | sed "s/,/ /g"); do 179 | if ! command -v "$c" 2>/dev/null 1>&2; then 180 | echo "Check \"$c\" doesn't seem to exist." 181 | continue 182 | fi 183 | if [ -z "$checkexclude" ]; then 184 | # No excludes just run the checks specified 185 | "$c" 186 | else 187 | # Exludes specified and check exists 188 | checkexcluded="$(echo ",$checkexclude" | sed -e 's/^/\^/g' -e 's/,/\$|/g' -e 's/$/\$/g')" 189 | 190 | if echo "$c" | grep -E "$checkexcluded" 2>/dev/null 1>&2; then 191 | # Excluded 192 | continue 193 | elif echo "$c" | grep -vE 'check_[0-9]|check_[a-z]' 2>/dev/null 1>&2; then 194 | # Function not a check, fill loop_checks with all check from function 195 | loop_checks="$(sed -ne "/$c() {/,/}/{/{/d; /}/d; p;}" functions/functions_lib.sh)" 196 | else 197 | # Just one check 198 | loop_checks="$c" 199 | fi 200 | 201 | for lc in $loop_checks; do 202 | if echo "$lc" | grep -vE "$checkexcluded" 2>/dev/null 1>&2; then 203 | # Not excluded 204 | "$lc" 205 | fi 206 | done 207 | fi 208 | done 209 | 210 | if [ -n "${globalRemediation}" ] && [ "$printremediation" = "1" ]; then 211 | logit "\n\n${bldylw}Section B - Remediation measures${txtrst}" 212 | logit "${globalRemediation}" 213 | fi 214 | 215 | logit "\n\n${bldylw}Section C - Score${txtrst}\n" 216 | info "Checks: $totalChecks" 217 | info "Score: $currentScore\n" 218 | 219 | endjson "$totalChecks" "$currentScore" "$(date +%s)" 220 | } 221 | 222 | main "$@" 223 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | docker-bench-security: 3 | # use image if you have a dedicated build step: 4 | # docker build --rm -t docker-bench-security . 5 | # image: docker-bench-security 6 | 7 | # use build path to Dockerfile if docker-compose should build the image 8 | build: . 9 | 10 | cap_add: 11 | - audit_control 12 | labels: 13 | - docker_bench_security 14 | pid: host 15 | stdin_open: true 16 | tty: true 17 | volumes: 18 | - /var/lib:/var/lib:ro 19 | - /var/run/docker.sock:/var/run/docker.sock:ro 20 | - /usr/lib/systemd:/usr/lib/systemd:ro 21 | - /etc:/etc:ro 22 | -------------------------------------------------------------------------------- /functions/functions_lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | host_configuration() { 4 | check_1 5 | check_1_1 6 | check_1_1_1 7 | check_1_1_2 8 | check_1_1_3 9 | check_1_1_4 10 | check_1_1_5 11 | check_1_1_6 12 | check_1_1_7 13 | check_1_1_8 14 | check_1_1_9 15 | check_1_1_10 16 | check_1_1_11 17 | check_1_1_12 18 | check_1_1_13 19 | check_1_1_14 20 | check_1_1_15 21 | check_1_1_16 22 | check_1_1_17 23 | check_1_1_18 24 | check_1_2 25 | check_1_2_1 26 | check_1_2_2 27 | check_1_end 28 | } 29 | 30 | host_configuration_level1() { 31 | check_1 32 | check_1_end 33 | } 34 | 35 | linux_hosts_specific_configuration() { 36 | check_1_1 37 | check_1_1_1 38 | check_1_1_2 39 | check_1_1_3 40 | check_1_1_4 41 | check_1_1_5 42 | check_1_1_6 43 | check_1_1_7 44 | check_1_1_8 45 | check_1_1_9 46 | check_1_1_10 47 | check_1_1_11 48 | check_1_1_12 49 | check_1_1_13 50 | check_1_1_14 51 | check_1_1_15 52 | check_1_1_16 53 | check_1_1_17 54 | check_1_1_18 55 | } 56 | 57 | host_general_configuration() { 58 | check_1 59 | check_1_2 60 | check_1_2_1 61 | check_1_2_2 62 | check_1_end 63 | } 64 | 65 | docker_daemon_configuration() { 66 | check_2 67 | check_2_1 68 | check_2_2 69 | check_2_3 70 | check_2_4 71 | check_2_5 72 | check_2_6 73 | check_2_7 74 | check_2_8 75 | check_2_9 76 | check_2_10 77 | check_2_11 78 | check_2_12 79 | check_2_13 80 | check_2_14 81 | check_2_15 82 | check_2_16 83 | check_2_17 84 | check_2_18 85 | check_2_end 86 | } 87 | 88 | docker_daemon_configuration_level1() { 89 | check_2 90 | check_2_end 91 | } 92 | 93 | docker_daemon_files() { 94 | check_3 95 | check_3_1 96 | check_3_2 97 | check_3_3 98 | check_3_4 99 | check_3_5 100 | check_3_6 101 | check_3_7 102 | check_3_8 103 | check_3_9 104 | check_3_10 105 | check_3_11 106 | check_3_12 107 | check_3_13 108 | check_3_14 109 | check_3_15 110 | check_3_16 111 | check_3_17 112 | check_3_18 113 | check_3_19 114 | check_3_20 115 | check_3_21 116 | check_3_22 117 | check_3_23 118 | check_3_24 119 | check_3_end 120 | } 121 | 122 | docker_daemon_files_level1() { 123 | check_3 124 | check_3_end 125 | } 126 | 127 | container_images() { 128 | check_4 129 | check_4_1 130 | check_4_2 131 | check_4_3 132 | check_4_4 133 | check_4_5 134 | check_4_6 135 | check_4_7 136 | check_4_8 137 | check_4_9 138 | check_4_10 139 | check_4_11 140 | check_4_12 141 | check_4_end 142 | } 143 | 144 | container_images_level1() { 145 | check_4 146 | check_4_end 147 | } 148 | 149 | container_runtime() { 150 | check_5 151 | check_running_containers 152 | check_5_1 153 | check_5_2 154 | check_5_3 155 | check_5_4 156 | check_5_5 157 | check_5_6 158 | check_5_7 159 | check_5_8 160 | check_5_9 161 | check_5_10 162 | check_5_11 163 | check_5_12 164 | check_5_13 165 | check_5_14 166 | check_5_15 167 | check_5_16 168 | check_5_17 169 | check_5_18 170 | check_5_19 171 | check_5_20 172 | check_5_21 173 | check_5_22 174 | check_5_23 175 | check_5_24 176 | check_5_25 177 | check_5_26 178 | check_5_27 179 | check_5_28 180 | check_5_29 181 | check_5_30 182 | check_5_31 183 | check_5_32 184 | check_5_end 185 | } 186 | 187 | container_runtime_level1() { 188 | check_5 189 | check_5_end 190 | } 191 | 192 | docker_security_operations() { 193 | check_6 194 | check_6_1 195 | check_6_2 196 | check_6_end 197 | } 198 | 199 | docker_security_operations_level1() { 200 | check_6 201 | check_6_1 202 | check_6_2 203 | check_6_end 204 | } 205 | 206 | docker_swarm_configuration() { 207 | check_7 208 | check_7_1 209 | check_7_2 210 | check_7_3 211 | check_7_4 212 | check_7_5 213 | check_7_6 214 | check_7_7 215 | check_7_8 216 | check_7_9 217 | check_7_end 218 | } 219 | 220 | docker_swarm_configuration_level1() { 221 | check_7 222 | check_7_end 223 | } 224 | 225 | docker_enterprise_configuration() { 226 | check_8 227 | check_product_license 228 | check_8_1 229 | check_8_1_1 230 | check_8_1_2 231 | check_8_1_3 232 | check_8_1_4 233 | check_8_1_5 234 | check_8_1_6 235 | check_8_1_7 236 | check_8_2 237 | check_8_2_1 238 | check_8_end 239 | } 240 | 241 | docker_enterprise_configuration_level1() { 242 | check_8 243 | check_product_license 244 | check_8_1 245 | check_8_1_1 246 | check_8_1_2 247 | check_8_1_3 248 | check_8_1_4 249 | check_8_1_5 250 | check_8_1_6 251 | check_8_1_7 252 | check_8_2 253 | check_8_2_1 254 | check_8_end 255 | } 256 | 257 | universal_control_plane_configuration() { 258 | check_8 259 | check_8_1 260 | check_8_1_1 261 | check_8_1_2 262 | check_8_1_3 263 | check_8_1_4 264 | check_8_1_5 265 | check_8_1_6 266 | check_8_1_7 267 | check_8_end 268 | } 269 | 270 | docker_trusted_registry_configuration() { 271 | check_8 272 | check_8_2 273 | check_8_2_1 274 | check_8_end 275 | } 276 | 277 | community_checks() { 278 | check_c 279 | check_c_1 280 | check_c_1_1 281 | check_c_2 282 | check_c_5_3_1 283 | check_c_5_3_2 284 | check_c_5_3_3 285 | check_c_5_3_4 286 | check_c_end 287 | } 288 | 289 | # CIS 290 | cis() { 291 | host_configuration 292 | docker_daemon_configuration 293 | docker_daemon_files 294 | container_images 295 | container_runtime 296 | docker_security_operations 297 | docker_swarm_configuration 298 | } 299 | 300 | cis_level1() { 301 | host_configuration_level1 302 | docker_daemon_configuration_level1 303 | docker_daemon_files_level1 304 | container_images_level1 305 | container_runtime_level1 306 | docker_security_operations_level1 307 | docker_swarm_configuration_level1 308 | } 309 | 310 | cis_controls_v8_ig1() { 311 | check_1_1_2 312 | check_1_1_3 313 | check_2_1 314 | check_2_13 315 | check_2_14 316 | check_3_1 317 | check_3_2 318 | check_3_3 319 | check_3_4 320 | check_3_5 321 | check_3_6 322 | check_3_7 323 | check_3_8 324 | check_3_9 325 | check_3_10 326 | check_3_11 327 | check_3_12 328 | check_3_13 329 | check_3_14 330 | check_3_15 331 | check_3_16 332 | check_3_17 333 | check_3_18 334 | check_3_19 335 | check_3_20 336 | check_3_21 337 | check_3_22 338 | check_3_23 339 | check_3_24 340 | check_4_8 341 | check_4_11 342 | check_5_5 343 | check_5_14 344 | check_5_18 345 | check_5_22 346 | check_5_23 347 | check_5_24 348 | check_5_25 349 | check_5_26 350 | check_5_32 351 | check_7_2 352 | check_7_6 353 | check_7_7 354 | check_7_8 355 | } 356 | 357 | cis_controls_v8_ig2() { 358 | check_1_1_1 359 | check_1_1_2 360 | check_1_1_3 361 | check_1_1_4 362 | check_1_1_5 363 | check_1_1_6 364 | check_1_1_7 365 | check_1_1_8 366 | check_1_1_9 367 | check_1_1_10 368 | check_1_1_11 369 | check_1_1_12 370 | check_1_1_13 371 | check_1_1_14 372 | check_1_1_15 373 | check_1_1_16 374 | check_1_1_17 375 | check_1_1_18 376 | check_1_2_1 377 | check_1_2_2 378 | check_2_1 379 | check_2_2 380 | check_2_3 381 | check_2_4 382 | check_2_5 383 | check_2_7 384 | check_2_8 385 | check_2_11 386 | check_2_13 387 | check_2_14 388 | check_2_15 389 | check_2_16 390 | check_2_18 391 | check_3_1 392 | check_3_2 393 | check_3_3 394 | check_3_4 395 | check_3_5 396 | check_3_6 397 | check_3_7 398 | check_3_8 399 | check_3_9 400 | check_3_10 401 | check_3_11 402 | check_3_12 403 | check_3_13 404 | check_3_14 405 | check_3_15 406 | check_3_16 407 | check_3_17 408 | check_3_18 409 | check_3_19 410 | check_3_20 411 | check_3_21 412 | check_3_22 413 | check_3_23 414 | check_3_24 415 | check_4_2 416 | check_4_3 417 | check_4_4 418 | check_4_7 419 | check_4_8 420 | check_4_9 421 | check_4_11 422 | check_5_1 423 | check_5_2 424 | check_5_3 425 | check_5_4 426 | check_5_5 427 | check_5_7 428 | check_5_10 429 | check_5_11 430 | check_5_12 431 | check_5_14 432 | check_5_16 433 | check_5_17 434 | check_5_18 435 | check_5_19 436 | check_5_21 437 | check_5_22 438 | check_5_23 439 | check_5_24 440 | check_5_25 441 | check_5_26 442 | check_5_27 443 | check_5_30 444 | check_5_31 445 | check_5_32 446 | check_6_1 447 | check_6_2 448 | check_7_2 449 | check_7_3 450 | check_7_5 451 | check_7_6 452 | check_7_7 453 | check_7_8 454 | check_7_9 455 | } 456 | 457 | cis_controls_v8_ig3() { 458 | check_1_1_1 459 | check_1_1_2 460 | check_1_1_3 461 | check_1_1_4 462 | check_1_1_5 463 | check_1_1_6 464 | check_1_1_7 465 | check_1_1_8 466 | check_1_1_9 467 | check_1_1_10 468 | check_1_1_11 469 | check_1_1_12 470 | check_1_1_13 471 | check_1_1_14 472 | check_1_1_15 473 | check_1_1_16 474 | check_1_1_17 475 | check_1_1_18 476 | check_1_2_1 477 | check_1_2_2 478 | check_2_1 479 | check_2_2 480 | check_2_3 481 | check_2_4 482 | check_2_5 483 | check_2_7 484 | check_2_8 485 | check_2_11 486 | check_2_13 487 | check_2_14 488 | check_2_15 489 | check_2_16 490 | check_2_18 491 | check_3_1 492 | check_3_2 493 | check_3_3 494 | check_3_4 495 | check_3_5 496 | check_3_6 497 | check_3_7 498 | check_3_8 499 | check_3_9 500 | check_3_10 501 | check_3_11 502 | check_3_12 503 | check_3_13 504 | check_3_14 505 | check_3_15 506 | check_3_16 507 | check_3_17 508 | check_3_18 509 | check_3_19 510 | check_3_20 511 | check_3_21 512 | check_3_22 513 | check_3_23 514 | check_3_24 515 | check_4_2 516 | check_4_3 517 | check_4_4 518 | check_4_6 519 | check_4_7 520 | check_4_8 521 | check_4_9 522 | check_4_11 523 | check_4_12 524 | check_5_1 525 | check_5_2 526 | check_5_3 527 | check_5_4 528 | check_5_5 529 | check_5_7 530 | check_5_8 531 | check_5_9 532 | check_5_10 533 | check_5_11 534 | check_5_12 535 | check_5_14 536 | check_5_16 537 | check_5_17 538 | check_5_18 539 | check_5_19 540 | check_5_21 541 | check_5_22 542 | check_5_23 543 | check_5_24 544 | check_5_25 545 | check_5_26 546 | check_5_27 547 | check_5_30 548 | check_5_31 549 | check_5_32 550 | check_6_1 551 | check_6_2 552 | check_7_2 553 | check_7_3 554 | check_7_5 555 | check_7_6 556 | check_7_7 557 | check_7_8 558 | check_7_9 559 | } 560 | 561 | # Community contributed 562 | community() { 563 | community_checks 564 | } 565 | 566 | # All 567 | all() { 568 | cis 569 | docker_enterprise_configuration 570 | community 571 | } 572 | -------------------------------------------------------------------------------- /functions/helper_lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Returns the absolute path of a given string 4 | abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; } 5 | 6 | # Audit rules default path 7 | auditrules="/etc/audit/audit.rules" 8 | 9 | # Check for required program(s) 10 | req_programs() { 11 | for p in $1; do 12 | command -v "$p" >/dev/null 2>&1 || { printf "Required program not found: %s\n" "$p"; exit 1; } 13 | done 14 | if command -v jq >/dev/null 2>&1; then 15 | HAVE_JQ=true 16 | else 17 | HAVE_JQ=false 18 | fi 19 | if command -v ss >/dev/null 2>&1; then 20 | netbin=ss 21 | return 22 | fi 23 | if command -v netstat >/dev/null 2>&1; then 24 | netbin=netstat 25 | return 26 | fi 27 | echo "ss or netstat command not found." 28 | exit 1 29 | } 30 | 31 | # Compares versions of software of the format X.Y.Z 32 | do_version_check() { 33 | [ "$1" = "$2" ] && return 10 34 | 35 | ver1front=$(printf "%s" "$1" | cut -d "." -f -1) 36 | ver1back=$(printf "%s" "$1" | cut -d "." -f 2-) 37 | ver2front=$(printf "%s" "$2" | cut -d "." -f -1) 38 | ver2back=$(printf "%s" "$2" | cut -d "." -f 2-) 39 | 40 | if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then 41 | [ "$ver1front" -gt "$ver2front" ] && return 11 42 | [ "$ver1front" -lt "$ver2front" ] && return 9 43 | 44 | [ "$ver1front" = "$1" ] || [ -z "$ver1back" ] && ver1back=0 45 | [ "$ver2front" = "$2" ] || [ -z "$ver2back" ] && ver2back=0 46 | do_version_check "$ver1back" "$ver2back" 47 | return $? 48 | fi 49 | [ "$1" -gt "$2" ] && return 11 || return 9 50 | } 51 | 52 | # Extracts commandline args from the newest running processes named like the first parameter 53 | get_command_line_args() { 54 | PROC="$1" 55 | 56 | for PID in $(pgrep -f -n "$PROC"); do 57 | tr "\0" " " < /proc/"$PID"/cmdline 58 | done 59 | } 60 | 61 | # Extract the cumulative command line arguments for the docker daemon 62 | # 63 | # If specified multiple times, all matches are returned. 64 | # Accounts for long and short variants, call with short option. 65 | # Does not account for option defaults or implicit options. 66 | get_docker_cumulative_command_line_args() { 67 | OPTION="$1" 68 | 69 | line_arg="dockerd" 70 | if ! get_command_line_args "docker daemon" >/dev/null 2>&1 ; then 71 | line_arg="docker daemon" 72 | fi 73 | 74 | get_command_line_args "$line_arg" | 75 | # normalize known long options to their short versions 76 | sed \ 77 | -e 's/\-\-debug/-D/g' \ 78 | -e 's/\-\-host/-H/g' \ 79 | -e 's/\-\-log-level/-l/g' \ 80 | -e 's/\-\-version/-v/g' \ 81 | | 82 | # normalize parameters separated by space(s) to -O=VALUE 83 | sed \ 84 | -e 's/\-\([DHlv]\)[= ]\([^- ][^ ]\)/-\1=\2/g' \ 85 | | 86 | # get the last interesting option 87 | tr ' ' "\n" | 88 | grep "^${OPTION}" | 89 | # normalize quoting of values 90 | sed \ 91 | -e 's/"//g' \ 92 | -e "s/'//g" 93 | } 94 | 95 | # Extract the effective command line arguments for the docker daemon 96 | # 97 | # Accounts for multiple specifications, takes the last option. 98 | # Accounts for long and short variants, call with short option 99 | # Does not account for option default or implicit options. 100 | get_docker_effective_command_line_args() { 101 | OPTION="$1" 102 | get_docker_cumulative_command_line_args "$OPTION" | tail -n1 103 | } 104 | 105 | get_docker_configuration_file() { 106 | FILE="$(get_docker_effective_command_line_args '--config-file' | \ 107 | sed 's/.*=//g')" 108 | 109 | if [ -f "$FILE" ]; then 110 | CONFIG_FILE="$FILE" 111 | return 112 | fi 113 | if [ -f '/etc/docker/daemon.json' ]; then 114 | CONFIG_FILE='/etc/docker/daemon.json' 115 | return 116 | fi 117 | CONFIG_FILE='/dev/null' 118 | } 119 | 120 | get_docker_configuration_file_args() { 121 | OPTION="$1" 122 | 123 | get_docker_configuration_file 124 | 125 | if "$HAVE_JQ"; then 126 | jq --monochrome-output --raw-output "if has(\"${OPTION}\") then .[\"${OPTION}\"] else \"\" end" "$CONFIG_FILE" 127 | else 128 | cat "$CONFIG_FILE" | tr , '\n' | grep "$OPTION" | sed 's/.*://g' | tr -d '" ', 129 | fi 130 | } 131 | 132 | get_service_file() { 133 | SERVICE="$1" 134 | 135 | if [ -f "/etc/systemd/system/$SERVICE" ]; then 136 | echo "/etc/systemd/system/$SERVICE" 137 | return 138 | fi 139 | if [ -f "/lib/systemd/system/$SERVICE" ]; then 140 | echo "/lib/systemd/system/$SERVICE" 141 | return 142 | fi 143 | if find /run -name "$SERVICE" 2> /dev/null 1>&2; then 144 | find /run -name "$SERVICE" | head -n1 145 | return 146 | fi 147 | if [ "$(systemctl show -p FragmentPath "$SERVICE" | sed 's/.*=//')" != "" ]; then 148 | systemctl show -p FragmentPath "$SERVICE" | sed 's/.*=//' 149 | return 150 | fi 151 | echo "/usr/lib/systemd/system/$SERVICE" 152 | } 153 | 154 | yell_info() { 155 | yell "# -------------------------------------------------------------------------------------------- 156 | # Docker Bench for Security v$version 157 | # 158 | # Docker, Inc. (c) 2015-$(date +"%Y") 159 | # 160 | # Checks for dozens of common best-practices around deploying Docker containers in production. 161 | # Based on the CIS Docker Benchmark 1.6.0. 162 | # --------------------------------------------------------------------------------------------" 163 | } 164 | -------------------------------------------------------------------------------- /functions/output_lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bldred='\033[1;31m' # Bold Red 4 | bldgrn='\033[1;32m' # Bold Green 5 | bldblu='\033[1;34m' # Bold Blue 6 | bldylw='\033[1;33m' # Bold Yellow 7 | txtrst='\033[0m' 8 | 9 | if [ -n "$nocolor" ] && [ "$nocolor" = "nocolor" ]; then 10 | bldred='' 11 | bldgrn='' 12 | bldblu='' 13 | bldylw='' 14 | txtrst='' 15 | fi 16 | 17 | logit () { 18 | printf "%b\n" "$1" | tee -a "$logger" 19 | } 20 | 21 | info () { 22 | local infoCountCheck 23 | local OPTIND c 24 | while getopts c args 25 | do 26 | case $args in 27 | c) infoCountCheck="true" ;; 28 | *) exit 1 ;; 29 | esac 30 | done 31 | if [ "$infoCountCheck" = "true" ]; then 32 | printf "%b\n" "${bldblu}[INFO]${txtrst} $2" | tee -a "$logger" 33 | totalChecks=$((totalChecks + 1)) 34 | return 35 | fi 36 | printf "%b\n" "${bldblu}[INFO]${txtrst} $1" | tee -a "$logger" 37 | } 38 | 39 | pass () { 40 | local passScored 41 | local passCountCheck 42 | local OPTIND s c 43 | while getopts sc args 44 | do 45 | case $args in 46 | s) passScored="true" ;; 47 | c) passCountCheck="true" ;; 48 | *) exit 1 ;; 49 | esac 50 | done 51 | if [ "$passScored" = "true" ] || [ "$passCountCheck" = "true" ]; then 52 | printf "%b\n" "${bldgrn}[PASS]${txtrst} $2" | tee -a "$logger" 53 | totalChecks=$((totalChecks + 1)) 54 | fi 55 | if [ "$passScored" = "true" ]; then 56 | currentScore=$((currentScore + 1)) 57 | fi 58 | if [ "$passScored" != "true" ] && [ "$passCountCheck" != "true" ]; then 59 | printf "%b\n" "${bldgrn}[PASS]${txtrst} $1" | tee -a "$logger" 60 | fi 61 | } 62 | 63 | warn () { 64 | local warnScored 65 | local OPTIND s 66 | while getopts s args 67 | do 68 | case $args in 69 | s) warnScored="true" ;; 70 | *) exit 1 ;; 71 | esac 72 | done 73 | if [ "$warnScored" = "true" ]; then 74 | printf "%b\n" "${bldred}[WARN]${txtrst} $2" | tee -a "$logger" 75 | totalChecks=$((totalChecks + 1)) 76 | currentScore=$((currentScore - 1)) 77 | return 78 | fi 79 | printf "%b\n" "${bldred}[WARN]${txtrst} $1" | tee -a "$logger" 80 | } 81 | 82 | note () { 83 | local noteCountCheck 84 | local OPTIND c 85 | while getopts c args 86 | do 87 | case $args in 88 | c) noteCountCheck="true" ;; 89 | *) exit 1 ;; 90 | esac 91 | done 92 | if [ "$noteCountCheck" = "true" ]; then 93 | printf "%b\n" "${bldylw}[NOTE]${txtrst} $2" | tee -a "$logger" 94 | totalChecks=$((totalChecks + 1)) 95 | return 96 | fi 97 | printf "%b\n" "${bldylw}[NOTE]${txtrst} $1" | tee -a "$logger" 98 | } 99 | 100 | yell () { 101 | printf "%b\n" "${bldylw}$1${txtrst}\n" 102 | } 103 | 104 | beginjson () { 105 | printf "{\n \"dockerbenchsecurity\": \"%s\",\n \"start\": %s,\n \"tests\": [" "$1" "$2" | tee "$logger.json" 2>/dev/null 1>&2 106 | } 107 | 108 | endjson (){ 109 | printf "\n ],\n \"checks\": %s,\n \"score\": %s,\n \"end\": %s\n}" "$1" "$2" "$3" | tee -a "$logger.json" 2>/dev/null 1>&2 110 | } 111 | 112 | logjson (){ 113 | printf "\n \"%s\": \"%s\"," "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 114 | } 115 | 116 | SSEP= 117 | SEP= 118 | startsectionjson() { 119 | printf "%s\n {\n \"id\": \"%s\",\n \"desc\": \"%s\",\n \"results\": [" "$SSEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 120 | SEP= 121 | SSEP="," 122 | } 123 | 124 | endsectionjson() { 125 | printf "\n ]\n }" | tee -a "$logger.json" 2>/dev/null 1>&2 126 | } 127 | 128 | starttestjson() { 129 | printf "%s\n {\n \"id\": \"%s\",\n \"desc\": \"%s\",\n " "$SEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 130 | SEP="," 131 | } 132 | 133 | log_to_json() { 134 | if [ $# -eq 1 ]; then 135 | printf "\"result\": \"%s\"" "$1" | tee -a "$logger.json" 2>/dev/null 1>&2 136 | return 137 | fi 138 | if [ $# -eq 2 ] && [ $# -ne 1 ]; then 139 | # Result also contains details 140 | printf "\"result\": \"%s\",\n \"details\": \"%s\"" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 141 | return 142 | fi 143 | # Result also includes details and a list of items. Add that directly to details and to an array property "items" 144 | # Also limit the number of items to $limit, if $limit is non-zero 145 | truncItems=$3 146 | if [ "$limit" != 0 ]; then 147 | truncItems="" 148 | ITEM_COUNT=0 149 | for item in $3; do 150 | truncItems="$truncItems $item" 151 | ITEM_COUNT=$((ITEM_COUNT + 1)); 152 | if [ "$ITEM_COUNT" == "$limit" ]; then 153 | truncItems="$truncItems (truncated)" 154 | break; 155 | fi 156 | done 157 | fi 158 | itemsJson=$(printf "[\n "; ISEP=""; ITEMCOUNT=0; for item in $truncItems; do printf "%s\"%s\"" "$ISEP" "$item"; ISEP=","; done; printf "\n ]") 159 | printf "\"result\": \"%s\",\n \"details\": \"%s: %s\",\n \"items\": %s" "$1" "$2" "$truncItems" "$itemsJson" | tee -a "$logger.json" 2>/dev/null 1>&2 160 | } 161 | 162 | logcheckresult() { 163 | # Log to JSON 164 | log_to_json "$@" 165 | 166 | # Log remediation measure to JSON 167 | if [ -n "$remediation" ] && [ "$1" != "PASS" ] && [ "$printremediation" = "1" ]; then 168 | printf ",\n \"remediation\": \"%s\"" "$remediation" | tee -a "$logger.json" 2>/dev/null 1>&2 169 | if [ -n "$remediationImpact" ]; then 170 | printf ",\n \"remediation-impact\": \"%s\"" "$remediationImpact" | tee -a "$logger.json" 2>/dev/null 1>&2 171 | fi 172 | fi 173 | printf "\n }" | tee -a "$logger.json" 2>/dev/null 1>&2 174 | 175 | # Save remediation measure for print log to stdout 176 | if [ -n "$remediation" ] && [ "$1" != "PASS" ]; then 177 | if [ -n "${checkHeader}" ]; then 178 | if [ -n "${addSpaceHeader}" ]; then 179 | globalRemediation="${globalRemediation}\n" 180 | fi 181 | globalRemediation="${globalRemediation}\n${bldblu}[INFO]${txtrst} ${checkHeader}" 182 | checkHeader="" 183 | addSpaceHeader="1" 184 | fi 185 | globalRemediation="${globalRemediation}\n${bldblu}[INFO]${txtrst} ${id} - ${remediation}" 186 | if [ -n "${remediationImpact}" ]; then 187 | globalRemediation="${globalRemediation} Remediation Impact: ${remediationImpact}" 188 | fi 189 | fi 190 | } 191 | -------------------------------------------------------------------------------- /img/benchmark_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docker/docker-bench-security/ff26d67f25be60fc2d82a01710ee14570b2cb9db/img/benchmark_log.png -------------------------------------------------------------------------------- /tests/1_host_configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_1() { 4 | logit "" 5 | local id="1" 6 | local desc="Host Configuration" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_1_1() { 13 | local id="1.1" 14 | local desc="Linux Hosts Specific Configuration" 15 | local check="$id - $desc" 16 | info "$check" 17 | } 18 | 19 | check_1_1_1() { 20 | local id="1.1.1" 21 | local desc="Ensure a separate partition for containers has been created (Automated)" 22 | local remediation="For new installations, you should create a separate partition for the /var/lib/docker mount point. For systems that have already been installed, you should use the Logical Volume Manager (LVM) within Linux to create a new partition." 23 | local remediationImpact="None." 24 | local check="$id - $desc" 25 | starttestjson "$id" "$desc" 26 | 27 | docker_root_dir=$(docker info -f '{{ .DockerRootDir }}') 28 | if docker info | grep -q userns ; then 29 | docker_root_dir=$(readlink -f "$docker_root_dir/..") 30 | fi 31 | 32 | if mountpoint -q -- "$docker_root_dir" >/dev/null 2>&1; then 33 | pass -s "$check" 34 | logcheckresult "PASS" 35 | return 36 | fi 37 | warn -s "$check" 38 | logcheckresult "WARN" 39 | } 40 | 41 | check_1_1_2() { 42 | local id="1.1.2" 43 | local desc="Ensure only trusted users are allowed to control Docker daemon (Automated)" 44 | local remediation="You should remove any untrusted users from the docker group using command sudo gpasswd -d docker or add trusted users to the docker group using command sudo usermod -aG docker . You should not create a mapping of sensitive directories from the host to container volumes." 45 | local remediationImpact="Only trust user are allow to build and execute containers as normal user." 46 | local check="$id - $desc" 47 | starttestjson "$id" "$desc" 48 | 49 | docker_users=$(grep 'docker' /etc/group) 50 | if command -v getent >/dev/null 2>&1; then 51 | docker_users=$(getent group docker) 52 | fi 53 | docker_users=$(printf "%s" "$docker_users" | awk -F: '{print $4}') 54 | 55 | local doubtfulusers="" 56 | if [ -n "$dockertrustusers" ]; then 57 | for u in $(printf "%s" "$docker_users" | sed "s/,/ /g"); do 58 | if ! printf "%s" "$dockertrustusers" | grep -q "$u" ; then 59 | doubtfulusers="$u" 60 | if [ -n "${doubtfulusers}" ]; then 61 | doubtfulusers="${doubtfulusers},$u" 62 | fi 63 | fi 64 | done 65 | else 66 | info -c "$check" 67 | info " * Users: $docker_users" 68 | logcheckresult "INFO" "doubtfulusers" "$docker_users" 69 | fi 70 | 71 | if [ -n "${doubtfulusers}" ]; then 72 | warn -s "$check" 73 | warn " * Doubtful users: $doubtfulusers" 74 | logcheckresult "WARN" "doubtfulusers" "$doubtfulusers" 75 | fi 76 | 77 | if [ -z "${doubtfulusers}" ] && [ -n "${dockertrustusers}" ]; then 78 | pass -s "$check" 79 | logcheckresult "PASS" 80 | fi 81 | } 82 | 83 | check_1_1_3() { 84 | local id="1.1.3" 85 | local desc="Ensure auditing is configured for the Docker daemon (Automated)" 86 | local remediation="Install auditd. Add -w /usr/bin/dockerd -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 87 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 88 | local check="$id - $desc" 89 | starttestjson "$id" "$desc" 90 | 91 | file="/usr/bin/dockerd" 92 | if command -v auditctl >/dev/null 2>&1; then 93 | if auditctl -l | grep "$file" >/dev/null 2>&1; then 94 | pass -s "$check" 95 | logcheckresult "PASS" 96 | return 97 | fi 98 | warn -s "$check" 99 | logcheckresult "WARN" 100 | return 101 | fi 102 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 103 | pass -s "$check" 104 | logcheckresult "PASS" 105 | return 106 | fi 107 | warn -s "$check" 108 | logcheckresult "WARN" 109 | } 110 | 111 | check_1_1_4() { 112 | local id="1.1.4" 113 | local desc="Ensure auditing is configured for Docker files and directories -/run/containerd (Automated)" 114 | local remediation="Install auditd. Add -a exit,always -F path=/run/containerd -F perm=war -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 115 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 116 | local check="$id - $desc" 117 | starttestjson "$id" "$desc" 118 | 119 | file="/run/containerd" 120 | if command -v auditctl >/dev/null 2>&1; then 121 | if auditctl -l | grep "$file" >/dev/null 2>&1; then 122 | pass -s "$check" 123 | logcheckresult "PASS" 124 | return 125 | fi 126 | warn -s "$check" 127 | logcheckresult "WARN" 128 | return 129 | fi 130 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 131 | pass -s "$check" 132 | logcheckresult "PASS" 133 | return 134 | fi 135 | warn -s "$check" 136 | logcheckresult "WARN" 137 | } 138 | 139 | check_1_1_5() { 140 | local id="1.1.5" 141 | local desc="Ensure auditing is configured for Docker files and directories - /var/lib/docker (Automated)" 142 | local remediation="Install auditd. Add -w /var/lib/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 143 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 144 | local check="$id - $desc" 145 | starttestjson "$id" "$desc" 146 | 147 | directory="/var/lib/docker" 148 | if [ -d "$directory" ]; then 149 | if command -v auditctl >/dev/null 2>&1; then 150 | if auditctl -l | grep $directory >/dev/null 2>&1; then 151 | pass -s "$check" 152 | logcheckresult "PASS" 153 | return 154 | fi 155 | warn -s "$check" 156 | logcheckresult "WARN" 157 | return 158 | fi 159 | if grep -s "$directory" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 160 | pass -s "$check" 161 | logcheckresult "PASS" 162 | return 163 | fi 164 | warn -s "$check" 165 | logcheckresult "WARN" 166 | return 167 | fi 168 | info -c "$check" 169 | info " * Directory not found" 170 | logcheckresult "INFO" "Directory not found" 171 | } 172 | 173 | check_1_1_6() { 174 | local id="1.1.6" 175 | local desc="Ensure auditing is configured for Docker files and directories - /etc/docker (Automated)" 176 | local remediation="Install auditd. Add -w /etc/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 177 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 178 | local check="$id - $desc" 179 | starttestjson "$id" "$desc" 180 | 181 | directory="/etc/docker" 182 | if [ -d "$directory" ]; then 183 | if command -v auditctl >/dev/null 2>&1; then 184 | if auditctl -l | grep $directory >/dev/null 2>&1; then 185 | pass -s "$check" 186 | logcheckresult "PASS" 187 | return 188 | fi 189 | warn -s "$check" 190 | logcheckresult "WARN" 191 | return 192 | fi 193 | if grep -s "$directory" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 194 | pass -s "$check" 195 | logcheckresult "PASS" 196 | return 197 | fi 198 | warn -s "$check" 199 | logcheckresult "WARN" 200 | return 201 | fi 202 | info -c "$check" 203 | info " * Directory not found" 204 | logcheckresult "INFO" "Directory not found" 205 | } 206 | 207 | check_1_1_7() { 208 | local id="1.1.7" 209 | local desc="Ensure auditing is configured for Docker files and directories - docker.service (Automated)" 210 | local remediation 211 | remediation="Install auditd. Add -w $(get_service_file docker.service) -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 212 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 213 | local check="$id - $desc" 214 | starttestjson "$id" "$desc" 215 | 216 | file="$(get_service_file docker.service)" 217 | if [ -f "$file" ]; then 218 | if command -v auditctl >/dev/null 2>&1; then 219 | if auditctl -l | grep "$file" >/dev/null 2>&1; then 220 | pass -s "$check" 221 | logcheckresult "PASS" 222 | return 223 | fi 224 | warn -s "$check" 225 | logcheckresult "WARN" 226 | return 227 | fi 228 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 229 | pass -s "$check" 230 | logcheckresult "PASS" 231 | return 232 | fi 233 | warn -s "$check" 234 | logcheckresult "WARN" 235 | return 236 | fi 237 | info -c "$check" 238 | info " * File not found" 239 | logcheckresult "INFO" "File not found" 240 | } 241 | 242 | check_1_1_8() { 243 | local id="1.1.8" 244 | local desc="Ensure auditing is configured for Docker files and directories - containerd.sock (Automated)" 245 | local remediation 246 | remediation="Install auditd. Add -w $(get_service_file containerd.sock) -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 247 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 248 | local check="$id - $desc" 249 | starttestjson "$id" "$desc" 250 | 251 | file="$(get_service_file containerd.sock)" 252 | if [ -e "$file" ]; then 253 | if command -v auditctl >/dev/null 2>&1; then 254 | if auditctl -l | grep "$file" >/dev/null 2>&1; then 255 | pass -s "$check" 256 | logcheckresult "PASS" 257 | return 258 | fi 259 | warn -s "$check" 260 | logcheckresult "WARN" 261 | return 262 | fi 263 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 264 | pass -s "$check" 265 | logcheckresult "PASS" 266 | return 267 | fi 268 | warn -s "$check" 269 | logcheckresult "WARN" 270 | return 271 | fi 272 | info -c "$check" 273 | info " * File not found" 274 | logcheckresult "INFO" "File not found" 275 | } 276 | check_1_1_9() { 277 | local id="1.1.9" 278 | local desc="Ensure auditing is configured for Docker files and directories - docker.socket (Automated)" 279 | local remediation 280 | remediation="Install auditd. Add -w $(get_service_file docker.socket) -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 281 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 282 | local check="$id - $desc" 283 | starttestjson "$id" "$desc" 284 | 285 | file="$(get_service_file docker.socket)" 286 | if [ -e "$file" ]; then 287 | if command -v auditctl >/dev/null 2>&1; then 288 | if auditctl -l | grep "$file" >/dev/null 2>&1; then 289 | pass -s "$check" 290 | logcheckresult "PASS" 291 | return 292 | fi 293 | warn -s "$check" 294 | logcheckresult "WARN" 295 | return 296 | fi 297 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 298 | pass -s "$check" 299 | logcheckresult "PASS" 300 | return 301 | fi 302 | warn -s "$check" 303 | logcheckresult "WARN" 304 | return 305 | fi 306 | info -c "$check" 307 | info " * File not found" 308 | logcheckresult "INFO" "File not found" 309 | } 310 | 311 | check_1_1_10() { 312 | local id="1.1.10" 313 | local desc="Ensure auditing is configured for Docker files and directories - /etc/default/docker (Automated)" 314 | local remediation="Install auditd. Add -w /etc/default/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 315 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 316 | local check="$id - $desc" 317 | starttestjson "$id" "$desc" 318 | 319 | file="/etc/default/docker" 320 | if [ -f "$file" ]; then 321 | if command -v auditctl >/dev/null 2>&1; then 322 | if auditctl -l | grep $file >/dev/null 2>&1; then 323 | pass -s "$check" 324 | logcheckresult "PASS" 325 | return 326 | fi 327 | warn -s "$check" 328 | logcheckresult "WARN" 329 | return 330 | fi 331 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 332 | pass -s "$check" 333 | logcheckresult "PASS" 334 | return 335 | fi 336 | warn -s "$check" 337 | logcheckresult "WARN" 338 | return 339 | fi 340 | info -c "$check" 341 | info " * File not found" 342 | logcheckresult "INFO" "File not found" 343 | } 344 | 345 | check_1_1_11() { 346 | local id="1.1.11" 347 | local desc="Ensure auditing is configured for Dockerfiles and directories - /etc/docker/daemon.json (Automated)" 348 | local remediation="Install auditd. Add -w /etc/docker/daemon.json -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 349 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 350 | local check="$id - $desc" 351 | starttestjson "$id" "$desc" 352 | 353 | file="/etc/docker/daemon.json" 354 | if [ -f "$file" ]; then 355 | if command -v auditctl >/dev/null 2>&1; then 356 | if auditctl -l | grep $file >/dev/null 2>&1; then 357 | pass -s "$check" 358 | logcheckresult "PASS" 359 | return 360 | fi 361 | warn -s "$check" 362 | logcheckresult "WARN" 363 | return 364 | fi 365 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 366 | pass -s "$check" 367 | logcheckresult "PASS" 368 | return 369 | fi 370 | warn -s "$check" 371 | logcheckresult "WARN" 372 | return 373 | fi 374 | info -c "$check" 375 | info " * File not found" 376 | logcheckresult "INFO" "File not found" 377 | } 378 | 379 | check_1_1_12() { 380 | local id="1.1.12" 381 | local desc="1.1.12 Ensure auditing is configured for Dockerfiles and directories - /etc/containerd/config.toml (Automated)" 382 | local remediation="Install auditd. Add -w /etc/containerd/config.toml -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 383 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 384 | local check="$id - $desc" 385 | starttestjson "$id" "$desc" 386 | 387 | file="/etc/containerd/config.toml" 388 | if [ -f "$file" ]; then 389 | if command -v auditctl >/dev/null 2>&1; then 390 | if auditctl -l | grep $file >/dev/null 2>&1; then 391 | pass -s "$check" 392 | logcheckresult "PASS" 393 | return 394 | fi 395 | warn -s "$check" 396 | logcheckresult "WARN" 397 | return 398 | fi 399 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 400 | pass -s "$check" 401 | logcheckresult "PASS" 402 | return 403 | fi 404 | warn -s "$check" 405 | logcheckresult "WARN" 406 | return 407 | fi 408 | info -c "$check" 409 | info " * File not found" 410 | logcheckresult "INFO" "File not found" 411 | } 412 | 413 | check_1_1_13() { 414 | local id="1.1.13" 415 | local desc="Ensure auditing is configured for Docker files and directories - /etc/sysconfig/docker (Automated)" 416 | local remediation="Install auditd. Add -w /etc/sysconfig/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 417 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 418 | local check="$id - $desc" 419 | starttestjson "$id" "$desc" 420 | 421 | file="/etc/sysconfig/docker" 422 | if [ -f "$file" ]; then 423 | if command -v auditctl >/dev/null 2>&1; then 424 | if auditctl -l | grep $file >/dev/null 2>&1; then 425 | pass -s "$check" 426 | logcheckresult "PASS" 427 | return 428 | fi 429 | warn -s "$check" 430 | logcheckresult "WARN" 431 | return 432 | fi 433 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 434 | pass -s "$check" 435 | logcheckresult "PASS" 436 | return 437 | fi 438 | warn -s "$check" 439 | logcheckresult "WARN" 440 | return 441 | fi 442 | info -c "$check" 443 | info " * File not found" 444 | logcheckresult "INFO" "File not found" 445 | } 446 | 447 | check_1_1_14() { 448 | local id="1.1.14" 449 | local desc="Ensure auditing is configured for Docker files and directories - /usr/bin/containerd (Automated)" 450 | local remediation="Install auditd. Add -w /usr/bin/containerd -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 451 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 452 | local check="$id - $desc" 453 | starttestjson "$id" "$desc" 454 | 455 | file="/usr/bin/containerd" 456 | if [ -f "$file" ]; then 457 | if command -v auditctl >/dev/null 2>&1; then 458 | if auditctl -l | grep $file >/dev/null 2>&1; then 459 | pass -s "$check" 460 | logcheckresult "PASS" 461 | return 462 | fi 463 | warn -s "$check" 464 | logcheckresult "WARN" 465 | return 466 | fi 467 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 468 | pass -s "$check" 469 | logcheckresult "PASS" 470 | return 471 | fi 472 | warn -s "$check" 473 | logcheckresult "WARN" 474 | return 475 | fi 476 | info -c "$check" 477 | info " * File not found" 478 | logcheckresult "INFO" "File not found" 479 | } 480 | 481 | check_1_1_15() { 482 | local id="1.1.15" 483 | local desc="Ensure auditing is configured for Docker files and directories - /usr/bin/containerd-shim (Automated)" 484 | local remediation="Install auditd. Add -w /usr/bin/containerd-shim -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 485 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 486 | local check="$id - $desc" 487 | starttestjson "$id" "$desc" 488 | 489 | file="/usr/bin/containerd-shim" 490 | if [ -f "$file" ]; then 491 | if command -v auditctl >/dev/null 2>&1; then 492 | if auditctl -l | grep $file >/dev/null 2>&1; then 493 | pass -s "$check" 494 | logcheckresult "PASS" 495 | return 496 | fi 497 | warn -s "$check" 498 | logcheckresult "WARN" 499 | return 500 | fi 501 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 502 | pass -s "$check" 503 | logcheckresult "PASS" 504 | return 505 | fi 506 | warn -s "$check" 507 | logcheckresult "WARN" 508 | return 509 | fi 510 | info -c "$check" 511 | info " * File not found" 512 | logcheckresult "INFO" "File not found" 513 | } 514 | 515 | check_1_1_16() { 516 | local id="1.1.16" 517 | local desc="Ensure auditing is configured for Docker files and directories - /usr/bin/containerd-shim-runc-v1 (Automated)" 518 | local remediation="Install auditd. Add -w /usr/bin/containerd-shim-runc-v1 -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 519 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 520 | local check="$id - $desc" 521 | starttestjson "$id" "$desc" 522 | 523 | file="/usr/bin/containerd-shim-runc-v1" 524 | if [ -f "$file" ]; then 525 | if command -v auditctl >/dev/null 2>&1; then 526 | if auditctl -l | grep $file >/dev/null 2>&1; then 527 | pass -s "$check" 528 | logcheckresult "PASS" 529 | return 530 | fi 531 | warn -s "$check" 532 | logcheckresult "WARN" 533 | return 534 | fi 535 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 536 | pass -s "$check" 537 | logcheckresult "PASS" 538 | return 539 | fi 540 | warn -s "$check" 541 | logcheckresult "WARN" 542 | return 543 | fi 544 | info -c "$check" 545 | info " * File not found" 546 | logcheckresult "INFO" "File not found" 547 | } 548 | 549 | check_1_1_17() { 550 | local id="1.1.17" 551 | local desc="Ensure auditing is configured for Docker files and directories - /usr/bin/containerd-shim-runc-v2 (Automated)" 552 | local remediation="Install auditd. Add -w /usr/bin/containerd-shim-runc-v2 -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 553 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 554 | local check="$id - $desc" 555 | starttestjson "$id" "$desc" 556 | 557 | file="/usr/bin/containerd-shim-runc-v2" 558 | if [ -f "$file" ]; then 559 | if command -v auditctl >/dev/null 2>&1; then 560 | if auditctl -l | grep $file >/dev/null 2>&1; then 561 | pass -s "$check" 562 | logcheckresult "PASS" 563 | return 564 | fi 565 | warn -s "$check" 566 | logcheckresult "WARN" 567 | return 568 | fi 569 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 570 | pass -s "$check" 571 | logcheckresult "PASS" 572 | return 573 | fi 574 | warn -s "$check" 575 | logcheckresult "WARN" 576 | return 577 | fi 578 | info -c "$check" 579 | info " * File not found" 580 | logcheckresult "INFO" "File not found" 581 | } 582 | 583 | check_1_1_18() { 584 | local id="1.1.18" 585 | local desc="Ensure auditing is configured for Docker files and directories - /usr/bin/runc (Automated)" 586 | local remediation="Install auditd. Add -w /usr/bin/runc -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." 587 | local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." 588 | local check="$id - $desc" 589 | starttestjson "$id" "$desc" 590 | 591 | file="/usr/bin/runc" 592 | if [ -f "$file" ]; then 593 | if command -v auditctl >/dev/null 2>&1; then 594 | if auditctl -l | grep $file >/dev/null 2>&1; then 595 | pass -s "$check" 596 | logcheckresult "PASS" 597 | return 598 | fi 599 | warn -s "$check" 600 | logcheckresult "WARN" 601 | return 602 | fi 603 | if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then 604 | pass -s "$check" 605 | logcheckresult "PASS" 606 | return 607 | fi 608 | warn -s "$check" 609 | logcheckresult "WARN" 610 | return 611 | fi 612 | info -c "$check" 613 | info " * File not found" 614 | logcheckresult "INFO" "File not found" 615 | } 616 | 617 | check_1_2() { 618 | local id="1.2" 619 | local desc="General Configuration" 620 | local check="$id - $desc" 621 | info "$check" 622 | } 623 | 624 | check_1_2_1() { 625 | local id="1.2.1" 626 | local desc="Ensure the container host has been Hardened (Manual)" 627 | local remediation="You may consider various Security Benchmarks for your container host." 628 | local remediationImpact="None." 629 | local check="$id - $desc" 630 | starttestjson "$id" "$desc" 631 | 632 | note -c "$check" 633 | logcheckresult "INFO" 634 | } 635 | 636 | check_1_2_2() { 637 | local id="1.2.2" 638 | local desc="Ensure that the version of Docker is up to date (Manual)" 639 | local remediation="You should monitor versions of Docker releases and make sure your software is updated as required." 640 | local remediationImpact="You should perform a risk assessment regarding Docker version updates and review how they may impact your operations." 641 | local check="$id - $desc" 642 | starttestjson "$id" "$desc" 643 | 644 | docker_version=$(docker version | grep -i -A2 '^server' | grep ' Version:' \ 645 | | awk '{print $NF; exit}' | tr -d '[:alpha:]-,') 646 | docker_current_version="$(date +%y.%m.0 -d @$(( $(date +%s) - 2592000)))" 647 | do_version_check "$docker_current_version" "$docker_version" 648 | if [ $? -eq 11 ]; then 649 | pass -c "$check" 650 | info " * Using $docker_version, verify is it up to date as deemed necessary" 651 | logcheckresult "INFO" "Using $docker_version" 652 | return 653 | fi 654 | pass -c "$check" 655 | info " * Using $docker_version which is current" 656 | info " * Check with your operating system vendor for support and security maintenance for Docker" 657 | logcheckresult "PASS" "Using $docker_version" 658 | } 659 | 660 | check_1_end() { 661 | endsectionjson 662 | } 663 | -------------------------------------------------------------------------------- /tests/2_docker_daemon_configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_2() { 4 | logit "" 5 | local id="2" 6 | local desc="Docker daemon configuration" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_2_1() { 13 | local id="2.1" 14 | local desc="Run the Docker daemon as a non-root user, if possible (Manual)" 15 | local remediation="Follow the current Dockerdocumentation on how to install the Docker daemon as a non-root user." 16 | local remediationImpact="There are multiple prerequisites depending on which distribution that is in use, and also known limitations regarding networking and resource limitation. Running in rootless mode also changes the location of any configuration files in use, including all containers using the daemon." 17 | local check="$id - $desc" 18 | starttestjson "$id" "$desc" 19 | 20 | note -c "$check" 21 | logcheckresult "INFO" 22 | } 23 | 24 | check_2_2() { 25 | local id="2.2" 26 | local desc="Ensure network traffic is restricted between containers on the default bridge (Scored)" 27 | local remediation="Edit the Docker daemon configuration file to ensure that inter-container communication is disabled: icc: false." 28 | local remediationImpact="Inter-container communication is disabled on the default network bridge. If any communication between containers on the same host is desired, it needs to be explicitly defined using container linking or custom networks." 29 | local check="$id - $desc" 30 | starttestjson "$id" "$desc" 31 | 32 | if get_docker_effective_command_line_args '--icc' | grep false >/dev/null 2>&1; then 33 | pass -s "$check" 34 | logcheckresult "PASS" 35 | return 36 | fi 37 | if [[ $(get_docker_configuration_file_args 'icc' | grep "false") ]] && [[ $(get_docker_configuration_file_args 'icc' | grep "false") != "null" ]] ; then 38 | pass -s "$check" 39 | logcheckresult "PASS" 40 | return 41 | fi 42 | warn -s "$check" 43 | logcheckresult "WARN" 44 | } 45 | 46 | check_2_3() { 47 | local id="2.3" 48 | local desc="Ensure the logging level is set to 'info' (Scored)" 49 | local remediation="Ensure that the Docker daemon configuration file has the following configuration included log-level: info. Alternatively, run the Docker daemon as following: dockerd --log-level=info" 50 | local remediationImpact="None." 51 | local check="$id - $desc" 52 | starttestjson "$id" "$desc" 53 | 54 | if get_docker_configuration_file_args 'log-level' >/dev/null 2>&1; then 55 | if get_docker_configuration_file_args 'log-level' | grep info >/dev/null 2>&1; then 56 | pass -s "$check" 57 | logcheckresult "PASS" 58 | return 59 | fi 60 | if [ -z "$(get_docker_configuration_file_args 'log-level')" ]; then 61 | pass -s "$check" 62 | logcheckresult "PASS" 63 | return 64 | fi 65 | warn -s "$check" 66 | logcheckresult "WARN" 67 | return 68 | fi 69 | if get_docker_effective_command_line_args '-l'; then 70 | if get_docker_effective_command_line_args '-l' | grep "info" >/dev/null 2>&1; then 71 | pass -s "$check" 72 | logcheckresult "PASS" 73 | return 74 | fi 75 | warn -s "$check" 76 | logcheckresult "WARN" 77 | return 78 | fi 79 | pass -s "$check" 80 | logcheckresult "PASS" 81 | } 82 | 83 | check_2_4() { 84 | local id="2.4" 85 | local desc="Ensure Docker is allowed to make changes to iptables (Scored)" 86 | local remediation="Do not run the Docker daemon with --iptables=false option." 87 | local remediationImpact="The Docker daemon service requires iptables rules to be enabled before it starts." 88 | local check="$id - $desc" 89 | starttestjson "$id" "$desc" 90 | 91 | if get_docker_effective_command_line_args '--iptables' | grep "false" >/dev/null 2>&1; then 92 | warn -s "$check" 93 | logcheckresult "WARN" 94 | return 95 | fi 96 | if [[ $(get_docker_configuration_file_args 'iptables' | grep "false") ]] && [[ $(get_docker_configuration_file_args 'iptables' | grep "false") != "null" ]] ; then 97 | warn -s "$check" 98 | logcheckresult "WARN" 99 | return 100 | fi 101 | pass -s "$check" 102 | logcheckresult "PASS" 103 | } 104 | 105 | check_2_5() { 106 | local id="2.5" 107 | local desc="Ensure insecure registries are not used (Scored)" 108 | local remediation="You should ensure that no insecure registries are in use." 109 | local remediationImpact="None." 110 | local check="$id - $desc" 111 | starttestjson "$id" "$desc" 112 | 113 | if get_docker_effective_command_line_args '--insecure-registry' | grep "insecure-registry" >/dev/null 2>&1; then 114 | warn -s "$check" 115 | logcheckresult "WARN" 116 | return 117 | fi 118 | if [[ $(get_docker_configuration_file_args 'insecure-registries' | grep -v '\[]') ]] && [[ $(get_docker_configuration_file_args 'insecure-registries' | grep -v '\[]') != "null" ]] ; then 119 | warn -s "$check" 120 | logcheckresult "WARN" 121 | return 122 | fi 123 | pass -s "$check" 124 | logcheckresult "PASS" 125 | } 126 | 127 | check_2_6() { 128 | local id="2.6" 129 | local desc="Ensure aufs storage driver is not used (Scored)" 130 | local remediation="Do not start Docker daemon as using dockerd --storage-driver aufs option." 131 | local remediationImpact="aufs is the only storage driver that allows containers to share executable and shared library memory. Its use should be reviewed in line with your organization's security policy." 132 | local check="$id - $desc" 133 | starttestjson "$id" "$desc" 134 | 135 | if docker info 2>/dev/null | grep -e "^\sStorage Driver:\s*aufs\s*$" >/dev/null 2>&1; then 136 | warn -s "$check" 137 | logcheckresult "WARN" 138 | return 139 | fi 140 | pass -s "$check" 141 | logcheckresult "PASS" 142 | } 143 | 144 | check_2_7() { 145 | local id="2.7" 146 | local desc="Ensure TLS authentication for Docker daemon is configured (Scored)" 147 | local remediation="Follow the steps mentioned in the Docker documentation or other references. By default, TLS authentication is not configured." 148 | local remediationImpact="You would need to manage and guard certificates and keys for the Docker daemon and Docker clients." 149 | local check="$id - $desc" 150 | starttestjson "$id" "$desc" 151 | 152 | if $(grep -qE "host.*tcp://" "$CONFIG_FILE") || \ 153 | [ $(get_docker_cumulative_command_line_args '-H' | grep -vE '(unix|fd)://') > /dev/null 2>&1 ]; then 154 | if [ $(get_docker_configuration_file_args '"tlsverify":' | grep 'true') ] || \ 155 | [ $(get_docker_cumulative_command_line_args '--tlsverify' | grep 'tlsverify') >/dev/null 2>&1 ]; then 156 | pass -s "$check" 157 | logcheckresult "PASS" 158 | return 159 | fi 160 | if [ $(get_docker_configuration_file_args '"tls":' | grep 'true') ] || \ 161 | [ $(get_docker_cumulative_command_line_args '--tls' | grep 'tls$') >/dev/null 2>&1 ]; then 162 | warn -s "$check" 163 | warn " * Docker daemon currently listening on TCP with TLS, but no verification" 164 | logcheckresult "WARN" "Docker daemon currently listening on TCP with TLS, but no verification" 165 | return 166 | fi 167 | warn -s "$check" 168 | warn " * Docker daemon currently listening on TCP without TLS" 169 | logcheckresult "WARN" "Docker daemon currently listening on TCP without TLS" 170 | return 171 | fi 172 | info -c "$check" 173 | info " * Docker daemon not listening on TCP" 174 | logcheckresult "INFO" "Docker daemon not listening on TCP" 175 | } 176 | 177 | check_2_8() { 178 | local id="2.8" 179 | local desc="Ensure the default ulimit is configured appropriately (Manual)" 180 | local remediation="Run Docker in daemon mode and pass --default-ulimit as option with respective ulimits as appropriate in your environment and in line with your security policy. Example: dockerd --default-ulimit nproc=1024:2048 --default-ulimit nofile=100:200" 181 | local remediationImpact="If ulimits are set incorrectly this could cause issues with system resources, possibly causing a denial of service condition." 182 | local check="$id - $desc" 183 | starttestjson "$id" "$desc" 184 | 185 | if [[ $(get_docker_configuration_file_args 'default-ulimits' | grep -v '{}') ]] && [[ $(get_docker_configuration_file_args 'default-ulimits' | grep -v '{}') != "null" ]] ; then 186 | pass -c "$check" 187 | logcheckresult "PASS" 188 | return 189 | fi 190 | if get_docker_effective_command_line_args '--default-ulimit' | grep "default-ulimit" >/dev/null 2>&1; then 191 | pass -c "$check" 192 | logcheckresult "PASS" 193 | return 194 | fi 195 | info -c "$check" 196 | info " * Default ulimit doesn't appear to be set" 197 | logcheckresult "INFO" "Default ulimit doesn't appear to be set" 198 | } 199 | 200 | check_2_9() { 201 | local id="2.9" 202 | local desc="Enable user namespace support (Scored)" 203 | local remediation="Please consult the Docker documentation for various ways in which this can be configured depending upon your requirements. The high-level steps are: Ensure that the files /etc/subuid and /etc/subgid exist. Start the docker daemon with --userns-remap flag." 204 | local remediationImpact="User namespace remapping is incompatible with a number of Docker features and also currently breaks some of its functionalities." 205 | local check="$id - $desc" 206 | starttestjson "$id" "$desc" 207 | 208 | if [[ $(get_docker_configuration_file_args 'userns-remap' | grep -v '""') ]] && [[ $(get_docker_configuration_file_args 'userns-remap' | grep -v '""') != "null" ]] ; then 209 | pass -s "$check" 210 | logcheckresult "PASS" 211 | return 212 | fi 213 | if get_docker_effective_command_line_args '--userns-remap' | grep "userns-remap" >/dev/null 2>&1; then 214 | pass -s "$check" 215 | logcheckresult "PASS" 216 | return 217 | fi 218 | warn -s "$check" 219 | logcheckresult "WARN" 220 | } 221 | 222 | check_2_10() { 223 | local id="2.10" 224 | local desc="Ensure the default cgroup usage has been confirmed (Scored)" 225 | local remediation="The default setting is in line with good security practice and can be left in situ." 226 | local remediationImpact="None." 227 | local check="$id - $desc" 228 | starttestjson "$id" "$desc" 229 | 230 | if get_docker_configuration_file_args 'cgroup-parent' | grep -v ''; then 231 | warn -s "$check" 232 | info " * Confirm cgroup usage" 233 | logcheckresult "WARN" "Confirm cgroup usage" 234 | return 235 | fi 236 | if get_docker_effective_command_line_args '--cgroup-parent' | grep "cgroup-parent" >/dev/null 2>&1; then 237 | warn -s "$check" 238 | info " * Confirm cgroup usage" 239 | logcheckresult "WARN" "Confirm cgroup usage" 240 | return 241 | fi 242 | pass -s "$check" 243 | logcheckresult "PASS" 244 | } 245 | 246 | check_2_11() { 247 | local id="2.11" 248 | local desc="Ensure base device size is not changed until needed (Scored)" 249 | local remediation="Do not set --storage-opt dm.basesize until needed." 250 | local remediationImpact="None." 251 | local check="$id - $desc" 252 | starttestjson "$id" "$desc" 253 | 254 | if get_docker_configuration_file_args 'storage-opts' | grep "dm.basesize" >/dev/null 2>&1; then 255 | warn -s "$check" 256 | logcheckresult "WARN" 257 | return 258 | fi 259 | if get_docker_effective_command_line_args '--storage-opt' | grep "dm.basesize" >/dev/null 2>&1; then 260 | warn -s "$check" 261 | logcheckresult "WARN" 262 | return 263 | fi 264 | pass -s "$check" 265 | logcheckresult "PASS" 266 | } 267 | 268 | check_2_12() { 269 | local id="2.12" 270 | local desc="Ensure that authorization for Docker client commands is enabled (Scored)" 271 | local remediation="Install/Create an authorization plugin. Configure the authorization policy as desired. Start the docker daemon using command dockerd --authorization-plugin=" 272 | local remediationImpact="Each Docker command needs to pass through the authorization plugin mechanism. This may have a performance impact" 273 | local check="$id - $desc" 274 | starttestjson "$id" "$desc" 275 | 276 | if [[ $(get_docker_configuration_file_args 'authorization-plugins' | grep -v '\[]') ]] && [[ $(get_docker_configuration_file_args 'authorization-plugins' | grep -v '\[]') != "null" ]] ; then 277 | pass -s "$check" 278 | logcheckresult "PASS" 279 | return 280 | fi 281 | if get_docker_effective_command_line_args '--authorization-plugin' | grep "authorization-plugin" >/dev/null 2>&1; then 282 | pass -s "$check" 283 | logcheckresult "PASS" 284 | return 285 | fi 286 | warn -s "$check" 287 | logcheckresult "WARN" 288 | } 289 | 290 | check_2_13() { 291 | local id="2.13" 292 | local desc="Ensure centralized and remote logging is configured (Scored)" 293 | local remediation="Set up the desired log driver following its documentation. Start the docker daemon using that logging driver. Example: dockerd --log-driver=syslog --log-opt syslog-address=tcp://192.xxx.xxx.xxx" 294 | local remediationImpact="None." 295 | local check="$id - $desc" 296 | starttestjson "$id" "$desc" 297 | 298 | if docker info --format '{{ .LoggingDriver }}' | grep 'json-file' >/dev/null 2>&1; then 299 | warn -s "$check" 300 | logcheckresult "WARN" 301 | return 302 | fi 303 | pass -s "$check" 304 | logcheckresult "PASS" 305 | } 306 | 307 | check_2_14() { 308 | local id="2.14" 309 | local desc="Ensure containers are restricted from acquiring new privileges (Scored)" 310 | local remediation="You should run the Docker daemon using command: dockerd --no-new-privileges" 311 | local remediationImpact="no_new_priv prevents LSMs such as SELinux from escalating the privileges of individual containers." 312 | local check="$id - $desc" 313 | starttestjson "$id" "$desc" 314 | 315 | if get_docker_effective_command_line_args '--no-new-privileges' | grep "no-new-privileges" >/dev/null 2>&1; then 316 | pass -s "$check" 317 | logcheckresult "PASS" 318 | return 319 | fi 320 | if get_docker_configuration_file_args 'no-new-privileges' | grep true >/dev/null 2>&1; then 321 | pass -s "$check" 322 | logcheckresult "PASS" 323 | return 324 | fi 325 | warn -s "$check" 326 | logcheckresult "WARN" 327 | } 328 | 329 | check_2_15() { 330 | local id="2.15" 331 | local desc="Ensure live restore is enabled (Scored)" 332 | local remediation="Run Docker in daemon mode and pass --live-restore option." 333 | local remediationImpact="None." 334 | local check="$id - $desc" 335 | starttestjson "$id" "$desc" 336 | 337 | if docker info 2>/dev/null | grep -e "Live Restore Enabled:\s*true\s*" >/dev/null 2>&1; then 338 | pass -s "$check" 339 | logcheckresult "PASS" 340 | return 341 | fi 342 | if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then 343 | pass -s "$check (Incompatible with swarm mode)" 344 | logcheckresult "PASS" 345 | return 346 | fi 347 | if get_docker_effective_command_line_args '--live-restore' | grep "live-restore" >/dev/null 2>&1; then 348 | pass -s "$check" 349 | logcheckresult "PASS" 350 | return 351 | fi 352 | warn -s "$check" 353 | logcheckresult "WARN" 354 | } 355 | 356 | check_2_16() { 357 | local id="2.16" 358 | local desc="Ensure Userland Proxy is Disabled (Scored)" 359 | local remediation="You should run the Docker daemon using command: dockerd --userland-proxy=false" 360 | local remediationImpact="Some systems with older Linux kernels may not be able to support hairpin NAT and therefore require the userland proxy service. Also, some networking setups can be impacted by the removal of the userland proxy." 361 | local check="$id - $desc" 362 | starttestjson "$id" "$desc" 363 | 364 | if get_docker_configuration_file_args 'userland-proxy' | grep false >/dev/null 2>&1; then 365 | pass -s "$check" 366 | logcheckresult "PASS" 367 | return 368 | fi 369 | if get_docker_effective_command_line_args '--userland-proxy=false' 2>/dev/null | grep "userland-proxy=false" >/dev/null 2>&1; then 370 | pass -s "$check" 371 | logcheckresult "PASS" 372 | return 373 | fi 374 | warn -s "$check" 375 | logcheckresult "WARN" 376 | } 377 | 378 | check_2_17() { 379 | local id="2.17" 380 | local desc="Ensure that a daemon-wide custom seccomp profile is applied if appropriate (Manual)" 381 | local remediation="By default, Docker's default seccomp profile is applied. If this is adequate for your environment, no action is necessary." 382 | local remediationImpact="A misconfigured seccomp profile could possibly interrupt your container environment. You should therefore exercise extreme care if you choose to override the default settings." 383 | local check="$id - $desc" 384 | starttestjson "$id" "$desc" 385 | 386 | if docker info --format '{{ .SecurityOptions }}' | grep 'name=seccomp,profile=default' 2>/dev/null 1>&2; then 387 | pass -c "$check" 388 | logcheckresult "PASS" 389 | return 390 | fi 391 | info -c "$check" 392 | logcheckresult "INFO" 393 | } 394 | 395 | check_2_18() { 396 | docker_version=$(docker version | grep -i -A2 '^server' | grep ' Version:' \ 397 | | awk '{print $NF; exit}' | tr -d '[:alpha:]-,.' | cut -c 1-4) 398 | 399 | local id="2.18" 400 | local desc="Ensure that experimental features are not implemented in production (Scored)" 401 | local remediation="You should not pass --experimental as a runtime parameter to the Docker daemon on production systems." 402 | local remediationImpact="None." 403 | local check="$id - $desc" 404 | starttestjson "$id" "$desc" 405 | 406 | if [ "$docker_version" -le 1903 ]; then 407 | if docker version -f '{{.Server.Experimental}}' | grep false 2>/dev/null 1>&2; then 408 | pass -s "$check" 409 | logcheckresult "PASS" 410 | return 411 | fi 412 | warn -s "$check" 413 | logcheckresult "WARN" 414 | return 415 | fi 416 | local desc="$desc (Deprecated)" 417 | local check="$id - $desc" 418 | info -c "$desc" 419 | logcheckresult "INFO" 420 | } 421 | 422 | check_2_end() { 423 | endsectionjson 424 | } 425 | -------------------------------------------------------------------------------- /tests/3_docker_daemon_configuration_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_3() { 4 | logit "" 5 | local id="3" 6 | local desc="Docker daemon configuration files" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_3_1() { 13 | local id="3.1" 14 | local desc="Ensure that the docker.service file ownership is set to root:root (Automated)" 15 | local remediation="Find out the file location: systemctl show -p FragmentPath docker.service. If the file does not exist, this recommendation is not applicable. If the file does exist, you should run the command chown root:root , in order to set the ownership and group ownership for the file to root." 16 | local remediationImpact="None." 17 | local check="$id - $desc" 18 | starttestjson "$id" "$desc" 19 | 20 | file=$(get_service_file docker.service) 21 | if [ -f "$file" ]; then 22 | if [ "$(stat -c %u%g "$file")" -eq 00 ]; then 23 | pass -s "$check" 24 | logcheckresult "PASS" 25 | return 26 | fi 27 | warn -s "$check" 28 | warn " * Wrong ownership for $file" 29 | logcheckresult "WARN" "Wrong ownership for $file" 30 | return 31 | fi 32 | info -c "$check" 33 | info " * File not found" 34 | logcheckresult "INFO" "File not found" 35 | } 36 | 37 | check_3_2() { 38 | local id="3.2" 39 | local desc="Ensure that docker.service file permissions are appropriately set (Automated)" 40 | local remediation="Find out the file location: systemctl show -p FragmentPath docker.service. If the file does not exist, this recommendation is not applicable. If the file exists, run the command chmod 644 to set the file permissions to 644." 41 | local remediationImpact="None." 42 | local check="$id - $desc" 43 | starttestjson "$id" "$desc" 44 | 45 | file=$(get_service_file docker.service) 46 | if [ -f "$file" ]; then 47 | if [ "$(stat -c %a "$file")" -le 644 ]; then 48 | pass -s "$check" 49 | logcheckresult "PASS" 50 | return 51 | fi 52 | warn -s "$check" 53 | warn " * Wrong permissions for $file" 54 | logcheckresult "WARN" "Wrong permissions for $file" 55 | return 56 | fi 57 | info -c "$check" 58 | info " * File not found" 59 | logcheckresult "INFO" "File not found" 60 | } 61 | 62 | check_3_3() { 63 | local id="3.3" 64 | local desc="Ensure that docker.socket file ownership is set to root:root (Automated)" 65 | local remediation="Find out the file location: systemctl show -p FragmentPath docker.socket. If the file does not exist, this recommendation is not applicable. If the file exists, run the command chown root:root to set the ownership and group ownership for the file to root." 66 | local remediationImpact="None." 67 | local check="$id - $desc" 68 | starttestjson "$id" "$desc" 69 | 70 | file=$(get_service_file docker.socket) 71 | if [ -f "$file" ]; then 72 | if [ "$(stat -c %u%g "$file")" -eq 00 ]; then 73 | pass -s "$check" 74 | logcheckresult "PASS" 75 | return 76 | fi 77 | warn -s "$check" 78 | warn " * Wrong ownership for $file" 79 | logcheckresult "WARN" "Wrong ownership for $file" 80 | return 81 | fi 82 | info -c "$check" 83 | info " * File not found" 84 | logcheckresult "INFO" "File not found" 85 | } 86 | 87 | check_3_4() { 88 | local id="3.4" 89 | local desc="Ensure that docker.socket file permissions are set to 644 or more restrictive (Automated)" 90 | local remediation="Find out the file location: systemctl show -p FragmentPath docker.socket. If the file does not exist, this recommendation is not applicable. If the file does exist, you should run the command chmod 644 to set the file permissions to 644." 91 | local remediationImpact="None." 92 | local check="$id - $desc" 93 | starttestjson "$id" "$desc" 94 | 95 | file=$(get_service_file docker.socket) 96 | if [ -f "$file" ]; then 97 | if [ "$(stat -c %a "$file")" -le 644 ]; then 98 | pass -s "$check" 99 | logcheckresult "PASS" 100 | return 101 | fi 102 | warn -s "$check" 103 | warn " * Wrong permissions for $file" 104 | logcheckresult "WARN" "Wrong permissions for $file" 105 | return 106 | fi 107 | info -c "$check" 108 | info " * File not found" 109 | logcheckresult "INFO" "File not found" 110 | } 111 | 112 | check_3_5() { 113 | local id="3.5" 114 | local desc="Ensure that the /etc/docker directory ownership is set to root:root (Automated)" 115 | local remediation="You should run the following command: chown root:root /etc/docker. This sets the ownership and group ownership for the directory to root." 116 | local remediationImpact="None." 117 | local check="$id - $desc" 118 | starttestjson "$id" "$desc" 119 | 120 | directory="/etc/docker" 121 | if [ -d "$directory" ]; then 122 | if [ "$(stat -c %u%g $directory)" -eq 00 ]; then 123 | pass -s "$check" 124 | logcheckresult "PASS" 125 | return 126 | fi 127 | warn -s "$check" 128 | warn " * Wrong ownership for $directory" 129 | logcheckresult "WARN" "Wrong ownership for $directory" 130 | return 131 | fi 132 | info -c "$check" 133 | info " * Directory not found" 134 | logcheckresult "INFO" "Directory not found" 135 | } 136 | 137 | check_3_6() { 138 | local id="3.6" 139 | local desc="Ensure that /etc/docker directory permissions are set to 755 or more restrictively (Automated)" 140 | local remediation="You should run the following command: chmod 755 /etc/docker. This sets the permissions for the directory to 755." 141 | local remediationImpact="None." 142 | local check="$id - $desc" 143 | starttestjson "$id" "$desc" 144 | 145 | directory="/etc/docker" 146 | if [ -d "$directory" ]; then 147 | if [ "$(stat -c %a $directory)" -le 755 ]; then 148 | pass -s "$check" 149 | logcheckresult "PASS" 150 | return 151 | fi 152 | warn -s "$check" 153 | warn " * Wrong permissions for $directory" 154 | logcheckresult "WARN" "Wrong permissions for $directory" 155 | return 156 | fi 157 | info -c "$check" 158 | info " * Directory not found" 159 | logcheckresult "INFO" "Directory not found" 160 | } 161 | 162 | check_3_7() { 163 | local id="3.7" 164 | local desc="Ensure that registry certificate file ownership is set to root:root (Automated)" 165 | local remediation="You should run the following command: chown root:root /etc/docker/certs.d//*. This would set the individual ownership and group ownership for the registry certificate files to root." 166 | local remediationImpact="None." 167 | local check="$id - $desc" 168 | starttestjson "$id" "$desc" 169 | 170 | directory="/etc/docker/certs.d/" 171 | if [ -d "$directory" ]; then 172 | fail=0 173 | owners=$(find "$directory" -type f -name '*.crt') 174 | for p in $owners; do 175 | if [ "$(stat -c %u "$p")" -ne 0 ]; then 176 | fail=1 177 | fi 178 | done 179 | if [ $fail -eq 1 ]; then 180 | warn -s "$check" 181 | warn " * Wrong ownership for $directory" 182 | logcheckresult "WARN" "Wrong ownership for $directory" 183 | return 184 | fi 185 | pass -s "$check" 186 | logcheckresult "PASS" 187 | return 188 | fi 189 | info -c "$check" 190 | info " * Directory not found" 191 | logcheckresult "INFO" "Directory not found" 192 | } 193 | 194 | check_3_8() { 195 | local id="3.8" 196 | local desc="Ensure that registry certificate file permissions are set to 444 or more restrictively (Automated)" 197 | local remediation="You should run the following command: chmod 444 /etc/docker/certs.d//*. This would set the permissions for the registry certificate files to 444." 198 | local remediationImpact="None." 199 | local check="$id - $desc" 200 | starttestjson "$id" "$desc" 201 | 202 | directory="/etc/docker/certs.d/" 203 | if [ -d "$directory" ]; then 204 | fail=0 205 | perms=$(find "$directory" -type f -name '*.crt') 206 | for p in $perms; do 207 | if [ "$(stat -c %a "$p")" -gt 444 ]; then 208 | fail=1 209 | fi 210 | done 211 | if [ $fail -eq 1 ]; then 212 | warn -s "$check" 213 | warn " * Wrong permissions for $directory" 214 | logcheckresult "WARN" "Wrong permissions for $directory" 215 | return 216 | fi 217 | pass -s "$check" 218 | logcheckresult "PASS" 219 | return 220 | fi 221 | info -c "$check" 222 | info " * Directory not found" 223 | logcheckresult "INFO" "Directory not found" 224 | } 225 | 226 | check_3_9() { 227 | local id="3.9" 228 | local desc="Ensure that TLS CA certificate file ownership is set to root:root (Automated)" 229 | local remediation="You should run the following command: chown root:root . This sets the individual ownership and group ownership for the TLS CA certificate file to root." 230 | local remediationImpact="None." 231 | local check="$id - $desc" 232 | starttestjson "$id" "$desc" 233 | 234 | tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 235 | if [ -n "$(get_docker_configuration_file_args 'tlscacert')" ]; then 236 | tlscacert=$(get_docker_configuration_file_args 'tlscacert') 237 | fi 238 | if [ -f "$tlscacert" ]; then 239 | if [ "$(stat -c %u%g "$tlscacert")" -eq 00 ]; then 240 | pass -s "$check" 241 | logcheckresult "PASS" 242 | return 243 | fi 244 | warn -s "$check" 245 | warn " * Wrong ownership for $tlscacert" 246 | logcheckresult "WARN" "Wrong ownership for $tlscacert" 247 | return 248 | fi 249 | info -c "$check" 250 | info " * No TLS CA certificate found" 251 | logcheckresult "INFO" "No TLS CA certificate found" 252 | } 253 | 254 | check_3_10() { 255 | local id="3.10" 256 | local desc="Ensure that TLS CA certificate file permissions are set to 444 or more restrictively (Automated)" 257 | local remediation="You should run the following command: chmod 444 . This sets the file permissions on the TLS CA file to 444." 258 | local remediationImpact="None." 259 | local check="$id - $desc" 260 | starttestjson "$id" "$desc" 261 | 262 | tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 263 | if [ -n "$(get_docker_configuration_file_args 'tlscacert')" ]; then 264 | tlscacert=$(get_docker_configuration_file_args 'tlscacert') 265 | fi 266 | if [ -f "$tlscacert" ]; then 267 | if [ "$(stat -c %a "$tlscacert")" -le 444 ]; then 268 | pass -s "$check" 269 | logcheckresult "PASS" 270 | return 271 | fi 272 | warn -s "$check" 273 | warn " * Wrong permissions for $tlscacert" 274 | logcheckresult "WARN" "Wrong permissions for $tlscacert" 275 | return 276 | fi 277 | info -c "$check" 278 | info " * No TLS CA certificate found" 279 | logcheckresult "INFO" "No TLS CA certificate found" 280 | } 281 | 282 | check_3_11() { 283 | local id="3.11" 284 | local desc="Ensure that Docker server certificate file ownership is set to root:root (Automated)" 285 | local remediation="You should run the following command: chown root:root . This sets the individual ownership and the group ownership for the Docker server certificate file to root." 286 | local remediationImpact="None." 287 | local check="$id - $desc" 288 | starttestjson "$id" "$desc" 289 | 290 | tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 291 | if [ -n "$(get_docker_configuration_file_args 'tlscert')" ]; then 292 | tlscert=$(get_docker_configuration_file_args 'tlscert') 293 | fi 294 | if [ -f "$tlscert" ]; then 295 | if [ "$(stat -c %u%g "$tlscert")" -eq 00 ]; then 296 | pass -s "$check" 297 | logcheckresult "PASS" 298 | return 299 | fi 300 | warn -s "$check" 301 | warn " * Wrong ownership for $tlscert" 302 | logcheckresult "WARN" "Wrong ownership for $tlscert" 303 | return 304 | fi 305 | info -c "$check" 306 | info " * No TLS Server certificate found" 307 | logcheckresult "INFO" "No TLS Server certificate found" 308 | } 309 | 310 | check_3_12() { 311 | local id="3.12" 312 | local desc="Ensure that the Docker server certificate file permissions are set to 444 or more restrictively (Automated)" 313 | local remediation="You should run the following command: chmod 444 . This sets the file permissions of the Docker server certificate file to 444." 314 | local remediationImpact="None." 315 | local check="$id - $desc" 316 | starttestjson "$id" "$desc" 317 | 318 | tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 319 | if [ -n "$(get_docker_configuration_file_args 'tlscert')" ]; then 320 | tlscert=$(get_docker_configuration_file_args 'tlscert') 321 | fi 322 | if [ -f "$tlscert" ]; then 323 | if [ "$(stat -c %a "$tlscert")" -le 444 ]; then 324 | pass -s "$check" 325 | logcheckresult "PASS" 326 | return 327 | fi 328 | warn -s "$check" 329 | warn " * Wrong permissions for $tlscert" 330 | logcheckresult "WARN" "Wrong permissions for $tlscert" 331 | return 332 | fi 333 | info -c "$check" 334 | info " * No TLS Server certificate found" 335 | logcheckresult "INFO" "No TLS Server certificate found" 336 | } 337 | 338 | check_3_13() { 339 | local id="3.13" 340 | local desc="Ensure that the Docker server certificate key file ownership is set to root:root (Automated)" 341 | local remediation="You should run the following command: chown root:root . This sets the individual ownership and group ownership for the Docker server certificate key file to root." 342 | local remediationImpact="None." 343 | local check="$id - $desc" 344 | starttestjson "$id" "$desc" 345 | 346 | tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 347 | if [ -n "$(get_docker_configuration_file_args 'tlskey')" ]; then 348 | tlskey=$(get_docker_configuration_file_args 'tlskey') 349 | fi 350 | if [ -f "$tlskey" ]; then 351 | if [ "$(stat -c %u%g "$tlskey")" -eq 00 ]; then 352 | pass -s "$check" 353 | logcheckresult "PASS" 354 | return 355 | fi 356 | warn -s "$check" 357 | warn " * Wrong ownership for $tlskey" 358 | logcheckresult "WARN" "Wrong ownership for $tlskey" 359 | return 360 | fi 361 | info -c "$check" 362 | info " * No TLS Key found" 363 | logcheckresult "INFO" "No TLS Key found" 364 | } 365 | 366 | check_3_14() { 367 | local id="3.14" 368 | local desc="Ensure that the Docker server certificate key file permissions are set to 400 (Automated)" 369 | local remediation="You should run the following command: chmod 400 . This sets the Docker server certificate key file permissions to 400." 370 | local remediationImpact="None." 371 | local check="$id - $desc" 372 | starttestjson "$id" "$desc" 373 | 374 | tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 375 | if [ -n "$(get_docker_configuration_file_args 'tlskey')" ]; then 376 | tlskey=$(get_docker_configuration_file_args 'tlskey') 377 | fi 378 | if [ -f "$tlskey" ]; then 379 | if [ "$(stat -c %a "$tlskey")" -eq 400 ]; then 380 | pass -s "$check" 381 | logcheckresult "PASS" 382 | return 383 | fi 384 | warn -s "$check" 385 | warn " * Wrong permissions for $tlskey" 386 | logcheckresult "WARN" "Wrong permissions for $tlskey" 387 | return 388 | fi 389 | info -c "$check" 390 | info " * No TLS Key found" 391 | logcheckresult "INFO" "No TLS Key found" 392 | } 393 | 394 | check_3_15() { 395 | local id="3.15" 396 | local desc="Ensure that the Docker socket file ownership is set to root:docker (Automated)" 397 | local remediation="You should run the following command: chown root:docker /var/run/docker.sock. This sets the ownership to root and group ownership to docker for the default Docker socket file." 398 | local remediationImpact="None." 399 | local check="$id - $desc" 400 | starttestjson "$id" "$desc" 401 | 402 | file="/var/run/docker.sock" 403 | if [ -S "$file" ]; then 404 | if [ "$(stat -c %U:%G $file)" = 'root:docker' ]; then 405 | pass -s "$check" 406 | logcheckresult "PASS" 407 | return 408 | fi 409 | warn -s "$check" 410 | warn " * Wrong ownership for $file" 411 | logcheckresult "WARN" "Wrong ownership for $file" 412 | return 413 | fi 414 | info -c "$check" 415 | info " * File not found" 416 | logcheckresult "INFO" "File not found" 417 | } 418 | 419 | check_3_16() { 420 | local id="3.16" 421 | local desc="Ensure that the Docker socket file permissions are set to 660 or more restrictively (Automated)" 422 | local remediation="You should run the following command: chmod 660 /var/run/docker.sock. This sets the file permissions of the Docker socket file to 660." 423 | local remediationImpact="None." 424 | local check="$id - $desc" 425 | starttestjson "$id" "$desc" 426 | 427 | file="/var/run/docker.sock" 428 | if [ -S "$file" ]; then 429 | if [ "$(stat -c %a $file)" -le 660 ]; then 430 | pass -s "$check" 431 | logcheckresult "PASS" 432 | return 433 | fi 434 | warn -s "$check" 435 | warn " * Wrong permissions for $file" 436 | logcheckresult "WARN" "Wrong permissions for $file" 437 | return 438 | fi 439 | info -c "$check" 440 | info " * File not found" 441 | logcheckresult "INFO" "File not found" 442 | } 443 | 444 | check_3_17() { 445 | local id="3.17" 446 | local desc="Ensure that the daemon.json file ownership is set to root:root (Automated)" 447 | local remediation="You should run the following command: chown root:root /etc/docker/daemon.json. This sets the ownership and group ownership for the file to root." 448 | local remediationImpact="None." 449 | local check="$id - $desc" 450 | starttestjson "$id" "$desc" 451 | 452 | file="/etc/docker/daemon.json" 453 | if [ -f "$file" ]; then 454 | if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then 455 | pass -s "$check" 456 | logcheckresult "PASS" 457 | return 458 | fi 459 | warn -s "$check" 460 | warn " * Wrong ownership for $file" 461 | logcheckresult "WARN" "Wrong ownership for $file" 462 | return 463 | fi 464 | info -c "$check" 465 | info " * File not found" 466 | logcheckresult "INFO" "File not found" 467 | } 468 | 469 | check_3_18() { 470 | local id="3.18" 471 | local desc="Ensure that daemon.json file permissions are set to 644 or more restrictive (Automated)" 472 | local remediation="You should run the following command: chmod 644 /etc/docker/daemon.json. This sets the file permissions for this file to 644." 473 | local remediationImpact="None." 474 | local check="$id - $desc" 475 | starttestjson "$id" "$desc" 476 | 477 | file="/etc/docker/daemon.json" 478 | if [ -f "$file" ]; then 479 | if [ "$(stat -c %a $file)" -le 644 ]; then 480 | pass -s "$check" 481 | logcheckresult "PASS" 482 | return 483 | fi 484 | warn -s "$check" 485 | warn " * Wrong permissions for $file" 486 | logcheckresult "WARN" "Wrong permissions for $file" 487 | return 488 | fi 489 | info -c "$check" 490 | info " * File not found" 491 | logcheckresult "INFO" "File not found" 492 | } 493 | 494 | check_3_19() { 495 | local id="3.19" 496 | local desc="Ensure that the /etc/default/docker file ownership is set to root:root (Automated)" 497 | local remediation="You should run the following command: chown root:root /etc/default/docker. This sets the ownership and group ownership of the file to root." 498 | local remediationImpact="None." 499 | local check="$id - $desc" 500 | starttestjson "$id" "$desc" 501 | 502 | file="/etc/default/docker" 503 | if [ -f "$file" ]; then 504 | if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then 505 | pass -s "$check" 506 | logcheckresult "PASS" 507 | return 508 | fi 509 | warn -s "$check" 510 | warn " * Wrong ownership for $file" 511 | logcheckresult "WARN" "Wrong ownership for $file" 512 | return 513 | fi 514 | info -c "$check" 515 | info " * File not found" 516 | logcheckresult "INFO" "File not found" 517 | } 518 | 519 | check_3_20() { 520 | local id="3.20" 521 | local desc="Ensure that the /etc/default/docker file permissions are set to 644 or more restrictively (Automated)" 522 | local remediation="You should run the following command: chmod 644 /etc/default/docker. This sets the file permissions for this file to 644." 523 | local remediationImpact="None." 524 | local check="$id - $desc" 525 | starttestjson "$id" "$desc" 526 | 527 | file="/etc/default/docker" 528 | if [ -f "$file" ]; then 529 | if [ "$(stat -c %a $file)" -le 644 ]; then 530 | pass -s "$check" 531 | logcheckresult "PASS" 532 | return 533 | fi 534 | warn -s "$check" 535 | warn " * Wrong permissions for $file" 536 | logcheckresult "WARN" "Wrong permissions for $file" 537 | return 538 | fi 539 | info -c "$check" 540 | info " * File not found" 541 | logcheckresult "INFO" "File not found" 542 | } 543 | 544 | check_3_21() { 545 | local id="3.21" 546 | local desc="Ensure that the /etc/sysconfig/docker file permissions are set to 644 or more restrictively (Automated)" 547 | local remediation="You should run the following command: chmod 644 /etc/sysconfig/docker. This sets the file permissions for this file to 644." 548 | local remediationImpact="None." 549 | local check="$id - $desc" 550 | starttestjson "$id" "$desc" 551 | 552 | file="/etc/sysconfig/docker" 553 | if [ -f "$file" ]; then 554 | if [ "$(stat -c %a $file)" -le 644 ]; then 555 | pass -s "$check" 556 | logcheckresult "PASS" 557 | return 558 | fi 559 | warn -s "$check" 560 | warn " * Wrong permissions for $file" 561 | logcheckresult "WARN" "Wrong permissions for $file" 562 | return 563 | fi 564 | info -c "$check" 565 | info " * File not found" 566 | logcheckresult "INFO" "File not found" 567 | } 568 | 569 | check_3_22() { 570 | local id="3.22" 571 | local desc="Ensure that the /etc/sysconfig/docker file ownership is set to root:root (Automated)" 572 | local remediation="You should run the following command: chown root:root /etc/sysconfig/docker. This sets the ownership and group ownership for the file to root." 573 | local remediationImpact="None." 574 | local check="$id - $desc" 575 | starttestjson "$id" "$desc" 576 | 577 | file="/etc/sysconfig/docker" 578 | if [ -f "$file" ]; then 579 | if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then 580 | pass -s "$check" 581 | logcheckresult "PASS" 582 | return 583 | fi 584 | warn -s "$check" 585 | warn " * Wrong ownership for $file" 586 | logcheckresult "WARN" "Wrong ownership for $file" 587 | return 588 | fi 589 | info -c "$check" 590 | info " * File not found" 591 | logcheckresult "INFO" "File not found" 592 | } 593 | 594 | check_3_23() { 595 | local id="3.23" 596 | local desc="Ensure that the Containerd socket file ownership is set to root:root (Automated)" 597 | local remediation="You should run the following command: chown root:root /run/containerd/containerd.sock. This sets the ownership and group ownership for the file to root." 598 | local remediationImpact="None." 599 | local check="$id - $desc" 600 | starttestjson "$id" "$desc" 601 | 602 | file="/run/containerd/containerd.sock" 603 | if [ -S "$file" ]; then 604 | if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then 605 | pass -s "$check" 606 | logcheckresult "PASS" 607 | return 608 | fi 609 | warn -s "$check" 610 | warn " * Wrong ownership for $file" 611 | logcheckresult "WARN" "Wrong ownership for $file" 612 | return 613 | fi 614 | info -c "$check" 615 | info " * File not found" 616 | logcheckresult "INFO" "File not found" 617 | } 618 | 619 | check_3_24() { 620 | local id="3.24" 621 | local desc="Ensure that the Containerd socket file permissions are set to 660 or more restrictively (Automated)" 622 | local remediation="You should run the following command: chmod 660 /run/containerd/containerd.sock. This sets the file permissions for this file to 660." 623 | local remediationImpact="None." 624 | local check="$id - $desc" 625 | starttestjson "$id" "$desc" 626 | 627 | file="/run/containerd/containerd.sock" 628 | if [ -S "$file" ]; then 629 | if [ "$(stat -c %a $file)" -le 660 ]; then 630 | pass -s "$check" 631 | logcheckresult "PASS" 632 | return 633 | fi 634 | warn -s "$check" 635 | warn " * Wrong permissions for $file" 636 | logcheckresult "WARN" "Wrong permissions for $file" 637 | return 638 | fi 639 | info -c "$check" 640 | info " * File not found" 641 | logcheckresult "INFO" "File not found" 642 | } 643 | 644 | check_3_end() { 645 | endsectionjson 646 | } 647 | -------------------------------------------------------------------------------- /tests/4_container_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_4() { 4 | logit "" 5 | local id="4" 6 | local desc="Container Images and Build File" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_4_1() { 13 | local id="4.1" 14 | local desc="Ensure that a user for the container has been created (Automated)" 15 | local remediation="You should ensure that the Dockerfile for each container image contains the information: USER . If there is no specific user created in the container base image, then make use of the useradd command to add a specific user before the USER instruction in the Dockerfile." 16 | local remediationImpact="Running as a non-root user can present challenges where you wish to bind mount volumes from the underlying host. In this case, care should be taken to ensure that the user running the contained process can read and write to the bound directory, according to their requirements." 17 | local check="$id - $desc" 18 | starttestjson "$id" "$desc" 19 | 20 | # If container_users is empty, there are no running containers 21 | if [ -z "$containers" ]; then 22 | info -c "$check" 23 | info " * No containers running" 24 | logcheckresult "INFO" "No containers running" 25 | return 26 | fi 27 | # We have some containers running, set failure flag to 0. Check for Users. 28 | fail=0 29 | # Make the loop separator be a new-line in POSIX compliant fashion 30 | set -f; IFS=$' 31 | ' 32 | root_containers="" 33 | for c in $containers; do 34 | user=$(docker inspect --format 'User={{.Config.User}}' "$c") 35 | 36 | if [ "$user" = "User=0" ] || [ "$user" = "User=root" ] || [ "$user" = "User=" ] || [ "$user" = "User=[]" ] || [ "$user" = "User=" ]; then 37 | # If it's the first container, fail the test 38 | if [ $fail -eq 0 ]; then 39 | warn -s "$check" 40 | warn " * Running as root: $c" 41 | root_containers="$root_containers $c" 42 | fail=1 43 | continue 44 | fi 45 | warn " * Running as root: $c" 46 | root_containers="$root_containers $c" 47 | fi 48 | done 49 | # We went through all the containers and found none running as root 50 | if [ $fail -eq 0 ]; then 51 | pass -s "$check" 52 | logcheckresult "PASS" 53 | return 54 | fi 55 | logcheckresult "WARN" "running as root" "$root_containers" 56 | # Make the loop separator go back to space 57 | set +f; unset IFS 58 | } 59 | 60 | check_4_2() { 61 | local id="4.2" 62 | local desc="Ensure that containers use only trusted base images (Manual)" 63 | local remediation="Configure and use Docker Content trust. View the history of each Docker image to evaluate its risk, dependent on the sensitivity of the application you wish to deploy using it. Scan Docker images for vulnerabilities at regular intervals." 64 | local remediationImpact="None." 65 | local check="$id - $desc" 66 | starttestjson "$id" "$desc" 67 | 68 | note -c "$check" 69 | logcheckresult "NOTE" 70 | } 71 | 72 | check_4_3() { 73 | local id="4.3" 74 | local desc="Ensure that unnecessary packages are not installed in the container (Manual)" 75 | local remediation="You should not install anything within the container that is not required. You should consider using a minimal base image if you can. Some of the options available include BusyBox and Alpine. Not only can this trim your image size considerably, but there would also be fewer pieces of software which could contain vectors for attack." 76 | local remediationImpact="None." 77 | local check="$id - $desc" 78 | starttestjson "$id" "$desc" 79 | 80 | note -c "$check" 81 | logcheckresult "NOTE" 82 | } 83 | 84 | check_4_4() { 85 | local id="4.4" 86 | local desc="Ensure images are scanned and rebuilt to include security patches (Manual)" 87 | local remediation="Images should be re-built ensuring that the latest version of the base images are used, to keep the operating system patch level at an appropriate level. Once the images have been re-built, containers should be re-started making use of the updated images." 88 | local remediationImpact="None." 89 | local check="$id - $desc" 90 | starttestjson "$id" "$desc" 91 | 92 | note -c "$check" 93 | logcheckresult "NOTE" 94 | } 95 | 96 | check_4_5() { 97 | local id="4.5" 98 | local desc="Ensure Content trust for Docker is Enabled (Automated)" 99 | local remediation="Add DOCKER_CONTENT_TRUST variable to the /etc/environment file using command echo DOCKER_CONTENT_TRUST=1 | sudo tee -a /etc/environment." 100 | local remediationImpact="This prevents users from working with tagged images unless they contain a signature." 101 | local check="$id - $desc" 102 | starttestjson "$id" "$desc" 103 | 104 | if [ "$DOCKER_CONTENT_TRUST" = "1" ]; then 105 | pass -s "$check" 106 | logcheckresult "PASS" 107 | return 108 | fi 109 | warn -s "$check" 110 | logcheckresult "WARN" 111 | } 112 | 113 | check_4_6() { 114 | local id="4.6" 115 | local desc="Ensure that HEALTHCHECK instructions have been added to container images (Automated)" 116 | local remediation="You should follow the Docker documentation and rebuild your container images to include the HEALTHCHECK instruction." 117 | local remediationImpact="None." 118 | local check="$id - $desc" 119 | starttestjson "$id" "$desc" 120 | 121 | fail=0 122 | no_health_images="" 123 | for img in $images; do 124 | if docker inspect --format='{{.Config.Healthcheck}}' "$img" 2>/dev/null | grep -e "" >/dev/null 2>&1; then 125 | if [ $fail -eq 0 ]; then 126 | fail=1 127 | warn -s "$check" 128 | fi 129 | imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) 130 | if ! [ "$imgName" = '[]' ]; then 131 | warn " * No Healthcheck found: $imgName" 132 | no_health_images="$no_health_images $imgName" 133 | else 134 | warn " * No Healthcheck found: $img" 135 | no_health_images="$no_health_images $img" 136 | fi 137 | fi 138 | done 139 | if [ $fail -eq 0 ]; then 140 | pass -s "$check" 141 | logcheckresult "PASS" 142 | return 143 | fi 144 | logcheckresult "WARN" "Images w/o HEALTHCHECK" "$no_health_images" 145 | } 146 | 147 | check_4_7() { 148 | local id="4.7" 149 | local desc="Ensure update instructions are not used alone in the Dockerfile (Manual)" 150 | local remediation="You should use update instructions together with install instructions and version pinning for packages while installing them. This prevent caching and force the extraction of the required versions. Alternatively, you could use the --no-cache flag during the docker build process to avoid using cached layers." 151 | local remediationImpact="None." 152 | local check="$id - $desc" 153 | starttestjson "$id" "$desc" 154 | 155 | fail=0 156 | update_images="" 157 | for img in $images; do 158 | if docker history "$img" 2>/dev/null | grep -e "update" >/dev/null 2>&1; then 159 | if [ $fail -eq 0 ]; then 160 | fail=1 161 | info -c "$check" 162 | fi 163 | imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) 164 | if ! [ "$imgName" = '[]' ]; then 165 | info " * Update instruction found: $imgName" 166 | update_images="$update_images $imgName" 167 | fi 168 | fi 169 | done 170 | if [ $fail -eq 0 ]; then 171 | pass -c "$check" 172 | logcheckresult "PASS" 173 | return 174 | fi 175 | logcheckresult "INFO" "Update instructions found" "$update_images" 176 | } 177 | 178 | check_4_8() { 179 | local id="4.8" 180 | local desc="Ensure setuid and setgid permissions are removed (Manual)" 181 | local remediation="You should allow setuid and setgid permissions only on executables which require them. You could remove these permissions at build time by adding the following command in your Dockerfile, preferably towards the end of the Dockerfile: RUN find / -perm /6000 -type f -exec chmod a-s {} ; || true" 182 | local remediationImpact="The above command would break all executables that depend on setuid or setgid permissions including legitimate ones. You should therefore be careful to modify the command to suit your requirements so that it does not reduce the permissions of legitimate programs excessively. Because of this, you should exercise a degree of caution and examine all processes carefully before making this type of modification in order to avoid outages." 183 | local check="$id - $desc" 184 | starttestjson "$id" "$desc" 185 | 186 | note -c "$check" 187 | logcheckresult "NOTE" 188 | } 189 | 190 | check_4_9() { 191 | local id="4.9" 192 | local desc="Ensure that COPY is used instead of ADD in Dockerfiles (Manual)" 193 | local remediation="You should use COPY rather than ADD instructions in Dockerfiles." 194 | local remediationImpact="Care needs to be taken in implementing this control if the application requires functionality that is part of the ADD instruction, for example, if you need to retrieve files from remote URLS." 195 | local check="$id - $desc" 196 | starttestjson "$id" "$desc" 197 | 198 | fail=0 199 | add_images="" 200 | for img in $images; do 201 | if docker history --format "{{ .CreatedBy }}" --no-trunc "$img" | \ 202 | sed '$d' | grep -q 'ADD'; then 203 | if [ $fail -eq 0 ]; then 204 | fail=1 205 | info -c "$check" 206 | fi 207 | imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) 208 | if ! [ "$imgName" = '[]' ]; then 209 | info " * ADD in image history: $imgName" 210 | add_images="$add_images $imgName" 211 | fi 212 | fi 213 | done 214 | if [ $fail -eq 0 ]; then 215 | pass -c "$check" 216 | logcheckresult "PASS" 217 | return 218 | fi 219 | logcheckresult "INFO" "Images using ADD" "$add_images" 220 | } 221 | 222 | check_4_10() { 223 | local id="4.10" 224 | local desc="Ensure secrets are not stored in Dockerfiles (Manual)" 225 | local remediation="Do not store any kind of secrets within Dockerfiles. Where secrets are required during the build process, make use of a secrets management tool, such as the buildkit builder included with Docker." 226 | local remediationImpact="A proper secrets management process will be required for Docker image building." 227 | local check="$id - $desc" 228 | starttestjson "$id" "$desc" 229 | 230 | note -c "$check" 231 | logcheckresult "NOTE" 232 | } 233 | 234 | check_4_11() { 235 | local id="4.11" 236 | local desc="Ensure only verified packages are installed (Manual)" 237 | local remediation="You should use a secure package distribution mechanism of your choice to ensure the authenticity of software packages." 238 | local remediationImpact="None." 239 | local check="$id - $desc" 240 | starttestjson "$id" "$desc" 241 | 242 | note -c "$check" 243 | logcheckresult "NOTE" 244 | } 245 | 246 | check_4_12() { 247 | local id="4.12" 248 | local desc="Ensure all signed artifacts are validated (Manual)" 249 | local remediation="Validate artifacts signatures before uploading to the package registry." 250 | local remediationImpact="None." 251 | local check="$id - $desc" 252 | starttestjson "$id" "$desc" 253 | 254 | note -c "$check" 255 | logcheckresult "NOTE" 256 | } 257 | 258 | check_4_end() { 259 | endsectionjson 260 | } 261 | -------------------------------------------------------------------------------- /tests/5_container_runtime.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_5() { 4 | logit "" 5 | local id="5" 6 | local desc="Container Runtime" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_running_containers() { 13 | # If containers is empty, there are no running containers 14 | if [ -z "$containers" ]; then 15 | info " * No containers running, skipping Section 5" 16 | return 17 | fi 18 | # Make the loop separator be a new-line in POSIX compliant fashion 19 | set -f; IFS=$' 20 | ' 21 | } 22 | 23 | check_5_1() { 24 | local id="5.1" 25 | local desc="Ensure swarm mode is not Enabled, if not needed (Automated)" 26 | local remediation="If swarm mode has been enabled on a system in error, you should run the command: docker swarm leave" 27 | local remediationImpact="Disabling swarm mode will impact the operation of Docker Enterprise components if these are in use." 28 | local check="$id - $desc" 29 | starttestjson "$id" "$desc" 30 | 31 | if docker info 2>/dev/null | grep -e "Swarm:*\sinactive\s*" >/dev/null 2>&1; then 32 | pass -s "$check" 33 | logcheckresult "PASS" 34 | return 35 | fi 36 | warn -s "$check" 37 | logcheckresult "WARN" 38 | } 39 | 40 | check_5_2() { 41 | if [ -z "$containers" ]; then 42 | return 43 | fi 44 | 45 | local id="5.2" 46 | local desc="Ensure that, if applicable, an AppArmor Profile is enabled (Automated)" 47 | local remediation="If AppArmor is applicable for your Linux OS, you should enable it. Alternatively, Docker's default AppArmor policy can be used." 48 | local remediationImpact="The container will have the security controls defined in the AppArmor profile. It should be noted that if the AppArmor profile is misconfigured, this may cause issues with the operation of the container." 49 | local check="$id - $desc" 50 | starttestjson "$id" "$desc" 51 | 52 | fail=0 53 | no_apparmor_containers="" 54 | for c in $containers; do 55 | policy=$(docker inspect --format 'AppArmorProfile={{ .AppArmorProfile }}' "$c") 56 | 57 | if [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=[]" ] || [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=unconfined" ]; then 58 | # If it's the first container, fail the test 59 | if [ $fail -eq 0 ]; then 60 | warn -s "$check" 61 | warn " * No AppArmorProfile Found: $c" 62 | no_apparmor_containers="$no_apparmor_containers $c" 63 | fail=1 64 | continue 65 | fi 66 | warn " * No AppArmorProfile Found: $c" 67 | no_apparmor_containers="$no_apparmor_containers $c" 68 | fi 69 | done 70 | # We went through all the containers and found none without AppArmor 71 | if [ $fail -eq 0 ]; then 72 | pass -s "$check" 73 | logcheckresult "PASS" 74 | return 75 | fi 76 | logcheckresult "WARN" "Containers with no AppArmorProfile" "$no_apparmor_containers" 77 | } 78 | 79 | check_5_3() { 80 | if [ -z "$containers" ]; then 81 | return 82 | fi 83 | 84 | local id="5.3" 85 | local desc="Ensure that, if applicable, SELinux security options are set (Automated)" 86 | local remediation="Set the SELinux State. Set the SELinux Policy. Create or import a SELinux policy template for Docker containers. Start Docker in daemon mode with SELinux enabled. Start your Docker container using the security options." 87 | local remediationImpact="Any restrictions defined in the SELinux policy will be applied to your containers. It should be noted that if your SELinux policy is misconfigured, this may have an impact on the correct operation of the affected containers." 88 | local check="$id - $desc" 89 | starttestjson "$id" "$desc" 90 | 91 | fail=0 92 | no_securityoptions_containers="" 93 | for c in $containers; do 94 | policy=$(docker inspect --format 'SecurityOpt={{ .HostConfig.SecurityOpt }}' "$c") 95 | 96 | if [ "$policy" = "SecurityOpt=" ] || [ "$policy" = "SecurityOpt=[]" ] || [ "$policy" = "SecurityOpt=" ]; then 97 | # If it's the first container, fail the test 98 | if [ $fail -eq 0 ]; then 99 | warn -s "$check" 100 | warn " * No SecurityOptions Found: $c" 101 | no_securityoptions_containers="$no_securityoptions_containers $c" 102 | fail=1 103 | continue 104 | fi 105 | warn " * No SecurityOptions Found: $c" 106 | no_securityoptions_containers="$no_securityoptions_containers $c" 107 | fi 108 | done 109 | # We went through all the containers and found none without SELinux 110 | if [ $fail -eq 0 ]; then 111 | pass -s "$check" 112 | logcheckresult "PASS" 113 | return 114 | fi 115 | logcheckresult "WARN" "Containers with no SecurityOptions" "$no_securityoptions_containers" 116 | } 117 | 118 | check_5_4() { 119 | if [ -z "$containers" ]; then 120 | return 121 | fi 122 | 123 | local id="5.4" 124 | local desc="Ensure that Linux kernel capabilities are restricted within containers (Automated)" 125 | local remediation="You could remove all the currently configured capabilities and then restore only the ones you specifically use: docker run --cap-drop=all --cap-add={,} " 126 | local remediationImpact="Restrictions on processes within a container are based on which Linux capabilities are in force. Removal of the NET_RAW capability prevents the container from creating raw sockets which is good security practice under most circumstances, but may affect some networking utilities." 127 | local check="$id - $desc" 128 | starttestjson "$id" "$desc" 129 | 130 | fail=0 131 | caps_containers="" 132 | for c in $containers; do 133 | container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd }}' "$c") 134 | caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \ 135 | sed 's/CAPADD/CapAdd/' | \ 136 | sed -r "s/CAP_AUDIT_WRITE|CAP_CHOWN|CAP_DAC_OVERRIDE|CAP_FOWNER|CAP_FSETID|CAP_KILL|CAP_MKNOD|CAP_NET_BIND_SERVICE|CAP_NET_RAW|CAP_SETFCAP|CAP_SETGID|CAP_SETPCAP|CAP_SETUID|CAP_SYS_CHROOT|\s//g" | \ 137 | sed -r "s/AUDIT_WRITE|CHOWN|DAC_OVERRIDE|FOWNER|FSETID|KILL|MKNOD|NET_BIND_SERVICE|NET_RAW|SETFCAP|SETGID|SETPCAP|SETUID|SYS_CHROOT|\s//g") 138 | 139 | if [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=[]' ] && [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=' ]; then 140 | # If it's the first container, fail the test 141 | if [ $fail -eq 0 ]; then 142 | warn -s "$check" 143 | warn " * Capabilities added: $caps to $c" 144 | caps_containers="$caps_containers $c" 145 | fail=1 146 | continue 147 | fi 148 | warn " * Capabilities added: $caps to $c" 149 | caps_containers="$caps_containers $c" 150 | fi 151 | done 152 | # We went through all the containers and found none with extra capabilities 153 | if [ $fail -eq 0 ]; then 154 | pass -s "$check" 155 | logcheckresult "PASS" 156 | return 157 | fi 158 | logcheckresult "WARN" "Capabilities added for containers" "$caps_containers" 159 | } 160 | 161 | check_5_5() { 162 | if [ -z "$containers" ]; then 163 | return 164 | fi 165 | 166 | local id="5.5" 167 | local desc="Ensure that privileged containers are not used (Automated)" 168 | local remediation="You should not run containers with the --privileged flag." 169 | local remediationImpact="If you start a container without the --privileged flag, it will not have excessive default capabilities." 170 | local check="$id - $desc" 171 | starttestjson "$id" "$desc" 172 | 173 | fail=0 174 | privileged_containers="" 175 | for c in $containers; do 176 | privileged=$(docker inspect --format '{{ .HostConfig.Privileged }}' "$c") 177 | 178 | if [ "$privileged" = "true" ]; then 179 | # If it's the first container, fail the test 180 | if [ $fail -eq 0 ]; then 181 | warn -s "$check" 182 | warn " * Container running in Privileged mode: $c" 183 | privileged_containers="$privileged_containers $c" 184 | fail=1 185 | continue 186 | fi 187 | warn " * Container running in Privileged mode: $c" 188 | privileged_containers="$privileged_containers $c" 189 | fi 190 | done 191 | # We went through all the containers and found no privileged containers 192 | if [ $fail -eq 0 ]; then 193 | pass -s "$check" 194 | logcheckresult "PASS" 195 | return 196 | fi 197 | logcheckresult "WARN" "Containers running in privileged mode" "$privileged_containers" 198 | } 199 | 200 | check_5_6() { 201 | if [ -z "$containers" ]; then 202 | return 203 | fi 204 | 205 | local id="5.6" 206 | local desc="Ensure sensitive host system directories are not mounted on containers (Automated)" 207 | local remediation="You should not mount directories which are security sensitive on the host within containers, especially in read-write mode." 208 | local remediationImpact="None." 209 | local check="$id - $desc" 210 | starttestjson "$id" "$desc" 211 | 212 | # List of sensitive directories to test for. Script uses new-lines as a separator. 213 | # Note the lack of identation. It needs it for the substring comparison. 214 | sensitive_dirs='/ 215 | /boot 216 | /dev 217 | /etc 218 | /lib 219 | /proc 220 | /sys 221 | /usr' 222 | fail=0 223 | sensitive_mount_containers="" 224 | for c in $containers; do 225 | volumes=$(docker inspect --format '{{ .Mounts }}' "$c") 226 | if docker inspect --format '{{ .VolumesRW }}' "$c" 2>/dev/null 1>&2; then 227 | volumes=$(docker inspect --format '{{ .VolumesRW }}' "$c") 228 | fi 229 | # Go over each directory in sensitive dir and see if they exist in the volumes 230 | for v in $sensitive_dirs; do 231 | sensitive=0 232 | if echo "$volumes" | grep -e "{.*\s$v\s.*true\s.*}" 2>/tmp/null 1>&2; then 233 | sensitive=1 234 | fi 235 | if [ $sensitive -eq 1 ]; then 236 | # If it's the first container, fail the test 237 | if [ $fail -eq 0 ]; then 238 | warn -s "$check" 239 | warn " * Sensitive directory $v mounted in: $c" 240 | sensitive_mount_containers="$sensitive_mount_containers $c:$v" 241 | fail=1 242 | continue 243 | fi 244 | warn " * Sensitive directory $v mounted in: $c" 245 | sensitive_mount_containers="$sensitive_mount_containers $c:$v" 246 | fi 247 | done 248 | done 249 | # We went through all the containers and found none with sensitive mounts 250 | if [ $fail -eq 0 ]; then 251 | pass -s "$check" 252 | logcheckresult "PASS" 253 | return 254 | fi 255 | logcheckresult "WARN" "Containers with sensitive directories mounted" "$sensitive_mount_containers" 256 | } 257 | 258 | check_5_7() { 259 | if [ -z "$containers" ]; then 260 | return 261 | fi 262 | 263 | local id="5.7" 264 | local desc="Ensure sshd is not run within containers (Automated)" 265 | local remediation="Uninstall the SSH daemon from the container and use docker exec to enter a container on the remote host." 266 | local remediationImpact="None." 267 | local check="$id - $desc" 268 | starttestjson "$id" "$desc" 269 | 270 | fail=0 271 | ssh_exec_containers="" 272 | printcheck=0 273 | for c in $containers; do 274 | 275 | processes=$(docker exec "$c" ps -el 2>/dev/null | grep -c sshd | awk '{print $1}') 276 | if [ "$processes" -ge 1 ]; then 277 | # If it's the first container, fail the test 278 | if [ $fail -eq 0 ]; then 279 | warn -s "$check" 280 | warn " * Container running sshd: $c" 281 | ssh_exec_containers="$ssh_exec_containers $c" 282 | fail=1 283 | printcheck=1 284 | else 285 | warn " * Container running sshd: $c" 286 | ssh_exec_containers="$ssh_exec_containers $c" 287 | fi 288 | fi 289 | 290 | exec_check=$(docker exec "$c" ps -el 2>/dev/null) 291 | if [ $? -eq 265 ]; then 292 | if [ $printcheck -eq 0 ]; then 293 | warn -s "$check" 294 | printcheck=1 295 | fi 296 | warn " * Docker exec fails: $c" 297 | ssh_exec_containers="$ssh_exec_containers $c" 298 | fail=1 299 | fi 300 | 301 | done 302 | # We went through all the containers and found none with sshd 303 | if [ $fail -eq 0 ]; then 304 | pass -s "$check" 305 | logcheckresult "PASS" 306 | return 307 | fi 308 | logcheckresult "WARN" "Containers with sshd/docker exec failures" "$ssh_exec_containers" 309 | } 310 | 311 | check_5_8() { 312 | if [ -z "$containers" ]; then 313 | return 314 | fi 315 | 316 | local id="5.8" 317 | local desc="Ensure privileged ports are not mapped within containers (Automated)" 318 | local remediation="You should not map container ports to privileged host ports when starting a container. You should also, ensure that there is no such container to host privileged port mapping declarations in the Dockerfile." 319 | local remediationImpact="None." 320 | local check="$id - $desc" 321 | starttestjson "$id" "$desc" 322 | 323 | fail=0 324 | privileged_port_containers="" 325 | for c in $containers; do 326 | # Port format is private port -> ip: public port 327 | ports=$(docker port "$c" | awk '{print $0}' | cut -d ':' -f2) 328 | 329 | # iterate through port range (line delimited) 330 | for port in $ports; do 331 | if [ -n "$port" ] && [ "$port" -lt 1025 ]; then 332 | # If it's the first container, fail the test 333 | if [ $fail -eq 0 ]; then 334 | warn -s "$check" 335 | warn " * Privileged Port in use: $port in $c" 336 | privileged_port_containers="$privileged_port_containers $c:$port" 337 | fail=1 338 | continue 339 | fi 340 | warn " * Privileged Port in use: $port in $c" 341 | privileged_port_containers="$privileged_port_containers $c:$port" 342 | fi 343 | done 344 | done 345 | # We went through all the containers and found no privileged ports 346 | if [ $fail -eq 0 ]; then 347 | pass -s "$check" 348 | logcheckresult "PASS" 349 | return 350 | fi 351 | logcheckresult "WARN" "Containers using privileged ports" "$privileged_port_containers" 352 | } 353 | 354 | check_5_9() { 355 | if [ -z "$containers" ]; then 356 | return 357 | fi 358 | 359 | local id="5.9" 360 | local desc="Ensure that only needed ports are open on the container (Manual)" 361 | local remediation="You should ensure that the Dockerfile for each container image only exposes needed ports." 362 | local remediationImpact="None." 363 | local check="$id - $desc" 364 | starttestjson "$id" "$desc" 365 | 366 | fail=0 367 | open_port_containers="" 368 | for c in $containers; do 369 | ports=$(docker port "$c" | awk '{print $0}' | cut -d ':' -f2) 370 | 371 | for port in $ports; do 372 | if [ -n "$port" ]; then 373 | # If it's the first container, fail the test 374 | if [ $fail -eq 0 ]; then 375 | warn -s "$check" 376 | warn " * Port in use: $port in $c" 377 | open_port_containers="$open_port_containers $c:$port" 378 | fail=1 379 | continue 380 | fi 381 | warn " * Port in use: $port in $c" 382 | open_port_containers="$open_port_containers $c:$port" 383 | fi 384 | done 385 | done 386 | 387 | # We went through all the containers and found none with open ports 388 | if [ $fail -eq 0 ]; then 389 | pass -s "$check" 390 | logcheckresult "PASS" 391 | return 392 | fi 393 | logcheckresult "WARN" "Containers with open ports" "$open_port_containers" 394 | } 395 | 396 | check_5_10() { 397 | if [ -z "$containers" ]; then 398 | return 399 | fi 400 | 401 | local id="5.10" 402 | local desc="Ensure that the host's network namespace is not shared (Automated)" 403 | local remediation="You should not pass the --net=host option when starting any container." 404 | local remediationImpact="None." 405 | local check="$id - $desc" 406 | starttestjson "$id" "$desc" 407 | 408 | fail=0 409 | net_host_containers="" 410 | for c in $containers; do 411 | mode=$(docker inspect --format 'NetworkMode={{ .HostConfig.NetworkMode }}' "$c") 412 | 413 | if [ "$mode" = "NetworkMode=host" ]; then 414 | # If it's the first container, fail the test 415 | if [ $fail -eq 0 ]; then 416 | warn -s "$check" 417 | warn " * Container running with networking mode 'host': $c" 418 | net_host_containers="$net_host_containers $c" 419 | fail=1 420 | continue 421 | fi 422 | warn " * Container running with networking mode 'host': $c" 423 | net_host_containers="$net_host_containers $c" 424 | fi 425 | done 426 | # We went through all the containers and found no Network Mode host 427 | if [ $fail -eq 0 ]; then 428 | pass -s "$check" 429 | logcheckresult "PASS" 430 | return 431 | fi 432 | logcheckresult "WARN" "Containers running with networking mode 'host'" "$net_host_containers" 433 | } 434 | 435 | check_5_11() { 436 | if [ -z "$containers" ]; then 437 | return 438 | fi 439 | 440 | local id="5.11" 441 | local desc="Ensure that the memory usage for containers is limited (Automated)" 442 | local remediation="You should run the container with only as much memory as it requires by using the --memory argument." 443 | local remediationImpact="If correct memory limits are not set on each container, one process can expand its usage and cause other containers to run out of resources." 444 | local check="$id - $desc" 445 | starttestjson "$id" "$desc" 446 | 447 | fail=0 448 | mem_unlimited_containers="" 449 | for c in $containers; do 450 | memory=$(docker inspect --format '{{ .HostConfig.Memory }}' "$c") 451 | if docker inspect --format '{{ .Config.Memory }}' "$c" 2> /dev/null 1>&2; then 452 | memory=$(docker inspect --format '{{ .Config.Memory }}' "$c") 453 | fi 454 | 455 | if [ "$memory" = "0" ]; then 456 | # If it's the first container, fail the test 457 | if [ $fail -eq 0 ]; then 458 | warn -s "$check" 459 | warn " * Container running without memory restrictions: $c" 460 | mem_unlimited_containers="$mem_unlimited_containers $c" 461 | fail=1 462 | continue 463 | fi 464 | warn " * Container running without memory restrictions: $c" 465 | mem_unlimited_containers="$mem_unlimited_containers $c" 466 | fi 467 | done 468 | # We went through all the containers and found no lack of Memory restrictions 469 | if [ $fail -eq 0 ]; then 470 | pass -s "$check" 471 | logcheckresult "PASS" 472 | return 473 | fi 474 | logcheckresult "WARN" "Container running without memory restrictions" "$mem_unlimited_containers" 475 | } 476 | 477 | check_5_12() { 478 | if [ -z "$containers" ]; then 479 | return 480 | fi 481 | 482 | local id="5.12" 483 | local desc="Ensure that CPU priority is set appropriately on containers (Automated)" 484 | local remediation="You should manage the CPU runtime between your containers dependent on their priority within your organization. To do so start the container using the --cpu-shares argument." 485 | local remediationImpact="If you do not correctly assign CPU thresholds, the container process may run out of resources and become unresponsive. If CPU resources on the host are not constrainted, CPU shares do not place any restrictions on individual resources." 486 | local check="$id - $desc" 487 | starttestjson "$id" "$desc" 488 | 489 | fail=0 490 | cpu_unlimited_containers="" 491 | for c in $containers; do 492 | cpushares=$(docker inspect --format '{{ .HostConfig.CpuShares }}' "$c") 493 | nanocpus=$(docker inspect --format '{{ .HostConfig.NanoCpus }}' "$c") 494 | 495 | if docker inspect --format '{{ .Config.CpuShares }}' "$c" 2> /dev/null 1>&2; then 496 | cpushares=$(docker inspect --format '{{ .Config.CpuShares }}' "$c") 497 | nanocpus=$(docker inspect --format '{{ .Config.NanoCpus }}' "$c") 498 | fi 499 | 500 | if [ "$cpushares" = "0" ] && [ "$nanocpus" = "0" ]; then 501 | # If it's the first container, fail the test 502 | if [ $fail -eq 0 ]; then 503 | warn -s "$check" 504 | warn " * Container running without CPU restrictions: $c" 505 | cpu_unlimited_containers="$cpu_unlimited_containers $c" 506 | fail=1 507 | continue 508 | fi 509 | warn " * Container running without CPU restrictions: $c" 510 | cpu_unlimited_containers="$cpu_unlimited_containers $c" 511 | fi 512 | done 513 | # We went through all the containers and found no lack of CPUShare restrictions 514 | if [ $fail -eq 0 ]; then 515 | pass -s "$check" 516 | logcheckresult "PASS" 517 | return 518 | fi 519 | logcheckresult "WARN" "Containers running without CPU restrictions" "$cpu_unlimited_containers" 520 | } 521 | 522 | check_5_13() { 523 | if [ -z "$containers" ]; then 524 | return 525 | fi 526 | 527 | local id="5.13" 528 | local desc="Ensure that the container's root filesystem is mounted as read only (Automated)" 529 | local remediation="You should add a --read-only flag at a container's runtime to enforce the container's root filesystem being mounted as read only." 530 | local remediationImpact="Enabling --read-only at container runtime may break some container OS packages if a data writing strategy is not defined. You should define what the container's data should and should not persist at runtime in order to decide which strategy to use." 531 | local check="$id - $desc" 532 | starttestjson "$id" "$desc" 533 | 534 | fail=0 535 | fsroot_mount_containers="" 536 | for c in $containers; do 537 | read_status=$(docker inspect --format '{{ .HostConfig.ReadonlyRootfs }}' "$c") 538 | 539 | if [ "$read_status" = "false" ]; then 540 | # If it's the first container, fail the test 541 | if [ $fail -eq 0 ]; then 542 | warn -s "$check" 543 | warn " * Container running with root FS mounted R/W: $c" 544 | fsroot_mount_containers="$fsroot_mount_containers $c" 545 | fail=1 546 | continue 547 | fi 548 | warn " * Container running with root FS mounted R/W: $c" 549 | fsroot_mount_containers="$fsroot_mount_containers $c" 550 | fi 551 | done 552 | # We went through all the containers and found no R/W FS mounts 553 | if [ $fail -eq 0 ]; then 554 | pass -s "$check" 555 | logcheckresult "PASS" 556 | return 557 | fi 558 | logcheckresult "WARN" "Containers running with root FS mounted R/W" "$fsroot_mount_containers" 559 | } 560 | 561 | check_5_14() { 562 | if [ -z "$containers" ]; then 563 | return 564 | fi 565 | 566 | local id="5.14" 567 | local desc="Ensure that incoming container traffic is bound to a specific host interface (Automated)" 568 | local remediation="You should bind the container port to a specific host interface on the desired host port. Example: docker run --detach --publish 10.2.3.4:49153:80 nginx In this example, the container port 80 is bound to the host port on 49153 and would accept incoming connection only from the 10.2.3.4 external interface." 569 | local remediationImpact="None." 570 | local check="$id - $desc" 571 | starttestjson "$id" "$desc" 572 | 573 | fail=0 574 | incoming_unbound_containers="" 575 | for c in $containers; do 576 | for ip in $(docker port "$c" | awk '{print $3}' | cut -d ':' -f1); do 577 | if [ "$ip" = "0.0.0.0" ]; then 578 | # If it's the first container, fail the test 579 | if [ $fail -eq 0 ]; then 580 | warn -s "$check" 581 | warn " * Port being bound to wildcard IP: $ip in $c" 582 | incoming_unbound_containers="$incoming_unbound_containers $c:$ip" 583 | fail=1 584 | continue 585 | fi 586 | warn " * Port being bound to wildcard IP: $ip in $c" 587 | incoming_unbound_containers="$incoming_unbound_containers $c:$ip" 588 | fi 589 | done 590 | done 591 | # We went through all the containers and found no ports bound to 0.0.0.0 592 | if [ $fail -eq 0 ]; then 593 | pass -s "$check" 594 | logcheckresult "PASS" 595 | return 596 | fi 597 | logcheckresult "WARN" "Containers with port bound to wildcard IP" "$incoming_unbound_containers" 598 | } 599 | 600 | check_5_15() { 601 | if [ -z "$containers" ]; then 602 | return 603 | fi 604 | 605 | local id="5.15" 606 | local desc="Ensure that the 'on-failure' container restart policy is set to '5' (Automated)" 607 | local remediation="If you wish a container to be automatically restarted, a sample command is docker run --detach --restart=on-failure:5 nginx" 608 | local remediationImpact="If this option is set, a container will only attempt to restart itself 5 times." 609 | local check="$id - $desc" 610 | starttestjson "$id" "$desc" 611 | 612 | fail=0 613 | maxretry_unset_containers="" 614 | for c in $containers; do 615 | container_name=$(docker inspect "$c" --format '{{.Name}}') 616 | if [ "$(docker info --format '{{.Swarm.LocalNodeState}}')" = "active" ]; then 617 | for s in $(docker service ls --format '{{.Name}}'); do 618 | if echo $container_name | grep -q "$s"; then 619 | task_id=$(docker inspect "$c" --format '{{.Name}}' | awk -F '.' '{print $NF}') 620 | # a container name could arbitrary include a service one: it belongs to a service (created by Docker 621 | # as part of the service), if the container task ID matches one of the task IDs of the service. 622 | if docker service ps --no-trunc "$s" --format '{{.ID}}' | grep -q "$task_id"; then 623 | restart_policy=$(docker inspect --format '{{ .Spec.TaskTemplate.RestartPolicy.MaxAttempts }}' "$s") 624 | break 625 | fi 626 | fi 627 | done 628 | fi 629 | if docker inspect --format '{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' "$c" &>/dev/null; then 630 | restart_policy=$(docker inspect --format '{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' "$c") 631 | fi 632 | 633 | if [ "$restart_policy" -gt "5" ]; then 634 | # If it's the first container, fail the test 635 | if [ $fail -eq 0 ]; then 636 | warn -s "$check" 637 | warn " * MaximumRetryCount is not set to 5 or less: $c" 638 | maxretry_unset_containers="$maxretry_unset_containers $c" 639 | fail=1 640 | continue 641 | fi 642 | warn " * MaximumRetryCount is not set to 5 or less: $c" 643 | maxretry_unset_containers="$maxretry_unset_containers $c" 644 | fi 645 | done 646 | # We went through all the containers and they all had MaximumRetryCount=5 647 | if [ $fail -eq 0 ]; then 648 | pass -s "$check" 649 | logcheckresult "PASS" 650 | return 651 | fi 652 | logcheckresult "WARN" "Containers with MaximumRetryCount not set to 5 or less" "$maxretry_unset_containers" 653 | } 654 | 655 | check_5_16() { 656 | if [ -z "$containers" ]; then 657 | return 658 | fi 659 | 660 | local id="5.16" 661 | local desc="Ensure that the host's process namespace is not shared (Automated)" 662 | local remediation="You should not start a container with the --pid=host argument." 663 | local remediationImpact="Container processes cannot see processes on the host system." 664 | local check="$id - $desc" 665 | starttestjson "$id" "$desc" 666 | 667 | fail=0 668 | pidns_shared_containers="" 669 | for c in $containers; do 670 | mode=$(docker inspect --format 'PidMode={{.HostConfig.PidMode }}' "$c") 671 | 672 | if [ "$mode" = "PidMode=host" ]; then 673 | # If it's the first container, fail the test 674 | if [ $fail -eq 0 ]; then 675 | warn -s "$check" 676 | warn " * Host PID namespace being shared with: $c" 677 | pidns_shared_containers="$pidns_shared_containers $c" 678 | fail=1 679 | continue 680 | fi 681 | warn " * Host PID namespace being shared with: $c" 682 | pidns_shared_containers="$pidns_shared_containers $c" 683 | fi 684 | done 685 | # We went through all the containers and found none with PidMode as host 686 | if [ $fail -eq 0 ]; then 687 | pass -s "$check" 688 | logcheckresult "PASS" 689 | return 690 | fi 691 | logcheckresult "WARN" "Containers sharing host PID namespace" "$pidns_shared_containers" 692 | } 693 | 694 | check_5_17() { 695 | if [ -z "$containers" ]; then 696 | return 697 | fi 698 | 699 | local id="5.17" 700 | local desc="Ensure that the host's IPC namespace is not shared (Automated)" 701 | local remediation="You should not start a container with the --ipc=host argument." 702 | local remediationImpact="Shared memory segments are used in order to accelerate interprocess communications, commonly in high-performance applications. If this type of application is containerized into multiple containers, you might need to share the IPC namespace of the containers in order to achieve high performance. Under these circumstances, you should still only share container specific IPC namespaces and not the host IPC namespace." 703 | local check="$id - $desc" 704 | starttestjson "$id" "$desc" 705 | 706 | fail=0 707 | ipcns_shared_containers="" 708 | for c in $containers; do 709 | mode=$(docker inspect --format 'IpcMode={{.HostConfig.IpcMode }}' "$c") 710 | 711 | if [ "$mode" = "IpcMode=host" ]; then 712 | # If it's the first container, fail the test 713 | if [ $fail -eq 0 ]; then 714 | warn -s "$check" 715 | warn " * Host IPC namespace being shared with: $c" 716 | ipcns_shared_containers="$ipcns_shared_containers $c" 717 | fail=1 718 | continue 719 | fi 720 | warn " * Host IPC namespace being shared with: $c" 721 | ipcns_shared_containers="$ipcns_shared_containers $c" 722 | fi 723 | done 724 | # We went through all the containers and found none with IPCMode as host 725 | if [ $fail -eq 0 ]; then 726 | pass -s "$check" 727 | logcheckresult "PASS" 728 | return 729 | fi 730 | logcheckresult "WARN" "Containers sharing host IPC namespace" "$ipcns_shared_containers" 731 | } 732 | 733 | check_5_18() { 734 | if [ -z "$containers" ]; then 735 | return 736 | fi 737 | 738 | local id="5.18" 739 | local desc="Ensure that host devices are not directly exposed to containers (Manual)" 740 | local remediation="You should not directly expose host devices to containers. If you do need to expose host devices to containers, you should use granular permissions as appropriate to your organization." 741 | local remediationImpact="You would not be able to use host devices directly within containers." 742 | local check="$id - $desc" 743 | starttestjson "$id" "$desc" 744 | 745 | fail=0 746 | hostdev_exposed_containers="" 747 | for c in $containers; do 748 | devices=$(docker inspect --format 'Devices={{ .HostConfig.Devices }}' "$c") 749 | 750 | if [ "$devices" != "Devices=" ] && [ "$devices" != "Devices=[]" ] && [ "$devices" != "Devices=" ]; then 751 | # If it's the first container, fail the test 752 | if [ $fail -eq 0 ]; then 753 | info -c "$check" 754 | info " * Container has devices exposed directly: $c" 755 | hostdev_exposed_containers="$hostdev_exposed_containers $c" 756 | fail=1 757 | continue 758 | fi 759 | info " * Container has devices exposed directly: $c" 760 | hostdev_exposed_containers="$hostdev_exposed_containers $c" 761 | fi 762 | done 763 | # We went through all the containers and found none with devices 764 | if [ $fail -eq 0 ]; then 765 | pass -c "$check" 766 | logcheckresult "PASS" 767 | return 768 | fi 769 | logcheckresult "INFO" "Containers with host devices exposed directly" "$hostdev_exposed_containers" 770 | } 771 | 772 | check_5_19() { 773 | if [ -z "$containers" ]; then 774 | return 775 | fi 776 | 777 | local id="5.19" 778 | local desc="Ensure that the default ulimit is overwritten at runtime if needed (Manual)" 779 | local remediation="You should only override the default ulimit settings if needed in a specific case." 780 | local remediationImpact="If ulimits are not set correctly, overutilization by individual containers could make the host system unusable." 781 | local check="$id - $desc" 782 | starttestjson "$id" "$desc" 783 | 784 | fail=0 785 | no_ulimit_containers="" 786 | for c in $containers; do 787 | ulimits=$(docker inspect --format 'Ulimits={{ .HostConfig.Ulimits }}' "$c") 788 | 789 | if [ "$ulimits" = "Ulimits=" ] || [ "$ulimits" = "Ulimits=[]" ] || [ "$ulimits" = "Ulimits=" ]; then 790 | # If it's the first container, fail the test 791 | if [ $fail -eq 0 ]; then 792 | info -c "$check" 793 | info " * Container no default ulimit override: $c" 794 | no_ulimit_containers="$no_ulimit_containers $c" 795 | fail=1 796 | continue 797 | fi 798 | info " * Container no default ulimit override: $c" 799 | no_ulimit_containers="$no_ulimit_containers $c" 800 | fi 801 | done 802 | # We went through all the containers and found none without Ulimits 803 | if [ $fail -eq 0 ]; then 804 | pass -c "$check" 805 | logcheckresult "PASS" 806 | return 807 | fi 808 | logcheckresult "INFO" "Containers with no default ulimit override" "$no_ulimit_containers" 809 | } 810 | 811 | check_5_20() { 812 | if [ -z "$containers" ]; then 813 | return 814 | fi 815 | 816 | local id="5.20" 817 | local desc="Ensure mount propagation mode is not set to shared (Automated)" 818 | local remediation="Do not mount volumes in shared mode propagation." 819 | local remediationImpact="None." 820 | local check="$id - $desc" 821 | starttestjson "$id" "$desc" 822 | 823 | fail=0 824 | mountprop_shared_containers="" 825 | for c in $containers; do 826 | if docker inspect --format 'Propagation={{range $mnt := .Mounts}} {{json $mnt.Propagation}} {{end}}' "$c" | \ 827 | grep shared 2>/dev/null 1>&2; then 828 | # If it's the first container, fail the test 829 | if [ $fail -eq 0 ]; then 830 | warn -s "$check" 831 | warn " * Mount propagation mode is shared: $c" 832 | mountprop_shared_containers="$mountprop_shared_containers $c" 833 | fail=1 834 | continue 835 | fi 836 | warn " * Mount propagation mode is shared: $c" 837 | mountprop_shared_containers="$mountprop_shared_containers $c" 838 | fi 839 | done 840 | # We went through all the containers and found none with shared propagation mode 841 | if [ $fail -eq 0 ]; then 842 | pass -s "$check" 843 | logcheckresult "PASS" 844 | return 845 | fi 846 | logcheckresult "WARN" "Containers with shared mount propagation" "$mountprop_shared_containers" 847 | } 848 | 849 | check_5_21() { 850 | if [ -z "$containers" ]; then 851 | return 852 | fi 853 | 854 | local id="5.21" 855 | local desc="Ensure that the host's UTS namespace is not shared (Automated)" 856 | local remediation="You should not start a container with the --uts=host argument." 857 | local remediationImpact="None." 858 | local check="$id - $desc" 859 | starttestjson "$id" "$desc" 860 | 861 | fail=0 862 | utcns_shared_containers="" 863 | for c in $containers; do 864 | mode=$(docker inspect --format 'UTSMode={{.HostConfig.UTSMode }}' "$c") 865 | 866 | if [ "$mode" = "UTSMode=host" ]; then 867 | # If it's the first container, fail the test 868 | if [ $fail -eq 0 ]; then 869 | warn -s "$check" 870 | warn " * Host UTS namespace being shared with: $c" 871 | utcns_shared_containers="$utcns_shared_containers $c" 872 | fail=1 873 | continue 874 | fi 875 | warn " * Host UTS namespace being shared with: $c" 876 | utcns_shared_containers="$utcns_shared_containers $c" 877 | fi 878 | done 879 | # We went through all the containers and found none with UTSMode as host 880 | if [ $fail -eq 0 ]; then 881 | pass -s "$check" 882 | logcheckresult "PASS" 883 | return 884 | fi 885 | logcheckresult "WARN" "Containers sharing host UTS namespace" "$utcns_shared_containers" 886 | } 887 | 888 | check_5_22() { 889 | if [ -z "$containers" ]; then 890 | return 891 | fi 892 | 893 | local id="5.22" 894 | local desc="Ensure the default seccomp profile is not Disabled (Automated)" 895 | local remediation="By default, seccomp profiles are enabled. You do not need to do anything unless you want to modify and use a modified seccomp profile." 896 | local remediationImpact="With Docker 1.10 and greater, the default seccomp profile blocks syscalls, regardless of -- cap-add passed to the container." 897 | local check="$id - $desc" 898 | starttestjson "$id" "$desc" 899 | 900 | fail=0 901 | seccomp_disabled_containers="" 902 | for c in $containers; do 903 | if docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "$c" | \ 904 | grep -E 'seccomp:unconfined|seccomp=unconfined' 2>/dev/null 1>&2; then 905 | # If it's the first container, fail the test 906 | if [ $fail -eq 0 ]; then 907 | warn -s "$check" 908 | warn " * Default seccomp profile disabled: $c" 909 | seccomp_disabled_containers="$seccomp_disabled_containers $c" 910 | fail=1 911 | else 912 | warn " * Default seccomp profile disabled: $c" 913 | seccomp_disabled_containers="$seccomp_disabled_containers $c" 914 | fi 915 | fi 916 | done 917 | # We went through all the containers and found none with default secomp profile disabled 918 | if [ $fail -eq 0 ]; then 919 | pass -s "$check" 920 | logcheckresult "PASS" 921 | return 922 | fi 923 | logcheckresult "WARN" "Containers with default seccomp profile disabled" "$seccomp_disabled_containers" 924 | } 925 | 926 | check_5_23() { 927 | if [ -z "$containers" ]; then 928 | return 929 | fi 930 | 931 | local id="5.23" 932 | local desc="Ensure that docker exec commands are not used with the privileged option (Automated)" 933 | local remediation="You should not use the --privileged option in docker exec commands." 934 | local remediationImpact="If you need enhanced capabilities within a container, then run it with all the permissions it requires. These should be specified individually." 935 | local check="$id - $desc" 936 | starttestjson "$id" "$desc" 937 | 938 | note -c "$check" 939 | logcheckresult "NOTE" 940 | } 941 | 942 | check_5_24() { 943 | if [ -z "$containers" ]; then 944 | return 945 | fi 946 | 947 | local id="5.24" 948 | local desc="Ensure that docker exec commands are not used with the user=root option (Manual)" 949 | local remediation="You should not use the --user=root option in docker exec commands." 950 | local remediationImpact="None." 951 | local check="$id - $desc" 952 | starttestjson "$id" "$desc" 953 | 954 | note -c "$check" 955 | logcheckresult "NOTE" 956 | } 957 | 958 | check_5_25() { 959 | if [ -z "$containers" ]; then 960 | return 961 | fi 962 | 963 | local id="5.25" 964 | local desc="Ensure that cgroup usage is confirmed (Automated)" 965 | local remediation="You should not use the --cgroup-parent option within the docker run command unless strictly required." 966 | local remediationImpact="None." 967 | local check="$id - $desc" 968 | starttestjson "$id" "$desc" 969 | 970 | fail=0 971 | unexpected_cgroup_containers="" 972 | for c in $containers; do 973 | mode=$(docker inspect --format 'CgroupParent={{.HostConfig.CgroupParent }}x' "$c") 974 | 975 | if [ "$mode" != "CgroupParent=x" ]; then 976 | # If it's the first container, fail the test 977 | if [ $fail -eq 0 ]; then 978 | warn -s "$check" 979 | warn " * Confirm cgroup usage: $c" 980 | unexpected_cgroup_containers="$unexpected_cgroup_containers $c" 981 | fail=1 982 | continue 983 | fi 984 | warn " * Confirm cgroup usage: $c" 985 | unexpected_cgroup_containers="$unexpected_cgroup_containers $c" 986 | fi 987 | done 988 | # We went through all the containers and found none with UTSMode as host 989 | if [ $fail -eq 0 ]; then 990 | pass -s "$check" 991 | logcheckresult "PASS" 992 | return 993 | fi 994 | logcheckresult "WARN" "Containers using unexpected cgroup" "$unexpected_cgroup_containers" 995 | } 996 | 997 | check_5_26() { 998 | if [ -z "$containers" ]; then 999 | return 1000 | fi 1001 | local id="5.26" 1002 | local desc="Ensure that the container is restricted from acquiring additional privileges (Automated)" 1003 | local remediation="You should start your container with the options: docker run --rm -it --security-opt=no-new-privileges ubuntu bash" 1004 | local remediationImpact="The no_new_priv option prevents LSMs like SELinux from allowing processes to acquire new privileges." 1005 | local check="$id - $desc" 1006 | starttestjson "$id" "$desc" 1007 | 1008 | fail=0 1009 | no_priv_config=0 1010 | addprivs_containers="" 1011 | 1012 | if get_docker_effective_command_line_args '--no-new-privileges' | grep "no-new-privileges" >/dev/null 2>&1; then 1013 | no_priv_config=1 1014 | elif get_docker_configuration_file_args 'no-new-privileges' | grep true >/dev/null 2>&1; then 1015 | no_priv_config=1 1016 | else 1017 | for c in $containers; do 1018 | if ! docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "$c" | grep 'no-new-privileges' 2>/dev/null 1>&2; then 1019 | # If it's the first container, fail the test 1020 | if [ $fail -eq 0 ]; then 1021 | warn -s "$check" 1022 | warn " * Privileges not restricted: $c" 1023 | addprivs_containers="$addprivs_containers $c" 1024 | fail=1 1025 | continue 1026 | fi 1027 | warn " * Privileges not restricted: $c" 1028 | addprivs_containers="$addprivs_containers $c" 1029 | fi 1030 | done 1031 | fi 1032 | 1033 | # We went through all the containers and found none with capability to acquire additional privileges 1034 | if [ $fail -eq 0 ] || [ $no_priv_config -eq 1 ]; then 1035 | pass -s "$check" 1036 | logcheckresult "PASS" 1037 | return 1038 | fi 1039 | logcheckresult "WARN" "Containers without restricted privileges" "$addprivs_containers" 1040 | } 1041 | 1042 | check_5_27() { 1043 | if [ -z "$containers" ]; then 1044 | return 1045 | fi 1046 | 1047 | local id="5.27" 1048 | local desc="Ensure that container health is checked at runtime (Automated)" 1049 | local remediation="You should run the container using the --health-cmd parameter." 1050 | local remediationImpact="None." 1051 | local check="$id - $desc" 1052 | starttestjson "$id" "$desc" 1053 | 1054 | fail=0 1055 | nohealthcheck_containers="" 1056 | for c in $containers; do 1057 | if ! docker inspect --format '{{ .Id }}: Health={{ .State.Health.Status }}' "$c" 2>/dev/null 1>&2; then 1058 | if [ $fail -eq 0 ]; then 1059 | warn -s "$check" 1060 | warn " * Health check not set: $c" 1061 | nohealthcheck_containers="$nohealthcheck_containers $c" 1062 | fail=1 1063 | continue 1064 | fi 1065 | warn " * Health check not set: $c" 1066 | nohealthcheck_containers="$nohealthcheck_containers $c" 1067 | fi 1068 | done 1069 | if [ $fail -eq 0 ]; then 1070 | pass -s "$check" 1071 | logcheckresult "PASS" 1072 | return 1073 | fi 1074 | logcheckresult "WARN" "Containers without health check" "$nohealthcheck_containers" 1075 | } 1076 | 1077 | check_5_28() { 1078 | if [ -z "$containers" ]; then 1079 | return 1080 | fi 1081 | 1082 | local id="5.28" 1083 | local desc="Ensure that Docker commands always make use of the latest version of their image (Manual)" 1084 | local remediation="You should use proper version pinning mechanisms (the tag which is assigned by default is still vulnerable to caching attacks) to avoid extracting cached older versions. Version pinning mechanisms should be used for base images, packages, and entire images. You can customize version pinning rules according to your requirements." 1085 | local remediationImpact="None." 1086 | local check="$id - $desc" 1087 | starttestjson "$id" "$desc" 1088 | 1089 | info -c "$check" 1090 | logcheckresult "INFO" 1091 | } 1092 | 1093 | check_5_29() { 1094 | if [ -z "$containers" ]; then 1095 | return 1096 | fi 1097 | 1098 | local id="5.29" 1099 | local desc="Ensure that the PIDs cgroup limit is used (Automated)" 1100 | local remediation="Use --pids-limit flag with an appropriate value when launching the container." 1101 | local remediationImpact="Set the PIDs limit value as appropriate. Incorrect values might leave containers unusable." 1102 | local check="$id - $desc" 1103 | starttestjson "$id" "$desc" 1104 | 1105 | fail=0 1106 | nopids_limit_containers="" 1107 | for c in $containers; do 1108 | pidslimit="$(docker inspect --format '{{.HostConfig.PidsLimit }}' "$c")" 1109 | 1110 | if [ "$pidslimit" = "0" ] || [ "$pidslimit" = "" ] || [ "$pidslimit" = "-1" ]; then 1111 | # If it's the first container, fail the test 1112 | if [ $fail -eq 0 ]; then 1113 | warn -s "$check" 1114 | warn " * PIDs limit not set: $c" 1115 | nopids_limit_containers="$nopids_limit_containers $c" 1116 | fail=1 1117 | continue 1118 | fi 1119 | warn " * PIDs limit not set: $c" 1120 | nopids_limit_containers="$nopids_limit_containers $c" 1121 | fi 1122 | done 1123 | # We went through all the containers and found all with PIDs limit 1124 | if [ $fail -eq 0 ]; then 1125 | pass -s "$check" 1126 | logcheckresult "PASS" 1127 | return 1128 | fi 1129 | logcheckresult "WARN" "Containers without PIDs cgroup limit" "$nopids_limit_containers" 1130 | } 1131 | 1132 | check_5_30() { 1133 | if [ -z "$containers" ]; then 1134 | return 1135 | fi 1136 | 1137 | local id="5.30" 1138 | local desc="Ensure that Docker's default bridge 'docker0' is not used (Manual)" 1139 | local remediation="You should follow the Docker documentation and set up a user-defined network. All the containers should be run in this network." 1140 | local remediationImpact="User-defined networks need to be configured and managed in line with organizational security policy." 1141 | local check="$id - $desc" 1142 | starttestjson "$id" "$desc" 1143 | 1144 | fail=0 1145 | docker_network_containers="" 1146 | networks=$(docker network ls -q 2>/dev/null) 1147 | for net in $networks; do 1148 | if docker network inspect --format '{{ .Options }}' "$net" 2>/dev/null | grep "com.docker.network.bridge.name:docker0" >/dev/null 2>&1; then 1149 | docker0Containers=$(docker network inspect --format='{{ range $k, $v := .Containers }} {{ $k }} {{ end }}' "$net" | \ 1150 | sed -e 's/^ //' -e 's/ /\n/g' 2>/dev/null) 1151 | 1152 | if [ -n "$docker0Containers" ]; then 1153 | if [ $fail -eq 0 ]; then 1154 | info -c "$check" 1155 | fail=1 1156 | fi 1157 | for c in $docker0Containers; do 1158 | if [ -z "$exclude" ]; then 1159 | cName=$(docker inspect --format '{{.Name}}' "$c" 2>/dev/null | sed 's/\///g') 1160 | else 1161 | pattern=$(echo "$exclude" | sed 's/,/|/g') 1162 | cName=$(docker inspect --format '{{.Name}}' "$c" 2>/dev/null | sed 's/\///g' | grep -Ev "$pattern" ) 1163 | fi 1164 | if [ -n "$cName" ]; then 1165 | info " * Container in docker0 network: $cName" 1166 | docker_network_containers="$docker_network_containers $c:$cName" 1167 | fi 1168 | done 1169 | fi 1170 | fi 1171 | done 1172 | # We went through all the containers and found none in docker0 network 1173 | if [ $fail -eq 0 ]; then 1174 | pass -c "$check" 1175 | logcheckresult "PASS" 1176 | return 1177 | fi 1178 | logcheckresult "INFO" "Containers using docker0 network" "$docker_network_containers" 1179 | } 1180 | 1181 | check_5_31() { 1182 | if [ -z "$containers" ]; then 1183 | return 1184 | fi 1185 | 1186 | local id="5.31" 1187 | local desc="Ensure that the host's user namespaces are not shared (Automated)" 1188 | local remediation="You should not share user namespaces between host and containers." 1189 | local remediationImpact="None." 1190 | local check="$id - $desc" 1191 | starttestjson "$id" "$desc" 1192 | 1193 | fail=0 1194 | hostns_shared_containers="" 1195 | for c in $containers; do 1196 | if docker inspect --format '{{ .HostConfig.UsernsMode }}' "$c" 2>/dev/null | grep -i 'host' >/dev/null 2>&1; then 1197 | # If it's the first container, fail the test 1198 | if [ $fail -eq 0 ]; then 1199 | warn -s "$check" 1200 | warn " * Namespace shared: $c" 1201 | hostns_shared_containers="$hostns_shared_containers $c" 1202 | fail=1 1203 | continue 1204 | fi 1205 | warn " * Namespace shared: $c" 1206 | hostns_shared_containers="$hostns_shared_containers $c" 1207 | fi 1208 | done 1209 | # We went through all the containers and found none with host's user namespace shared 1210 | if [ $fail -eq 0 ]; then 1211 | pass -s "$check" 1212 | logcheckresult "PASS" 1213 | return 1214 | fi 1215 | logcheckresult "WARN" "Containers sharing host user namespace" "$hostns_shared_containers" 1216 | } 1217 | 1218 | check_5_32() { 1219 | if [ -z "$containers" ]; then 1220 | return 1221 | fi 1222 | 1223 | local id="5.32" 1224 | local desc="Ensure that the Docker socket is not mounted inside any containers (Automated)" 1225 | local remediation="You should ensure that no containers mount docker.sock as a volume." 1226 | local remediationImpact="None." 1227 | local check="$id - $desc" 1228 | starttestjson "$id" "$desc" 1229 | 1230 | fail=0 1231 | docker_sock_containers="" 1232 | for c in $containers; do 1233 | if docker inspect --format '{{ .Mounts }}' "$c" 2>/dev/null | grep 'docker.sock' >/dev/null 2>&1; then 1234 | # If it's the first container, fail the test 1235 | if [ $fail -eq 0 ]; then 1236 | warn -s "$check" 1237 | warn " * Docker socket shared: $c" 1238 | docker_sock_containers="$docker_sock_containers $c" 1239 | fail=1 1240 | continue 1241 | fi 1242 | warn " * Docker socket shared: $c" 1243 | docker_sock_containers="$docker_sock_containers $c" 1244 | fi 1245 | done 1246 | # We went through all the containers and found none with docker.sock shared 1247 | if [ $fail -eq 0 ]; then 1248 | pass -s "$check" 1249 | logcheckresult "PASS" 1250 | return 1251 | fi 1252 | logcheckresult "WARN" "Containers sharing docker socket" "$docker_sock_containers" 1253 | } 1254 | 1255 | check_5_end() { 1256 | endsectionjson 1257 | } 1258 | -------------------------------------------------------------------------------- /tests/6_docker_security_operations.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_6() { 4 | logit "" 5 | local id="6" 6 | local desc="Docker Security Operations" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_6_1() { 13 | local id="6.1" 14 | local desc="Ensure that image sprawl is avoided (Manual)" 15 | local remediation="You should keep only the images that you actually need and establish a workflow to remove old or stale images from the host. Additionally, you should use features such as pull-by-digest to get specific images from the registry." 16 | local remediationImpact="docker system prune -a removes all exited containers as well as all images and volumes that are not referenced by running containers, including for UCP and DTR." 17 | local check="$id - $desc" 18 | starttestjson "$id" "$desc" 19 | 20 | images=$(docker images -q | sort -u | wc -l | awk '{print $1}') 21 | active_images=0 22 | 23 | for c in $(docker inspect --format "{{.Image}}" $(docker ps -qa) 2>/dev/null); do 24 | if docker images --no-trunc -a | grep "$c" > /dev/null ; then 25 | active_images=$(( active_images += 1 )) 26 | fi 27 | done 28 | 29 | info -c "$check" 30 | info " * There are currently: $images images" 31 | 32 | if [ "$active_images" -lt "$((images / 2))" ]; then 33 | info " * Only $active_images out of $images are in use" 34 | fi 35 | logcheckresult "INFO" "$active_images active/$images in use" 36 | } 37 | 38 | check_6_2() { 39 | local id="6.2" 40 | local desc="Ensure that container sprawl is avoided (Manual)" 41 | local remediation="You should periodically check your container inventory on each host and clean up containers which are not in active use with the command: docker container prune" 42 | local remediationImpact="You should retain containers that are actively in use, and delete ones which are no longer needed." 43 | local check="$id - $desc" 44 | starttestjson "$id" "$desc" 45 | 46 | total_containers=$(docker info 2>/dev/null | grep "Containers" | awk '{print $2}') 47 | running_containers=$(docker ps -q | wc -l | awk '{print $1}') 48 | diff="$((total_containers - running_containers))" 49 | info -c "$check" 50 | if [ "$diff" -gt 25 ]; then 51 | info " * There are currently a total of $total_containers containers, with only $running_containers of them currently running" 52 | else 53 | info " * There are currently a total of $total_containers containers, with $running_containers of them currently running" 54 | fi 55 | logcheckresult "INFO" "$total_containers total/$running_containers running" 56 | } 57 | 58 | check_6_end() { 59 | endsectionjson 60 | } 61 | -------------------------------------------------------------------------------- /tests/7_docker_swarm_configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_7() { 4 | logit "" 5 | local id="7" 6 | local desc="Docker Swarm Configuration" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_7_1() { 13 | local id="7.1" 14 | local desc="Ensure that the minimum number of manager nodes have been created in a swarm (Automated)" 15 | local remediation="If an excessive number of managers is configured, the excess nodes can be demoted to workers using command: docker node demote " 16 | local remediationImpact="None." 17 | local check="$id - $desc" 18 | starttestjson "$id" "$desc" 19 | 20 | if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then 21 | managernodes=$(docker node ls | grep -c "Leader") 22 | if [ "$managernodes" -eq 1 ]; then 23 | pass -s "$check" 24 | logcheckresult "PASS" 25 | return 26 | fi 27 | warn -s "$check" 28 | logcheckresult "WARN" 29 | return 30 | fi 31 | pass -s "$check (Swarm mode not enabled)" 32 | logcheckresult "PASS" 33 | } 34 | 35 | check_7_2() { 36 | local id="7.2" 37 | local desc="Ensure that swarm services are bound to a specific host interface (Automated)" 38 | local remediation="Resolving this issues requires re-initialization of the swarm, specifying a specific interface for the --listen-addr parameter." 39 | local remediationImpact="None." 40 | local check="$id - $desc" 41 | starttestjson "$id" "$desc" 42 | 43 | if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then 44 | $netbin -lnt | grep -e '\[::]:2377 ' -e ':::2377' -e '*:2377 ' -e ' 0\.0\.0\.0:2377 ' >/dev/null 2>&1 45 | if [ $? -eq 1 ]; then 46 | pass -s "$check" 47 | logcheckresult "PASS" 48 | return 49 | fi 50 | warn -s "$check" 51 | logcheckresult "WARN" 52 | return 53 | fi 54 | pass -s "$check (Swarm mode not enabled)" 55 | logcheckresult "PASS" 56 | } 57 | 58 | check_7_3() { 59 | local id="7.3" 60 | local desc="Ensure that all Docker swarm overlay networks are encrypted (Automated)" 61 | local remediation="You should create overlay networks the with --opt encrypted flag." 62 | local remediationImpact="None." 63 | local check="$id - $desc" 64 | starttestjson "$id" "$desc" 65 | 66 | fail=0 67 | unencrypted_networks="" 68 | for encnet in $(docker network ls --filter driver=overlay --quiet); do 69 | if docker network inspect --format '{{.Name}} {{ .Options }}' "$encnet" | \ 70 | grep -v 'encrypted:' 2>/dev/null 1>&2; then 71 | # If it's the first container, fail the test 72 | if [ $fail -eq 0 ]; then 73 | warn -s "$check" 74 | fail=1 75 | fi 76 | warn " * Unencrypted overlay network: $(docker network inspect --format '{{ .Name }} ({{ .Scope }})' "$encnet")" 77 | unencrypted_networks="$unencrypted_networks $(docker network inspect --format '{{ .Name }} ({{ .Scope }})' "$encnet")" 78 | fi 79 | done 80 | # We went through all the networks and found none that are unencrypted 81 | if [ $fail -eq 0 ]; then 82 | pass -s "$check" 83 | logcheckresult "PASS" 84 | return 85 | fi 86 | logcheckresult "WARN" "Unencrypted overlay networks:" "$unencrypted_networks" 87 | } 88 | 89 | check_7_4() { 90 | local id="7.4" 91 | local desc="Ensure that Docker's secret management commands are used for managing secrets in a swarm cluster (Manual)" 92 | local remediation="You should follow the docker secret documentation and use it to manage secrets effectively." 93 | local remediationImpact="None." 94 | local check="$id - $desc" 95 | starttestjson "$id" "$desc" 96 | 97 | if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then 98 | if [ "$(docker secret ls -q | wc -l)" -ge 1 ]; then 99 | pass -c "$check" 100 | logcheckresult "PASS" 101 | return 102 | fi 103 | info -c "$check" 104 | logcheckresult "INFO" 105 | return 106 | fi 107 | pass -c "$check (Swarm mode not enabled)" 108 | logcheckresult "PASS" 109 | } 110 | 111 | check_7_5() { 112 | local id="7.5" 113 | local desc="Ensure that swarm manager is run in auto-lock mode (Automated)" 114 | local remediation="If you are initializing a swarm, use the command: docker swarm init --autolock. If you want to set --autolock on an existing swarm manager node, use the command: docker swarm update --autolock." 115 | local remediationImpact="A swarm in auto-lock mode will not recover from a restart without manual intervention from an administrator to enter the unlock key. This may not always be desirable, and should be reviewed at a policy level." 116 | local check="$id - $desc" 117 | starttestjson "$id" "$desc" 118 | 119 | if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then 120 | if ! docker swarm unlock-key 2>/dev/null | grep 'SWMKEY' 2>/dev/null 1>&2; then 121 | warn -s "$check" 122 | logcheckresult "WARN" 123 | return 124 | fi 125 | pass -s "$check" 126 | logcheckresult "PASS" 127 | return 128 | fi 129 | pass -s "$check (Swarm mode not enabled)" 130 | logcheckresult "PASS" 131 | } 132 | 133 | check_7_6() { 134 | local id="7.6" 135 | local desc="Ensure that the swarm manager auto-lock key is rotated periodically (Manual)" 136 | local remediation="You should run the command docker swarm unlock-key --rotate to rotate the keys. To facilitate auditing of this recommendation, you should maintain key rotation records and ensure that you establish a pre-defined frequency for key rotation." 137 | local remediationImpact="None." 138 | local check="$id - $desc" 139 | starttestjson "$id" "$desc" 140 | 141 | if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then 142 | note -c "$check" 143 | logcheckresult "NOTE" 144 | return 145 | fi 146 | pass -c "$check (Swarm mode not enabled)" 147 | logcheckresult "PASS" 148 | } 149 | 150 | check_7_7() { 151 | local id="7.7" 152 | local desc="Ensure that node certificates are rotated as appropriate (Manual)" 153 | local remediation="You should run the command docker swarm update --cert-expiry 48h to set the desired expiry time on the node certificate." 154 | local remediationImpact="None." 155 | local check="$id - $desc" 156 | starttestjson "$id" "$desc" 157 | 158 | if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then 159 | if docker info 2>/dev/null | grep "Expiry Duration: 2 days"; then 160 | pass -c "$check" 161 | logcheckresult "PASS" 162 | return 163 | fi 164 | info -c "$check" 165 | logcheckresult "INFO" 166 | return 167 | fi 168 | pass -c "$check (Swarm mode not enabled)" 169 | logcheckresult "PASS" 170 | } 171 | 172 | check_7_8() { 173 | local id="7.8" 174 | local desc="Ensure that CA certificates are rotated as appropriate (Manual)" 175 | local remediation="You should run the command docker swarm ca --rotate to rotate a certificate." 176 | local remediationImpact="None." 177 | local check="$id - $desc" 178 | starttestjson "$id" "$desc" 179 | 180 | if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then 181 | info -c "$check" 182 | logcheckresult "INFO" 183 | return 184 | fi 185 | pass -c "$check (Swarm mode not enabled)" 186 | logcheckresult "PASS" 187 | } 188 | 189 | check_7_9() { 190 | local id="7.9" 191 | local desc="Ensure that management plane traffic is separated from data plane traffic (Manual)" 192 | local remediation="You should initialize the swarm with dedicated interfaces for management and data planes respectively. Example: docker swarm init --advertise-addr=192.168.0.1 --data-path-addr=17.1.0.3" 193 | local remediationImpact="This requires two network interfaces per node." 194 | local check="$id - $desc" 195 | starttestjson "$id" "$desc" 196 | 197 | if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then 198 | info -c "$check" 199 | logcheckresult "INFO" 200 | return 201 | fi 202 | pass -c "$check (Swarm mode not enabled)" 203 | logcheckresult "PASS" 204 | } 205 | 206 | check_7_end() { 207 | endsectionjson 208 | } 209 | -------------------------------------------------------------------------------- /tests/8_docker_enterprise_configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_8() { 4 | logit "" 5 | local id="8" 6 | local desc="Docker Enterprise Configuration" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_product_license() { 13 | enterprise_license=1 14 | if docker version | grep -Eqi '^Server.*Community$|Version.*-ce$'; then 15 | info " * Community Engine license, skipping section 8" 16 | enterprise_license=0 17 | fi 18 | } 19 | 20 | check_8_1() { 21 | if [ "$enterprise_license" -ne 1 ]; then 22 | return 23 | fi 24 | 25 | local id="8.1" 26 | local desc="Universal Control Plane Configuration" 27 | local check="$id - $desc" 28 | info "$check" 29 | } 30 | 31 | check_8_1_1() { 32 | if [ "$enterprise_license" -ne 1 ]; then 33 | return 34 | fi 35 | 36 | local id="8.1.1" 37 | local desc="Configure the LDAP authentication service (Automated)" 38 | local remediation="You can configure LDAP integration via the UCP Admin Settings UI. LDAP integration can also be enabled via a configuration file" 39 | local remediationImpact="None." 40 | local check="$id - $desc" 41 | starttestjson "$id" "$desc" 42 | 43 | note -c "$check" 44 | logcheckresult "INFO" 45 | } 46 | 47 | check_8_1_2() { 48 | if [ "$enterprise_license" -ne 1 ]; then 49 | return 50 | fi 51 | 52 | local id="8.1.2" 53 | local desc="Use external certificates (Automated)" 54 | local remediation="You can configure your own certificates for UCP either during installation or after installation via the UCP Admin Settings user interface." 55 | local remediationImpact="None." 56 | local check="$id - $desc" 57 | starttestjson "$id" "$desc" 58 | 59 | note -c "$check" 60 | logcheckresult "INFO" 61 | } 62 | 63 | check_8_1_3() { 64 | if [ "$enterprise_license" -ne 1 ]; then 65 | return 66 | fi 67 | 68 | local id="8.1.3" 69 | local desc="Enforce the use of client certificate bundles for unprivileged users (Not Scored)" 70 | local remediation="Client certificate bundles can be created in one of two ways. User Management UI: UCP Administrators can provision client certificate bundles on behalf of users. Self-Provision: Users with access to the UCP console can create client certificate bundles themselves." 71 | local remediationImpact="None." 72 | local check="$id - $desc" 73 | starttestjson "$id" "$desc" 74 | 75 | note -c "$check" 76 | logcheckresult "INFO" 77 | } 78 | 79 | check_8_1_4() { 80 | if [ "$enterprise_license" -ne 1 ]; then 81 | return 82 | fi 83 | 84 | local id="8.1.4" 85 | local desc="Configure applicable cluster role-based access control policies (Not Scored)" 86 | local remediation="UCP RBAC components can be configured as required via the UCP User Management UI." 87 | local remediationImpact="None." 88 | local check="$id - $desc" 89 | starttestjson "$id" "$desc" 90 | 91 | note -c "$check" 92 | logcheckresult "INFO" 93 | } 94 | 95 | check_8_1_5() { 96 | if [ "$enterprise_license" -ne 1 ]; then 97 | return 98 | fi 99 | 100 | local id="8.1.5" 101 | local desc="Enable signed image enforcement (Automated)" 102 | local check="$id - $desc" 103 | starttestjson "$id" "$desc" 104 | 105 | note -c "$check" 106 | logcheckresult "INFO" 107 | } 108 | 109 | check_8_1_6() { 110 | if [ "$enterprise_license" -ne 1 ]; then 111 | return 112 | fi 113 | 114 | local id="8.1.6" 115 | local desc="Set the Per-User Session Limit to a value of '3' or lower (Automated)" 116 | local remediation="Retrieve a UCP API token. Retrieve and save UCP config. Open the ucp-config.toml file, set the per_user_limit entry under the [auth.sessions] section to a value of 3 or lower, but greater than 0. Update UCP with the new configuration." 117 | local remediationImpact="None." 118 | local check="$id - $desc" 119 | starttestjson "$id" "$desc" 120 | 121 | note -c "$check" 122 | logcheckresult "INFO" 123 | } 124 | 125 | check_8_1_7() { 126 | if [ "$enterprise_license" -ne 1 ]; then 127 | return 128 | fi 129 | 130 | local id="8.1.7" 131 | local desc="Set the 'Lifetime Minutes' and 'Renewal Threshold Minutes' values to '15' or lower and '0' respectively (Automated)" 132 | local remediation="Retrieve a UCP API token. Retrieve and save UCP config. Open the ucp-config.toml file, set the lifetime_minutes and renewal_threshold_minutes entries under the [auth.sessions] section to values of 15 or lower and 0 respectively. Update UCP with the new configuration." 133 | local remediationImpact="Setting the Lifetime Minutes setting to a value that is too lower would result in users having to constantly re-authenticate to their Docker Enterprise cluster." 134 | local check="$id - $desc" 135 | starttestjson "$id" "$desc" 136 | 137 | note -c "$check" 138 | logcheckresult "INFO" 139 | } 140 | 141 | check_8_2() { 142 | if [ "$enterprise_license" -ne 1 ]; then 143 | return 144 | fi 145 | 146 | local id="8.2" 147 | local desc="Docker Trusted Registry Configuration" 148 | local check="$id - $desc" 149 | info "$check" 150 | } 151 | 152 | check_8_2_1() { 153 | if [ "$enterprise_license" -ne 1 ]; then 154 | return 155 | fi 156 | 157 | local id="8.2.1" 158 | local desc="Enable image vulnerability scanning (Automated)" 159 | local remediation="You can navigate to DTR Settings UI and select the Security tab to access the image scanning configuration. Select the Enable Scanning slider to enable this functionality." 160 | local remediationImpact="None." 161 | local check="$id - $desc" 162 | starttestjson "$id" "$desc" 163 | 164 | note -c "$check" 165 | logcheckresult "INFO" 166 | } 167 | 168 | check_8_end() { 169 | endsectionjson 170 | } 171 | -------------------------------------------------------------------------------- /tests/99_community_checks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_c() { 4 | logit "" 5 | local id="99" 6 | local desc="Community contributed checks" 7 | checkHeader="$id - $desc" 8 | info "$checkHeader" 9 | startsectionjson "$id" "$desc" 10 | } 11 | 12 | check_c_1() { 13 | local id="C.1" 14 | local desc="This is a example check for a Automated check" 15 | local remediation="This is an example remediation measure for a Automated check" 16 | local remediationImpact="This is an example remediation impact for a Automated check" 17 | local check="$id - $desc" 18 | starttestjson "$id" "$desc" 19 | 20 | if docker info --format='{{ .Architecture }}' | grep 'x86_64' 2>/dev/null 1>&2; then 21 | pass -s "$check" 22 | logcheckresult "PASS" 23 | return 24 | fi 25 | if docker info --format='{{ .Architecture }}' | grep 'aarch64' 2>/dev/null 1>&2; then 26 | info -c "$check" 27 | logcheckresult "INFO" 28 | return 29 | fi 30 | warn -s "$check" 31 | logcheckresult "WARN" 32 | } 33 | 34 | check_c_1_1() { 35 | local id="C.1.1" 36 | local desc="This is a example check for a Manual check" 37 | local remediation="This is an example remediation measure for a Manual check" 38 | local remediationImpact="This is an example remediation impact for a Manual check" 39 | local check="$id - $desc" 40 | starttestjson "$id" "$desc" 41 | 42 | if docker info --format='{{ .Architecture }}' | grep 'x86_64' 2>/dev/null 1>&2; then 43 | pass -c "$check" 44 | logcheckresult "PASS" 45 | return 46 | fi 47 | if docker info --format='{{ .Architecture }}' | grep 'aarch64' 2>/dev/null 1>&2; then 48 | info -c "$check" 49 | logcheckresult "INFO" 50 | return 51 | fi 52 | warn -c "$check" 53 | logcheckresult "WARN" 54 | } 55 | 56 | check_c_2() { 57 | docker_version=$(docker version | grep -i -A2 '^server' | grep ' Version:' \ 58 | | awk '{print $NF; exit}' | tr -d '[:alpha:]-,.' | cut -c 1-4) 59 | 60 | local id="C.2" 61 | local desc="Ensure operations on legacy registry (v1) are Disabled" 62 | local remediation="Start docker daemon with --disable-legacy-registry=false flag. Starting with Docker 17.12, support for V1 registries has been removed, and the --disable-legacy-registry flag can no longer be used." 63 | local remediationImpact="Prevents the docker daemon from pull, push, and login operations against v1 registries." 64 | local check="$id - $desc" 65 | starttestjson "$id" "$desc" 66 | 67 | if [ "$docker_version" -lt 1712 ]; then 68 | if get_docker_configuration_file_args 'disable-legacy-registry' | grep 'true' >/dev/null 2>&1; then 69 | pass -s "$check" 70 | logcheckresult "PASS" 71 | return 72 | fi 73 | if get_docker_effective_command_line_args '--disable-legacy-registry' | grep "disable-legacy-registry" >/dev/null 2>&1; then 74 | pass -s "$check" 75 | logcheckresult "PASS" 76 | return 77 | fi 78 | warn -s "$check" 79 | logcheckresult "WARN" 80 | return 81 | fi 82 | local desc="$desc (Deprecated)" 83 | local check="$id - $desc" 84 | info -c "$check" 85 | logcheckresult "INFO" 86 | } 87 | 88 | check_c_5_3_1() { 89 | local id="C.5.3.1" 90 | local desc="Ensure that CAP_DAC_READ_SEARCH Linux kernel capability is disabled (Automated)" 91 | local remediation="Please refer to https://github.com/cdk-team/CDK/wiki/Exploit:-cap-dac-read-search for PoC." 92 | local remediationImpact="" 93 | local check="$id - $desc" 94 | starttestjson "$id" "$desc" 95 | 96 | fail=0 97 | caps_containers="" 98 | for c in $containers; do 99 | container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd }}' "$c") 100 | caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \ 101 | sed 's/CAPADD/CapAdd/') 102 | if echo "$caps" | grep -q "DAC_READ_SEARCH"; then 103 | # If it's the first container, fail the test 104 | if [ $fail -eq 0 ]; then 105 | warn -s "$check" 106 | warn " * CAP_DAC_READ_SEARCH added to $c" 107 | caps_containers="$caps_containers $c" 108 | fail=1 109 | continue 110 | fi 111 | warn " * CAP_DAC_READ_SEARCH added to $c" 112 | caps_containers="$caps_containers $c" 113 | fi 114 | done 115 | # We went through all the containers and found none with extra capabilities 116 | if [ $fail -eq 0 ]; then 117 | pass -s "$check" 118 | logcheckresult "PASS" 119 | return 120 | fi 121 | logcheckresult "WARN" "CAP_DAC_READ_SEARCH capability added for containers" "$caps_containers" 122 | } 123 | 124 | check_c_5_3_2() { 125 | local id="C.5.3.2" 126 | local desc="Ensure that CAP_SYS_MODULE Linux kernel capability is disabled (Automated)" 127 | local remediation="Please refer to https://xcellerator.github.io/posts/docker_escape/ for PoC." 128 | local remediationImpact="" 129 | local check="$id - $desc" 130 | starttestjson "$id" "$desc" 131 | 132 | fail=0 133 | caps_containers="" 134 | for c in $containers; do 135 | container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd }}' "$c") 136 | caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \ 137 | sed 's/CAPADD/CapAdd/') 138 | if echo "$caps" | grep -q "SYS_MODULE"; then 139 | # If it's the first container, fail the test 140 | if [ $fail -eq 0 ]; then 141 | warn -s "$check" 142 | warn " * CAP_SYS_MODULE added to $c" 143 | caps_containers="$caps_containers $c" 144 | fail=1 145 | continue 146 | fi 147 | warn " * CAP_SYS_MODULE added to $c" 148 | caps_containers="$caps_containers $c" 149 | fi 150 | done 151 | # We went through all the containers and found none with extra capabilities 152 | if [ $fail -eq 0 ]; then 153 | pass -s "$check" 154 | logcheckresult "PASS" 155 | return 156 | fi 157 | logcheckresult "WARN" "CAP_SYS_MODULE capability added for containers" "$caps_containers" 158 | } 159 | 160 | check_c_5_3_3() { 161 | local id="C.5.3.3" 162 | local desc="Ensure that CAP_SYS_ADMIN Linux kernel capability is disabled (Automated)" 163 | local remediation="Please refer to https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/ for PoC." 164 | local remediationImpact="" 165 | local check="$id - $desc" 166 | starttestjson "$id" "$desc" 167 | 168 | fail=0 169 | caps_containers="" 170 | for c in $containers; do 171 | container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd }}' "$c") 172 | caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \ 173 | sed 's/CAPADD/CapAdd/') 174 | if echo "$caps" | grep -q "SYS_ADMIN"; then 175 | # If it's the first container, fail the test 176 | if [ $fail -eq 0 ]; then 177 | warn -s "$check" 178 | warn " * CAP_SYS_ADMIN added to $c" 179 | caps_containers="$caps_containers $c" 180 | fail=1 181 | continue 182 | fi 183 | warn " * CAP_SYS_ADMIN added to $c" 184 | caps_containers="$caps_containers $c" 185 | fi 186 | done 187 | # We went through all the containers and found none with extra capabilities 188 | if [ $fail -eq 0 ]; then 189 | pass -s "$check" 190 | logcheckresult "PASS" 191 | return 192 | fi 193 | logcheckresult "WARN" "CAP_SYS_ADMIN capability added for containers" "$caps_containers" 194 | } 195 | 196 | check_c_5_3_4() { 197 | local id="C.5.3.4" 198 | local desc="Ensure that CAP_SYS_PTRACE Linux kernel capability is disabled (Automated)" 199 | local remediation="Please refer to https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities#cap_sys_ptrace" 200 | local remediationImpact="" 201 | local check="$id - $desc" 202 | starttestjson "$id" "$desc" 203 | 204 | fail=0 205 | caps_containers="" 206 | for c in $containers; do 207 | container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd }}' "$c") 208 | caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \ 209 | sed 's/CAPADD/CapAdd/') 210 | if echo "$caps" | grep -q "SYS_PTRACE"; then 211 | # If it's the first container, fail the test 212 | if [ $fail -eq 0 ]; then 213 | warn -s "$check" 214 | warn " * CAP_SYS_PTRACE added to $c" 215 | caps_containers="$caps_containers $c" 216 | fail=1 217 | continue 218 | fi 219 | warn " * CAP_SYS_PTRACE added to $c" 220 | caps_containers="$caps_containers $c" 221 | fi 222 | done 223 | # We went through all the containers and found none with extra capabilities 224 | if [ $fail -eq 0 ]; then 225 | pass -s "$check" 226 | logcheckresult "PASS" 227 | return 228 | fi 229 | logcheckresult "WARN" "CAP_SYS_PTRACE capability added for containers" "$caps_containers" 230 | } 231 | 232 | check_c_end() { 233 | endsectionjson 234 | } 235 | --------------------------------------------------------------------------------