diff --git a/.dockerignore b/.dockerignore index 6b8710a..db3e3b9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,5 @@ .git +*.md +*.png +distros + diff --git a/.gitignore b/.gitignore index 397b4a7..e5ebf25 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -*.log +*.log* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36d2d2f..c1e3d98 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,46 +3,62 @@ Want to hack on Docker Bench? Awesome! Here are instructions to get you started. -The Docker Bench for Security is a part of the [Docker](https://www.docker.com) project, and follows -the same rules and principles. If you're already familiar with the way -Docker does things, you'll feel right at home. +The Docker Bench for Security is a part of the [Docker](https://www.docker.com) +project, and follows the same rules and principles. If you're already familiar +with the way Docker does things, you'll feel right at home. Otherwise, go read [Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md). -### Development Environment Setup +## Development Environment Setup -The only thing you need to hack on Docker Bench for Security is a POSIX 2004 compliant shell. We try to keep the project compliant for maximum portability +The only thing you need to hack on Docker Bench for Security is a POSIX 2004 +compliant shell. We try to keep the project compliant for maximum portability. -#### Start hacking +### Start hacking You can build the container that wraps the docker-bench for security: + ```sh -✗ git clone git@github.com:docker/docker-bench-security.git -✗ docker build -t diogomonica/docker-bench-security . +git clone git@github.com:docker/docker-bench-security.git +cd docker-bench-security +docker build -t docker-bench-security . ``` Or you can simply run the shell script locally: ```sh -✗ sh docker-bench-security.sh +git clone git@github.com:docker/docker-bench-security.git +cd docker-bench-security +sudo sh docker-bench-security.sh ``` -The Docker Bench has the main script called `docker-bench-security.sh`. This is the main script that checks for all the dependencies, deals with command line arguments and loads all the tests. +The Docker Bench has the main script called `docker-bench-security.sh`. +This is the main script that checks for all the dependencies, deals with +command line arguments and loads all the tests. -The tests are split in 6 different files: +The tests are split into the following files: ```sh -✗ docker-bench-security git:(master) ✗ tree tests -tests +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 +├── 6_docker_security_operations.sh +├── 7_docker_swarm_configuration.sh +├── 8_docker_enterprise_configuration.sh +└── 99_community_checks.sh ``` -To modify the Docker Bench for Security you should first clone the repository, make your changes, and then sign off on your commits. After that feel free to send us a pull-request with the changes. +To modify the Docker Bench for Security you should first clone the repository, +make your changes, check your code with `shellcheck`, `checkbashisms` or similar +tools, and then sign off on your commits. After that feel free to send us a +pull request with the changes. -While this tool is inspired in the CIS Docker 1.6 Benchmark, feel free to add new tests. We will try to turn dockerbench.com into a list of good community benchmarks for both security and performance, and we would love community contributions. +While this tool was inspired by the [CIS Docker 1.11.0 benchmark](https://www.cisecurity.org/benchmark/docker/) +and its successors, feel free to add new tests. We will try to turn +[dockerbench.com](https://dockerbench.com) into a list of good community +benchmarks for both security and performance, and we would love community +contributions. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000..ca9fcb6 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,58 @@ +The following people, listed in alphabetical order, have contributed to docker-bench-security: + +* alberto <alberto@tutum.co> +* Andreas Stieger <astieger@suse.com> +* Anthony Roger <aroger@softwaymedical.fr> +* Aurélien Gasser <aurelien.gasser@gmail.com> +* binary <binary@webdev.fritz.box> +* Boris Gorbylev <ekho@ekho.name> +* Cheng-Li Jerry Ma <chengli.ma@gmail.com> +* Csaba Palfi <csaba@palfi.me> +* Daniele Marcocci <daniele.marcocci@par-tec.it> +* Dhawal Patel <dhawal.patel@nordstrom.com> +* Diogo Monica <diogo@docker.com> +* Diogo Mónica <diogo.monica@gmail.com> +* Ernst de Haan <ernst@ernstdehaan.com> +* HuKeping <hukeping@huawei.com> +* Ivan Angelov <iangelov@users.noreply.github.com> +* J0WI <J0WI@users.noreply.github.com> +* jammasterj89 <jammasterj89@gmail.com> +* Jessica Frazelle <princess@docker.com> +* Joachim Lusiardi <jlusiardi@users.noreply.github.com> +* Joachim Lusiardi <joachim@lusiardi.de> +* Joachim Lusiardi <shing19m@dev1.lusiardi.de> +* Joe Williams <joe.williams@github.com> +* Julien Garcia Gonzalez <julien@giantswarm.io> +* Jürgen Hermann <jh@web.de> +* kakakakakku <y.yoshida22@gmail.com> +* Karol Babioch <kbabioch@suse.de> +* Kevin Lim <kevin.lim@sap.com> +* kevinll <imhael@gmail.com> +* Liron Levin <liron@twistlock.com> +* liron-l <levinlir@gmail.com> +* LorensK <LorensK@users.noreply.github.com> +* lusitania <lusitania@users.noreply.github.com> +* Maik Ellerbrock <opensource@frapsoft.com> +* Mark Stemm <mark.stemm@gmail.com> +* Matt Fellows <matt.fellows@onegeek.com.au> +* Michael Crosby <crosbymichael@gmail.com> +* Michael Stahn <michael.stahn.42@gmail.com> +* Mike Ritter <mike.ritter@target.com> +* Mr. Secure <ben.github@mrsecure.org> +* MrSecure <MrSecure@users.noreply.github.com> +* Nigel Brown <nigel@windsock.io> +* Paul Czarkowski <username.taken@gmail.com> +* Paul Morgan <jumanjiman@gmail.com> +* Pete Sellars <psellars@gmail.com> +* Peter <lusitania@users.noreply.github.com> +* Ravi Kumar Vadapalli <vadapalli.ravikumar@gmail.com> +* Scott McCarty <scott.mccarty@gmail.com> +* Sebastiaan van Stijn <github@gone.nl> +* telepresencebot2 <telepresencebot2@users.noreply.github.com> +* Thomas Sjögren <konstruktoid@users.noreply.github.com> +* Tom Partington <tom.partington@cevo.com.au> +* Werner Buck <wernerbuck@gmail.com> +* will Farrell <willfarrell@users.noreply.github.com> +* Zvi "Viz" Effron <zeffron@riotgames.com> + +This list was generated Tue Nov 5 09:45:35 UTC 2019. diff --git a/Dockerfile b/Dockerfile index a552ef4..0ad1564 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,25 @@ -FROM alpine:3.1 +FROM alpine:3.12 -RUN apk --update add docker +LABEL \ + org.label-schema.name="docker-bench-security" \ + org.label-schema.url="https://dockerbench.com" \ + org.label-schema.vcs-url="https://github.com/docker/docker-bench-security.git" -RUN mkdir /docker-bench-security +# Switch to the HTTPS endpoint for the apk repositories +# https://github.com/gliderlabs/docker-alpine/issues/184 +RUN set -eux; \ + sed -i 's!http://dl-cdn.alpinelinux.org/!https://alpine.global.ssl.fastly.net/!g' /etc/apk/repositories && \ + apk add --no-cache \ + iproute2 \ + docker-cli \ + dumb-init -COPY . /docker-bench-security +COPY ./*.sh /usr/local/bin/ +COPY ./tests/*.sh /usr/local/bin/tests/ -WORKDIR /docker-bench-security +HEALTHCHECK CMD exit 0 -ENTRYPOINT ["/bin/sh", "docker-bench-security.sh"] +WORKDIR /usr/local/bin + +ENTRYPOINT [ "/usr/bin/dumb-init", "docker-bench-security.sh" ] +CMD [""] diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..bd38003 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,34 @@ +# Docker Bench for Security maintainers file +# +# This file describes who runs the docker/docker-bench-security project and how. +# This is a living document - if you see something out of date or missing, speak up! +# +# It is structured to be consumable by both humans and programs. +# To extract its contents programmatically, use any TOML-compliant parser. +# +# This file is compiled into the MAINTAINERS file in docker/opensource. +# +[Org] + [Org."Core maintainers"] + people = [ + "diogomonica", + "konstruktoid", + ] + +[people] + +# A reference list of all people associated with the project. +# All other sections should refer to people by their canonical key +# in the people section. + + # ADD YOURSELF HERE IN ALPHABETICAL ORDER + + [people.diogomonica] + Name = "Dr. Diogo Mónica" + Email = "diogo@docker.com" + GitHub = "diogomonica" + + [people.konstruktoid] + Name = "Thomas Sjögren" + Email = "thomas.sjogren@protonmail.com" + GitHub = "konstruktoid" diff --git a/README.md b/README.md index bcedb95..618ca0b 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,150 @@ # Docker Bench for Security - + -The Docker Bench for Security is a script that checks for all the automatable tests included in the [CIS Docker 1.6 Benchmark](https://benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf). We are releasing this as a follow-up to our [Understanding Docker Security and Best Practices](https://blog.docker.com/2015/05/understanding-docker-security-and-best-practices/) blog post. +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 inspired by the [CIS Docker Benchmark v1.2.0](https://www.cisecurity.org/benchmark/docker/). -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. +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. ## Running Docker Bench for Security -We packaged docker bench as a small container for your convenience. 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. +We packaged docker bench as a small container for your convenience. 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. -The easiest way to run your hosts against the CIS Docker 1.6 benchmark is by running our pre-built container: +The easiest way to run your hosts against the Docker Bench for Security is by +running our pre-built container: - -``` -docker run -it --net host --pid host -v /var/run/docker.sock:/var/run/docker.sock \ --v /usr/lib/systemd:/usr/lib/systemd -v /etc:/etc --label docker-bench-security \ -diogomonica/docker-bench-security +```sh +docker run --rm --net host --pid host --userns host --cap-add audit_control \ + -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ + -v /etc:/etc:ro \ + -v /usr/bin/containerd:/usr/bin/containerd:ro \ + -v /usr/bin/runc:/usr/bin/runc:ro \ + -v /usr/lib/systemd:/usr/lib/systemd:ro \ + -v /var/lib:/var/lib:ro \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + --label docker_bench_security \ + docker/docker-bench-security ``` -Docker bench requires Docker 1.6.2 or later in order to run, since it depends on the `--label` to exclude the current container from being inspected. If you can't upgrade to 1.6.2, I feel free to remove the `--label` flag or run the shell script locally (see below). +Don't forget to adjust the shared volumes according to your operating system. +Some examples are: -Additionally, there was a bug in Docker 1.6.0 that would not allow mounting `-v /dev:/dev`. If you are getting an error while accessing `resolv.conf`, please update your docker to 1.6.2. +1. `Docker Desktop` on macOS don't have `/usr/lib/systemd` or the above Docker + binaries. + +```sh +docker run --rm --net host --pid host --userns host --cap-add audit_control \ + -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ + -v /etc:/etc \ + -v /var/lib:/var/lib:ro \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + --label docker_bench_security \ + docker/docker-bench-security +``` + +2. On Ubuntu the `docker.service` and `docker.secret` files are located in + `/lib/systemd/system` folder by default. + +```sh +docker run --rm --net host --pid host --userns host --cap-add audit_control \ + -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ + -v /etc:/etc:ro \ + -v /lib/systemd/system:/lib/systemd/system:ro \ + -v /usr/bin/containerd:/usr/bin/containerd:ro \ + -v /usr/bin/runc:/usr/bin/runc:ro \ + -v /usr/lib/systemd:/usr/lib/systemd:ro \ + -v /var/lib:/var/lib:ro \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + --label docker_bench_security \ + docker/docker-bench-security +``` + +Docker bench requires Docker 1.13.0 or later in order to run. + +Note that when distributions doesn't contain `auditctl`, the audit tests will +check `/etc/audit/audit.rules` to see if a rule is present instead. + +Distribution specific Dockerfiles that fixes this issue are available in the +[distros directory](https://github.com/docker/docker-bench-security/tree/master/distros). + +The [distribution specific Dockerfiles](https://github.com/docker/docker-bench-security/tree/master/distros) +may also help if the distribution you're using haven't yet shipped Docker +version 1.13.0 or later. + +### Docker Bench for Security options + +```sh + -b optional Do not print colors + -h optional Print this help message + -l FILE optional Log output in FILE + -c CHECK optional Comma delimited list of specific check(s) + -e CHECK optional Comma delimited list of specific check(s) to exclude + -i INCLUDE optional Comma delimited list of patterns within a container or image name to check + -x EXCLUDE optional Comma delimited list of patterns within a container or image name to exclude from check +``` + +By default the Docker Bench for Security script will run all available CIS tests +and produce logs in the current directory named `docker-bench-security.sh.log.json` +and `docker-bench-security.sh.log`. +The CIS based checks are named `check_<section>_<number>`, e.g. `check_2_6` +and community contributed checks are named `check_c_<number>`. +A complete list of checks are present in [functions_lib.sh](functions_lib.sh). + +`sh docker-bench-security.sh -l /tmp/docker-bench-security.sh.log -c check_2_2` +will only run check `2.2 Ensure the logging level is set to 'info'`. + +`sh docker-bench-security.sh -l /tmp/docker-bench-security.sh.log -e check_2_2` +will run all available checks except `2.2 Ensure the logging level is set to 'info'`. + +`sh docker-bench-security.sh -l /tmp/docker-bench-security.sh.log -e docker_enterprise_configuration` +will run all available checks except the docker_enterprise_configuration group + +`sh docker-bench-security.sh -l /tmp/docker-bench-security.sh.log -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'` + +`sh docker-bench-security.sh -l /tmp/docker-bench-security.sh.log -c container_images -e check_4_5` +will run just the container_images checks except +`4.5 Ensure Content trust for Docker is Enabled` + +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. ## Building Docker Bench for Security -If you wish to build and run this container yourself, you can follow the following steps: +If you wish to build and run this container yourself, you can follow the +following steps: +```sh +git clone https://github.com/docker/docker-bench-security.git +cd docker-bench-security +docker build --no-cache -t docker-bench-security . ``` -git clone https://github.com/diogomonica/docker-bench-security.git -cd docker-bench-security; docker build -t docker-bench-security . -docker run -it --net host --pid host -v /var/run/docker.sock:/var/run/docker.sock \ --v /usr/lib/systemd:/usr/lib/systemd -v /etc:/etc --label security-benchmark \ -docker-bench-security + +followed by an appropriate `docker run` command as stated above +or use [Docker Compose](https://docs.docker.com/compose/): + +```sh +git clone https://github.com/docker/docker-bench-security.git +cd docker-bench-security +docker-compose run --rm docker-bench-security ``` Also, this script can also be simply run from your base host by running: -``` -git clone https://github.com/diogomonica/docker-bench-security.git -cd docker-bench-security; sh docker-bench-security.sh +```sh +git clone https://github.com/docker/docker-bench-security.git +cd docker-bench-security +sudo sh docker-bench-security.sh ``` -This script was build to be POSIX 2004 compliant, so it should be portable across any Unix platform. +This script was built to be POSIX 2004 compliant, so it should be portable +across any Unix platform. diff --git a/benchmark_log.png b/benchmark_log.png index a9e605e..8399924 100644 Binary files a/benchmark_log.png and b/benchmark_log.png differ diff --git a/distros/Dockerfile.alpine b/distros/Dockerfile.alpine new file mode 100644 index 0000000..0ad1564 --- /dev/null +++ b/distros/Dockerfile.alpine @@ -0,0 +1,25 @@ +FROM alpine:3.12 + +LABEL \ + org.label-schema.name="docker-bench-security" \ + org.label-schema.url="https://dockerbench.com" \ + org.label-schema.vcs-url="https://github.com/docker/docker-bench-security.git" + +# Switch to the HTTPS endpoint for the apk repositories +# https://github.com/gliderlabs/docker-alpine/issues/184 +RUN set -eux; \ + sed -i 's!http://dl-cdn.alpinelinux.org/!https://alpine.global.ssl.fastly.net/!g' /etc/apk/repositories && \ + apk add --no-cache \ + iproute2 \ + docker-cli \ + dumb-init + +COPY ./*.sh /usr/local/bin/ +COPY ./tests/*.sh /usr/local/bin/tests/ + +HEALTHCHECK CMD exit 0 + +WORKDIR /usr/local/bin + +ENTRYPOINT [ "/usr/bin/dumb-init", "docker-bench-security.sh" ] +CMD [""] diff --git a/distros/Dockerfile.centos b/distros/Dockerfile.centos new file mode 100644 index 0000000..4c0dcfb --- /dev/null +++ b/distros/Dockerfile.centos @@ -0,0 +1,15 @@ +# REPOSITORY https://github.com/fatherlinux/docker-bench-security + +FROM centos + +MAINTAINER smccarty@redhat.com + +RUN yum install -y docker iproute audit procps-ng; yum clean all + +RUN mkdir /docker-bench-security + +COPY . /docker-bench-security + +WORKDIR /docker-bench-security + +ENTRYPOINT ["/bin/sh", "docker-bench-security.sh"] diff --git a/distros/Dockerfile.debian b/distros/Dockerfile.debian new file mode 100644 index 0000000..4284f54 --- /dev/null +++ b/distros/Dockerfile.debian @@ -0,0 +1,24 @@ +FROM debian:sid + +LABEL org.label-schema.name="docker-bench-security" \ + org.label-schema.url="https://github.com/konstruktoid/docker-bench-security" \ + org.label-schema.vcs-url="https://github.com/konstruktoid/docker-bench-security.git" + +RUN \ + apt-get update && \ + apt-get -y upgrade && \ + apt-get -y install auditd ca-certificates docker.io \ + gawk iproute2 procps --no-install-recommends && \ + apt-get -y clean && \ + apt-get -y autoremove && \ + rm -rf /var/lib/apt/lists/* \ + /usr/share/doc /usr/share/doc-base \ + /usr/share/man /usr/share/locale /usr/share/zoneinfo + +RUN mkdir /docker-bench-security + +COPY . /docker-bench-security + +WORKDIR /docker-bench-security + +ENTRYPOINT ["/bin/sh", "docker-bench-security.sh"] diff --git a/distros/Dockerfile.openSUSE b/distros/Dockerfile.openSUSE new file mode 100644 index 0000000..d5a0add --- /dev/null +++ b/distros/Dockerfile.openSUSE @@ -0,0 +1,13 @@ +# REPOSITORY https://github.com/docker/docker-bench-security +FROM opensuse/leap:latest + +RUN zypper -n in audit docker iproute2 && \ + mkdir /docker-bench-security && \ + rm /usr/bin/awk && \ + cp /usr/bin/gawk /usr/bin/awk + +COPY . /docker-bench-security + +WORKDIR /docker-bench-security + +ENTRYPOINT ["/bin/bash", "docker-bench-security.sh"] diff --git a/distros/Dockerfile.rhel b/distros/Dockerfile.rhel new file mode 100644 index 0000000..c1b5618 --- /dev/null +++ b/distros/Dockerfile.rhel @@ -0,0 +1,18 @@ +# REPOSITORY https://github.com/fatherlinux/docker-bench-security + +FROM rhel7 + +MAINTAINER smccarty@redhat.com + +RUN yum install -y yum-utils; yum clean all +RUN yum-config-manager --disable "*" &>/dev/null +RUN yum-config-manager --enable rhel-7-server-rpms --enable rhel-7-server-extras-rpms +RUN yum install -y docker iproute audit procps-ng; yum clean all + +RUN mkdir /docker-bench-security + +COPY . /docker-bench-security + +WORKDIR /docker-bench-security + +ENTRYPOINT ["/bin/sh", "docker-bench-security.sh"] diff --git a/distros/README.md b/distros/README.md new file mode 100644 index 0000000..be4de17 --- /dev/null +++ b/distros/README.md @@ -0,0 +1,21 @@ +# Distribution specific Dockerfiles + +## Requirements + +### Dockerfile name + +The format should be `Dockerfile.{distribution name}`. + +### Keep your images up-to-date + +Use the distribution package manager to keep your image up-to-date. + +### Labels + +Use the following labels in your Dockerfile: + +``` +LABEL org.label-schema.name="docker-bench-security" \ + org.label-schema.url="<YOUR GIT REPOSITORY HTTPS ADDRESS>" \ + org.label-schema.vcs-url="<YOUR REPOSITORY HTTPS GIT ADDRESS" +``` diff --git a/docker-bench-security.sh b/docker-bench-security.sh index d2cb12c..0d5424e 100755 --- a/docker-bench-security.sh +++ b/docker-bench-security.sh @@ -1,91 +1,202 @@ #!/bin/sh # ------------------------------------------------------------------------------ -# CIS Docker 1.6 Benchmark v1.0.0 checker +# Docker Bench for Security # -# Docker, Inc. (c) 2015 -# -# Provides automated tests for the CIS Docker 1.6 Benchmark: -# https://benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf +# Docker, Inc. (c) 2015- # +# Checks for dozens of common best-practices around deploying Docker containers in production. # ------------------------------------------------------------------------------ +version='1.3.5' + # Load dependencies -. ./output_lib.sh +. ./functions_lib.sh . ./helper_lib.sh # Setup the paths -this_path=$(abspath "$0") ## Path of this file including filenamel +this_path=$(abspath "$0") ## Path of this file including filename myname=$(basename "${this_path}") ## file name of this script. -export PATH=/bin:/sbin:/usr/bin:/usr/local/bin:/usr/sbin/ -logger="${myname}.log" +readonly version +readonly this_path +readonly myname + +export PATH="$PATH:/bin:/sbin:/usr/bin:/usr/local/bin:/usr/sbin/" # Check for required program(s) -req_progs='docker netstat grep awk' +req_progs='awk docker grep stat' for p in $req_progs; do command -v "$p" >/dev/null 2>&1 || { printf "%s command not found.\n" "$p"; exit 1; } done +if command -v ss >/dev/null 2>&1; then + netbin=ss +elif command -v netstat >/dev/null 2>&1; then + netbin=netstat +else + echo "ss or netstat command not found." + exit 1 +fi + # Ensure we can connect to docker daemon -docker ps -q >/dev/null 2>&1 -if [ $? -ne 0 ]; then +if ! docker ps -q >/dev/null 2>&1; then printf "Error connecting to docker daemon (does docker ps work?)\n" exit 1 fi usage () { - printf " - usage: %s [options] + cat <<EOF + usage: ${myname} [options] - -h optional Print this help message\n" "$myname" - exit 1 + -b optional Do not print colors + -h optional Print this help message + -l FILE optional Log output in FILE + -c CHECK optional Comma delimited list of specific check(s) + -e CHECK optional Comma delimited list of specific check(s) to exclude + -i INCLUDE optional Comma delimited list of patterns within a container or image name to check + -x EXCLUDE optional Comma delimited list of patterns within a container or image name to exclude from check + -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). +EOF } -yell "# ------------------------------------------------------------------------------ -# CIS Docker 1.6 Benchmark v1.0.0 checker -# -# Docker, Inc. (c) 2015 -# -# Provides automated tests for the CIS Docker 1.6 Benchmark: -# https://benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf -# ------------------------------------------------------------------------------" +# Get the flags +# If you add an option here, please +# remember to update usage() above. +while getopts bhl:c:e:i:x:t:n: args +do + case $args in + b) nocolor="nocolor";; + h) usage; exit 0 ;; + l) logger="$OPTARG" ;; + c) check="$OPTARG" ;; + e) checkexclude="$OPTARG" ;; + i) include="$OPTARG" ;; + x) exclude="$OPTARG" ;; + n) limit="$OPTARG" ;; + *) usage; exit 1 ;; + esac +done -logit "Initializing $(date)\n" +if [ -z "$logger" ]; then + logger="${myname}.log" +fi + +if [ -z "$limit" ]; then + limit=0 +fi + +# Load output formating +. ./output_lib.sh + +yell_info # Warn if not root ID=$(id -u) if [ "x$ID" != "x0" ]; then - warn "Some tests might require root to run" - sleep 3 + warn "Some tests might require root to run" + sleep 3 fi -# Get the flags -while getopts :hlfi: args -do - case $args in - h) usage ;; - l) logger="$OPTARG" ;; - *) usage ;; - esac -done +# Total Score +# Warn Scored -1, Pass Scored +1, Not Score -0 + +totalChecks=0 +currentScore=0 + +logit "Initializing $(date)\n" +beginjson "$version" "$(date +%s)" # Load all the tests from tests/ and run them main () { - # List all running containers - containers=$(docker ps -q) - # If there is a container with label docker-bench, memorize it: - benchcont="nil" - for c in $containers; do - labels=$(docker inspect --format '{{ .Config.Labels }}' "$c") - contains "$labels" "docker-bench" && benchcont="$c" - done - # List all running containers except docker-bench - containers=$(docker ps -q | grep -v "$benchcont") + # Get configuration location + get_docker_configuration_file - for test in tests/*.sh - do - . ./"$test" + # If there is a container with label docker_bench_security, memorize it: + benchcont="nil" + for c in $(docker ps | sed '1d' | awk '{print $NF}'); do + if docker inspect --format '{{ .Config.Labels }}' "$c" | \ + grep -e 'docker.bench.security' >/dev/null 2>&1; then + benchcont="$c" + fi done + + # Get the image id of the docker_bench_security_image, memorize it: + benchimagecont="nil" + for c in $(docker images | sed '1d' | awk '{print $3}'); do + if docker inspect --format '{{ .Config.Labels }}' "$c" | \ + grep -e 'docker.bench.security' >/dev/null 2>&1; then + benchimagecont="$c" + fi + done + + if [ -n "$include" ]; then + pattern=$(echo "$include" | sed 's/,/|/g') + containers=$(docker ps | sed '1d' | awk '{print $NF}' | grep -v "$benchcont" | grep -E "$pattern") + images=$(docker images | sed '1d' | grep -E "$pattern" | awk '{print $3}' | grep -v "$benchimagecont") + elif [ -n "$exclude" ]; then + pattern=$(echo "$exclude" | sed 's/,/|/g') + containers=$(docker ps | sed '1d' | awk '{print $NF}' | grep -v "$benchcont" | grep -Ev "$pattern") + images=$(docker images | sed '1d' | grep -Ev "$pattern" | awk '{print $3}' | grep -v "$benchimagecont") + else + containers=$(docker ps | sed '1d' | awk '{print $NF}' | grep -v "$benchcont") + images=$(docker images -q | grep -v "$benchcont") + fi + + if [ -z "$containers" ]; then + running_containers=0 + else + running_containers=1 + fi + + for test in tests/*.sh; do + . ./"$test" + done + + if [ -z "$check" ] && [ ! "$checkexclude" ]; then + # No options just run + cis + elif [ -z "$check" ]; then + # No check defined but excludes defined set to calls in cis() function + check=$(sed -ne "/cis() {/,/}/{/{/d; /}/d; p}" functions_lib.sh) + fi + + for c in $(echo "$check" | sed "s/,/ /g"); do + if ! command -v "$c" 2>/dev/null 1>&2; then + echo "Check \"$c\" doesn't seem to exist." + continue + fi + if [ -z "$checkexclude" ]; then + # No excludes just run the checks specified + "$c" + else + # Exludes specified and check exists + checkexcluded="$(echo ",$checkexclude" | sed -e 's/^/\^/g' -e 's/,/\$|/g' -e 's/$/\$/g')" + + if echo "$c" | grep -E "$checkexcluded" 2>/dev/null 1>&2; then + # Excluded + continue + elif echo "$c" | grep -vE 'check_[0-9]|check_[a-z]' 2>/dev/null 1>&2; then + # Function not a check, fill loop_checks with all check from function + loop_checks="$(sed -ne "/$c() {/,/}/{/{/d; /}/d; p}" functions_lib.sh)" + else + # Just one check + loop_checks="$c" + fi + + for lc in $loop_checks; do + if echo "$lc" | grep -vE "$checkexcluded" 2>/dev/null 1>&2; then + # Not excluded + "$lc" + fi + done + fi + done + + printf "\n" + info "Checks: $totalChecks" + info "Score: $currentScore" + + endjson "$totalChecks" "$currentScore" "$(date +%s)" } main "$@" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7a88f50 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +docker-bench-security: + # use image if you have a dedicated build step: + # docker build --rm -t docker-bench-security . + # image: docker-bench-security + + # use build path to Dockerfile if docker-compose should build the image + build: . + + cap_add: + - audit_control + labels: + - docker_bench_security + net: host + pid: host + stdin_open: true + tty: true + volumes: + - /var/lib:/var/lib:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - /usr/lib/systemd:/usr/lib/systemd:ro + - /etc:/etc:ro diff --git a/functions_lib.sh b/functions_lib.sh new file mode 100644 index 0000000..1156986 --- /dev/null +++ b/functions_lib.sh @@ -0,0 +1,345 @@ +#!/bin/sh + +host_configuration() { + check_1 + check_1_1 + check_1_1_1 + check_1_1_2 + check_1_2 + check_1_2_1 + check_1_2_2 + check_1_2_3 + check_1_2_4 + check_1_2_5 + check_1_2_6 + check_1_2_7 + check_1_2_8 + check_1_2_9 + check_1_2_10 + check_1_2_11 + check_1_2_12 + check_1_end +} + +host_configuration_level1() { + check_1 + check_1_1 + check_1_1_1 + check_1_1_2 + check_1_2 + check_1_2_1 + check_1_2_2 + check_1_2_3 + check_1_2_5 + check_1_2_6 + check_1_2_7 + check_1_2_8 + check_1_2_9 + check_1_2_10 + check_1_2_11 + check_1_2_12 + check_1_end +} + +docker_daemon_configuration() { + check_2 + check_2_1 + check_2_2 + check_2_3 + check_2_4 + check_2_5 + check_2_6 + check_2_7 + check_2_8 + check_2_9 + check_2_10 + check_2_11 + check_2_12 + check_2_13 + check_2_14 + check_2_15 + check_2_16 + check_2_17 + check_2_end +} + +docker_daemon_configuration_level1() { + check_2 + check_2_1 + check_2_2 + check_2_3 + check_2_4 + check_2_5 + check_2_6 + check_2_7 + check_2_13 + check_2_14 + check_2_16 + check_2_17 + check_2_end +} + +docker_daemon_files() { + check_3 + check_3_1 + check_3_2 + check_3_3 + check_3_4 + check_3_5 + check_3_6 + check_3_7 + check_3_8 + check_3_9 + check_3_10 + check_3_11 + check_3_12 + check_3_13 + check_3_14 + check_3_15 + check_3_16 + check_3_17 + check_3_18 + check_3_19 + check_3_20 + check_3_21 + check_3_22 + check_3_end +} + +docker_daemon_files_level1() { + check_3 + check_3_1 + check_3_2 + check_3_3 + check_3_4 + check_3_5 + check_3_6 + check_3_7 + check_3_8 + check_3_9 + check_3_10 + check_3_11 + check_3_12 + check_3_13 + check_3_14 + check_3_15 + check_3_16 + check_3_17 + check_3_18 + check_3_19 + check_3_20 + check_3_21 + check_3_22 + check_3_end +} + +container_images() { + check_4 + check_4_1 + check_4_2 + check_4_3 + check_4_4 + check_4_5 + check_4_6 + check_4_7 + check_4_8 + check_4_9 + check_4_10 + check_4_11 + check_4_end +} + +container_images_level1() { + check_4 + check_4_1 + check_4_2 + check_4_3 + check_4_4 + check_4_6 + check_4_7 + check_4_9 + check_4_10 + check_4_end +} + +container_runtime() { + check_5 + check_running_containers + check_5_1 + check_5_2 + check_5_3 + check_5_4 + check_5_5 + check_5_6 + check_5_7 + check_5_8 + check_5_9 + check_5_10 + check_5_11 + check_5_12 + check_5_13 + check_5_14 + check_5_15 + check_5_16 + check_5_17 + check_5_18 + check_5_19 + check_5_20 + check_5_21 + check_5_22 + check_5_23 + check_5_24 + check_5_25 + check_5_26 + check_5_27 + check_5_28 + check_5_29 + check_5_30 + check_5_31 + check_5_end +} + +container_runtime_level1() { + check_5 + check_running_containers + check_5_1 + check_5_3 + check_5_4 + check_5_5 + check_5_6 + check_5_7 + check_5_8 + check_5_9 + check_5_10 + check_5_11 + check_5_12 + check_5_13 + check_5_14 + check_5_15 + check_5_16 + check_5_17 + check_5_18 + check_5_19 + check_5_20 + check_5_21 + check_5_24 + check_5_25 + check_5_26 + check_5_27 + check_5_28 + check_5_30 + check_5_31 + check_5_end +} + +docker_security_operations() { + check_6 + check_6_1 + check_6_2 + check_6_end +} + +docker_security_operations_level1() { + check_6 + check_6_1 + check_6_2 + check_6_end +} + +docker_swarm_configuration() { + check_7 + check_7_1 + check_7_2 + check_7_3 + check_7_4 + check_7_5 + check_7_6 + check_7_7 + check_7_8 + check_7_9 + check_7_10 + check_7_end +} + +docker_swarm_configuration_level1() { + check_7 + check_7_1 + check_7_2 + check_7_3 + check_7_4 + check_7_7 + check_7_end +} + +docker_enterprise_configuration() { + check_8 + check_product_license + check_8_1 + check_8_1_1 + check_8_1_2 + check_8_1_3 + check_8_1_4 + check_8_1_5 + check_8_1_6 + check_8_1_7 + check_8_2 + check_8_2_1 + check_8_end +} + +docker_enterprise_configuration_level1() { + check_8 + check_product_license + check_8_1 + check_8_1_1 + check_8_1_2 + check_8_1_3 + check_8_1_4 + check_8_1_5 + check_8_1_6 + check_8_1_7 + check_8_2 + check_8_2_1 + check_8_end +} + +community_checks() { + check_c + check_c_1 + check_c_2 + check_c_end +} + +# CIS +cis() { + host_configuration + docker_daemon_configuration + docker_daemon_files + container_images + container_runtime + docker_security_operations + docker_swarm_configuration + docker_enterprise_configuration +} + +cis_level1() { + host_configuration_level1 + docker_daemon_configuration_level1 + docker_daemon_files_level1 + container_images_level1 + container_runtime_level1 + docker_security_operations_level1 + docker_swarm_configuration_level1 + docker_enterprise_configuration_level1 +} + +# Community contributed +community() { + community_checks +} + +# All +all() { + cis + community +} diff --git a/helper_lib.sh b/helper_lib.sh index ece69c8..bb89aee 100644 --- a/helper_lib.sh +++ b/helper_lib.sh @@ -3,36 +3,127 @@ # Returns the absolute path of a given string abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; } +# Audit rules default path +auditrules="/etc/audit/audit.rules" + # Compares versions of software of the format X.Y.Z do_version_check() { - [ "$1" = "$2" ] && return 10 + [ "$1" = "$2" ] && return 10 - ver1front=$(printf "%s" "$1" | cut -d "." -f -1) - ver1back=$(printf "%s" "$1" | cut -d "." -f 2-) - ver2front=$(printf "%s" "$2" | cut -d "." -f -1) - ver2back=$(printf "%s" "$2" | cut -d "." -f 2-) + ver1front=$(printf "%s" "$1" | cut -d "." -f -1) + ver1back=$(printf "%s" "$1" | cut -d "." -f 2-) + ver2front=$(printf "%s" "$2" | cut -d "." -f -1) + ver2back=$(printf "%s" "$2" | cut -d "." -f 2-) - if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then - [ "$ver1front" -gt "$ver2front" ] && return 11 - [ "$ver1front" -lt "$ver2front" ] && return 9 + if [ "$ver1front" != "$1" ] || [ "$ver2front" != "$2" ]; then + [ "$ver1front" -gt "$ver2front" ] && return 11 + [ "$ver1front" -lt "$ver2front" ] && return 9 - [ "$ver1front" = "$1" ] || [ -z "$ver1back" ] && ver1back=0 - [ "$ver2front" = "$2" ] || [ -z "$ver2back" ] && ver2back=0 - do_version_check "$ver1back" "$ver2back" - return $? - else - [ "$1" -gt "$2" ] && return 11 || return 9 - fi + [ "$ver1front" = "$1" ] || [ -z "$ver1back" ] && ver1back=0 + [ "$ver2front" = "$2" ] || [ -z "$ver2back" ] && ver2back=0 + do_version_check "$ver1back" "$ver2back" + return $? + else + [ "$1" -gt "$2" ] && return 11 || return 9 + fi } -# Compares two strings and returns 0 if the second is a substring of the first -contains() { - string="$1" - substring="$2" - if [ "${string#*$substring}" != "$string" ] - then - return 0 # $substring is in $string - else - return 1 # $substring is not in $string - fi +# Extracts commandline args from the newest running processes named like the first parameter +get_command_line_args() { + PROC="$1" + + for PID in $(pgrep -f -n "$PROC"); do + tr "\0" " " < /proc/"$PID"/cmdline + done +} + +# Extract the cumulative command line arguments for the docker daemon +# +# If specified multiple times, all matches are returned. +# Accounts for long and short variants, call with short option. +# Does not account for option defaults or implicit options. +get_docker_cumulative_command_line_args() { + OPTION="$1" + + if ! get_command_line_args "docker daemon" >/dev/null 2>&1 ; then + line_arg="docker daemon" + else + line_arg="dockerd" + fi + + get_command_line_args "$line_arg" | + # normalize known long options to their short versions + sed \ + -e 's/\-\-debug/-D/g' \ + -e 's/\-\-host/-H/g' \ + -e 's/\-\-log-level/-l/g' \ + -e 's/\-\-version/-v/g' \ + | + # normalize parameters separated by space(s) to -O=VALUE + sed \ + -e 's/\-\([DHlv]\)[= ]\([^- ][^ ]\)/-\1=\2/g' \ + | + # get the last interesting option + tr ' ' "\n" | + grep "^${OPTION}" | + # normalize quoting of values + sed \ + -e 's/"//g' \ + -e "s/'//g" +} + +# Extract the effective command line arguments for the docker daemon +# +# Accounts for multiple specifications, takes the last option. +# Accounts for long and short variants, call with short option +# Does not account for option default or implicit options. +get_docker_effective_command_line_args() { + OPTION="$1" + get_docker_cumulative_command_line_args "$OPTION" | tail -n1 +} + +get_docker_configuration_file() { + FILE="$(get_docker_effective_command_line_args '--config-file' | \ + sed 's/.*=//g')" + + if [ -f "$FILE" ]; then + CONFIG_FILE="$FILE" + elif [ -f '/etc/docker/daemon.json' ]; then + CONFIG_FILE='/etc/docker/daemon.json' + else + CONFIG_FILE='/dev/null' + fi +} + +get_docker_configuration_file_args() { + OPTION="$1" + + get_docker_configuration_file + + grep "$OPTION" "$CONFIG_FILE" | sed 's/.*://g' | tr -d '" ', +} + +get_service_file() { + SERVICE="$1" + + if [ -f "/etc/systemd/system/$SERVICE" ]; then + echo "/etc/systemd/system/$SERVICE" + elif [ -f "/lib/systemd/system/$SERVICE" ]; then + echo "/lib/systemd/system/$SERVICE" + elif systemctl show -p FragmentPath "$SERVICE" 2> /dev/null 1>&2; then + systemctl show -p FragmentPath "$SERVICE" | sed 's/.*=//' + else + echo "/usr/lib/systemd/system/$SERVICE" + fi +} + +yell_info() { +yell "# ------------------------------------------------------------------------------ +# Docker Bench for Security v$version +# +# Docker, Inc. (c) 2015- +# +# Checks for dozens of common best-practices around deploying Docker containers in production. +# Inspired by the CIS Docker Benchmark v1.2.0. +# ------------------------------------------------------------------------------" } diff --git a/output_lib.sh b/output_lib.sh index f4f61bc..384dfe0 100644 --- a/output_lib.sh +++ b/output_lib.sh @@ -1,9 +1,18 @@ #!/bin/sh -bldred='\033[1;31m' -bldgrn='\033[1;32m' -bldblu='\033[1;34m' -bldylw='\033[1;33m' # Yellow -txtrst='\033[0m' + +if [ -n "$nocolor" ] && [ "$nocolor" = "nocolor" ]; then + bldred='' + bldgrn='' + bldblu='' + bldylw='' + txtrst='' +else + bldred='\033[1;31m' + bldgrn='\033[1;32m' + bldblu='\033[1;34m' + bldylw='\033[1;33m' # Yellow + txtrst='\033[0m' +fi logit () { printf "%b\n" "$1" | tee -a "$logger" @@ -21,6 +30,67 @@ warn () { printf "%b\n" "${bldred}[WARN]${txtrst} $1" | tee -a "$logger" } +note () { + printf "%b\n" "${bldylw}[NOTE]${txtrst} $1" | tee -a "$logger" +} + yell () { printf "%b\n" "${bldylw}$1${txtrst}\n" } + +beginjson () { + printf "{\n \"dockerbenchsecurity\": \"%s\",\n \"start\": %s,\n \"tests\": [" "$1" "$2" | tee "$logger.json" 2>/dev/null 1>&2 +} + +endjson (){ + printf "\n ], \"checks\": %s, \"score\": %s, \"end\": %s \n}\n" "$1" "$2" "$3" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +logjson (){ + printf "\n \"%s\": \"%s\"," "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +SSEP= +SEP= +startsectionjson() { + printf "%s\n {\"id\": \"%s\", \"desc\": \"%s\", \"results\": [" "$SSEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + SEP= + SSEP="," +} + +endsectionjson() { + printf "\n ]}" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +starttestjson() { + printf "%s\n {\"id\": \"%s\", \"desc\": \"%s\", " "$SEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + SEP="," +} + +resulttestjson() { + if [ $# -eq 1 ]; then + printf "\"result\": \"%s\"}" "$1" | tee -a "$logger.json" 2>/dev/null 1>&2 + elif [ $# -eq 2 ]; then + # Result also contains details + printf "\"result\": \"%s\", \"details\": \"%s\"}" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + else + # Result also includes details and a list of items. Add that directly to details and to an array property "items" + # Also limit the number of items to $limit, if $limit is non-zero + if [ $limit != 0 ]; then + truncItems="" + ITEM_COUNT=0 + for item in $3; do + truncItems="$truncItems $item" + ITEM_COUNT=$((ITEM_COUNT + 1)); + if [ "$ITEM_COUNT" == "$limit" ]; then + truncItems="$truncItems (truncated)" + break; + fi + done + else + truncItems=$3 + fi + itemsJson=$(printf "["; ISEP=""; ITEMCOUNT=0; for item in $truncItems; do printf "%s\"%s\"" "$ISEP" "$item"; ISEP=","; done; printf "]") + printf "\"result\": \"%s\", \"details\": \"%s: %s\", \"items\": %s}" "$1" "$2" "$truncItems" "$itemsJson" | tee -a "$logger.json" 2>/dev/null 1>&2 + fi +} diff --git a/tests/1_host_configuration.sh b/tests/1_host_configuration.sh index 6e2b66d..3a2e843 100644 --- a/tests/1_host_configuration.sh +++ b/tests/1_host_configuration.sh @@ -1,210 +1,478 @@ #!/bin/sh -logit "" -info "1 - Host Configuration" +check_1() { + logit "" + id_1="1" + desc_1="Host Configuration" + check_1="$id_1 - $desc_1" + info "$check_1" + startsectionjson "$id_1" "$desc_1" +} -# 1.1 -check_1_1="1.1 - Create a separate partition for containers" -grep /var/lib/docker /etc/fstab >/dev/null 2>&1 -if [ $? -eq 0 ]; then - pass "$check_1_1" -else - warn "$check_1_1" -fi +check_1_1() { + logit "" + id_1_1="1.1" + desc_1_1="General Configuration" + check_1_1="$id_1_1 - $desc_1_1" + info "$check_1_1" +} -# 1.2 -check_1_2="1.2 - Use an updated Linux Kernel" -kernel_version=$(uname -r | cut -d "-" -f 1) -do_version_check 3.10 "$kernel_version" -if [ $? -eq 11 ]; then - warn "$check_1_2" -else - pass "$check_1_2" -fi +# 1.1.1 +check_1_1_1() { + id_1_1_1="1.1.1" + desc_1_1_1="Ensure the container host has been Hardened (Not Scored)" + check_1_1_1="$id_1_1_1 - $desc_1_1_1" + starttestjson "$id_1_1_1" "$desc_1_1_1" -# 1.5 -check_1_5="1.5 - Remove all non-essential services from the host - Network" -# Check for listening network services. -listening_services=$(netstat -na | grep -v tcp6 | grep -v unix | grep -c LISTEN) -if [ "$listening_services" -eq 0 ]; then - warn "1.5 - Failed to get listening services for check: $check_1_5" -else - if [ "$listening_services" -gt 5 ]; then - warn "$check_1_5" - warn " * Host listening on: $listening_services ports" + totalChecks=$((totalChecks + 1)) + note "$check_1_1_1" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 1.1.2 +check_1_1_2() { + id_1_1_2="1.1.2" + desc_1_1_2="Ensure that the version of Docker is up to date (Not Scored)" + check_1_1_2="$id_1_1_2 - $desc_1_1_2" + starttestjson "$id_1_1_2" "$desc_1_1_2" + + totalChecks=$((totalChecks + 1)) + docker_version=$(docker version | grep -i -A2 '^server' | grep ' Version:' \ + | awk '{print $NF; exit}' | tr -d '[:alpha:]-,') + docker_current_version="$(date +%y.%m.0 -d @$(( $(date +%s) - 2592000)))" + do_version_check "$docker_current_version" "$docker_version" + if [ $? -eq 11 ]; then + info "$check_1_1_2" + info " * Using $docker_version, verify is it up to date as deemed necessary" + info " * Your operating system vendor may provide support and security maintenance for Docker" + resulttestjson "INFO" "Using $docker_version" + currentScore=$((currentScore + 0)) else - pass "$check_1_5" + pass "$check_1_1_2" + info " * Using $docker_version which is current" + info " * Check with your operating system vendor for support and security maintenance for Docker" + resulttestjson "PASS" "Using $docker_version" + currentScore=$((currentScore + 0)) fi -fi +} -# 1.6 -check_1_6="1.6 - Keep Docker up to date" -docker_version=$(docker version | grep 'Server version' | awk '{print $3}') -do_version_check 1.6.2 $docker_version -if [ $? -eq 11 ]; then - warn "$check_1_6" -else - pass "$check_1_6" -fi +check_1_2() { + logit "" + id_1_2="1.2" + desc_1_2="Linux Hosts Specific Configuration" + check_1_2="$id_1_2 - $desc_1_2" + info "$check_1_2" +} -# 1.7 -check_1_7="1.7 - Only allow trusted users to control Docker daemon" -docker_users=$(grep docker /etc/group) -info "$check_1_7" -for u in $docker_users; do - info " * $u" -done +# 1.2.1 +check_1_2_1() { + id_1_2_1="1.2.1" + desc_1_2_1="Ensure a separate partition for containers has been created (Scored)" + check_1_2_1="$id_1_2_1 - $desc_1_2_1" + starttestjson "$id_1_2_1" "$desc_1_2_1" -# 1.8 -check_1_8="1.8 - Audit docker daemon" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /usr/bin/docker >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_8" + totalChecks=$((totalChecks + 1)) + docker_root_dir=$(docker info -f '{{ .DockerRootDir }}') + if docker info | grep -q userns ; then + docker_root_dir=$(readlink -f "$docker_root_dir/..") + fi + + if mountpoint -q -- "$docker_root_dir" >/dev/null 2>&1; then + pass "$check_1_2_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) else - warn "$check_1_8" + warn "$check_1_2_1" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) fi -else - warn "1.8 - Failed to inspect: auditctl command not found." -fi +} -# 1.9 -check_1_9="1.9 - Audit Docker files and directories - /var/lib/docker" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /var/lib/docker >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_9" - else - warn "$check_1_9" - fi -else - warn "1.9 - Failed to inspect: auditctl command not found." -fi +# 1.2.2 +check_1_2_2() { + id_1_2_2="1.2.2" + desc_1_2_2="Ensure only trusted users are allowed to control Docker daemon (Scored)" + check_1_2_2="$id_1_2_2 - $desc_1_2_2" + starttestjson "$id_1_2_2" "$desc_1_2_2" -# 1.10 -check_1_10="1.10 - Audit Docker files and directories - /etc/docker" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /etc/docker >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_10" + totalChecks=$((totalChecks + 1)) + if command -v getent >/dev/null 2>&1; then + docker_users=$(getent group docker) else - warn "$check_1_10" + docker_users=$(grep 'docker' /etc/group) fi -else - warn "1.10 - Failed to inspect: auditctl command not found." -fi + info "$check_1_2_2" + for u in $docker_users; do + info " * $u" + done + resulttestjson "INFO" "users" "$docker_users" + currentScore=$((currentScore + 0)) +} -# 1.11 -check_1_11="1.11 - Audit Docker files and directories - docker-registry.service" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /usr/lib/systemd/system/docker-registry.service >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_11" - else - warn "$check_1_11" - fi -else - warn "1.11 - Failed to inspect: auditctl command not found." -fi +# 1.2.3 +check_1_2_3() { + id_1_2_3="1.2.3" + desc_1_2_3="Ensure auditing is configured for the Docker daemon (Scored)" + check_1_2_3="$id_1_2_3 - $desc_1_2_3" + starttestjson "$id_1_2_3" "$desc_1_2_3" -# 1.12 -check_1_12="1.12 - Audit Docker files and directories - docker.service" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /usr/lib/systemd/system/docker.service >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_12" + totalChecks=$((totalChecks + 1)) + file="/usr/bin/dockerd" + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep "$file" >/dev/null 2>&1; then + pass "$check_1_2_3" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_3" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_3" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) else - warn "$check_1_12" + warn "$check_1_2_3" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) fi -else - warn "1.12 - Failed to inspect: auditctl command not found." -fi +} -# 1.13 -check_1_13="1.13 - Audit Docker files and directories - /var/run/docker.sock" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /var/run/docker.sock >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_13" - else - warn "$check_1_13" - fi -else - warn "1.13 - Failed to inspect: auditctl command not found." -fi +# 1.2.4 +check_1_2_4() { + id_1_2_4="1.2.4" + desc_1_2_4="Ensure auditing is configured for Docker files and directories - /var/lib/docker (Scored)" + check_1_2_4="$id_1_2_4 - $desc_1_2_4" + starttestjson "$id_1_2_4" "$desc_1_2_4" -# 1.14 -check_1_14="1.14 - Audit Docker files and directories - /etc/sysconfig/docker" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /etc/sysconfig/docker >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_14" + totalChecks=$((totalChecks + 1)) + directory="/var/lib/docker" + if [ -d "$directory" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $directory >/dev/null 2>&1; then + pass "$check_1_2_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_4" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$directory" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_4" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi else - warn "$check_1_14" + info "$check_1_2_4" + info " * Directory not found" + resulttestjson "INFO" "Directory not found" + currentScore=$((currentScore + 0)) fi -else - warn "1.14 - Failed to inspect: auditctl command not found." -fi +} -# 1.15 -check_1_15="1.15 - Audit Docker files and directories - /etc/sysconfig/docker-network" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /etc/sysconfig/docker-network >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_15" - else - warn "$check_1_15" - fi -else - warn "1.15 - Failed to inspect: auditctl command not found." -fi +# 1.2.5 +check_1_2_5() { + id_1_2_5="1.2.5" + desc_1_2_5="Ensure auditing is configured for Docker files and directories - /etc/docker (Scored)" + check_1_2_5="$id_1_2_5 - $desc_1_2_5" + starttestjson "$id_1_2_5" "$desc_1_2_5" -# 1.16 -check_1_16="1.16 - Audit Docker files and directories - /etc/sysconfig/docker-registry" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /etc/sysconfig/docker-registry >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_16" + totalChecks=$((totalChecks + 1)) + directory="/etc/docker" + if [ -d "$directory" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $directory >/dev/null 2>&1; then + pass "$check_1_2_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_5" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$directory" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_5" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi else - warn "$check_1_16" - fi -else - warn "1.16 - Failed to inspect: auditctl command not found." + info "$check_1_2_5" + info " * Directory not found" + resulttestjson "INFO" "Directory not found" + currentScore=$((currentScore + 0)) fi +} -# 1.17 -check_1_17="1.17 - Audit Docker files and directories - /etc/sysconfig/docker-storage" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /etc/sysconfig/docker-storage >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_17" - else - warn "$check_1_17" - fi -else - warn "1.17 - Failed to inspect: auditctl command not found." -fi +# 1.2.6 +check_1_2_6() { + id_1_2_6="1.2.6" + desc_1_2_6="Ensure auditing is configured for Docker files and directories - docker.service (Scored)" + check_1_2_6="$id_1_2_6 - $desc_1_2_6" + starttestjson "$id_1_2_6" "$desc_1_2_6" -# 1.18 -check_1_18="1.18 - Audit Docker files and directories - /etc/default/docker" -command -v auditctl >/dev/null 2>&1 -if [ $? -eq 0 ]; then - auditctl -l | grep /etc/default/docker >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_1_18" + totalChecks=$((totalChecks + 1)) + file="$(get_service_file docker.service)" + if [ -f "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep "$file" >/dev/null 2>&1; then + pass "$check_1_2_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_6" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_6" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi else - warn "$check_1_18" + info "$check_1_2_6" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - warn "1.18 - Failed to inspect: auditctl command not found." -fi +} + +# 1.2.7 +check_1_2_7() { + id_1_2_7="1.2.7" + desc_1_2_7="Ensure auditing is configured for Docker files and directories - docker.socket (Scored)" + check_1_2_7="$id_1_2_7 - $desc_1_2_7" + starttestjson "$id_1_2_7" "$desc_1_2_7" + + totalChecks=$((totalChecks + 1)) + file="$(get_service_file docker.socket)" + if [ -e "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep "$file" >/dev/null 2>&1; then + pass "$check_1_2_7" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_7" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_7" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_7" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + info "$check_1_2_7" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) + fi +} + +# 1.2.8 +check_1_2_8() { + id_1_2_8="1.2.8" + desc_1_2_8="Ensure auditing is configured for Docker files and directories - /etc/default/docker (Scored)" + check_1_2_8="$id_1_2_8 - $desc_1_2_8" + starttestjson "$id_1_2_8" "$desc_1_2_8" + + totalChecks=$((totalChecks + 1)) + file="/etc/default/docker" + if [ -f "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $file >/dev/null 2>&1; then + pass "$check_1_2_8" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_8" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_8" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_8" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + info "$check_1_2_8" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) + fi +} + +# 1.2.9 +check_1_2_9() { + id_1_2_9="1.2.9" + desc_1_2_9="Ensure auditing is configured for Docker files and directories - /etc/sysconfig/docker (Scored)" + check_1_2_9="$id_1_2_9 - $desc_1_2_9" + starttestjson "$id_1_2_9" "$desc_1_2_9" + + totalChecks=$((totalChecks + 1)) + file="/etc/sysconfig/docker" + if [ -f "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $file >/dev/null 2>&1; then + pass "$check_1_2_9" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_9" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_9" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_9" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + info "$check_1_2_9" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) + fi +} + +# 1.2.10 +check_1_2_10() { + id_1_2_10="1.2.10" + desc_1_2_10="Ensure auditing is configured for Docker files and directories - /etc/docker/daemon.json (Scored)" + check_1_2_10="$id_1_2_10 - $desc_1_2_10" + starttestjson "$id_1_2_10" "$desc_1_2_10" + + totalChecks=$((totalChecks + 1)) + file="/etc/docker/daemon.json" + if [ -f "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $file >/dev/null 2>&1; then + pass "$check_1_2_10" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_10" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_10" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_10" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + info "$check_1_2_10" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) + fi +} + +# 1.2.11 +check_1_2_11() { + id_1_2_11="1.2.11" + desc_1_2_11="Ensure auditing is configured for Docker files and directories - /usr/bin/containerd (Scored)" + check_1_2_11="$id_1_2_11 - $desc_1_2_11" + starttestjson "$id_1_2_11" "$desc_1_2_11" + + totalChecks=$((totalChecks + 1)) + file="/usr/bin/containerd" + if [ -f "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $file >/dev/null 2>&1; then + pass "$check_1_2_11" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_11" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_11" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_11" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + info "$check_1_2_11" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) + fi +} + +# 1.2.12 +check_1_2_12() { + id_1_2_12="1.2.12" + desc_1_2_12="Ensure auditing is configured for Docker files and directories - /usr/sbin/runc (Scored)" + check_1_2_12="$id_1_2_12 - $desc_1_2_12" + starttestjson "$id_1_2_12" "$desc_1_2_12" + + totalChecks=$((totalChecks + 1)) + file="/usr/sbin/runc" + if [ -f "$file" ]; then + if command -v auditctl >/dev/null 2>&1; then + if auditctl -l | grep $file >/dev/null 2>&1; then + pass "$check_1_2_12" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_12" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass "$check_1_2_12" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_1_2_12" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + info "$check_1_2_12" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) + fi +} + +check_1_end() { + endsectionjson +} diff --git a/tests/2_docker_daemon_configuration.sh b/tests/2_docker_daemon_configuration.sh index ed94926..83cc8eb 100644 --- a/tests/2_docker_daemon_configuration.sh +++ b/tests/2_docker_daemon_configuration.sh @@ -1,105 +1,442 @@ #!/bin/sh -logit "\n" -info "2 - Docker Daemon Configuration" +check_2() { + logit "\n" + id_2="2" + desc_2="Docker daemon configuration" + check_2="$id_2 - $desc_2" + info "$check_2" + startsectionjson "$id_2" "$desc_2" +} # 2.1 -check_2_1="2.1 - Do not use lxc execution driver" -pgrep -lf docker | grep lxc >/dev/null 2>&1 -if [ $? -eq 0 ]; then - warn "$check_2_1" -else - pass "$check_2_1" -fi +check_2_1() { + id_2_1="2.1" + desc_2_1="Ensure network traffic is restricted between containers on the default bridge (Scored)" + check_2_1="$id_2_1 - $desc_2_1" + starttestjson "$id_2_1" "$desc_2_1" + + totalChecks=$((totalChecks + 1)) + if get_docker_effective_command_line_args '--icc' | grep false >/dev/null 2>&1; then + pass "$check_2_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_configuration_file_args 'icc' | grep "false" >/dev/null 2>&1; then + pass "$check_2_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_1" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} # 2.2 -check_2_2="2.2 - Restrict network traffic between containers" -pgrep -lf docker | grep "icc=false" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - pass "$check_2_2" -else - warn "$check_2_2" -fi +check_2_2() { + id_2_2="2.2" + desc_2_2="Ensure the logging level is set to 'info' (Scored)" + check_2_2="$id_2_2 - $desc_2_2" + starttestjson "$id_2_2" "$desc_2_2" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'log-level' >/dev/null 2>&1; then + if get_docker_configuration_file_args 'log-level' | grep info >/dev/null 2>&1; then + pass "$check_2_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif [ -z "$(get_docker_configuration_file_args 'log-level')" ]; then + pass "$check_2_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_2" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + elif get_docker_effective_command_line_args '-l'; then + if get_docker_effective_command_line_args '-l' | grep "info" >/dev/null 2>&1; then + pass "$check_2_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_2" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + pass "$check_2_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} # 2.3 -check_2_3="2.3 - Set the logging level" -pgrep -lf docker | grep "log-level=\"debug\"" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - warn "$check_2_3" -else - pass "$check_2_3" -fi +check_2_3() { + id_2_3="2.3" + desc_2_3="Ensure Docker is allowed to make changes to iptables (Scored)" + check_2_3="$id_2_3 - $desc_2_3" + starttestjson "$id_2_3" "$desc_2_3" + + totalChecks=$((totalChecks + 1)) + if get_docker_effective_command_line_args '--iptables' | grep "false" >/dev/null 2>&1; then + warn "$check_2_3" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + elif get_docker_configuration_file_args 'iptables' | grep "false" >/dev/null 2>&1; then + warn "$check_2_3" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + else + pass "$check_2_3" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} # 2.4 -check_2_4="2.4 - Allow Docker to make changes to iptables" -pgrep -lf docker | grep "iptables=false" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - warn "$check_2_4" -else - pass "$check_2_4" -fi +check_2_4() { + id_2_4="2.4" + desc_2_4="Ensure insecure registries are not used (Scored)" + check_2_4="$id_2_4 - $desc_2_4" + starttestjson "$id_2_4" "$desc_2_4" + + totalChecks=$((totalChecks + 1)) + if get_docker_effective_command_line_args '--insecure-registry' | grep "insecure-registry" >/dev/null 2>&1; then + warn "$check_2_4" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + elif ! [ -z "$(get_docker_configuration_file_args 'insecure-registries')" ]; then + if get_docker_configuration_file_args 'insecure-registries' | grep '\[]' >/dev/null 2>&1; then + pass "$check_2_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_4" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + pass "$check_2_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} # 2.5 -check_2_5="2.5 - Do not use insecure registries" -pgrep -lf docker | grep "insecure-registry" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - warn "$check_2_5" -else - pass "$check_2_5" -fi +check_2_5() { + id_2_5="2.5" + desc_2_5="Ensure aufs storage driver is not used (Scored)" + check_2_5="$id_2_5 - $desc_2_5" + starttestjson "$id_2_5" "$desc_2_5" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "^\sStorage Driver:\s*aufs\s*$" >/dev/null 2>&1; then + warn "$check_2_5" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + else + pass "$check_2_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} # 2.6 -check_2_6="2.6 - Setup a local registry mirror" -pgrep -lf docker | grep "registry-mirror" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - pass "$check_2_6" -else - info "$check_2_6" - info " * No local registry currently configured" -fi +check_2_6() { + id_2_6="2.6" + desc_2_6="Ensure TLS authentication for Docker daemon is configured (Scored)" + check_2_6="$id_2_6 - $desc_2_6" + starttestjson "$id_2_6" "$desc_2_6" + + totalChecks=$((totalChecks + 1)) + if [ $(get_docker_configuration_file_args 'tcp://') ] || \ + [ $(get_docker_cumulative_command_line_args '-H' | grep -vE '(unix|fd)://') >/dev/null 2>&1 ]; then + if [ $(get_docker_configuration_file_args '"tlsverify":' | grep 'true') ] || \ + [ $(get_docker_cumulative_command_line_args '--tlsverify' | grep 'tlsverify') >/dev/null 2>&1 ]; then + pass "$check_2_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif [ $(get_docker_configuration_file_args '"tls":' | grep 'true') ] || \ + [ $(get_docker_cumulative_command_line_args '--tls' | grep 'tls$') >/dev/null 2>&1 ]; then + warn "$check_2_6" + warn " * Docker daemon currently listening on TCP with TLS, but no verification" + resulttestjson "WARN" "Docker daemon currently listening on TCP with TLS, but no verification" + currentScore=$((currentScore - 1)) + else + warn "$check_2_6" + warn " * Docker daemon currently listening on TCP without TLS" + resulttestjson "WARN" "Docker daemon currently listening on TCP without TLS" + currentScore=$((currentScore - 1)) + fi + else + info "$check_2_6" + info " * Docker daemon not listening on TCP" + resulttestjson "INFO" "Docker daemon not listening on TCP" + currentScore=$((currentScore + 0)) + fi +} # 2.7 -check_2_7="2.7 - Do not use the aufs storage driver" -docker info 2>/dev/null | grep -e "^Storage Driver:\s*aufs\s*$" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - warn "$check_2_7" -else - pass "$check_2_7" -fi +check_2_7() { + id_2_7="2.7" + desc_2_7="Ensure the default ulimit is configured appropriately (Not Scored)" + check_2_7="$id_2_7 - $desc_2_7" + starttestjson "$id_2_7" "$desc_2_7" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'default-ulimit' | grep -v '{}' >/dev/null 2>&1; then + pass "$check_2_7" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_effective_command_line_args '--default-ulimit' | grep "default-ulimit" >/dev/null 2>&1; then + pass "$check_2_7" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + info "$check_2_7" + info " * Default ulimit doesn't appear to be set" + resulttestjson "INFO" "Default ulimit doesn't appear to be set" + currentScore=$((currentScore + 0)) + fi +} # 2.8 -check_2_8="2.8 - Do not bind Docker to another IP/Port or a Unix socket" -pgrep -lf docker | grep "\-H" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - info "$check_2_8" - info " * Docker daemon running with -H" -else - pass "$check_2_8" -fi +check_2_8() { + id_2_8="2.8" + desc_2_8="Enable user namespace support (Scored)" + check_2_8="$id_2_8 - $desc_2_8" + starttestjson "$id_2_8" "$desc_2_8" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'userns-remap' | grep -v '""'; then + pass "$check_2_8" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_effective_command_line_args '--userns-remap' | grep "userns-remap" >/dev/null 2>&1; then + pass "$check_2_8" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_8" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} # 2.9 -check_2_9="2.9 - Configure TLS authentication for Docker daemon" -pgrep -lf docker | grep "tcp://" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - pgrep -lf docker | grep "tlsverify" | grep "tlskey" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_2_9" - info " * Docker daemon currently listening on TCP" - else +check_2_9() { + id_2_9="2.9" + desc_2_9="Ensure the default cgroup usage has been confirmed (Scored)" + check_2_9="$id_2_9 - $desc_2_9" + starttestjson "$id_2_9" "$desc_2_9" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'cgroup-parent' | grep -v ''; then warn "$check_2_9" - warn " * Docker daemon currently listening on TCP without --tlsverify" + info " * Confirm cgroup usage" + resulttestjson "WARN" "Confirm cgroup usage" + currentScore=$((currentScore + 0)) + elif get_docker_effective_command_line_args '--cgroup-parent' | grep "cgroup-parent" >/dev/null 2>&1; then + warn "$check_2_9" + info " * Confirm cgroup usage" + resulttestjson "WARN" "Confirm cgroup usage" + currentScore=$((currentScore + 0)) + else + pass "$check_2_9" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) fi -else - info "$check_2_9" - info " * Docker daemon not listening on TCP" -fi +} # 2.10 -check_2_10="2.10 - Set default ulimit as appropriate" -pgrep -lf docker | grep "default-ulimit" >/dev/null 2>&1 -if [ $? -eq 0 ]; then - pass "$check_2_10" -else - info "$check_2_10" - info " * Default ulimit doesn't appear to be set" -fi +check_2_10() { + id_2_10="2.10" + desc_2_10="Ensure base device size is not changed until needed (Scored)" + check_2_10="$id_2_10 - $desc_2_10" + starttestjson "$id_2_10" "$desc_2_10" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'storage-opts' | grep "dm.basesize" >/dev/null 2>&1; then + warn "$check_2_10" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + elif get_docker_effective_command_line_args '--storage-opt' | grep "dm.basesize" >/dev/null 2>&1; then + warn "$check_2_10" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + else + pass "$check_2_10" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 2.11 +check_2_11() { + id_2_11="2.11" + desc_2_11="Ensure that authorization for Docker client commands is enabled (Scored)" + check_2_11="$id_2_11 - $desc_2_11" + starttestjson "$id_2_11" "$desc_2_11" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'authorization-plugins' | grep -v '\[]'; then + pass "$check_2_11" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_effective_command_line_args '--authorization-plugin' | grep "authorization-plugin" >/dev/null 2>&1; then + pass "$check_2_11" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_11" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} + +# 2.12 +check_2_12() { + id_2_12="2.12" + desc_2_12="Ensure centralized and remote logging is configured (Scored)" + check_2_12="$id_2_12 - $desc_2_12" + starttestjson "$id_2_12" "$desc_2_12" + + totalChecks=$((totalChecks + 1)) + if docker info --format '{{ .LoggingDriver }}' | grep 'json-file' >/dev/null 2>&1; then + warn "$check_2_12" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + else + pass "$check_2_12" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 2.13 +check_2_13() { + id_2_13="2.13" + desc_2_13="Ensure live restore is enabled (Scored)" + check_2_13="$id_2_13 - $desc_2_13" + starttestjson "$id_2_13" "$desc_2_13" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Live Restore Enabled:\s*true\s*" >/dev/null 2>&1; then + pass "$check_2_13" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then + pass "$check_2_13 (Incompatible with swarm mode)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_effective_command_line_args '--live-restore' | grep "live-restore" >/dev/null 2>&1; then + pass "$check_2_13" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_13" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + fi +} + +# 2.14 +check_2_14() { + id_2_14="2.14" + desc_2_14="Ensure Userland Proxy is Disabled (Scored)" + check_2_14="$id_2_14 - $desc_2_14" + starttestjson "$id_2_14" "$desc_2_14" + + totalChecks=$((totalChecks + 1)) + if get_docker_configuration_file_args 'userland-proxy' | grep false >/dev/null 2>&1; then + pass "$check_2_14" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_effective_command_line_args '--userland-proxy=false' 2>/dev/null | grep "userland-proxy=false" >/dev/null 2>&1; then + pass "$check_2_14" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_14" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} + +# 2.15 +check_2_15() { + id_2_15="2.15" + desc_2_15="Ensure that a daemon-wide custom seccomp profile is applied if appropriate (Not Scored)" + check_2_15="$id_2_15 - $desc_2_15" + starttestjson "$id_2_15" "$desc_2_15" + + totalChecks=$((totalChecks + 1)) + if docker info --format '{{ .SecurityOptions }}' | grep 'name=seccomp,profile=default' 2>/dev/null 1>&2; then + pass "$check_2_15" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + info "$check_2_15" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) + fi +} + +# 2.16 +check_2_16() { + docker_version=$(docker version | grep -i -A2 '^server' | grep ' Version:' \ + | awk '{print $NF; exit}' | tr -d '[:alpha:]-,.' | cut -c 1-4) + + id_2_16="2.16" + desc_2_16="Ensure that experimental features are not implemented in production (Scored)" + check_2_16="$id_2_16 - $desc_2_16" + starttestjson "$id_2_16" "$desc_2_16" + + totalChecks=$((totalChecks + 1)) + if [ "$docker_version" -le 1903 ]; then + if docker version -f '{{.Server.Experimental}}' | grep false 2>/dev/null 1>&2; then + pass "$check_2_16" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_16" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + desc_2_16="$desc_2_16 (Deprecated)" + check_2_16="$id_2_16 - $desc_2_16" + info "$desc_2_16" + resulttestjson "INFO" + fi +} + +# 2.17 +check_2_17() { + id_2_17="2.17" + desc_2_17="Ensure containers are restricted from acquiring new privileges (Scored)" + check_2_17="$id_2_17 - $desc_2_17" + starttestjson "$id_2_17" "$desc_2_17" + + totalChecks=$((totalChecks + 1)) + if get_docker_effective_command_line_args '--no-new-privileges' | grep "no-new-privileges" >/dev/null 2>&1; then + pass "$check_2_17" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_configuration_file_args 'no-new-privileges' | grep true >/dev/null 2>&1; then + pass "$check_2_17" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_2_17" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} + +check_2_end() { + endsectionjson +} diff --git a/tests/3_docker_daemon_configuration_files.sh b/tests/3_docker_daemon_configuration_files.sh index 36e49ab..3d29598 100644 --- a/tests/3_docker_daemon_configuration_files.sh +++ b/tests/3_docker_daemon_configuration_files.sh @@ -1,435 +1,668 @@ #!/bin/sh -logit "\n" -info "3 - Docker Daemon Configuration Files" +check_3() { + logit "\n" + id_3="3" + desc_3="Docker daemon configuration files" + check_3="$id_3 - $desc_3" + info "$check_3" + startsectionjson "$id_3" "$desc_3" +} # 3.1 -check_3_1="3.1 - Verify that docker.service file ownership is set to root:root" -file="/usr/lib/systemd/system/docker.service" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_1" +check_3_1() { + id_3_1="3.1" + desc_3_1="Ensure that the docker.service file ownership is set to root:root (Scored)" + check_3_1="$id_3_1 - $desc_3_1" + starttestjson "$id_3_1" "$desc_3_1" + + totalChecks=$((totalChecks + 1)) + file="$(get_service_file docker.service)" + if [ -f "$file" ]; then + if [ "$(stat -c %u%g $file)" -eq 00 ]; then + pass "$check_3_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_1" + warn " * Wrong ownership for $file" + resulttestjson "WARN" "Wrong ownership for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_1" - warn " * Wrong ownership for $file" + info "$check_3_1" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_1" - info " * File not found" -fi +} # 3.2 -check_3_2="3.2 - Verify that docker.service file permissions are set to 644" -file="/usr/lib/systemd/system/docker.service" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_2" +check_3_2() { + id_3_2="3.2" + desc_3_2="Ensure that docker.service file permissions are appropriately set (Scored)" + check_3_2="$id_3_2 - $desc_3_2" + starttestjson "$id_3_2" "$desc_3_2" + + totalChecks=$((totalChecks + 1)) + file="$(get_service_file docker.service)" + if [ -f "$file" ]; then + if [ "$(stat -c %a $file)" -eq 644 ] || [ "$(stat -c %a $file)" -eq 600 ]; then + pass "$check_3_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_2" + warn " * Wrong permissions for $file" + resulttestjson "WARN" "Wrong permissions for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_2" - warn " * Wrong permissions for $file" + info "$check_3_2" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_2" - info " * File not found" -fi +} # 3.3 -check_3_3="3.3 - Verify that docker-registry.service file ownership is set to root:root" -file="/usr/lib/systemd/system/docker-registry.service" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_3" +check_3_3() { + id_3_3="3.3" + desc_3_3="Ensure that docker.socket file ownership is set to root:root (Scored)" + check_3_3="$id_3_3 - $desc_3_3" + starttestjson "$id_3_3" "$desc_3_3" + + totalChecks=$((totalChecks + 1)) + file="$(get_service_file docker.socket)" + if [ -f "$file" ]; then + if [ "$(stat -c %u%g $file)" -eq 00 ]; then + pass "$check_3_3" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_3" + warn " * Wrong ownership for $file" + resulttestjson "WARN" "Wrong ownership for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_3" - warn " * Wrong ownership for $file" + info "$check_3_3" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_3" - info " * File not found" -fi +} # 3.4 -check_3_4="3.4 - Verify that docker-registry.service file permissions are set to 644" -file="/usr/lib/systemd/system/docker-registry.service" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_4" +check_3_4() { + id_3_4="3.4" + desc_3_4="Ensure that docker.socket file permissions are set to 644 or more restrictive (Scored)" + check_3_4="$id_3_4 - $desc_3_4" + starttestjson "$id_3_4" "$desc_3_4" + + totalChecks=$((totalChecks + 1)) + file="$(get_service_file docker.socket)" + if [ -f "$file" ]; then + if [ "$(stat -c %a $file)" -eq 644 ] || [ "$(stat -c %a $file)" -eq 600 ]; then + pass "$check_3_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_4" + warn " * Wrong permissions for $file" + resulttestjson "WARN" "Wrong permissions for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_4" - warn " * Wrong permissions for $file" + info "$check_3_4" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_4" - info " * File not found" -fi +} # 3.5 -check_3_5="3.5 - Verify that docker.socket file ownership is set to root:root" -file="/usr/lib/systemd/system/docker.socket" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_5" +check_3_5() { + id_3_5="3.5" + desc_3_5="Ensure that the /etc/docker directory ownership is set to root:root (Scored)" + check_3_5="$id_3_5 - $desc_3_5" + starttestjson "$id_3_5" "$desc_3_5" + + totalChecks=$((totalChecks + 1)) + directory="/etc/docker" + if [ -d "$directory" ]; then + if [ "$(stat -c %u%g $directory)" -eq 00 ]; then + pass "$check_3_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_5" + warn " * Wrong ownership for $directory" + resulttestjson "WARN" "Wrong ownership for $directory" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_5" - warn " * Wrong ownership for $file" + info "$check_3_5" + info " * Directory not found" + resulttestjson "INFO" "Directory not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_5" - info " * File not found" -fi +} # 3.6 -check_3_6="3.6 - Verify that docker.socket file permissions are set to 644" -file="/usr/lib/systemd/system/docker.socket" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_6" +check_3_6() { + id_3_6="3.6" + desc_3_6="Ensure that /etc/docker directory permissions are set to 755 or more restrictively (Scored)" + check_3_6="$id_3_6 - $desc_3_6" + starttestjson "$id_3_6" "$desc_3_6" + + totalChecks=$((totalChecks + 1)) + directory="/etc/docker" + if [ -d "$directory" ]; then + if [ "$(stat -c %a $directory)" -eq 755 ] || [ "$(stat -c %a $directory)" -eq 700 ]; then + pass "$check_3_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_6" + warn " * Wrong permissions for $directory" + resulttestjson "WARN" "Wrong permissions for $directory" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_6" - warn " * Wrong permissions for $file" + info "$check_3_6" + info " * Directory not found" + resulttestjson "INFO" "Directory not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_6" - info " * File not found" -fi +} # 3.7 -check_3_7="3.7 - Verify that Docker environment file ownership is set to root:root " -file="/etc/sysconfig/docker" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_7" +check_3_7() { + id_3_7="3.7" + desc_3_7="Ensure that registry certificate file ownership is set to root:root (Scored)" + check_3_7="$id_3_7 - $desc_3_7" + starttestjson "$id_3_7" "$desc_3_7" + + totalChecks=$((totalChecks + 1)) + directory="/etc/docker/certs.d/" + if [ -d "$directory" ]; then + fail=0 + owners=$(find "$directory" -type f -name '*.crt') + for p in $owners; do + if [ "$(stat -c %u $p)" -ne 0 ]; then + fail=1 + fi + done + if [ $fail -eq 1 ]; then + warn "$check_3_7" + warn " * Wrong ownership for $directory" + resulttestjson "WARN" "Wrong ownership for $directory" + currentScore=$((currentScore - 1)) + else + pass "$check_3_7" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi else - warn "$check_3_7" - warn " * Wrong ownership for $file" + info "$check_3_7" + info " * Directory not found" + resulttestjson "INFO" "Directory not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_7" - info " * File not found" -fi +} # 3.8 -check_3_8="3.8 - Verify that Docker environment file permissions are set to 644" -file="/etc/sysconfig/docker" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_8" +check_3_8() { + id_3_8="3.8" + desc_3_8="Ensure that registry certificate file permissions are set to 444 or more restrictively (Scored)" + check_3_8="$id_3_8 - $desc_3_8" + starttestjson "$id_3_8" "$desc_3_8" + + totalChecks=$((totalChecks + 1)) + directory="/etc/docker/certs.d/" + if [ -d "$directory" ]; then + fail=0 + perms=$(find "$directory" -type f -name '*.crt') + for p in $perms; do + if [ "$(stat -c %a $p)" -ne 444 ] && [ "$(stat -c %a $p)" -ne 400 ]; then + fail=1 + fi + done + if [ $fail -eq 1 ]; then + warn "$check_3_8" + warn " * Wrong permissions for $directory" + resulttestjson "WARN" "Wrong permissions for $directory" + currentScore=$((currentScore - 1)) + else + pass "$check_3_8" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi else - warn "$check_3_8" - warn " * Wrong permissions for $file" + info "$check_3_8" + info " * Directory not found" + resulttestjson "INFO" "Directory not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_8" - info " * File not found" -fi +} # 3.9 -check_3_9="3.9 - Verify that docker-network environment file ownership is set to root:root" -file="/etc/sysconfig/docker-network" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_9" +check_3_9() { + id_3_9="3.9" + desc_3_9="Ensure that TLS CA certificate file ownership is set to root:root (Scored)" + check_3_9="$id_3_9 - $desc_3_9" + starttestjson "$id_3_9" "$desc_3_9" + + totalChecks=$((totalChecks + 1)) + if [ -n "$(get_docker_configuration_file_args 'tlscacert')" ]; then + tlscacert=$(get_docker_configuration_file_args 'tlscacert') else - warn "$check_3_9" - warn " * Wrong ownership for $file" + tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi -else - info "$check_3_9" - info " * File not found" -fi + if [ -f "$tlscacert" ]; then + if [ "$(stat -c %u%g "$tlscacert")" -eq 00 ]; then + pass "$check_3_9" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_9" + warn " * Wrong ownership for $tlscacert" + resulttestjson "WARN" "Wrong ownership for $tlscacert" + currentScore=$((currentScore - 1)) + fi + else + info "$check_3_9" + info " * No TLS CA certificate found" + resulttestjson "INFO" "No TLS CA certificate found" + currentScore=$((currentScore + 0)) + fi +} # 3.10 -check_3_10="3.10 - Verify that docker-network environment file permissions are set to 644" -file="/etc/sysconfig/docker-network" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_10" +check_3_10() { + id_3_10="3.10" + desc_3_10="Ensure that TLS CA certificate file permissions are set to 444 or more restrictively (Scored)" + check_3_10="$id_3_10 - $desc_3_10" + starttestjson "$id_3_10" "$desc_3_10" + + totalChecks=$((totalChecks + 1)) + if [ -n "$(get_docker_configuration_file_args 'tlscacert')" ]; then + tlscacert=$(get_docker_configuration_file_args 'tlscacert') else - warn "$check_3_10" - warn " * Wrong permissions for $file" + tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi -else - info "$check_3_10" - info " * File not found" -fi + if [ -f "$tlscacert" ]; then + if [ "$(stat -c %a $tlscacert)" -eq 444 ] || [ "$(stat -c %a $tlscacert)" -eq 400 ]; then + pass "$check_3_10" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_10" + warn " * Wrong permissions for $tlscacert" + resulttestjson "WARN" "Wrong permissions for $tlscacert" + currentScore=$((currentScore - 1)) + fi + else + info "$check_3_10" + info " * No TLS CA certificate found" + resulttestjson "INFO" "No TLS CA certificate found" + currentScore=$((currentScore + 0)) + fi +} # 3.11 -check_3_11="3.11 - Verify that docker-registry environment file ownership is set to root:root" -file="/etc/sysconfig/docker-registry" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_11" +check_3_11() { + id_3_11="3.11" + desc_3_11="Ensure that Docker server certificate file ownership is set to root:root (Scored)" + check_3_11="$id_3_11 - $desc_3_11" + starttestjson "$id_3_11" "$desc_3_11" + + totalChecks=$((totalChecks + 1)) + if [ -n "$(get_docker_configuration_file_args 'tlscert')" ]; then + tlscert=$(get_docker_configuration_file_args 'tlscert') else - warn "$check_3_11" - warn " * Wrong ownership for $file" + tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi -else - info "$check_3_11" - info " * File not found" -fi + if [ -f "$tlscert" ]; then + if [ "$(stat -c %u%g "$tlscert")" -eq 00 ]; then + pass "$check_3_11" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_11" + warn " * Wrong ownership for $tlscert" + resulttestjson "WARN" "Wrong ownership for $tlscert" + currentScore=$((currentScore - 1)) + fi + else + info "$check_3_11" + info " * No TLS Server certificate found" + resulttestjson "INFO" "No TLS Server certificate found" + currentScore=$((currentScore + 0)) + fi +} # 3.12 -check_3_12="3.12 - Verify that docker-registry environment file permissions are set to 644" -file="/etc/sysconfig/docker-registry" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_12" +check_3_12() { + id_3_12="3.12" + desc_3_12="Ensure that the Docker server certificate file permissions are set to 444 or more restrictively (Scored)" + check_3_12="$id_3_12 - $desc_3_12" + starttestjson "$id_3_12" "$desc_3_12" + + totalChecks=$((totalChecks + 1)) + if [ -n "$(get_docker_configuration_file_args 'tlscert')" ]; then + tlscert=$(get_docker_configuration_file_args 'tlscert') else - warn "$check_3_12" - warn " * Wrong permissions for $file" + tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi -else - info "$check_3_12" - info " * File not found" -fi + if [ -f "$tlscert" ]; then + if [ "$(stat -c %a $tlscert)" -eq 444 ] || [ "$(stat -c %a $tlscert)" -eq 400 ]; then + pass "$check_3_12" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_12" + warn " * Wrong permissions for $tlscert" + resulttestjson "WARN" "Wrong permissions for $tlscert" + currentScore=$((currentScore - 1)) + fi + else + info "$check_3_12" + info " * No TLS Server certificate found" + resulttestjson "INFO" "No TLS Server certificate found" + currentScore=$((currentScore + 0)) + fi +} # 3.13 -check_3_13="3.13 - Verify that docker-storage environment file ownership is set to root:root" -file="/etc/sysconfig/docker-storage" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_13" +check_3_13() { + id_3_13="3.13" + desc_3_13="Ensure that the Docker server certificate key file ownership is set to root:root (Scored)" + check_3_13="$id_3_13 - $desc_3_13" + starttestjson "$id_3_13" "$desc_3_13" + + totalChecks=$((totalChecks + 1)) + if [ -n "$(get_docker_configuration_file_args 'tlskey')" ]; then + tlskey=$(get_docker_configuration_file_args 'tlskey') else - warn "$check_3_13" - warn " * Wrong ownership for $file" + tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi -else - info "$check_3_13" - info " * File not found" -fi + if [ -f "$tlskey" ]; then + if [ "$(stat -c %u%g "$tlskey")" -eq 00 ]; then + pass "$check_3_13" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_13" + warn " * Wrong ownership for $tlskey" + resulttestjson "WARN" "Wrong ownership for $tlskey" + currentScore=$((currentScore - 1)) + fi + else + info "$check_3_13" + info " * No TLS Key found" + resulttestjson "INFO" "No TLS Key found" + currentScore=$((currentScore + 0)) + fi +} # 3.14 -check_3_14="3.14 - Verify that docker-storage environment file permissions are set to 644" -file="/etc/sysconfig/docker-storage" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $1}' | grep "rw-r--r--" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_14" +check_3_14() { + id_3_14="3.14" + desc_3_14="Ensure that the Docker server certificate key file permissions are set to 400 (Scored)" + check_3_14="$id_3_14 - $desc_3_14" + starttestjson "$id_3_14" "$desc_3_14" + + totalChecks=$((totalChecks + 1)) + if [ -n "$(get_docker_configuration_file_args 'tlskey')" ]; then + tlskey=$(get_docker_configuration_file_args 'tlskey') else - warn "$check_3_14" - warn " * Wrong permissions for $file" + tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi -else - info "$check_3_14" - info " * File not found" -fi + if [ -f "$tlskey" ]; then + if [ "$(stat -c %a $tlskey)" -eq 400 ]; then + pass "$check_3_14" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_14" + warn " * Wrong permissions for $tlskey" + resulttestjson "WARN" "Wrong permissions for $tlskey" + currentScore=$((currentScore - 1)) + fi + else + info "$check_3_14" + info " * No TLS Key found" + resulttestjson "INFO" "No TLS Key found" + currentScore=$((currentScore + 0)) + fi +} # 3.15 -check_3_15="3.15 - Verify that /etc/docker directory ownership is set to root:root" -directory="/etc/docker" -if [ -d "$directory" ]; then - ls -ld "$directory" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_15" +check_3_15() { + id_3_15="3.15" + desc_3_15="Ensure that the Docker socket file ownership is set to root:docker (Scored)" + check_3_15="$id_3_15 - $desc_3_15" + starttestjson "$id_3_15" "$desc_3_15" + + totalChecks=$((totalChecks + 1)) + file="/var/run/docker.sock" + if [ -S "$file" ]; then + if [ "$(stat -c %U:%G $file)" = 'root:docker' ]; then + pass "$check_3_15" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_15" + warn " * Wrong ownership for $file" + resulttestjson "WARN" "Wrong ownership for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_15" - warn " * Wrong ownership for $directory" + info "$check_3_15" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_15" - info " * Directory not found" -fi +} # 3.16 -check_3_16="3.16 - Verify that /etc/docker directory permissions are set to 755" -directory="/etc/docker" -if [ -d "$directory" ]; then - perms=$(ls -ld $directory | awk '{print $1}') - if [ "$perms" = "drwxr-xr-x." ]; then - pass "$check_3_16" - elif [ "$perms" = "drwx------" ]; then - pass "$check_3_16" +check_3_16() { + id_3_16="3.16" + desc_3_16="Ensure that the Docker socket file permissions are set to 660 or more restrictively (Scored)" + check_3_16="$id_3_16 - $desc_3_16" + starttestjson "$id_3_16" "$desc_3_16" + + totalChecks=$((totalChecks + 1)) + file="/var/run/docker.sock" + if [ -S "$file" ]; then + if [ "$(stat -c %a $file)" -eq 660 ] || [ "$(stat -c %a $file)" -eq 600 ]; then + pass "$check_3_16" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_16" + warn " * Wrong permissions for $file" + resulttestjson "WARN" "Wrong permissions for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_16" - warn " * Wrong permissions for $directory" + info "$check_3_16" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_16" - info " * Directory not found" -fi +} # 3.17 -check_3_17="3.17 - Verify that registry certificate file ownership is set to root:root" -directory="/etc/docker/certs.d/" -if [ -d "$directory" ]; then - fail=0 - owners=$(ls -lL "$directory"/*.crt | awk '{print "$3", "$4"}') - for p in $owners; do - printf "%s" "$p" | grep "root" >/dev/null 2>&1 - if [ $? -ne 0 ]; then - fail=1 +check_3_17() { + id_3_17="3.17" + desc_3_17="Ensure that the daemon.json file ownership is set to root:root (Scored)" + check_3_17="$id_3_17 - $desc_3_17" + starttestjson "$id_3_17" "$desc_3_17" + + totalChecks=$((totalChecks + 1)) + file="/etc/docker/daemon.json" + if [ -f "$file" ]; then + if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then + pass "$check_3_17" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_17" + warn " * Wrong ownership for $file" + resulttestjson "WARN" "Wrong ownership for $file" + currentScore=$((currentScore - 1)) fi - done - if [ $fail -eq 1 ]; then - warn "$check_3_17" - warn " * Wrong ownership for $directory" else - pass "$check_3_17" + info "$check_3_17" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_17" - info " * Directory not found" -fi +} # 3.18 -check_3_18="3.18 - Verify that registry certificate file permissions are set to 444" -directory="/etc/docker/certs.d/" -if [ -d "$directory" ]; then - fail=0 - perms=$(ls -lL "$directory"/*.crt | awk '{print $1}') - for p in $perms; do - if [ "$p" != "-rw-r--r--." -a "$p" = "-rw-------." ]; then - fail=1 +check_3_18() { + id_3_18="3.18" + desc_3_18="Ensure that daemon.json file permissions are set to 644 or more restrictive (Scored)" + check_3_18="$id_3_18 - $desc_3_18" + starttestjson "$id_3_18" "$desc_3_18" + + totalChecks=$((totalChecks + 1)) + file="/etc/docker/daemon.json" + if [ -f "$file" ]; then + if [ "$(stat -c %a $file)" -eq 644 ] || [ "$(stat -c %a $file)" -eq 640 ] || [ "$(stat -c %a $file)" -eq 600 ]; then + pass "$check_3_18" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_18" + warn " * Wrong permissions for $file" + resulttestjson "WARN" "Wrong permissions for $file" + currentScore=$((currentScore - 1)) fi - done - if [ $fail -eq 1 ]; then - warn "$check_3_18" - warn " * Wrong permissions for $directory" else - pass "$check_3_18" + info "$check_3_18" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_18" - info " * Directory not found" -fi +} # 3.19 -check_3_19="3.19 - Verify that TLS CA certificate file ownership is set to root:root" -tlscacert=$(pgrep -lf docker | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | cut -d " " -f 1) -if [ -f "$tlscacert" ]; then - ls -ld "$tlscacert" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_19" +check_3_19() { + id_3_19="3.19" + desc_3_19="Ensure that the /etc/default/docker file ownership is set to root:root (Scored)" + check_3_19="$id_3_19 - $desc_3_19" + starttestjson "$id_3_19" "$desc_3_19" + + totalChecks=$((totalChecks + 1)) + file="/etc/default/docker" + if [ -f "$file" ]; then + if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then + pass "$check_3_19" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_19" + warn " * Wrong ownership for $file" + resulttestjson "WARN" "Wrong ownership for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_19" - warn " * Wrong ownership for $tlscacert" + info "$check_3_19" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_19" - info " * No TLS CA certificate found" -fi +} # 3.20 -check_3_20="3.20 - Verify that TLS CA certificate file permissions are set to 444" -tlscacert=$(pgrep -lf docker | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | cut -d " " -f 1) -if [ -f "$tlscacert" ]; then - perms=$(ls -ld "$tlscacert" | awk '{print $1}') - if [ "$perms" = "-rw-r--r--" ]; then - pass "$check_3_20" +check_3_20() { + id_3_20="3.20" + desc_3_20="Ensure that the /etc/sysconfig/docker file ownership is set to root:root (Scored)" + check_3_20="$id_3_20 - $desc_3_20" + starttestjson "$id_3_20" "$desc_3_20" + + totalChecks=$((totalChecks + 1)) + file="/etc/sysconfig/docker" + if [ -f "$file" ]; then + if [ "$(stat -c %U:%G $file)" = 'root:root' ]; then + pass "$check_3_20" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_20" + warn " * Wrong ownership for $file" + resulttestjson "WARN" "Wrong ownership for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_20" - warn " * Wrong permissions for $tlscacert" + info "$check_3_20" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_20" - info " * No TLS CA certificate found" -fi +} # 3.21 -check_3_21="3.21 - Verify that Docker server certificate file ownership is set to root:root" -tlscert=$(pgrep -lf docker | sed -n 's/.*tlscert=\([^s]\)/\1/p' | cut -d " " -f 1) -if [ -f "$tlscert" ]; then - ls -ld "$tlscert" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_21" +check_3_21() { + id_3_21="3.21" + desc_3_21="Ensure that the /etc/sysconfig/docker file permissions are set to 644 or more restrictively (Scored)" + check_3_21="$id_3_21 - $desc_3_21" + starttestjson "$id_3_21" "$desc_3_21" + + totalChecks=$((totalChecks + 1)) + file="/etc/sysconfig/docker" + if [ -f "$file" ]; then + if [ "$(stat -c %a $file)" -eq 644 ] || [ "$(stat -c %a $file)" -eq 600 ]; then + pass "$check_3_21" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_21" + warn " * Wrong permissions for $file" + resulttestjson "WARN" "Wrong permissions for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_21" - warn " * Wrong ownership for $tlscert" + info "$check_3_21" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_21" - info " * No TLS Server certificate found" -fi +} # 3.22 -check_3_22="3.22 - Verify that Docker server certificate file permissions are set to 444" -tlscacert=$(pgrep -lf docker | sed -n 's/.*tlscert=\([^s]\)/\1/p' | cut -d " " -f 1) -if [ -f "$tlscert" ]; then - perms=$(ls -ld "$tlscert" | awk '{print $1}') - if [ "$perms" = "-rw-r--r--" ]; then - pass "$check_3_22" - else - warn "$check_3_22" - warn " * Wrong permissions for $tlscert" - fi -else - info "$check_3_22" - info " * No TLS Server certificate found" -fi +check_3_22() { + id_3_22="3.22" + desc_3_22="Ensure that the /etc/default/docker file permissions are set to 644 or more restrictively (Scored)" + check_3_22="$id_3_22 - $desc_3_22" + starttestjson "$id_3_22" "$desc_3_22" -# 3.23 -check_3_23="3.23 - Verify that Docker server key file ownership is set to root:root" -tlskey=$(pgrep -lf docker | sed -n 's/.*tlskey=\([^s]\)/\1/p' | cut -d " " -f 1) -if [ -f "$tlskey" ]; then - ls -ld "$tlskey" | awk '{print $3, $4}' | grep "root root" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_23" + totalChecks=$((totalChecks + 1)) + file="/etc/default/docker" + if [ -f "$file" ]; then + if [ "$(stat -c %a $file)" -eq 644 ] || [ "$(stat -c %a $file)" -eq 600 ]; then + pass "$check_3_22" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_3_22" + warn " * Wrong permissions for $file" + resulttestjson "WARN" "Wrong permissions for $file" + currentScore=$((currentScore - 1)) + fi else - warn "$check_3_23" - warn " * Wrong ownership for $tlskey" + info "$check_3_22" + info " * File not found" + resulttestjson "INFO" "File not found" + currentScore=$((currentScore + 0)) fi -else - info "$check_3_23" - info " * No TLS Key found" -fi +} -# 3.24 -check_3_24="3.24 - Verify that Docker server key file permissions are set to 400" -tlskey=$(pgrep -lf docker | sed -n 's/.*tlskey=\([^s]\)/\1/p' | cut -d " " -f 1) -if [ -f "$tlskey" ]; then - perms=$(ls -ld "$tlskey" | awk '{print $1}') - if [ "$perms" = "-r--------" ]; then - pass "$check_3_24" - else - warn "$check_3_24" - warn " * Wrong permissions for $tlskey" - fi -else - info "$check_3_24" - info " * No TLS Key found" -fi - -# 3.25 -check_3_25="3.25 - Verify that Docker socket file ownership is set to root:docker" -file="/var/run/docker.sock" -if [ -f "$file" ]; then - ls -ld "$file" | awk '{print $3, $4}' | grep "root docker" >/dev/null 2>&1 - if [ $? -eq 0 ]; then - pass "$check_3_25" - else - warn "$check_3_25" - warn " * Wrong ownership for $file" - fi -else - info "$check_3_25" - info " * File not found" -fi - -# 3.26 -check_3_26="3.26 - Verify that Docker socket file permissions are set to 660" -file="/var/run/docker.sock" -if [ -f "$file" ]; then - perms=$(ls -ld "$file" | awk '{print $1}') - if [ "$perms" = "srw-rw----" ]; then - pass "$check_3_26" - else - warn "$check_3_26" - warn " * Wrong permissions for $file" - fi -else - info "$check_3_26" - info " * File not found" -fi +check_3_end() { + endsectionjson +} diff --git a/tests/4_container_images.sh b/tests/4_container_images.sh index 6cf9f66..2e3ce69 100644 --- a/tests/4_container_images.sh +++ b/tests/4_container_images.sh @@ -1,39 +1,266 @@ #!/bin/sh -logit "\n" -info "4 - Container Images and Build Files" +check_4() { + logit "\n" + id_4="4" + desc_4="Container Images and Build File" + check_4="$id_4 - $desc_4" + info "$check_4" + startsectionjson "$id_4" "$desc_4" +} # 4.1 -check_4_1="4.1 - Create a user for the container" +check_4_1() { + id_4_1="4.1" + desc_4_1="Ensure that a user for the container has been created (Scored)" + check_4_1="$id_4_1 - $desc_4_1" + starttestjson "$id_4_1" "$desc_4_1" -# If container_users is empty, there are no running containers -if [ -z "$containers" ]; then - info "$check_4_1" - info " * No containers running" -else - # We have some containers running, set failure flag to 0. Check for Users. + totalChecks=$((totalChecks + 1)) + + # If container_users is empty, there are no running containers + if [ -z "$containers" ]; then + info "$check_4_1" + info " * No containers running" + resulttestjson "INFO" "No containers running" + currentScore=$((currentScore + 0)) + else + # We have some containers running, set failure flag to 0. Check for Users. + fail=0 + # Make the loop separator be a new-line in POSIX compliant fashion + set -f; IFS=$' + ' + root_containers="" + for c in $containers; do + user=$(docker inspect --format 'User={{.Config.User}}' "$c") + + if [ "$user" = "User=0" ] || [ "$user" = "User=root" ] || [ "$user" = "User=" ] || [ "$user" = "User=[]" ] || [ "$user" = "User=<no value>" ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_4_1" + warn " * Running as root: $c" + root_containers="$root_containers $c" + fail=1 + else + warn " * Running as root: $c" + root_containers="$root_containers $c" + fi + fi + done + # We went through all the containers and found none running as root + if [ $fail -eq 0 ]; then + pass "$check_4_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "running as root" "$root_containers" + currentScore=$((currentScore - 1)) + fi + fi + # Make the loop separator go back to space + set +f; unset IFS +} + +# 4.2 +check_4_2() { + id_4_2="4.2" + desc_4_2="Ensure that containers use only trusted base images (Not Scored)" + check_4_2="$id_4_2 - $desc_4_2" + starttestjson "$id_4_2" "$desc_4_2" + + totalChecks=$((totalChecks + 1)) + note "$check_4_2" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 4.3 +check_4_3() { + id_4_3="4.3" + desc_4_3="Ensure that unnecessary packages are not installed in the container (Not Scored)" + check_4_3="$id_4_3 - $desc_4_3" + starttestjson "$id_4_3" "$desc_4_3" + + totalChecks=$((totalChecks + 1)) + note "$check_4_3" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 4.4 +check_4_4() { + id_4_4="4.4" + desc_4_4="Ensure images are scanned and rebuilt to include security patches (Not Scored)" + check_4_4="$id_4_4 - $desc_4_4" + starttestjson "$id_4_4" "$desc_4_4" + + totalChecks=$((totalChecks + 1)) + note "$check_4_4" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 4.5 +check_4_5() { + id_4_5="4.5" + desc_4_5="Ensure Content trust for Docker is Enabled (Scored)" + check_4_5="$id_4_5 - $desc_4_5" + starttestjson "$id_4_5" "$desc_4_5" + + totalChecks=$((totalChecks + 1)) + if [ "x$DOCKER_CONTENT_TRUST" = "x1" ]; then + pass "$check_4_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_4_5" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} + +# 4.6 +check_4_6() { + id_4_6="4.6" + desc_4_6="Ensure that HEALTHCHECK instructions have been added to container images (Scored)" + check_4_6="$id_4_6 - $desc_4_6" + starttestjson "$id_4_6" "$desc_4_6" + + totalChecks=$((totalChecks + 1)) fail=0 - # Make the loop separator be a new-line in POSIX compliant fashion - set -f; IFS=$' -' - for c in $containers; do - user=$(docker inspect --format 'User={{.Config.User}}' "$c") - - if [ "$user" = "User=" -o "$user" = "User=[]" -o "$user" = "User=<no value>" ]; then - # If it's the first container, fail the test + no_health_images="" + for img in $images; do + if docker inspect --format='{{.Config.Healthcheck}}' "$img" 2>/dev/null | grep -e "<nil>" >/dev/null 2>&1; then if [ $fail -eq 0 ]; then - warn "$check_4_1" - warn " * Running as root: $c" fail=1 + warn "$check_4_6" + fi + imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) + if ! [ "$imgName" = '[]' ]; then + warn " * No Healthcheck found: $imgName" + no_health_images="$no_health_images $imgName" else - warn " * Running as root: $c" + warn " * No Healthcheck found: $img" + no_health_images="$no_health_images $img" fi fi done - # We went through all the containers and found none running as root if [ $fail -eq 0 ]; then - pass "$check_4_1" + pass "$check_4_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Images w/o HEALTHCHECK" "$no_health_images" + currentScore=$((currentScore - 1)) fi -fi -# Make the loop separator go back to space -set +f; unset IFS +} + +# 4.7 +check_4_7() { + id_4_7="4.7" + desc_4_7="Ensure update instructions are not use alone in the Dockerfile (Not Scored)" + check_4_7="$id_4_7 - $desc_4_7" + starttestjson "$id_4_7" "$desc_4_7" + + totalChecks=$((totalChecks + 1)) + fail=0 + update_images="" + for img in $images; do + if docker history "$img" 2>/dev/null | grep -e "update" >/dev/null 2>&1; then + if [ $fail -eq 0 ]; then + fail=1 + info "$check_4_7" + fi + imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) + if ! [ "$imgName" = '[]' ]; then + info " * Update instruction found: $imgName" + update_images="$update_images $imgName" + fi + fi + done + if [ $fail -eq 0 ]; then + pass "$check_4_7" + resulttestjson "PASS" + currentScore=$((currentScore + 0)) + else + resulttestjson "INFO" "Update instructions found" "$update_images" + currentScore=$((currentScore + 0)) + fi +} + +# 4.8 +check_4_8() { + id_4_8="4.8" + desc_4_8="Ensure setuid and setgid permissions are removed (Not Scored)" + check_4_8="$id_4_8 - $desc_4_8" + starttestjson "$id_4_8" "$desc_4_8" + + totalChecks=$((totalChecks + 1)) + note "$check_4_8" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 4.9 +check_4_9() { + id_4_9="4.9" + desc_4_9="Ensure that COPY is used instead of ADD in Dockerfiles (Not Scored)" + check_4_9="$id_4_9 - $desc_4_9" + starttestjson "$id_4_9" "$desc_4_9" + + totalChecks=$((totalChecks + 1)) + fail=0 + add_images="" + for img in $images; do + if docker history --format "{{ .CreatedBy }}" --no-trunc "$img" | \ + sed '$d' | grep -q 'ADD'; then + if [ $fail -eq 0 ]; then + fail=1 + info "$check_4_9" + fi + imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) + if ! [ "$imgName" = '[]' ]; then + info " * ADD in image history: $imgName" + add_images="$add_images $imgName" + fi + currentScore=$((currentScore + 0)) + fi + done + if [ $fail -eq 0 ]; then + pass "$check_4_9" + resulttestjson "PASS" + currentScore=$((currentScore + 0)) + else + resulttestjson "INFO" "Images using ADD" "$add_images" + fi +} + +# 4.10 +check_4_10() { + id_4_10="4.10" + desc_4_10="Ensure secrets are not stored in Dockerfiles (Not Scored)" + check_4_10="$id_4_10 - $desc_4_10" + starttestjson "$id_4_10" "$desc_4_10" + + totalChecks=$((totalChecks + 1)) + note "$check_4_10" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 4.11 +check_4_11() { + id_4_11="4.11" + desc_4_11="Ensure only verified packages are are installed (Not Scored)" + check_4_11="$id_4_11 - $desc_4_11" + starttestjson "$id_4_11" "$desc_4_11" + + totalChecks=$((totalChecks + 1)) + note "$check_4_11" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +check_4_end() { + endsectionjson +} diff --git a/tests/5_container_runtime.sh b/tests/5_container_runtime.sh index 7133b9e..a42be93 100644 --- a/tests/5_container_runtime.sh +++ b/tests/5_container_runtime.sh @@ -1,142 +1,215 @@ #!/bin/sh -logit "\n" -info "5 - Container Runtime" +check_5() { + logit "\n" + id_5="5" + desc_5="Container Runtime" + check_5="$id_5 - $desc_5" + info "$check_5" + startsectionjson "$id_5" "$desc_5" +} -# If containers is empty, there are no running containers -if [ -z "$containers" ]; then - info " * No containers running, skipping Section 5" -else - # Make the loop separator be a new-line in POSIX compliant fashion - set -f; IFS=$' -' - # 5.1 - check_5_1="5.1 - Verify AppArmor Profile, if applicable" +check_running_containers() { + # If containers is empty, there are no running containers + if [ -z "$containers" ]; then + info " * No containers running, skipping Section 5" + running_containers=0 + else + running_containers=1 + # Make the loop separator be a new-line in POSIX compliant fashion + set -f; IFS=$' + ' + fi +} + +# 5.1 +check_5_1() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_1="5.1" + desc_5_1="Ensure that, if applicable, an AppArmor Profile is enabled (Scored)" + check_5_1="$id_5_1 - $desc_5_1" + starttestjson "$id_5_1" "$desc_5_1" + + totalChecks=$((totalChecks + 1)) fail=0 + no_apparmor_containers="" for c in $containers; do policy=$(docker inspect --format 'AppArmorProfile={{ .AppArmorProfile }}' "$c") - if [ "$policy" = "AppArmorProfile=" -o "$policy" = "AppArmorProfile=[]" -o "$policy" = "AppArmorProfile=<no value>" ]; then + if [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=[]" ] || [ "$policy" = "AppArmorProfile=<no value>" ] || [ "$policy" = "AppArmorProfile=unconfined" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then warn "$check_5_1" warn " * No AppArmorProfile Found: $c" + no_apparmor_containers="$no_apparmor_containers $c" fail=1 else warn " * No AppArmorProfile Found: $c" + no_apparmor_containers="$no_apparmor_containers $c" fi fi done # We went through all the containers and found none without AppArmor if [ $fail -eq 0 ]; then pass "$check_5_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with no AppArmorProfile" "$no_apparmor_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.2 +check_5_2() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.2 - check_5_2="5.2 - Verify SELinux security options, if applicable" + id_5_2="5.2" + desc_5_2="Ensure that, if applicable, SELinux security options are set (Scored)" + check_5_2="$id_5_2 - $desc_5_2" + starttestjson "$id_5_2" "$desc_5_2" + + totalChecks=$((totalChecks + 1)) fail=0 + no_securityoptions_containers="" for c in $containers; do policy=$(docker inspect --format 'SecurityOpt={{ .HostConfig.SecurityOpt }}' "$c") - if [ "$policy" = "SecurityOpt=" -o "$policy" = "SecurityOpt=[]" -o "$policy" = "SecurityOpt=<no value>" ]; then + if [ "$policy" = "SecurityOpt=" ] || [ "$policy" = "SecurityOpt=[]" ] || [ "$policy" = "SecurityOpt=<no value>" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then warn "$check_5_2" warn " * No SecurityOptions Found: $c" + no_securityoptions_containers="$no_securityoptions_containers $c" fail=1 else warn " * No SecurityOptions Found: $c" + no_securityoptions_containers="$no_securityoptions_containers $c" fi fi done # We went through all the containers and found none without SELinux if [ $fail -eq 0 ]; then pass "$check_5_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with no SecurityOptions" "$no_securityoptions_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.3 +check_5_3() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.3 - check_5_3="5.3 - Verify that containers are running only a single main process" + id_5_3="5.3" + desc_5_3="Ensure that Linux kernel capabilities are restricted within containers (Scored)" + check_5_3="$id_5_3 - $desc_5_3" + starttestjson "$id_5_3" "$desc_5_3" + + totalChecks=$((totalChecks + 1)) fail=0 + caps_containers="" for c in $containers; do - exec_check=$(docker exec "$c" ps -el 2>/dev/null) - if [ $? -eq 255 ]; then - warn "$check_5_3" - warn " * Docker exec fails: $c" - fail=1 - fi + container_caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd}}' "$c") + caps=$(echo "$container_caps" | tr "[:lower:]" "[:upper:]" | \ + sed 's/CAPADD/CapAdd/' | \ + 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") - processes=$(docker exec "$c" ps -el 2>/dev/null | wc -l | awk '{print $1}') - if [ "$processes" -gt 5 ]; then + if [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=[]' ] && [ "$caps" != 'CapAdd=<no value>' ] && [ "$caps" != 'CapAdd=<nil>' ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then warn "$check_5_3" - warn " * Too many proccesses running: $c" - fail=1 - else - warn " * Too many proccesses running: $c" - fi - fi - done - # We went through all the containers and found none with toom any processes - if [ $fail -eq 0 ]; then - pass "$check_5_3" - fi - - # 5.4 - check_5_4="5.4 - Restrict Linux Kernel Capabilities within containers" - - fail=0 - for c in $containers; do - caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd}}' "$c") - - if [ "$caps" != "CapAdd=" -a "$caps" != "CapAdd=[]" -a "$caps" != "CapAdd=<no value>" ]; then - # If it's the first container, fail the test - if [ $fail -eq 0 ]; then - warn "$check_5_4" warn " * Capabilities added: $caps to $c" + caps_containers="$caps_containers $c" fail=1 else warn " * Capabilities added: $caps to $c" + caps_containers="$caps_containers $c" fi fi done # We went through all the containers and found none with extra capabilities if [ $fail -eq 0 ]; then - pass "$check_5_4" + pass "$check_5_3" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Capabilities added for containers" "$caps_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.4 +check_5_4() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.5 - check_5_5="5.5 - Do not use privileged containers" + id_5_4="5.4" + desc_5_4="Ensure that privileged containers are not used (Scored)" + check_5_4="$id_5_4 - $desc_5_4" + starttestjson "$id_5_4" "$desc_5_4" + + totalChecks=$((totalChecks + 1)) fail=0 + privileged_containers="" for c in $containers; do privileged=$(docker inspect --format '{{ .HostConfig.Privileged }}' "$c") if [ "$privileged" = "true" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_5" + warn "$check_5_4" warn " * Container running in Privileged mode: $c" + privileged_containers="$privileged_containers $c" fail=1 else warn " * Container running in Privileged mode: $c" + privileged_containers="$privileged_containers $c" fi fi done # We went through all the containers and found no privileged containers if [ $fail -eq 0 ]; then - pass "$check_5_5" + pass "$check_5_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers running in privileged mode" "$privileged_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.5 +check_5_5() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.6 - check_5_6="5.6 - Do not mount sensitive host system directories on containers" + id_5_5="5.5" + desc_5_5="Ensure sensitive host system directories are not mounted on containers (Scored)" + check_5_5="$id_5_5 - $desc_5_5" + starttestjson "$id_5_5" "$desc_5_5" + + totalChecks=$((totalChecks + 1)) # List of sensitive directories to test for. Script uses new-lines as a separator. # Note the lack of identation. It needs it for the substring comparison. - sensitive_dirs='/boot + sensitive_dirs='/ +/boot /dev /etc /lib @@ -144,311 +217,1060 @@ else /sys /usr' fail=0 + sensitive_mount_containers="" for c in $containers; do - volumes=$(docker inspect --format '{{ .VolumesRW }}' "$c") + if docker inspect --format '{{ .VolumesRW }}' "$c" 2>/dev/null 1>&2; then + volumes=$(docker inspect --format '{{ .VolumesRW }}' "$c") + else + volumes=$(docker inspect --format '{{ .Mounts }}' "$c") + fi # Go over each directory in sensitive dir and see if they exist in the volumes for v in $sensitive_dirs; do sensitive=0 - contains "$volumes" "$v:" && sensitive=1 + if echo "$volumes" | grep -e "{.*\s$v\s.*true\s.*}" 2>/tmp/null 1>&2; then + sensitive=1 + fi if [ $sensitive -eq 1 ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_6" + warn "$check_5_5" warn " * Sensitive directory $v mounted in: $c" + sensitive_mount_containers="$sensitive_mount_containers $c:$v" fail=1 else warn " * Sensitive directory $v mounted in: $c" + sensitive_mount_containers="$sensitive_mount_containers $c:$v" fi fi done done # We went through all the containers and found none with sensitive mounts if [ $fail -eq 0 ]; then - pass "$check_5_6" + pass "$check_5_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with sensitive directories mounted" "$sensitive_mount_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.6 +check_5_6() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.7 - check_5_7="5.7 - Do not run ssh within containers" + id_5_6="5.6" + desc_5_6="Ensure sshd is not run within containers (Scored)" + check_5_6="$id_5_6 - $desc_5_6" + starttestjson "$id_5_6" "$desc_5_6" + + totalChecks=$((totalChecks + 1)) fail=0 + ssh_exec_containers="" + printcheck=0 for c in $containers; do + + processes=$(docker exec "$c" ps -el 2>/dev/null | grep -c sshd | awk '{print $1}') + if [ "$processes" -ge 1 ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_6" + warn " * Container running sshd: $c" + ssh_exec_containers="$ssh_exec_containers $c" + fail=1 + printcheck=1 + else + warn " * Container running sshd: $c" + ssh_exec_containers="$ssh_exec_containers $c" + fi + fi + exec_check=$(docker exec "$c" ps -el 2>/dev/null) if [ $? -eq 255 ]; then - warn "$check_5_7" + if [ $printcheck -eq 0 ]; then + warn "$check_5_6" + printcheck=1 + fi warn " * Docker exec fails: $c" + ssh_exec_containers="$ssh_exec_containers $c" fail=1 fi - processes=$(docker exec "$c" ps -el 2>/dev/null | grep -c sshd | awk '{print $1}') - if [ $processes -gt 1 ]; then - # If it's the first container, fail the test - if [ $fail -eq 0 ]; then - warn "$check_5_7" - warn " * Container running sshd: $c" - fail=1 - else - warn " * Container running sshd: $c" - fi - fi done # We went through all the containers and found none with sshd if [ $fail -eq 0 ]; then - pass "$check_5_7" + pass "$check_5_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with sshd/docker exec failures" "$ssh_exec_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.7 +check_5_7() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.8 - check_5_8="5.8 - Do not map privileged ports within containers" + id_5_7="5.7" + desc_5_7="Ensure privileged ports are not mapped within containers (Scored)" + check_5_7="$id_5_7 - $desc_5_7" + starttestjson "$id_5_7" "$desc_5_7" + + totalChecks=$((totalChecks + 1)) fail=0 + privileged_port_containers="" for c in $containers; do - ports=$(docker port "$c" | awk '{print $1}' | cut -d '/' -f1) + # Port format is private port -> ip: public port + ports=$(docker port "$c" | awk '{print $0}' | cut -d ':' -f2) # iterate through port range (line delimited) for port in $ports; do - if [ ! -z "$port" ] && [ "0$port" -lt 1025 ]; then + if [ -n "$port" ] && [ "$port" -lt 1024 ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_8" + warn "$check_5_7" warn " * Privileged Port in use: $port in $c" + privileged_port_containers="$privileged_port_containers $c:$port" fail=1 else warn " * Privileged Port in use: $port in $c" + privileged_port_containers="$privileged_port_containers $c:$port" fi fi done done # We went through all the containers and found no privileged ports if [ $fail -eq 0 ]; then - pass "$check_5_8" + pass "$check_5_7" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers using privileged ports" "$privileged_port_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.8 +check_5_8() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.10 - check_5_10="5.10 - Do not use host network mode on container" + id_5_8="5.8" + desc_5_8="Ensure that only needed ports are open on the container (Not Scored)" + check_5_8="$id_5_8 - $desc_5_8" + starttestjson "$id_5_8" "$desc_5_8" + + totalChecks=$((totalChecks + 1)) + note "$check_5_8" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 5.9 +check_5_9() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_9="5.9" + desc_5_9="Ensure that the host's network namespace is not shared (Scored)" + check_5_9="$id_5_9 - $desc_5_9" + starttestjson "$id_5_9" "$desc_5_9" + + totalChecks=$((totalChecks + 1)) fail=0 + net_host_containers="" for c in $containers; do mode=$(docker inspect --format 'NetworkMode={{ .HostConfig.NetworkMode }}' "$c") if [ "$mode" = "NetworkMode=host" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_10" + warn "$check_5_9" warn " * Container running with networking mode 'host': $c" + net_host_containers="$net_host_containers $c" fail=1 else warn " * Container running with networking mode 'host': $c" + net_host_containers="$net_host_containers $c" fi fi done # We went through all the containers and found no Network Mode host if [ $fail -eq 0 ]; then - pass "$check_5_10" + pass "$check_5_9" + resulttestjson "PASS" + currentScore=$((currentScore + 0)) + else + resulttestjson "WARN" "Containers running with networking mode 'host'" "$net_host_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.10 +check_5_10() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.11 - check_5_11="5.11 - Limit memory usage for container" + id_5_10="5.10" + desc_5_10="Ensure that the memory usage for containers is limited (Scored)" + check_5_10="$id_5_10 - $desc_5_10" + starttestjson "$id_5_10" "$desc_5_10" + + totalChecks=$((totalChecks + 1)) fail=0 + mem_unlimited_containers="" for c in $containers; do - memory=$(docker inspect --format '{{ .Config.Memory }}' "$c") + if docker inspect --format '{{ .Config.Memory }}' "$c" 2> /dev/null 1>&2; then + memory=$(docker inspect --format '{{ .Config.Memory }}' "$c") + else + memory=$(docker inspect --format '{{ .HostConfig.Memory }}' "$c") + fi if [ "$memory" = "0" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_11" + warn "$check_5_10" warn " * Container running without memory restrictions: $c" + mem_unlimited_containers="$mem_unlimited_containers $c" fail=1 else warn " * Container running without memory restrictions: $c" + mem_unlimited_containers="$mem_unlimited_containers $c" fi fi done # We went through all the containers and found no lack of Memory restrictions if [ $fail -eq 0 ]; then - pass "$check_5_11" + pass "$check_5_10" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Container running without memory restrictions" "$mem_unlimited_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.11 +check_5_11() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.12 - check_5_12="5.12 - Set container CPU priority appropriately" + id_5_11="5.11" + desc_5_11="Ensure that CPU priority is set appropriately on containers (Scored)" + check_5_11="$id_5_11 - $desc_5_11" + starttestjson "$id_5_11" "$desc_5_11" + + totalChecks=$((totalChecks + 1)) fail=0 + cpu_unlimited_containers="" for c in $containers; do - shares=$(docker inspect --format '{{ .Config.CpuShares }}' "$c") + if docker inspect --format '{{ .Config.CpuShares }}' "$c" 2> /dev/null 1>&2; then + shares=$(docker inspect --format '{{ .Config.CpuShares }}' "$c") + else + shares=$(docker inspect --format '{{ .HostConfig.CpuShares }}' "$c") + fi if [ "$shares" = "0" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_12" + warn "$check_5_11" warn " * Container running without CPU restrictions: $c" + cpu_unlimited_containers="$cpu_unlimited_containers $c" fail=1 else warn " * Container running without CPU restrictions: $c" + cpu_unlimited_containers="$cpu_unlimited_containers $c" fi fi done # We went through all the containers and found no lack of CPUShare restrictions if [ $fail -eq 0 ]; then - pass "$check_5_12" + pass "$check_5_11" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers running without CPU restrictions" "$cpu_unlimited_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.12 +check_5_12() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.13 - check_5_13="5.13 - Mount container's root filesystem as read only" + id_5_12="5.12" + desc_5_12="Ensure that the container's root filesystem is mounted as read only (Scored)" + check_5_12="$id_5_12 - $desc_5_12" + starttestjson "$id_5_12" "$desc_5_12" + + totalChecks=$((totalChecks + 1)) fail=0 + fsroot_mount_containers="" for c in $containers; do read_status=$(docker inspect --format '{{ .HostConfig.ReadonlyRootfs }}' "$c") if [ "$read_status" = "false" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_13" + warn "$check_5_12" warn " * Container running with root FS mounted R/W: $c" + fsroot_mount_containers="$fsroot_mount_containers $c" fail=1 else warn " * Container running with root FS mounted R/W: $c" + fsroot_mount_containers="$fsroot_mount_containers $c" fi fi done # We went through all the containers and found no R/W FS mounts if [ $fail -eq 0 ]; then - pass "$check_5_13" + pass "$check_5_12" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers running with root FS mounted R/W" "$fsroot_mount_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.13 +check_5_13() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.14 - check_5_14="5.14 - Bind incoming container traffic to a specific host interface" + id_5_13="5.13" + desc_5_13="Ensure that incoming container traffic is bound to a specific host interface (Scored)" + check_5_13="$id_5_13 - $desc_5_13" + starttestjson "$id_5_13" "$desc_5_13" + + totalChecks=$((totalChecks + 1)) fail=0 + incoming_unbound_containers="" for c in $containers; do - ip=$(docker port "$c" | awk '{print $3}' | cut -d ':' -f1) - if [ "$ip" = "0.0.0.0" ]; then - # If it's the first container, fail the test - if [ $fail -eq 0 ]; then - warn "$check_5_14" - warn " * Port being bound to wildcard IP: $ip in $c" - fail=1 - else - warn " * Port being bound to wildcard IP: $ip in $c" + for ip in $(docker port "$c" | awk '{print $3}' | cut -d ':' -f1); do + if [ "$ip" = "0.0.0.0" ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_13" + warn " * Port being bound to wildcard IP: $ip in $c" + incoming_unbound_containers="$incoming_unbound_containers $c:$ip" + fail=1 + else + warn " * Port being bound to wildcard IP: $ip in $c" + incoming_unbound_containers="$incoming_unbound_containers $c:$ip" + fi fi - fi + done done # We went through all the containers and found no ports bound to 0.0.0.0 if [ $fail -eq 0 ]; then - pass "$check_5_14" + pass "$check_5_13" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with port bound to wildcard IP" "$incoming_unbound_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.14 +check_5_14() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.15 - check_5_15="5.15 - Do not set the 'on-failure' container restart policy to always" + id_5_14="5.14" + desc_5_14="Ensure that the 'on-failure' container restart policy is set to '5' (Scored)" + check_5_14="$id_5_14 - $desc_5_14" + starttestjson "$id_5_14" "$desc_5_14" + + totalChecks=$((totalChecks + 1)) fail=0 + maxretry_unset_containers="" for c in $containers; do - policy=$(docker inspect --format 'RestartPolicyName={{ .HostConfig.RestartPolicy.Name }}' "$c") + policy=$(docker inspect --format MaximumRetryCount='{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' "$c") - if [ "$policy" = "RestartPolicyName=always" ]; then + if [ "$policy" != "MaximumRetryCount=5" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_15" - warn " * Restart Policy set to always: $c" + warn "$check_5_14" + warn " * MaximumRetryCount is not set to 5: $c" + maxretry_unset_containers="$maxretry_unset_containers $c" fail=1 else - warn " * Restart Policy set to always: $c" + warn " * MaximumRetryCount is not set to 5: $c" + maxretry_unset_containers="$maxretry_unset_containers $c" fi fi done - # We went through all the containers and found none with restart policy always + # We went through all the containers and they all had MaximumRetryCount=5 if [ $fail -eq 0 ]; then - pass "$check_5_15" + pass "$check_5_14" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with MaximumRetryCount not set to 5" "$maxretry_unset_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.15 +check_5_15() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.16 - check_5_16="5.16 - Do not share the host's process namespace" + id_5_15="5.15" + desc_5_15="Ensure that the host's process namespace is not shared (Scored)" + check_5_15="$id_5_15 - $desc_5_15" + starttestjson "$id_5_15" "$desc_5_15" + + totalChecks=$((totalChecks + 1)) fail=0 + pidns_shared_containers="" for c in $containers; do mode=$(docker inspect --format 'PidMode={{.HostConfig.PidMode }}' "$c") if [ "$mode" = "PidMode=host" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_16" + warn "$check_5_15" warn " * Host PID namespace being shared with: $c" + pidns_shared_containers="$pidns_shared_containers $c" fail=1 else warn " * Host PID namespace being shared with: $c" + pidns_shared_containers="$pidns_shared_containers $c" fi fi done # We went through all the containers and found none with PidMode as host if [ $fail -eq 0 ]; then - pass "$check_5_16" + pass "$check_5_15" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers sharing host PID namespace" "$pidns_shared_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.16 +check_5_16() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.17 - check_5_17="5.17 - Do not share the host's IPC namespace" + id_5_16="5.16" + desc_5_16="Ensure that the host's IPC namespace is not shared (Scored)" + check_5_16="$id_5_16 - $desc_5_16" + starttestjson "$id_5_16" "$desc_5_16" + + totalChecks=$((totalChecks + 1)) fail=0 + ipcns_shared_containers="" for c in $containers; do mode=$(docker inspect --format 'IpcMode={{.HostConfig.IpcMode }}' "$c") if [ "$mode" = "IpcMode=host" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_17" + warn "$check_5_16" warn " * Host IPC namespace being shared with: $c" + ipcns_shared_containers="$ipcns_shared_containers $c" fail=1 else warn " * Host IPC namespace being shared with: $c" + ipcns_shared_containers="$ipcns_shared_containers $c" fi fi done # We went through all the containers and found none with IPCMode as host if [ $fail -eq 0 ]; then - pass "$check_5_17" + pass "$check_5_16" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers sharing host IPC namespace" "$ipcns_shared_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.17 +check_5_17() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.18 - check_5_18="5.18 - Do not directly expose host devices to containers" + id_5_17="5.17" + desc_5_17="Ensure that host devices are not directly exposed to containers (Not Scored)" + check_5_17="$id_5_17 - $desc_5_17" + starttestjson "$id_5_17" "$desc_5_17" + + totalChecks=$((totalChecks + 1)) fail=0 + hostdev_exposed_containers="" for c in $containers; do devices=$(docker inspect --format 'Devices={{ .HostConfig.Devices }}' "$c") - if [ "$devices" != "Devices=" -a "$devices" != "Devices=[]" -a "$devices" != "Devices=<no value>" ]; then + if [ "$devices" != "Devices=" ] && [ "$devices" != "Devices=[]" ] && [ "$devices" != "Devices=<no value>" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - info "$check_5_18" + info "$check_5_17" info " * Container has devices exposed directly: $c" + hostdev_exposed_containers="$hostdev_exposed_containers $c" fail=1 else info " * Container has devices exposed directly: $c" + hostdev_exposed_containers="$hostdev_exposed_containers $c" fi fi done # We went through all the containers and found none with devices if [ $fail -eq 0 ]; then - pass "$check_5_18" + pass "$check_5_17" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "INFO" "Containers with host devices exposed directly" "$hostdev_exposed_containers" + currentScore=$((currentScore + 0)) + fi +} + +# 5.18 +check_5_18() { + if [ "$running_containers" -ne 1 ]; then + return fi - # 5.19 - check_5_19="5.19 - Override default ulimit at runtime only if needed" + id_5_18="5.18" + desc_5_18="Ensure that the default ulimit is overwritten at runtime if needed (Not Scored)" + check_5_18="$id_5_18 - $desc_5_18" + starttestjson "$id_5_18" "$desc_5_18" + + totalChecks=$((totalChecks + 1)) - # List all the running containers, ouput their ID and host devices fail=0 + no_ulimit_containers="" for c in $containers; do ulimits=$(docker inspect --format 'Ulimits={{ .HostConfig.Ulimits }}' "$c") - if [ "$ulimits" = "Ulimits=" -o "$ulimits" = "Ulimits=[]" -o "$ulimits" = "Ulimits=<no value>" ]; then + if [ "$ulimits" = "Ulimits=" ] || [ "$ulimits" = "Ulimits=[]" ] || [ "$ulimits" = "Ulimits=<no value>" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - info "$check_5_19" + info "$check_5_18" info " * Container no default ulimit override: $c" + no_ulimit_containers="$no_ulimit_containers $c" fail=1 else info " * Container no default ulimit override: $c" + no_ulimit_containers="$no_ulimit_containers $c" fi fi done # We went through all the containers and found none without Ulimits if [ $fail -eq 0 ]; then - pass "$check_5_19" + pass "$check_5_18" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "INFO" "Containers with no default ulimit override" "$no_ulimit_containers" + currentScore=$((currentScore + 0)) fi -fi +} + +# 5.19 +check_5_19() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_19="5.19" + desc_5_19="Ensure mount propagation mode is not set to shared (Scored)" + check_5_19="$id_5_19 - $desc_5_19" + starttestjson "$id_5_19" "$desc_5_19" + + totalChecks=$((totalChecks + 1)) + + fail=0 + mountprop_shared_containers="" + for c in $containers; do + if docker inspect --format 'Propagation={{range $mnt := .Mounts}} {{json $mnt.Propagation}} {{end}}' "$c" | \ + grep shared 2>/dev/null 1>&2; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_19" + warn " * Mount propagation mode is shared: $c" + mountprop_shared_containers="$mountprop_shared_containers $c" + fail=1 + else + warn " * Mount propagation mode is shared: $c" + mountprop_shared_containers="$mountprop_shared_containers $c" + fi + fi + done + # We went through all the containers and found none with shared propagation mode + if [ $fail -eq 0 ]; then + pass "$check_5_19" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with shared mount propagation" "$mountprop_shared_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.20 +check_5_20() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_20="5.20" + desc_5_20="Ensure that the host's UTS namespace is not shared (Scored)" + check_5_20="$id_5_20 - $desc_5_20" + starttestjson "$id_5_20" "$desc_5_20" + + totalChecks=$((totalChecks + 1)) + + fail=0 + utcns_shared_containers="" + for c in $containers; do + mode=$(docker inspect --format 'UTSMode={{.HostConfig.UTSMode }}' "$c") + + if [ "$mode" = "UTSMode=host" ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_20" + warn " * Host UTS namespace being shared with: $c" + utcns_shared_containers="$utcns_shared_containers $c" + fail=1 + else + warn " * Host UTS namespace being shared with: $c" + utcns_shared_containers="$utcns_shared_containers $c" + fi + fi + done + # We went through all the containers and found none with UTSMode as host + if [ $fail -eq 0 ]; then + pass "$check_5_20" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers sharing host UTS namespace" "$utcns_shared_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.21 +check_5_21() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_21="5.21" + desc_5_21="Ensurethe default seccomp profile is not Disabled (Scored)" + check_5_21="$id_5_21 - $desc_5_21" + starttestjson "$id_5_21" "$desc_5_21" + + totalChecks=$((totalChecks + 1)) + + fail=0 + seccomp_disabled_containers="" + for c in $containers; do + if docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "$c" | \ + grep -E 'seccomp:unconfined|seccomp=unconfined' 2>/dev/null 1>&2; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_21" + warn " * Default seccomp profile disabled: $c" + seccomp_disabled_containers="$seccomp_disabled_containers $c" + fail=1 + else + warn " * Default seccomp profile disabled: $c" + seccomp_disabled_containers="$seccomp_disabled_containers $c" + fi + fi + done + # We went through all the containers and found none with default secomp profile disabled + if [ $fail -eq 0 ]; then + pass "$check_5_21" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers with default seccomp profile disabled" "$seccomp_disabled_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.22 +check_5_22() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_22="5.22" + desc_5_22="Ensure that docker exec commands are not used with the privileged option (Scored)" + check_5_22="$id_5_22 - $desc_5_22" + starttestjson "$id_5_22" "$desc_5_22" + + totalChecks=$((totalChecks + 1)) + note "$check_5_22" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 5.23 +check_5_23() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_23="5.23" + desc_5_23="Ensure that docker exec commands are not used with the user=root option (Not Scored)" + check_5_23="$id_5_23 - $desc_5_23" + starttestjson "$id_5_23" "$desc_5_23" + + totalChecks=$((totalChecks + 1)) + note "$check_5_23" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) +} + +# 5.24 +check_5_24() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_24="5.24" + desc_5_24="Ensure that cgroup usage is confirmed (Scored)" + check_5_24="$id_5_24 - $desc_5_24" + starttestjson "$id_5_24" "$desc_5_24" + + totalChecks=$((totalChecks + 1)) + + fail=0 + unexpected_cgroup_containers="" + for c in $containers; do + mode=$(docker inspect --format 'CgroupParent={{.HostConfig.CgroupParent }}x' "$c") + + if [ "$mode" != "CgroupParent=x" ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_24" + warn " * Confirm cgroup usage: $c" + unexpected_cgroup_containers="$unexpected_cgroup_containers $c" + fail=1 + else + warn " * Confirm cgroup usage: $c" + unexpected_cgroup_containers="$unexpected_cgroup_containers $c" + fi + fi + done + # We went through all the containers and found none with UTSMode as host + if [ $fail -eq 0 ]; then + pass "$check_5_24" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers using unexpected cgroup" "$unexpected_cgroup_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.25 +check_5_25() { + if [ "$running_containers" -ne 1 ]; then + return + fi + id_5_25="5.25" + desc_5_25="Ensure that the container is restricted from acquiring additional privileges (Scored)" + check_5_25="$id_5_25 - $desc_5_25" + starttestjson "$id_5_25" "$desc_5_25" + + totalChecks=$((totalChecks + 1)) + + fail=0 + addprivs_containers="" + for c in $containers; do + if ! docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "$c" | grep 'no-new-privileges' 2>/dev/null 1>&2; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_25" + warn " * Privileges not restricted: $c" + addprivs_containers="$addprivs_containers $c" + fail=1 + else + warn " * Privileges not restricted: $c" + addprivs_containers="$addprivs_containers $c" + fi + fi + done + # We went through all the containers and found none with capability to acquire additional privileges + if [ $fail -eq 0 ]; then + pass "$check_5_25" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers without restricted privileges" "$addprivs_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.26 +check_5_26() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_26="5.26" + desc_5_26="Ensure that container health is checked at runtime (Scored)" + check_5_26="$id_5_26 - $desc_5_26" + starttestjson "$id_5_26" "$desc_5_26" + + totalChecks=$((totalChecks + 1)) + + fail=0 + nohealthcheck_containers="" + for c in $containers; do + if ! docker inspect --format '{{ .Id }}: Health={{ .State.Health.Status }}' "$c" 2>/dev/null 1>&2; then + if [ $fail -eq 0 ]; then + warn "$check_5_26" + warn " * Health check not set: $c" + nohealthcheck_containers="$nohealthcheck_containers $c" + fail=1 + else + warn " * Health check not set: $c" + nohealthcheck_containers="$nohealthcheck_containers $c" + fi + fi + done + if [ $fail -eq 0 ]; then + pass "$check_5_26" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers without health check" "$nohealthcheck_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.27 +check_5_27() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_27="5.27" + desc_5_27="Ensure that Docker commands always make use of the latest version of their image (Not Scored)" + check_5_27="$id_5_27 - $desc_5_27" + starttestjson "$id_5_27" "$desc_5_27" + + totalChecks=$((totalChecks + 1)) + info "$check_5_27" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 5.28 +check_5_28() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_28="5.28" + desc_5_28="Ensure that the PIDs cgroup limit is used (Scored)" + check_5_28="$id_5_28 - $desc_5_28" + starttestjson "$id_5_28" "$desc_5_28" + + totalChecks=$((totalChecks + 1)) + + fail=0 + nopids_limit_containers="" + for c in $containers; do + pidslimit="$(docker inspect --format '{{.HostConfig.PidsLimit }}' "$c")" + + if [ "$pidslimit" = "0" ] || [ "$pidslimit" = "<nil>" ] || [ "$pidslimit" = "-1" ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_28" + warn " * PIDs limit not set: $c" + nopids_limit_containers="$nopids_limit_containers $c" + fail=1 + else + warn " * PIDs limit not set: $c" + nopids_limit_containers="$nopids_limit_containers $c" + fi + fi + done + # We went through all the containers and found all with PIDs limit + if [ $fail -eq 0 ]; then + pass "$check_5_28" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers without PIDs cgroup limit" "$nopids_limit_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.29 +check_5_29() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_29="5.29" + desc_5_29="Ensure that Docker's default bridge "docker0" is not used (Not Scored)" + check_5_29="$id_5_29 - $desc_5_29" + starttestjson "$id_5_29" "$desc_5_29" + + totalChecks=$((totalChecks + 1)) + + fail=0 + docker_network_containers="" + networks=$(docker network ls -q 2>/dev/null) + for net in $networks; do + if docker network inspect --format '{{ .Options }}' "$net" 2>/dev/null | grep "com.docker.network.bridge.name:docker0" >/dev/null 2>&1; then + docker0Containers=$(docker network inspect --format='{{ range $k, $v := .Containers }} {{ $k }} {{ end }}' "$net" | \ + sed -e 's/^ //' -e 's/ /\n/g' 2>/dev/null) + + if [ -n "$docker0Containers" ]; then + if [ $fail -eq 0 ]; then + info "$check_5_29" + fail=1 + fi + for c in $docker0Containers; do + if [ -z "$exclude" ]; then + cName=$(docker inspect --format '{{.Name}}' "$c" 2>/dev/null | sed 's/\///g') + else + pattern=$(echo "$exclude" | sed 's/,/|/g') + cName=$(docker inspect --format '{{.Name}}' "$c" 2>/dev/null | sed 's/\///g' | grep -Ev "$pattern" ) + fi + if [ -n "$cName" ]; then + info " * Container in docker0 network: $cName" + docker_network_containers="$docker_network_containers $c:$cName" + fi + done + fi + currentScore=$((currentScore + 0)) + fi + done + # We went through all the containers and found none in docker0 network + if [ $fail -eq 0 ]; then + pass "$check_5_29" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "INFO" "Containers using docker0 network" "$docker_network_containers" + currentScore=$((currentScore + 0)) + fi +} + +# 5.30 +check_5_30() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_30="5.30" + desc_5_30="Ensure that the host's user namespaces are not shared (Scored)" + check_5_30="$id_5_30 - $desc_5_30" + starttestjson "$id_5_30" "$desc_5_30" + + totalChecks=$((totalChecks + 1)) + + fail=0 + hostns_shared_containers="" + for c in $containers; do + if docker inspect --format '{{ .HostConfig.UsernsMode }}' "$c" 2>/dev/null | grep -i 'host' >/dev/null 2>&1; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_30" + warn " * Namespace shared: $c" + hostns_shared_containers="$hostns_shared_containers $c" + fail=1 + else + warn " * Namespace shared: $c" + hostns_shared_containers="$hostns_shared_containers $c" + fi + fi + done + # We went through all the containers and found none with host's user namespace shared + if [ $fail -eq 0 ]; then + pass "$check_5_30" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers sharing host user namespace" "$hostns_shared_containers" + currentScore=$((currentScore - 1)) + fi +} + +# 5.31 +check_5_31() { + if [ "$running_containers" -ne 1 ]; then + return + fi + + id_5_31="5.31" + desc_5_31="Ensure that the Docker socket is not mounted inside any containers (Scored)" + check_5_31="$id_5_31 - $desc_5_31" + starttestjson "$id_5_31" "$desc_5_31" + + totalChecks=$((totalChecks + 1)) + + fail=0 + docker_sock_containers="" + for c in $containers; do + if docker inspect --format '{{ .Mounts }}' "$c" 2>/dev/null | grep 'docker.sock' >/dev/null 2>&1; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_5_31" + warn " * Docker socket shared: $c" + docker_sock_containers="$docker_sock_containers $c" + fail=1 + else + warn " * Docker socket shared: $c" + docker_sock_containers="$docker_sock_containers $c" + fi + fi + done + # We went through all the containers and found none with docker.sock shared + if [ $fail -eq 0 ]; then + pass "$check_5_31" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Containers sharing docker socket" "$docker_sock_containers" + currentScore=$((currentScore - 1)) + fi +} + +check_5_end() { + endsectionjson +} diff --git a/tests/6_docker_security_operations.sh b/tests/6_docker_security_operations.sh index 3165986..ce9b257 100644 --- a/tests/6_docker_security_operations.sh +++ b/tests/6_docker_security_operations.sh @@ -1,71 +1,64 @@ #!/bin/sh -logit "\n" -info "6 - Docker Security Operations" +check_6() { + logit "\n" + id_6="6" + desc_6="Docker Security Operations" + check_6="$id_6 - $desc_6" + info "$check_6" + startsectionjson "$id_6" "$desc_6" +} -# 6.5 -check_6_5="6.5 - Use a centralized and remote log collection service" +# 6.1 +check_6_1() { + id_6_1="6.1" + desc_6_1="Ensure that image sprawl is avoided (Not Scored)" + check_6_1="$id_6_1 - $desc_6_1" + starttestjson "$id_6_1" "$desc_6_1" -# If containers is empty, there are no running containers -if [ -z "$containers" ]; then - info "$check_6_5" - info " * No containers running" -else - fail=0 - set -f; IFS=$' -' - for c in $containers; do - volumes=$(docker inspect --format '{{ .Volumes }}' "$c") + totalChecks=$((totalChecks + 1)) + images=$(docker images -q | sort -u | wc -l | awk '{print $1}') + active_images=0 - if [ "$volumes" = "map[]" ]; then - # If it's the first container, fail the test - if [ $fail -eq 0 ]; then - info "$check_6_5" - info " * Container has no volumes, ensure centralized logging is enabled : $c" - fail=1 - else - info " * Container has no volumes, ensure centralized logging is enabled : $c" - fi + for c in $(docker inspect --format "{{.Image}}" $(docker ps -qa) 2>/dev/null); do + if docker images --no-trunc -a | grep "$c" > /dev/null ; then + active_images=$(( active_images += 1 )) fi done - # Only alert if there are no volumes. If there are volumes, can't know if they - # are used for logs -fi -# Make the loop separator go back to space -set +f; unset IFS -# 6.6 -check_6_6="6.6 - Avoid image sprawl" -images=$(docker images -q | wc -l | awk '{print $1}') -active_images=0 + info "$check_6_1" + info " * There are currently: $images images" -for c in $(docker inspect -f "{{.Image}}" $(docker ps -qa)); do - if docker images --no-trunc -a | grep $c > /dev/null ; then - active_images=$(( active_images += 1 )) + if [ "$active_images" -lt "$((images / 2))" ]; then + info " * Only $active_images out of $images are in use" fi -done + resulttestjson "INFO" "$active_images active/$images in use" + currentScore=$((currentScore + 0)) +} -if [ "$images" -gt 100 ]; then - warn "$check_6_6" - warn " * There are currently: $images images" -else - info "$check_6_6" - info " * There are currently: $images images" -fi +# 6.2 +check_6_2() { + id_6_2="6.2" + desc_6_2="Ensure that container sprawl is avoided (Not Scored)" + check_6_2="$id_6_2 - $desc_6_2" + starttestjson "$id_6_2" "$desc_6_2" -if [ "$active_images" -lt "$((images / 2))" ]; then - warn " * Only $active_images out of $images are in use" -fi + totalChecks=$((totalChecks + 1)) + total_containers=$(docker info 2>/dev/null | grep "Containers" | awk '{print $2}') + running_containers=$(docker ps -q | wc -l | awk '{print $1}') + diff="$((total_containers - running_containers))" + if [ "$diff" -gt 25 ]; then + info "$check_6_2" + info " * There are currently a total of $total_containers containers, with only $running_containers of them currently running" + resulttestjson "INFO" "$total_containers total/$running_containers running" + else + info "$check_6_2" + info " * There are currently a total of $total_containers containers, with $running_containers of them currently running" + resulttestjson "INFO" "$total_containers total/$running_containers running" + fi + currentScore=$((currentScore + 0)) +} -# 6.7 -check_6_7="6.7 - Avoid container sprawl" -total_containers=$(docker info 2>/dev/null | grep "Containers" | awk '{print $2}') -running_containers=$(docker ps -q | wc -l | awk '{print $1}') -diff="$(($total_containers - $running_containers))" -if [ "$diff" -gt 25 ]; then - warn "$check_6_7" - warn " * There are currently a total of $total_containers containers, with only $running_containers of them currently running" -else - info "$check_6_7" - info " * There are currently a total of $total_containers containers, with $running_containers of them currently running" -fi +check_6_end() { + endsectionjson +} diff --git a/tests/7_docker_swarm_configuration.sh b/tests/7_docker_swarm_configuration.sh new file mode 100644 index 0000000..c3b9187 --- /dev/null +++ b/tests/7_docker_swarm_configuration.sh @@ -0,0 +1,250 @@ +#!/bin/sh + +check_7() { + logit "\n" + id_7="7" + desc_7="Docker Swarm Configuration" + check_7="$id_7 - $desc_7" + info "$check_7" + startsectionjson "$id_7" "$desc_7" +} + +# 7.1 +check_7_1() { + id_7_1="7.1" + desc_7_1="Ensure swarm mode is not Enabled, if not needed (Scored)" + check_7_1="$id_7_1 - $desc_7_1" + starttestjson "$id_7_1" "$desc_7_1" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:*\sinactive\s*" >/dev/null 2>&1; then + pass "$check_7_1" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_7_1" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi +} + +# 7.2 +check_7_2() { + id_7_2="7.2" + desc_7_2="Ensure that the minimum number of manager nodes have been created in a swarm (Scored)" + check_7_2="$id_7_2 - $desc_7_2" + starttestjson "$id_7_2" "$desc_7_2" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then + managernodes=$(docker node ls | grep -c "Leader") + if [ "$managernodes" -eq 1 ]; then + pass "$check_7_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_7_2" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + pass "$check_7_2 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.3 +check_7_3() { + id_7_3="7.3" + desc_7_3="Ensure that swarm services are bound to a specific host interface (Scored)" + check_7_3="$id_7_3 - $desc_7_3" + starttestjson "$id_7_3" "$desc_7_3" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then + $netbin -lnt | grep -e '\[::]:2377 ' -e ':::2377' -e '*:2377 ' -e ' 0\.0\.0\.0:2377 ' >/dev/null 2>&1 + if [ $? -eq 1 ]; then + pass "$check_7_3" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_7_3" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + pass "$check_7_3 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.4 +check_7_4() { + id_7_4="7.4" + desc_7_4="Ensure that all Docker swarm overlay networks are encrypted (Scored)" + check_7_4="$id_7_4 - $desc_7_4" + starttestjson "$id_7_4" "$desc_7_4" + + totalChecks=$((totalChecks + 1)) + fail=0 + unencrypted_networks="" + for encnet in $(docker network ls --filter driver=overlay --quiet); do + if docker network inspect --format '{{.Name}} {{ .Options }}' "$encnet" | \ + grep -v 'encrypted:' 2>/dev/null 1>&2; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn "$check_7_4" + fail=1 + fi + warn " * Unencrypted overlay network: $(docker network inspect --format '{{ .Name }} ({{ .Scope }})' "$encnet")" + unencrypted_networks="$unencrypted_networks $(docker network inspect --format '{{ .Name }} ({{ .Scope }})' "$encnet")" + fi + done + # We went through all the networks and found none that are unencrypted + if [ $fail -eq 0 ]; then + pass "$check_7_4" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + resulttestjson "WARN" "Unencrypted overlay networks:" "$unencrypted_networks" + currentScore=$((currentScore - 1)) + fi +} + +# 7.5 +check_7_5() { + id_7_5="7.5" + desc_7_5="Ensure that Docker's secret management commands are used for managing secrets in a swarm cluster (Not Scored)" + check_7_5="$id_7_5 - $desc_7_5" + starttestjson "$id_7_5" "$desc_7_5" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then + if [ "$(docker secret ls -q | wc -l)" -ge 1 ]; then + pass "$check_7_5" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + info "$check_7_5" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) + fi + else + pass "$check_7_5 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.6 +check_7_6() { + id_7_6="7.6" + desc_7_6="Ensure that swarm manager is run in auto-lock mode (Scored)" + check_7_6="$id_7_6 - $desc_7_6" + starttestjson "$id_7_6" "$desc_7_6" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then + if ! docker swarm unlock-key 2>/dev/null | grep 'SWMKEY' 2>/dev/null 1>&2; then + warn "$check_7_6" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + else + pass "$check_7_6" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi + else + pass "$check_7_6 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.7 +check_7_7() { + id_7_7="7.7" + desc_7_7="Ensure that the swarm manager auto-lock key is rotated periodically (Not Scored)" + check_7_7="$id_7_7 - $desc_7_7" + starttestjson "$id_7_7" "$desc_7_7" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then + note "$check_7_7" + resulttestjson "NOTE" + currentScore=$((currentScore + 0)) + else + pass "$check_7_7 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.8 +check_7_8() { + id_7_8="7.8" + desc_7_8="Ensure that node certificates are rotated as appropriate (Not Scored)" + check_7_8="$id_7_8 - $desc_7_8" + starttestjson "$id_7_8" "$desc_7_8" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then + if docker info 2>/dev/null | grep "Expiry Duration: 2 days"; then + pass "$check_7_8" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + info "$check_7_8" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) + fi + else + pass "$check_7_8 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.9 +check_7_9() { + id_7_9="7.9" + desc_7_9="Ensure that CA certificates are rotated as appropriate (Not Scored)" + check_7_9="$id_7_9 - $desc_7_9" + starttestjson "$id_7_9" "$desc_7_9" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then + info "$check_7_9" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) + else + pass "$check_7_9 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +# 7.10 +check_7_10() { + id_7_10="7.10" + desc_7_10="Ensure that management plane traffic is separated from data plane traffic (Not Scored)" + check_7_10="$id_7_10 - $desc_7_10" + starttestjson "$id_7_10" "$desc_7_10" + + totalChecks=$((totalChecks + 1)) + if docker info 2>/dev/null | grep -e "Swarm:\s*active\s*" >/dev/null 2>&1; then + info "$check_7_10" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) + else + pass "$check_7_10 (Swarm mode not enabled)" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + fi +} + +check_7_end() { + endsectionjson +} diff --git a/tests/8_docker_enterprise_configuration.sh b/tests/8_docker_enterprise_configuration.sh new file mode 100644 index 0000000..9fce978 --- /dev/null +++ b/tests/8_docker_enterprise_configuration.sh @@ -0,0 +1,181 @@ +#!/bin/sh + +check_8() { + logit "\n" + id_8="8" + desc_8="Docker Enterprise Configuration" + check_8="$id_8 - $desc_8" + info "$check_8" + startsectionjson "$id_8" "$desc_8" +} + +check_product_license() { + if docker version | grep -Eqi '^Server.*Community$|Version.*-ce$'; then + info " * Community Engine license, skipping section 8" + enterprise_license=0 + else + enterprise_license=1 + fi +} + +check_8_1() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1="8.1" + desc_8_1="Universal Control Plane Configuration" + check_8_1="$id_8_1 - $desc_8_1" + info "$check_8_1" +} + +# 8.1.1 +check_8_1_1() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_1="8.1.1" + desc_8_1_1="Configure the LDAP authentication service (Scored)" + check_8_1_1="$id_8_1_1 - $desc_8_1_1" + starttestjson "$id_8_1_1" "$desc_8_1_1" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_1" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 8.1.2 +check_8_1_2() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_2="8.1.2" + desc_8_1_2="Use external certificates (Scored)" + check_8_1_2="$id_8_1_2 - $desc_8_1_2" + starttestjson "$id_8_1_2" "$desc_8_1_2" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_2" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 8.1.3 +check_8_1_3() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_3="8.1.3" + desc_8_1_3="Enforce the use of client certificate bundles for unprivileged users (Not Scored)" + check_8_1_3="$id_8_1_3 - $desc_8_1_3" + starttestjson "$id_8_1_3" "$desc_8_1_3" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_3" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 8.1.4 +check_8_1_4() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_4="8.1.4" + desc_8_1_4="Configure applicable cluster role-based access control policies (Not Scored)" + check_8_1_4="$id_8_1_4 - $desc_8_1_4" + starttestjson "$id_8_1_4" "$desc_8_1_4" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_4" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 8.1.5 +check_8_1_5() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_5="8.1.5" + desc_8_1_5="Enable signed image enforcement (Scored)" + check_8_1_5="$id_8_1_5 - $desc_8_1_5" + starttestjson "$id_8_1_5" "$desc_8_1_5" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_5" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 8.1.6 +check_8_1_6() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_6="8.1.6" + desc_8_1_6="Set the Per-User Session Limit to a value of '3' or lower (Scored)" + check_8_1_6="$id_8_1_6 - $desc_8_1_6" + starttestjson "$id_8_1_6" "$desc_8_1_6" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_6" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +# 8.1.7 +check_8_1_7() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_1_7="8.1.7" + desc_8_1_7="Set the 'Lifetime Minutes' and 'Renewal Threshold Minutes' values to '15' or lower and '0' respectively (Scored)" + check_8_1_7="$id_8_1_7 - $desc_8_1_7" + starttestjson "$id_8_1_7" "$desc_8_1_7" + + totalChecks=$((totalChecks + 1)) + note "$check_8_1_7" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +check_8_2() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + logit "\n" + id_8_2="8.2" + desc_8_2="Docker Trusted Registry Configuration" + check_8_2="$id_8_2 - $desc_8_2" + info "$check_8_2" +} + +check_8_2_1() { + if [ "$enterprise_license" -ne 1 ]; then + return + fi + + id_8_2_1="8.2.1" + desc_8_2_1="Enable image vulnerability scanning (Scored)" + check_8_2_1="$id_8_2_1 - $desc_8_2_1" + starttestjson "$id_8_2_1" "$desc_8_2_1" + + totalChecks=$((totalChecks + 1)) + note "$check_8_2_1" + resulttestjson "INFO" + currentScore=$((currentScore + 0)) +} + +check_8_end() { + endsectionjson +} diff --git a/tests/99_community_checks.sh b/tests/99_community_checks.sh new file mode 100644 index 0000000..9ad1774 --- /dev/null +++ b/tests/99_community_checks.sh @@ -0,0 +1,59 @@ +#!/bin/sh +check_c() { + logit "\n" + id_99="99" + desc_99="Community contributed checks" + check_99="$id_99 - $desc_99" + info "$check_99" + startsectionjson "$id_99" "$desc_99" +} + +# check_c_1 +check_c_1() { + check_c_1="C.1 - This is a example check" + totalChecks=$((totalChecks + 1)) + if docker info --format='{{ .Architecture }}' | grep 'x86_64' 2>/dev/null 1>&2; then + pass "$check_c_1" + resulttestjson "PASS" + else + warn "$check_c_1" + resulttestjson "WARN" + fi +} + +# check_c_2 +check_c_2() { + docker_version=$(docker version | grep -i -A2 '^server' | grep ' Version:' \ + | awk '{print $NF; exit}' | tr -d '[:alpha:]-,.' | cut -c 1-4) + totalChecks=$((totalChecks + 1)) + + id_c_2="C.2" + desc_c_2="Ensure operations on legacy registry (v1) are Disabled" + check_c_2="$id_c_2 - $desc_c_2" + starttestjson "$id_c_2" "$desc_c_2" + + if [ "$docker_version" -lt 1712 ]; then + if get_docker_configuration_file_args 'disable-legacy-registry' | grep 'true' >/dev/null 2>&1; then + pass "$check_c_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + elif get_docker_effective_command_line_args '--disable-legacy-registry' | grep "disable-legacy-registry" >/dev/null 2>&1; then + pass "$check_c_2" + resulttestjson "PASS" + currentScore=$((currentScore + 1)) + else + warn "$check_c_2" + resulttestjson "WARN" + currentScore=$((currentScore - 1)) + fi + else + desc_c_2="$desc_c_2 (Deprecated)" + check_c_2="$id_c_2 - $desc_c_2" + info "$check_c_2" + resulttestjson "INFO" + fi +} + +check_c_end() { + endsectionjson +}