diff --git a/.dockerignore b/.dockerignore index db3e3b9..8abc26a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ -.git -*.md -*.png -distros - +* +!docker-bench-security.sh +!functions/ +!tests/ +!log/ +log/* diff --git a/.gitignore b/.gitignore index e5ebf25..b783cca 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.log* +log/* +*.swp* diff --git a/Dockerfile b/Dockerfile index 33cd79a..ed4596a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,17 +5,11 @@ LABEL \ 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 +RUN apk add --no-cache iproute2 \ + docker-cli \ + dumb-init -COPY ./*.sh /usr/local/bin/ -COPY ./tests/*.sh /usr/local/bin/tests/ +COPY . /usr/local/bin/ HEALTHCHECK CMD exit 0 diff --git a/README.md b/README.md index 57b13ca..cc1f270 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,30 @@ # Docker Bench for Security -![Docker Bench for Security running](https://raw.githubusercontent.com/docker/docker-bench-security/master/benchmark_log.png) +![Docker Bench for Security running](img/benchmark_log.png) -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/). +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. +The list with all tests is available [here](tests/TESTS.md). + +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. +### Run from your base host -The easiest way to run your hosts against the Docker Bench for Security is by -running our pre-built container: +You can simply run this script from your base host by running: + +```sh +git clone https://github.com/docker/docker-bench-security.git +cd docker-bench-security +sudo sh docker-bench-security.sh +``` + +### Run with Docker + +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 Docker Bench for Security is by running our pre-built container: ```sh docker run --rm --net host --pid host --userns host --cap-add audit_control \ @@ -36,20 +42,7 @@ docker run --rm --net host --pid host --userns host --cap-add audit_control \ Don't forget to adjust the shared volumes according to your operating system. Some examples are: -1. `Docker Desktop` on macOS doesn'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 +1. On Ubuntu the `docker.service` and `docker.secret` files are located in `/lib/systemd/system` folder by default. ```sh @@ -66,17 +59,24 @@ docker run --rm --net host --pid host --userns host --cap-add audit_control \ docker/docker-bench-security ``` +2. `Docker Desktop` on macOS doesn'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 +``` + +### Note + Docker bench requires Docker 1.13.0 or later in order to run. -Note that when distributions don't contain `auditctl`, the audit tests will -check `/etc/audit/audit.rules` to see if a rule is present instead. - -Distribution specific Dockerfiles that fix these issues 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 hasn't yet shipped Docker -version 1.13.0 or later. +Note that when distributions don't contain `auditctl`, the audit tests will check `/etc/audit/audit.rules` to see if a rule is present instead. ### Docker Bench for Security options @@ -84,47 +84,40 @@ version 1.13.0 or later. -b optional Do not print colors -h optional Print this help message -l FILE optional Log output in FILE, inside container if run using docker - -c CHECK optional Comma delimited list of specific check(s) - -e CHECK optional Comma delimited list of specific check(s) to exclude + -u USERS optional Comma delimited list of trusted docker user(s) + -c CHECK optional Comma delimited list of specific check(s) id + -e CHECK optional Comma delimited list of specific check(s) id 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). + -p PRINT optional Disable the printing of remediation measures. Default: print remediation measures. ``` -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`. +By default the Docker Bench for Security script will run all available CIS tests and produce +logs in the log folder from current directory, named `docker-bench-security.sh.log.json` and +`docker-bench-security.sh.log`. -If the docker container is used then the log files will be created inside the container. If you wish to access them from the host after the container has been run you will need to mount a volume for storing them in. +If the docker container is used then the log files will be created inside the container in location `/usr/local/bin/log/`. If you wish to access them from the host after the container has been run you will need to mount a volume for storing them in. -The CIS based checks are named `check_
_`, e.g. `check_2_6` -and community contributed checks are named `check_c_`. -A complete list of checks is present in [functions_lib.sh](functions_lib.sh). +The CIS based checks are named `check_
_`, e.g. `check_2_6` and community contributed checks are named `check_c_`. A complete list of checks is present in [TESTS.md](tests/TESTS.md). -`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 -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 -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 -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 -e docker_enterprise_configuration,check_2_2` will run allavailable 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` +`sh docker-bench-security.sh -c container_images -e check_4_5` will run just the container_images checks except `4.5 Ensure Content trust for Docker is Enabled` -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. +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 +## Building Docker image -If you wish to build and run this container yourself, you can follow the -following steps: +You have two options if you wish to build and run this container yourself: + +1. Use Docker Build: ```sh git clone https://github.com/docker/docker-bench-security.git @@ -132,8 +125,9 @@ cd docker-bench-security docker build --no-cache -t docker-bench-security . ``` -followed by an appropriate `docker run` command as stated above -or use [Docker Compose](https://docs.docker.com/compose/): +Followed by an appropriate `docker run` command as stated above. + +2. Use Docker Compose: ```sh git clone https://github.com/docker/docker-bench-security.git @@ -141,13 +135,4 @@ 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: - -```sh -git clone https://github.com/docker/docker-bench-security.git -cd docker-bench-security -sudo sh docker-bench-security.sh -``` - -This script was built 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 deleted file mode 100644 index 8399924..0000000 Binary files a/benchmark_log.png and /dev/null differ diff --git a/distros/Dockerfile.alpine b/distros/Dockerfile.alpine index 0ad1564..ed4596a 100644 --- a/distros/Dockerfile.alpine +++ b/distros/Dockerfile.alpine @@ -1,21 +1,15 @@ -FROM alpine:3.12 +FROM alpine:3.13 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 +RUN apk add --no-cache iproute2 \ + docker-cli \ + dumb-init -COPY ./*.sh /usr/local/bin/ -COPY ./tests/*.sh /usr/local/bin/tests/ +COPY . /usr/local/bin/ HEALTHCHECK CMD exit 0 diff --git a/docker-bench-security.sh b/docker-bench-security.sh index 0d5424e..c9de3fb 100755 --- a/docker-bench-security.sh +++ b/docker-bench-security.sh @@ -1,21 +1,21 @@ #!/bin/sh -# ------------------------------------------------------------------------------ +# -------------------------------------------------------------------------------------------- # Docker Bench for Security # -# Docker, Inc. (c) 2015- +# Docker, Inc. (c) 2015-2021 # # Checks for dozens of common best-practices around deploying Docker containers in production. -# ------------------------------------------------------------------------------ +# -------------------------------------------------------------------------------------------- -version='1.3.5' +version='1.3.6' # Load dependencies -. ./functions_lib.sh -. ./helper_lib.sh +. ./functions/functions_lib.sh +. ./functions/helper_lib.sh # Setup the paths this_path=$(abspath "$0") ## Path of this file including filename -myname=$(basename "${this_path}") ## file name of this script. +myname=$(basename "${this_path%.*}") ## file name of this script. readonly version readonly this_path @@ -24,19 +24,7 @@ readonly myname export PATH="$PATH:/bin:/sbin:/usr/bin:/usr/local/bin:/usr/sbin/" # Check for required program(s) -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 +req_programs 'awk docker grep stat tee tail wc xargs truncate sed' # Ensure we can connect to docker daemon if ! docker ps -q >/dev/null 2>&1; then @@ -46,54 +34,76 @@ fi usage () { cat < +Full documentation: +Released under the Apache-2.0 License. EOF } +# Default values +if [ ! -d log ]; then + mkdir log +fi +logger="log/${myname}.log" +limit=0 +printremediation="1" +globalRemediation="" + # 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 +while getopts bhl:u:c:e:i:x:t:n:p args do case $args in b) nocolor="nocolor";; h) usage; exit 0 ;; l) logger="$OPTARG" ;; + u) dockertrustusers="$OPTARG" ;; c) check="$OPTARG" ;; e) checkexclude="$OPTARG" ;; i) include="$OPTARG" ;; x) exclude="$OPTARG" ;; n) limit="$OPTARG" ;; + p) printremediation="0" ;; *) usage; exit 1 ;; esac done -if [ -z "$logger" ]; then - logger="${myname}.log" -fi - -if [ -z "$limit" ]; then - limit=0 -fi - # Load output formating -. ./output_lib.sh +. ./functions/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" + warn "$(yell 'Some tests might require root to run')\n" sleep 3 fi @@ -103,11 +113,13 @@ fi totalChecks=0 currentScore=0 -logit "Initializing $(date)\n" +logit "Initializing $(date +%Y-%m-%dT%H:%M:%S%:z)\n" beginjson "$version" "$(date +%s)" # Load all the tests from tests/ and run them main () { + logit "\n${bldylw}Section A - Check results${txtrsr}" + # Get configuration location get_docker_configuration_file @@ -142,12 +154,6 @@ main () { 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 @@ -157,7 +163,7 @@ main () { 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) + check=$(sed -ne "/cis() {/,/}/{/{/d; /}/d; p}" functions/functions_lib.sh) fi for c in $(echo "$check" | sed "s/,/ /g"); do @@ -177,7 +183,7 @@ main () { 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)" + loop_checks="$(sed -ne "/$c() {/,/}/{/{/d; /}/d; p}" functions/functions_lib.sh)" else # Just one check loop_checks="$c" @@ -192,9 +198,14 @@ main () { fi done - printf "\n" + if [ -n "${globalRemediation}" ] && [ "$printremediation" = "1" ]; then + logit "\n\n${bldylw}Section B - Remediation measures${txtrst}" + logit "${globalRemediation}" + fi + + logit "\n\n${bldylw}Section C - Score${txtrst}\n" info "Checks: $totalChecks" - info "Score: $currentScore" + info "Score: $currentScore\n" endjson "$totalChecks" "$currentScore" "$(date +%s)" } diff --git a/functions_lib.sh b/functions/functions_lib.sh similarity index 87% rename from functions_lib.sh rename to functions/functions_lib.sh index 1156986..1dec4c5 100644 --- a/functions_lib.sh +++ b/functions/functions_lib.sh @@ -41,6 +41,32 @@ host_configuration_level1() { check_1_end } +host_general_configuration() { + check_1 + check_1_1 + check_1_1_1 + check_1_1_2 + check_1_end +} + +linux_hosts_specific_configuration() { + check_1 + 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 +} + docker_daemon_configuration() { check_2 check_2_1 @@ -303,9 +329,30 @@ docker_enterprise_configuration_level1() { check_8_end } +universal_control_plane_configuration() { + check_8 + 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_end +} + +docker_trusted_registry_configuration() { + check_8 + check_8_2 + check_8_2_1 + check_8_end +} + community_checks() { check_c check_c_1 + check_c_1_1 check_c_2 check_c_end } diff --git a/helper_lib.sh b/functions/helper_lib.sh similarity index 82% rename from helper_lib.sh rename to functions/helper_lib.sh index e24a14c..96ac49c 100644 --- a/helper_lib.sh +++ b/functions/helper_lib.sh @@ -6,6 +6,23 @@ abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; es # Audit rules default path auditrules="/etc/audit/audit.rules" +# Check for required program(s) +req_programs() { + for p in $1; do + command -v "$p" >/dev/null 2>&1 || { printf "Required program not found: %s\n" "$p"; exit 1; } + done + if command -v ss >/dev/null 2>&1; then + netbin=ss + return + fi + if command -v netstat >/dev/null 2>&1; then + netbin=netstat + return + fi + echo "ss or netstat command not found." + exit 1 +} + # Compares versions of software of the format X.Y.Z do_version_check() { [ "$1" = "$2" ] && return 10 @@ -23,9 +40,8 @@ do_version_check() { [ "$ver2front" = "$2" ] || [ -z "$ver2back" ] && ver2back=0 do_version_check "$ver1back" "$ver2back" return $? - else - [ "$1" -gt "$2" ] && return 11 || return 9 fi + [ "$1" -gt "$2" ] && return 11 || return 9 } # Extracts commandline args from the newest running processes named like the first parameter @@ -45,10 +61,9 @@ get_command_line_args() { get_docker_cumulative_command_line_args() { OPTION="$1" + line_arg="dockerd" 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" | @@ -88,11 +103,13 @@ get_docker_configuration_file() { 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' + return fi + if [ -f '/etc/docker/daemon.json' ]; then + CONFIG_FILE='/etc/docker/daemon.json' + return + fi + CONFIG_FILE='/dev/null' } get_docker_configuration_file_args() { @@ -108,13 +125,17 @@ get_service_file() { 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" + return fi + if [ -f "/lib/systemd/system/$SERVICE" ]; then + echo "/lib/systemd/system/$SERVICE" + return + fi + if systemctl show -p FragmentPath "$SERVICE" 2> /dev/null 1>&2; then + systemctl show -p FragmentPath "$SERVICE" | sed 's/.*=//' + return + fi + echo "/usr/lib/systemd/system/$SERVICE" } yell_info() { diff --git a/functions/output_lib.sh b/functions/output_lib.sh new file mode 100644 index 0000000..10d58c3 --- /dev/null +++ b/functions/output_lib.sh @@ -0,0 +1,190 @@ +#!/bin/sh + +bldred='\033[1;31m' # Bold Red +bldgrn='\033[1;32m' # Bold Green +bldblu='\033[1;34m' # Bold Blue +bldylw='\033[1;33m' # Bold Yellow +txtrst='\033[0m' + +if [ -n "$nocolor" ] && [ "$nocolor" = "nocolor" ]; then + bldred='' + bldgrn='' + bldblu='' + bldylw='' + txtrst='' +fi + +logit () { + printf "%b\n" "$1" | tee -a "$logger" +} + +info () { + local infoCountCheck + local OPTIND c + while getopts c args + do + case $args in + c) infoCountCheck="true" ;; + *) exit 1 ;; + esac + done + if [ "$infoCountCheck" = "true" ]; then + printf "%b\n" "${bldblu}[INFO]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + return + fi + printf "%b\n" "${bldblu}[INFO]${txtrst} $1" | tee -a "$logger" +} + +pass () { + local passScored + local passCountCheck + local OPTIND s c + while getopts sc args + do + case $args in + s) passScored="true" ;; + c) passCountCheck="true" ;; + *) exit 1 ;; + esac + done + if [ "$passScored" = "true" ] || [ "$passCountCheck" = "true" ]; then + printf "%b\n" "${bldgrn}[PASS]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + fi + if [ "$passScored" = "true" ]; then + currentScore=$((currentScore + 1)) + fi + if [ "$passScored" != "true" ] && [ "$passCountCheck" != "true" ]; then + printf "%b\n" "${bldgrn}[PASS]${txtrst} $1" | tee -a "$logger" + fi +} + +warn () { + local warnScored + local OPTIND s + while getopts s args + do + case $args in + s) warnScored="true" ;; + *) exit 1 ;; + esac + done + if [ "$warnScored" = "true" ]; then + printf "%b\n" "${bldred}[WARN]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + currentScore=$((currentScore - 1)) + return + fi + printf "%b\n" "${bldred}[WARN]${txtrst} $1" | tee -a "$logger" +} + +note () { + local noteCountCheck + local OPTIND c + while getopts c args + do + case $args in + c) noteCountCheck="true" ;; + *) exit 1 ;; + esac + done + if [ "$noteCountCheck" = "true" ]; then + printf "%b\n" "${bldylw}[NOTE]${txtrst} $2" | tee -a "$logger" + totalChecks=$((totalChecks + 1)) + return + fi + 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 ],\n \"checks\": %s,\n \"score\": %s,\n \"end\": %s\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 {\n \"id\": \"%s\",\n \"desc\": \"%s\",\n \"results\": [" "$SSEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + SEP= + SSEP="," +} + +endsectionjson() { + printf "\n ]\n }" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +starttestjson() { + printf "%s\n {\n \"id\": \"%s\",\n \"desc\": \"%s\",\n " "$SEP" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + SEP="," +} + +log_to_json() { + if [ $# -eq 1 ]; then + printf "\"result\": \"%s\"" "$1" | tee -a "$logger.json" 2>/dev/null 1>&2 + return + fi + if [ $# -eq 2 ] && [ $# -ne 1 ]; then + # Result also contains details + printf "\"result\": \"%s\",\n \"details\": \"%s\"" "$1" "$2" | tee -a "$logger.json" 2>/dev/null 1>&2 + return + fi + # 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 + truncItems=$3 + 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 + fi + itemsJson=$(printf "[\n "; ISEP=""; ITEMCOUNT=0; for item in $truncItems; do printf "%s\"%s\"" "$ISEP" "$item"; ISEP=","; done; printf "\n ]") + printf "\"result\": \"%s\",\n \"details\": \"%s: %s\",\n \"items\": %s" "$1" "$2" "$truncItems" "$itemsJson" | tee -a "$logger.json" 2>/dev/null 1>&2 +} + +logcheckresult() { + # Log to JSON + log_to_json "$@" + + # Log remediation measure to JSON + if [ -n "$remediation" ] && [ "$1" != "PASS" ] && [ "$printremediation" = "1" ]; then + printf ",\n \"remediation\": \"%s\"" "$remediation" | tee -a "$logger.json" 2>/dev/null 1>&2 + if [ -n "$remediationImpact" ]; then + printf ",\n \"remediation-impact\": \"%s\"" "$remediationImpact" | tee -a "$logger.json" 2>/dev/null 1>&2 + fi + fi + printf "\n }" | tee -a "$logger.json" 2>/dev/null 1>&2 + + # Save remediation measure for print log to stdout + if [ -n "$remediation" ] && [ "$1" != "PASS" ]; then + if [ -n "${checkHeader}" ]; then + if [ -n "${addSpaceHeader}" ]; then + globalRemediation="${globalRemediation}\n" + fi + globalRemediation="${globalRemediation}\n${bldblu}[INFO]${txtrst} ${checkHeader}" + checkHeader="" + addSpaceHeader="1" + fi + globalRemediation="${globalRemediation}\n${bldblu}[INFO]${txtrst} ${id} - ${remediation}" + if [ -n "${remediationImpact}" ]; then + globalRemediation="${globalRemediation} Remediation Impact: ${remediationImpact}" + fi + fi +} diff --git a/img/benchmark_log.png b/img/benchmark_log.png new file mode 100644 index 0000000..7101c17 Binary files /dev/null and b/img/benchmark_log.png differ diff --git a/output_lib.sh b/output_lib.sh deleted file mode 100644 index 384dfe0..0000000 --- a/output_lib.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/sh - -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" -} - -info () { - printf "%b\n" "${bldblu}[INFO]${txtrst} $1" | tee -a "$logger" -} - -pass () { - printf "%b\n" "${bldgrn}[PASS]${txtrst} $1" | tee -a "$logger" -} - -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 3a2e843..9fc9402 100644 --- a/tests/1_host_configuration.sh +++ b/tests/1_host_configuration.sh @@ -2,475 +2,461 @@ check_1() { logit "" - id_1="1" - desc_1="Host Configuration" - check_1="$id_1 - $desc_1" - info "$check_1" - startsectionjson "$id_1" "$desc_1" + local id="1" + local desc="Host Configuration" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } 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" + local id="1.1" + local desc="General Configuration" + local check="$id - $desc" + info "$check" } -# 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" + local id="1.1.1" + local desc="Ensure the container host has been Hardened (Not Scored)" + local remediation="You may consider various Security Benchmarks for your container host." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_1_1_1" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="1.1.2" + local desc="Ensure that the version of Docker is up to date (Not Scored)" + local remediation="You should monitor versions of Docker releases and make sure your software is updated as required." + local remediationImpact="You should perform a risk assessment regarding Docker version updates and review how they may impact your operations." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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" + pass -c "$check" 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_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)) + logcheckresult "INFO" "Using $docker_version" + return fi + pass -c "$check" + info " * Using $docker_version which is current" + info " * Check with your operating system vendor for support and security maintenance for Docker" + logcheckresult "PASS" "Using $docker_version" } 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" + local id="1.2" + local desc="Linux Hosts Specific Configuration" + local check="$id - $desc" + info "$check" } -# 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" + local id="1.2.1" + local desc="Ensure a separate partition for containers has been created (Scored)" + local remediation="For new installations, you should create a separate partition for the /var/lib/docker mount point. For systems that have already been installed, you should use the Logical Volume Manager (LVM) within Linux to create a new partition." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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_2_1" - resulttestjson "WARN" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + warn -s "$check" + logcheckresult "WARN" } -# 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" +check_1_2_2() { + local id="1.2.2" + local desc="Ensure only trusted users are allowed to control Docker daemon (Scored)" + local remediation="You should remove any untrusted users from the docker group using command sudo gpasswd -d docker or add trusted users to the docker group using command sudo usermod -aG docker . You should not create a mapping of sensitive directories from the host to container volumes." + local remediationImpact="Only trust user are allow to build and execute containers as normal user." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + docker_users=$(grep 'docker' /etc/group) if command -v getent >/dev/null 2>&1; then docker_users=$(getent group docker) - else - docker_users=$(grep 'docker' /etc/group) fi - info "$check_1_2_2" - for u in $docker_users; do - info " * $u" - done - resulttestjson "INFO" "users" "$docker_users" - currentScore=$((currentScore + 0)) + docker_users=$(printf "%s" "$docker_users" | awk -F: '{print $4}') + + local doubtfulusers="" + if [ -n "$dockertrustusers" ]; then + for u in $(printf "%s" "$docker_users" | sed "s/,/ /g"); do + if ! printf "%s" "$dockertrustusers" | grep -q "$u" ; then + doubtfulusers="$u" + if [ -n "${doubtfulusers}" ]; then + doubtfulusers="${doubtfulusers},$u" + fi + fi + done + else + info -c "$check" + info " * Users: $docker_users" + logcheckresult "INFO" "doubtfulusers" "$docker_users" + fi + + if [ -n "${doubtfulusers}" ]; then + warn -s "$check" + warn " * Doubtful users: $doubtfulusers" + logcheckresult "WARN" "doubtfulusers" "$doubtfulusers" + fi + + if [ -z "${doubtfulusers}" ] && [ -n "${dockertrustusers}" ]; then + pass -s "$check" + logcheckresult "PASS" + 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" + local id="1.2.3" + local desc="Ensure auditing is configured for the Docker daemon (Scored)" + local remediation="Install auditd. Add -w /usr/bin/dockerd -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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_2_3" - resulttestjson "WARN" - currentScore=$((currentScore - 1)) + warn -s "$check" + logcheckresult "WARN" + return fi + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } -# 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" + local id="1.2.4" + local desc="Ensure auditing is configured for Docker files and directories - /var/lib/docker (Scored)" + local remediation="Install auditd. Add -w /var/lib/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_4" - info " * Directory not found" - resulttestjson "INFO" "Directory not found" - currentScore=$((currentScore + 0)) + if grep -s "$directory" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * Directory not found" + logcheckresult "INFO" "Directory not found" } -# 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" + local id="1.2.5" + local desc="Ensure auditing is configured for Docker files and directories - /etc/docker (Scored)" + local remediation="Install auditd. Add -w /etc/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_5" - info " * Directory not found" - resulttestjson "INFO" "Directory not found" - currentScore=$((currentScore + 0)) -fi + if grep -s "$directory" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return + fi + info -c "$check" + info " * Directory not found" + logcheckresult "INFO" "Directory not found" } -# 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" + local id="1.2.6" + local desc="Ensure auditing is configured for Docker files and directories - docker.service (Scored)" + local remediation + remediation="Install auditd. Add -w $(get_service_file docker.service) -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_6" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="1.2.7" + local desc="Ensure auditing is configured for Docker files and directories - docker.socket (Scored)" + local remediation + remediation="Install auditd. Add -w $(get_service_file docker.socket) -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_7" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="1.2.8" + local desc="Ensure auditing is configured for Docker files and directories - /etc/default/docker (Scored)" + local remediation="Install auditd. Add -w /etc/default/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_8" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="1.2.9" + local desc="Ensure auditing is configured for Docker files and directories - /etc/sysconfig/docker (Scored)" + local remediation="Install auditd. Add -w /etc/sysconfig/docker -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_9" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="1.2.10" + local desc="Ensure auditing is configured for Docker files and directories - /etc/docker/daemon.json (Scored)" + local remediation="Install auditd. Add -w /etc/docker/daemon.json -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_10" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="1.2.11" + local desc="Ensure auditing is configured for Docker files and directories - /usr/bin/containerd (Scored)" + local remediation="Install auditd. Add -w /usr/bin/containerd -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_11" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="1.2.12" + local desc="Ensure auditing is configured for Docker files and directories - /usr/sbin/runc (Scored)" + local remediation="Install auditd. Add -w /usr/sbin/runc -k docker to the /etc/audit/rules.d/audit.rules file. Then restart the audit daemon using command service auditd restart." + local remediationImpact="Audit can generate large log files. So you need to make sure that they are rotated and archived periodically. Create a separate partition for audit logs to avoid filling up other critical partitions." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - info "$check_1_2_12" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + if grep -s "$file" "$auditrules" | grep "^[^#;]" 2>/dev/null 1>&2; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } check_1_end() { diff --git a/tests/2_docker_daemon_configuration.sh b/tests/2_docker_daemon_configuration.sh index 83cc8eb..a985eae 100644 --- a/tests/2_docker_daemon_configuration.sh +++ b/tests/2_docker_daemon_configuration.sh @@ -1,440 +1,415 @@ #!/bin/sh 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" + logit "" + local id="2" + local desc="Docker daemon configuration" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } -# 2.1 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" + local id="2.1" + local desc="Ensure network traffic is restricted between containers on the default bridge (Scored)" + local remediation="Edit the Docker daemon configuration file to ensure that inter-container communication is disabled: icc: false." + local remediationImpact="Inter-container communication is disabled on the default network bridge. If any communication between containers on the same host is desired, it needs to be explicitly defined using container linking or custom networks." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + if get_docker_configuration_file_args 'icc' | grep "false" >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } -# 2.2 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" + local id="2.2" + local desc="Ensure the logging level is set to 'info' (Scored)" + local remediation="Ensure that the Docker daemon configuration file has the following configuration included log-level: info. Alternatively, run the Docker daemon as following: dockerd --log-level=info" + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - elif get_docker_effective_command_line_args '-l'; then + if [ -z "$(get_docker_configuration_file_args 'log-level')" ]; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return + fi + if 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - pass "$check_2_2" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + warn -s "$check" + logcheckresult "WARN" + return fi + pass -s "$check" + logcheckresult "PASS" } -# 2.3 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" + local id="2.3" + local desc="Ensure Docker is allowed to make changes to iptables (Scored)" + local remediation="Do not run the Docker daemon with --iptables=false option." + local remediationImpact="The Docker daemon service requires iptables rules to be enabled before it starts." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + warn -s "$check" + logcheckresult "WARN" + return fi + if get_docker_configuration_file_args 'iptables' | grep "false" >/dev/null 2>&1; then + warn -s "$check" + logcheckresult "WARN" + return + fi + pass -s "$check" + logcheckresult "PASS" } -# 2.4 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" + local id="2.4" + local desc="Ensure insecure registries are not used (Scored)" + local remediation="You should ensure that no insecure registries are in use." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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 + warn -s "$check" + logcheckresult "WARN" + return + fi + if ! [ -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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - pass "$check_2_4" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + warn -s "$check" + logcheckresult "WARN" + return fi + pass -s "$check" + logcheckresult "PASS" } -# 2.5 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" + local id="2.5" + local desc="Ensure aufs storage driver is not used (Scored)" + local remediation="Do not start Docker daemon as using dockerd --storage-driver aufs option." + local remediationImpact="aufs is the only storage driver that allows containers to share executable and shared library memory. Its use should be reviewed in line with your organization's security policy." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + warn -s "$check" + logcheckresult "WARN" + return fi + pass -s "$check" + logcheckresult "PASS" } -# 2.6 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" + local id="2.6" + local desc="Ensure TLS authentication for Docker daemon is configured (Scored)" + local remediation="Follow the steps mentioned in the Docker documentation or other references. By default, TLS authentication is not configured." + local remediationImpact="You would need to manage and guard certificates and keys for the Docker daemon and Docker clients." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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') ] || \ + pass -s "$check" + logcheckresult "PASS" + return + fi + if [ $(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 -s "$check" 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)) + logcheckresult "WARN" "Docker daemon currently listening on TCP with TLS, but no verification" + return fi - else - info "$check_2_6" - info " * Docker daemon not listening on TCP" - resulttestjson "INFO" "Docker daemon not listening on TCP" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Docker daemon currently listening on TCP without TLS" + logcheckresult "WARN" "Docker daemon currently listening on TCP without TLS" + return fi + info -c "$check" + info " * Docker daemon not listening on TCP" + logcheckresult "INFO" "Docker daemon not listening on TCP" } -# 2.7 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" + local id="2.7" + local desc="Ensure the default ulimit is configured appropriately (Not Scored)" + local remediation="Run Docker in daemon mode and pass --default-ulimit as option with respective ulimits as appropriate in your environment and in line with your security policy. Example: dockerd --default-ulimit nproc=1024:2048 --default-ulimit nofile=100:200" + local remediationImpact="If ulimits are set incorrectly this could cause issues with system resources, possibly causing a denial of service condition." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -c "$check" + logcheckresult "PASS" + return fi + if get_docker_effective_command_line_args '--default-ulimit' | grep "default-ulimit" >/dev/null 2>&1; then + pass -c "$check" + logcheckresult "PASS" + return + fi + info -c "$check" + info " * Default ulimit doesn't appear to be set" + logcheckresult "INFO" "Default ulimit doesn't appear to be set" } -# 2.8 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" + local id="2.8" + local desc="Enable user namespace support (Scored)" + local remediation="Please consult the Docker documentation for various ways in which this can be configured depending upon your requirements. The high-level steps are: Ensure that the files /etc/subuid and /etc/subgid exist. Start the docker daemon with --userns-remap flag." + local remediationImpact="User namespace remapping is incompatible with a number of Docker features and also currently breaks some of its functionalities." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + if get_docker_effective_command_line_args '--userns-remap' | grep "userns-remap" >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } -# 2.9 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" + local id="2.9" + local desc="Ensure the default cgroup usage has been confirmed (Scored)" + local remediation="The default setting is in line with good security practice and can be left in situ." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) if get_docker_configuration_file_args 'cgroup-parent' | grep -v ''; then - warn "$check_2_9" + warn -s "$check" 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)) + logcheckresult "WARN" "Confirm cgroup usage" + return fi + if get_docker_effective_command_line_args '--cgroup-parent' | grep "cgroup-parent" >/dev/null 2>&1; then + warn -s "$check" + info " * Confirm cgroup usage" + logcheckresult "WARN" "Confirm cgroup usage" + return + fi + pass -s "$check" + logcheckresult "PASS" } -# 2.10 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" + local id="2.10" + local desc="Ensure base device size is not changed until needed (Scored)" + local remediation="Do not set --storage-opt dm.basesize until needed." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + warn -s "$check" + logcheckresult "WARN" + return fi + if get_docker_effective_command_line_args '--storage-opt' | grep "dm.basesize" >/dev/null 2>&1; then + warn -s "$check" + logcheckresult "WARN" + return + fi + pass -s "$check" + logcheckresult "PASS" } -# 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" + local id="2.11" + local desc="Ensure that authorization for Docker client commands is enabled (Scored)" + local remediation="Install/Create an authorization plugin. Configure the authorization policy as desired. Start the docker daemon using command dockerd --authorization-plugin=" + local remediationImpact="Each Docker command needs to pass through the authorization plugin mechanism. This may have a performance impact" + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + if get_docker_effective_command_line_args '--authorization-plugin' | grep "authorization-plugin" >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } -# 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" + local id="2.12" + local desc="Ensure centralized and remote logging is configured (Scored)" + local remediation="Set up the desired log driver following its documentation. Start the docker daemon using that logging driver. Example: dockerd --log-driver=syslog --log-opt syslog-address=tcp://192.xxx.xxx.xxx" + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + warn -s "$check" + logcheckresult "WARN" + return fi + pass -s "$check" + logcheckresult "PASS" } -# 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" + local id="2.13" + local desc="Ensure live restore is enabled (Scored)" + local remediation="Run Docker in daemon mode and pass --live-restore option." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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 + pass -s "$check" + logcheckresult "PASS" + return fi + if docker info 2>/dev/null | grep -e "Swarm:*\sactive\s*" >/dev/null 2>&1; then + pass -s "$check (Incompatible with swarm mode)" + logcheckresult "PASS" + return + fi + if get_docker_effective_command_line_args '--live-restore' | grep "live-restore" >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } -# 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" + local id="2.14" + local desc="Ensure Userland Proxy is Disabled (Scored)" + local remediation="You should run the Docker daemon using command: dockerd --userland-proxy=false" + local remediationImpact="Some systems with older Linux kernels may not be able to support hairpin NAT and therefore require the userland proxy service. Also, some networking setups can be impacted by the removal of the userland proxy." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + if get_docker_effective_command_line_args '--userland-proxy=false' 2>/dev/null | grep "userland-proxy=false" >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } -# 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" + local id="2.15" + local desc="Ensure that a daemon-wide custom seccomp profile is applied if appropriate (Not Scored)" + local remediation="By default, Docker's default seccomp profile is applied. If this is adequate for your environment, no action is necessary." + local remediationImpact="A misconfigured seccomp profile could possibly interrupt your container environment. You should therefore exercise extreme care if you choose to override the default settings." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -c "$check" + logcheckresult "PASS" + return fi + info -c "$check" + logcheckresult "INFO" } -# 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" + local id="2.16" + local desc="Ensure that experimental features are not implemented in production (Scored)" + local remediation="You should not pass --experimental as a runtime parameter to the Docker daemon on production systems." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - desc_2_16="$desc_2_16 (Deprecated)" - check_2_16="$id_2_16 - $desc_2_16" - info "$desc_2_16" - resulttestjson "INFO" + warn -s "$check" + logcheckresult "WARN" + return fi + local desc="$desc (Deprecated)" + local check="$id - $desc" + info -c "$desc" + logcheckresult "INFO" } -# 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" + local id="2.17" + local desc="Ensure containers are restricted from acquiring new privileges (Scored)" + local remediation="You should run the Docker daemon using command: dockerd --no-new-privileges" + local remediationImpact="no_new_priv prevents LSMs such as SELinux from escalating the privileges of individual containers." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + if get_docker_configuration_file_args 'no-new-privileges' | grep true >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" } check_2_end() { diff --git a/tests/3_docker_daemon_configuration_files.sh b/tests/3_docker_daemon_configuration_files.sh index 7b77829..6d8c984 100644 --- a/tests/3_docker_daemon_configuration_files.sh +++ b/tests/3_docker_daemon_configuration_files.sh @@ -1,190 +1,172 @@ #!/bin/sh 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" + logit "" + local id="3" + local desc="Docker daemon configuration files" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } -# 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" + local id="3.1" + local desc="Ensure that the docker.service file ownership is set to root:root (Scored)" + local remediation="Find out the file location: systemctl show -p FragmentPath docker.service. If the file does not exist, this recommendation is not applicable. If the file does exist, you should run the command chown root:root , in order to set the ownership and group ownership for the file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_1" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $file" + logcheckresult "WARN" "Wrong ownership for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.2" + local desc="Ensure that docker.service file permissions are appropriately set (Scored)" + local remediation="Find out the file location: systemctl show -p FragmentPath docker.service. If the file does not exist, this recommendation is not applicable. If the file exists, run the command chmod 644 to set the file permissions to 644." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) file="$(get_service_file docker.service)" if [ -f "$file" ]; then if [ "$(stat -c %a $file)" -le 644 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_2" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $file" + logcheckresult "WARN" "Wrong permissions for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.3" + local desc="Ensure that docker.socket file ownership is set to root:root (Scored)" + local remediation="Find out the file location: systemctl show -p FragmentPath docker.socket. If the file does not exist, this recommendation is not applicable. If the file exists, run the command chown root:root to set the ownership and group ownership for the file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_3" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $file" + logcheckresult "WARN" "Wrong ownership for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.4" + local desc="Ensure that docker.socket file permissions are set to 644 or more restrictive (Scored)" + local remediation="Find out the file location: systemctl show -p FragmentPath docker.socket. If the file does not exist, this recommendation is not applicable. If the file does exist, you should run the command chmod 644 to set the file permissions to 644." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) file="$(get_service_file docker.socket)" if [ -f "$file" ]; then if [ "$(stat -c %a $file)" -le 644 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_4" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $file" + logcheckresult "WARN" "Wrong permissions for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.5" + local desc="Ensure that the /etc/docker directory ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root /etc/docker. This sets the ownership and group ownership for the directory to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_5" - info " * Directory not found" - resulttestjson "INFO" "Directory not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $directory" + logcheckresult "WARN" "Wrong ownership for $directory" + return fi + info -c "$check" + info " * Directory not found" + logcheckresult "INFO" "Directory not found" } -# 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" + local id="3.6" + local desc="Ensure that /etc/docker directory permissions are set to 755 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 755 /etc/docker. This sets the permissions for the directory to 755." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) directory="/etc/docker" if [ -d "$directory" ]; then if [ "$(stat -c %a $directory)" -le 755 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_6" - info " * Directory not found" - resulttestjson "INFO" "Directory not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $directory" + logcheckresult "WARN" "Wrong permissions for $directory" + return fi + info -c "$check" + info " * Directory not found" + logcheckresult "INFO" "Directory not found" } -# 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" + local id="3.7" + local desc="Ensure that registry certificate file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root /etc/docker/certs.d//*. This would set the individual ownership and group ownership for the registry certificate files to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) directory="/etc/docker/certs.d/" if [ -d "$directory" ]; then fail=0 @@ -195,31 +177,28 @@ check_3_7() { fi done if [ $fail -eq 1 ]; then - warn "$check_3_7" + warn -s "$check" warn " * Wrong ownership for $directory" - resulttestjson "WARN" "Wrong ownership for $directory" - currentScore=$((currentScore - 1)) - else - pass "$check_3_7" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + logcheckresult "WARN" "Wrong ownership for $directory" + return fi - else - info "$check_3_7" - info " * Directory not found" - resulttestjson "INFO" "Directory not found" - currentScore=$((currentScore + 0)) + pass -s "$check" + logcheckresult "PASS" + return fi + info -c "$check" + info " * Directory not found" + logcheckresult "INFO" "Directory not found" } -# 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" + local id="3.8" + local desc="Ensure that registry certificate file permissions are set to 444 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 444 /etc/docker/certs.d//*. This would set the permissions for the registry certificate files to 444." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) directory="/etc/docker/certs.d/" if [ -d "$directory" ]; then fail=0 @@ -230,437 +209,386 @@ check_3_8() { fi done if [ $fail -eq 1 ]; then - warn "$check_3_8" + warn -s "$check" warn " * Wrong permissions for $directory" - resulttestjson "WARN" "Wrong permissions for $directory" - currentScore=$((currentScore - 1)) - else - pass "$check_3_8" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + logcheckresult "WARN" "Wrong permissions for $directory" + return fi - else - info "$check_3_8" - info " * Directory not found" - resulttestjson "INFO" "Directory not found" - currentScore=$((currentScore + 0)) + pass -s "$check" + logcheckresult "PASS" + return fi + info -c "$check" + info " * Directory not found" + logcheckresult "INFO" "Directory not found" } -# 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" + local id="3.9" + local desc="Ensure that TLS CA certificate file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root . This sets the individual ownership and group ownership for the TLS CA certificate file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) if [ -n "$(get_docker_configuration_file_args 'tlscacert')" ]; then tlscacert=$(get_docker_configuration_file_args 'tlscacert') - else - tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_9" - info " * No TLS CA certificate found" - resulttestjson "INFO" "No TLS CA certificate found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $tlscacert" + logcheckresult "WARN" "Wrong ownership for $tlscacert" + return fi + info -c "$check" + info " * No TLS CA certificate found" + logcheckresult "INFO" "No TLS CA certificate found" } -# 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" + local id="3.10" + local desc="Ensure that TLS CA certificate file permissions are set to 444 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 444 . This sets the file permissions on the TLS CA file to 444." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) if [ -n "$(get_docker_configuration_file_args 'tlscacert')" ]; then tlscacert=$(get_docker_configuration_file_args 'tlscacert') - else - tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi if [ -f "$tlscacert" ]; then if [ "$(stat -c %a $tlscacert)" -le 444 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_10" - info " * No TLS CA certificate found" - resulttestjson "INFO" "No TLS CA certificate found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $tlscacert" + logcheckresult "WARN" "Wrong permissions for $tlscacert" + return fi + info -c "$check" + info " * No TLS CA certificate found" + logcheckresult "INFO" "No TLS CA certificate found" } -# 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" + local id="3.11" + local desc="Ensure that Docker server certificate file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root . This sets the individual ownership and the group ownership for the Docker server certificate file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) if [ -n "$(get_docker_configuration_file_args 'tlscert')" ]; then tlscert=$(get_docker_configuration_file_args 'tlscert') - else - tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_11" - info " * No TLS Server certificate found" - resulttestjson "INFO" "No TLS Server certificate found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $tlscert" + logcheckresult "WARN" "Wrong ownership for $tlscert" + return fi + info -c "$check" + info " * No TLS Server certificate found" + logcheckresult "INFO" "No TLS Server certificate found" } -# 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" + local id="3.12" + local desc="Ensure that the Docker server certificate file permissions are set to 444 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 444 . This sets the file permissions of the Docker server certificate file to 444." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) if [ -n "$(get_docker_configuration_file_args 'tlscert')" ]; then tlscert=$(get_docker_configuration_file_args 'tlscert') - else - tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) fi if [ -f "$tlscert" ]; then if [ "$(stat -c %a $tlscert)" -le 444 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_12" - info " * No TLS Server certificate found" - resulttestjson "INFO" "No TLS Server certificate found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $tlscert" + logcheckresult "WARN" "Wrong permissions for $tlscert" + return fi + info -c "$check" + info " * No TLS Server certificate found" + logcheckresult "INFO" "No TLS Server certificate found" } -# 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" + local id="3.13" + local desc="Ensure that the Docker server certificate key file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root . This sets the individual ownership and group ownership for the Docker server certificate key file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) if [ -n "$(get_docker_configuration_file_args 'tlskey')" ]; then tlskey=$(get_docker_configuration_file_args 'tlskey') - else - tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_13" - info " * No TLS Key found" - resulttestjson "INFO" "No TLS Key found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $tlskey" + logcheckresult "WARN" "Wrong ownership for $tlskey" + return fi + info -c "$check" + info " * No TLS Key found" + logcheckresult "INFO" "No TLS Key found" } -# 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" + local id="3.14" + local desc="Ensure that the Docker server certificate key file permissions are set to 400 (Scored)" + local remediation="You should run the following command: chmod 400 . This sets the Docker server certificate key file permissions to 400." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) + tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) if [ -n "$(get_docker_configuration_file_args 'tlskey')" ]; then tlskey=$(get_docker_configuration_file_args 'tlskey') - else - tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1) 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_14" - info " * No TLS Key found" - resulttestjson "INFO" "No TLS Key found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $tlskey" + logcheckresult "WARN" "Wrong permissions for $tlskey" + return fi + info -c "$check" + info " * No TLS Key found" + logcheckresult "INFO" "No TLS Key found" } -# 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" + local id="3.15" + local desc="Ensure that the Docker socket file ownership is set to root:docker (Scored)" + local remediation="You should run the following command: chown root:docker /var/run/docker.sock. This sets the ownership to root and group ownership to docker for the default Docker socket file." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_15" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $file" + logcheckresult "WARN" "Wrong ownership for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.16" + local desc="Ensure that the Docker socket file permissions are set to 660 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 660 /var/run/docker.sock. This sets the file permissions of the Docker socket file to 660." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) file="/var/run/docker.sock" if [ -S "$file" ]; then if [ "$(stat -c %a $file)" -le 660 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_16" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $file" + logcheckresult "WARN" "Wrong permissions for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 3.17 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" + local id="3.17" + local desc="Ensure that the daemon.json file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root /etc/docker/daemon.json. This sets the ownership and group ownership for the file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_17" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $file" + logcheckresult "WARN" "Wrong ownership for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 3.18 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" + local id="3.18" + local desc="Ensure that daemon.json file permissions are set to 644 or more restrictive (Scored)" + local remediation="You should run the following command: chmod 644 /etc/docker/daemon.json. This sets the file permissions for this file to 644." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) file="/etc/docker/daemon.json" if [ -f "$file" ]; then if [ "$(stat -c %a $file)" -le 644 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_18" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $file" + logcheckresult "WARN" "Wrong permissions for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.19" + local desc="Ensure that the /etc/default/docker file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root /etc/default/docker. This sets the ownership and group ownership of the file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_19" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $file" + logcheckresult "WARN" "Wrong ownership for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.20" + local desc="Ensure that the /etc/sysconfig/docker file ownership is set to root:root (Scored)" + local remediation="You should run the following command: chown root:root /etc/sysconfig/docker. This sets the ownership and group ownership for the file to root." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_20" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong ownership for $file" + logcheckresult "WARN" "Wrong ownership for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 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" + local id="3.21" + local desc="Ensure that the /etc/sysconfig/docker file permissions are set to 644 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 644 /etc/sysconfig/docker. This sets the file permissions for this file to 644." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) file="/etc/sysconfig/docker" if [ -f "$file" ]; then if [ "$(stat -c %a $file)" -le 644 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_21" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $file" + logcheckresult "WARN" "Wrong permissions for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } -# 3.22 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" + local id="3.22" + local desc="Ensure that the /etc/default/docker file permissions are set to 644 or more restrictively (Scored)" + local remediation="You should run the following command: chmod 644 /etc/default/docker. This sets the file permissions for this file to 644." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) file="/etc/default/docker" if [ -f "$file" ]; then if [ "$(stat -c %a $file)" -le 644 ]; 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - info "$check_3_22" - info " * File not found" - resulttestjson "INFO" "File not found" - currentScore=$((currentScore + 0)) + warn -s "$check" + warn " * Wrong permissions for $file" + logcheckresult "WARN" "Wrong permissions for $file" + return fi + info -c "$check" + info " * File not found" + logcheckresult "INFO" "File not found" } check_3_end() { diff --git a/tests/4_container_images.sh b/tests/4_container_images.sh index 4e63507..3e83a84 100644 --- a/tests/4_container_images.sh +++ b/tests/4_container_images.sh @@ -1,139 +1,130 @@ #!/bin/sh 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" + logit "" + local id="4" + local desc="Container Images and Build File" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } -# 4.1 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" - - totalChecks=$((totalChecks + 1)) + local id="4.1" + local desc="Ensure that a user for the container has been created (Scored)" + local remediation="You should ensure that the Dockerfile for each container image contains the information: USER . If there is no specific user created in the container base image, then make use of the useradd command to add a specific user before the USER instruction in the Dockerfile." + local remediationImpact="Running as a non-root user can present challenges where you wish to bind mount volumes from the underlying host. In this case, care should be taken to ensure that the user running the contained process can read and write to the bound directory, according to their requirements." + local check="$id - $desc" + starttestjson "$id" "$desc" # If container_users is empty, there are no running containers if [ -z "$containers" ]; then - info "$check_4_1" + info -c "$check" 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=" ]; 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 + logcheckresult "INFO" "No containers running" + return fi + # 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=" ]; then + # If it's the first container, fail the test + if [ $fail -eq 0 ]; then + warn -s "$check" + warn " * Running as root: $c" + root_containers="$root_containers $c" + fail=1 + continue + fi + warn " * Running as root: $c" + root_containers="$root_containers $c" + fi + done + # We went through all the containers and found none running as root + if [ $fail -eq 0 ]; then + pass -s "$check" + logcheckresult "PASS" + return + fi + logcheckresult "WARN" "running as root" "$root_containers" # 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" + local id="4.2" + local desc="Ensure that containers use only trusted base images (Not Scored)" + local remediation="Configure and use Docker Content trust. View the history of each Docker image to evaluate its risk, dependent on the sensitivity of the application you wish to deploy using it. Scan Docker images for vulnerabilities at regular intervals." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_4_2" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 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" + local id="4.3" + local desc="Ensure that unnecessary packages are not installed in the container (Not Scored)" + local remediation="You should not install anything within the container that is not required. You should consider using a minimal base image if you can. Some of the options available include BusyBox and Alpine. Not only can this trim your image size considerably, but there would also be fewer pieces of software which could contain vectors for attack." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_4_3" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 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" + local id="4.4" + local desc="Ensure images are scanned and rebuilt to include security patches (Not Scored)" + local remediation="Images should be re-built ensuring that the latest version of the base images are used, to keep the operating system patch level at an appropriate level. Once the images have been re-built, containers should be re-started making use of the updated images." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_4_4" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 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" + local id="4.5" + local desc="Ensure Content trust for Docker is Enabled (Scored)" + local remediation="Add DOCKER_CONTENT_TRUST variable to the /etc/environment file using command echo DOCKER_CONTENT_TRUST=1 | sudo tee -a /etc/environment." + local remediationImpact="This prevents users from working with tagged images unless they contain a signature." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + warn -s "$check" + logcheckresult "WARN" } -# 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" + local id="4.6" + local desc="Ensure that HEALTHCHECK instructions have been added to container images (Scored)" + local remediation="You should follow the Docker documentation and rebuild your container images to include the HEALTHCHECK instruction." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) fail=0 no_health_images="" for img in $images; do if docker inspect --format='{{.Config.Healthcheck}}' "$img" 2>/dev/null | grep -e "" >/dev/null 2>&1; then if [ $fail -eq 0 ]; then fail=1 - warn "$check_4_6" + warn -s "$check" fi imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) if ! [ "$imgName" = '[]' ]; then @@ -146,30 +137,28 @@ check_4_6() { fi done if [ $fail -eq 0 ]; then - pass "$check_4_6" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Images w/o HEALTHCHECK" "$no_health_images" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Images w/o HEALTHCHECK" "$no_health_images" } -# 4.7 check_4_7() { - id_4_7="4.7" - desc_4_7="Ensure update instructions are not used alone in the Dockerfile (Not Scored)" - check_4_7="$id_4_7 - $desc_4_7" - starttestjson "$id_4_7" "$desc_4_7" + local id="4.7" + local desc="Ensure update instructions are not used alone in the Dockerfile (Not Scored)" + local remediation="You should use update instructions together with install instructions and version pinning for packages while installing them. This prevent caching and force the extraction of the required versions. Alternatively, you could use the --no-cache flag during the docker build process to avoid using cached layers." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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" + info -c "$check" fi imgName=$(docker inspect --format='{{.RepoTags}}' "$img" 2>/dev/null) if ! [ "$imgName" = '[]' ]; then @@ -179,36 +168,33 @@ check_4_7() { 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)) + pass -c "$check" + logcheckresult "PASS" + return fi + logcheckresult "INFO" "Update instructions found" "$update_images" } -# 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" + local id="4.8" + local desc="Ensure setuid and setgid permissions are removed (Not Scored)" + local remediation="You should allow setuid and setgid permissions only on executables which require them. You could remove these permissions at build time by adding the following command in your Dockerfile, preferably towards the end of the Dockerfile: RUN find / -perm /6000 -type f -exec chmod a-s {} ; || true" + local remediationImpact="The above command would break all executables that depend on setuid or setgid permissions including legitimate ones. You should therefore be careful to modify the command to suit your requirements so that it does not reduce the permissions of legitimate programs excessively. Because of this, you should exercise a degree of caution and examine all processes carefully before making this type of modification in order to avoid outages." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_4_8" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 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" + local id="4.9" + local desc="Ensure that COPY is used instead of ADD in Dockerfiles (Not Scored)" + local remediation="You should use COPY rather than ADD instructions in Dockerfiles." + local remediationImpact="Care needs to be taken in implementing this control if the application requires functionality that is part of the ADD instruction, for example, if you need to retrieve files from remote URLS." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) fail=0 add_images="" for img in $images; do @@ -216,49 +202,45 @@ check_4_9() { sed '$d' | grep -q 'ADD'; then if [ $fail -eq 0 ]; then fail=1 - info "$check_4_9" + info -c "$check" 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" + pass -c "$check" + logcheckresult "PASS" + return fi + logcheckresult "INFO" "Images using ADD" "$add_images" } -# 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" + local id="4.10" + local desc="Ensure secrets are not stored in Dockerfiles (Not Scored)" + local remediation="Do not store any kind of secrets within Dockerfiles. Where secrets are required during the build process, make use of a secrets management tool, such as the buildkit builder included with Docker." + local remediationImpact="A proper secrets management process will be required for Docker image building." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_4_10" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 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" + local id="4.11" + local desc="Ensure only verified packages are are installed (Not Scored)" + local remediation="You should use a secure package distribution mechanism of your choice to ensure the authenticity of software packages." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_4_11" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } check_4_end() { diff --git a/tests/5_container_runtime.sh b/tests/5_container_runtime.sh index a42be93..50eecc5 100644 --- a/tests/5_container_runtime.sh +++ b/tests/5_container_runtime.sh @@ -1,39 +1,36 @@ #!/bin/sh 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" + logit "" + local id="5" + local desc="Container Runtime" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } 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=$' - ' + return fi + # Make the loop separator be a new-line in POSIX compliant fashion + set -f; IFS=$' + ' } -# 5.1 check_5_1() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.1" + local desc="Ensure that, if applicable, an AppArmor Profile is enabled (Scored)" + local remediation="If AppArmor is applicable for your Linux OS, you should enable it. Alternatively, Docker's default AppArmor policy can be used." + local remediationImpact="The container will have the security controls defined in the AppArmor profile. It should be noted that if the AppArmor profile is misconfigured, this may cause issues with the operation of the container." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 no_apparmor_containers="" @@ -43,39 +40,36 @@ check_5_1() { if [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=[]" ] || [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=unconfined" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_1" + warn -s "$check" warn " * No AppArmorProfile Found: $c" - no_apparmor_containers="$no_apparmor_containers $c" + no_apparmor_containers="$no_apparmor_containers $c" fail=1 - else - warn " * No AppArmorProfile Found: $c" - no_apparmor_containers="$no_apparmor_containers $c" + continue fi + warn " * No AppArmorProfile Found: $c" + no_apparmor_containers="$no_apparmor_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with no AppArmorProfile" "$no_apparmor_containers" } -# 5.2 check_5_2() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.2" + local desc="Ensure that, if applicable, SELinux security options are set (Scored)" + local remediation="Set the SELinux State. Set the SELinux Policy. Create or import a SELinux policy template for Docker containers. Start Docker in daemon mode with SELinux enabled. Start your Docker container using the security options." + local remediationImpact="Any restrictions defined in the SELinux policy will be applied to your containers. It should be noted that if your SELinux policy is misconfigured, this may have an impact on the correct operation of the affected containers." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 no_securityoptions_containers="" @@ -85,39 +79,36 @@ check_5_2() { if [ "$policy" = "SecurityOpt=" ] || [ "$policy" = "SecurityOpt=[]" ] || [ "$policy" = "SecurityOpt=" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_2" + warn -s "$check" warn " * No SecurityOptions Found: $c" - no_securityoptions_containers="$no_securityoptions_containers $c" + no_securityoptions_containers="$no_securityoptions_containers $c" fail=1 - else - warn " * No SecurityOptions Found: $c" - no_securityoptions_containers="$no_securityoptions_containers $c" + continue fi + warn " * No SecurityOptions Found: $c" + no_securityoptions_containers="$no_securityoptions_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with no SecurityOptions" "$no_securityoptions_containers" } -# 5.3 check_5_3() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.3" + local desc="Ensure that Linux kernel capabilities are restricted within containers (Scored)" + local remediation="You could remove all the currently configured capabilities and then restore only the ones you specifically use: docker run --cap-drop=all --cap-add={,} " + local remediationImpact="Restrictions on processes within a container are based on which Linux capabilities are in force. Removal of the NET_RAW capability prevents the container from creating raw sockets which is good security practice under most circumstances, but may affect some networking utilities." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 caps_containers="" @@ -130,39 +121,36 @@ check_5_3() { if [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=[]' ] && [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=' ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_3" + warn -s "$check" warn " * Capabilities added: $caps to $c" - caps_containers="$caps_containers $c" + caps_containers="$caps_containers $c" fail=1 - else - warn " * Capabilities added: $caps to $c" - caps_containers="$caps_containers $c" + continue fi + warn " * Capabilities added: $caps to $c" + caps_containers="$caps_containers $c" fi done # We went through all the containers and found none with extra capabilities if [ $fail -eq 0 ]; then - pass "$check_5_3" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Capabilities added for containers" "$caps_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Capabilities added for containers" "$caps_containers" } -# 5.4 check_5_4() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.4" + local desc="Ensure that privileged containers are not used (Scored)" + local remediation="You should not run containers with the --privileged flag." + local remediationImpact="If you start a container without the --privileged flag, it will not have excessive default capabilities." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 privileged_containers="" @@ -172,39 +160,36 @@ check_5_4() { if [ "$privileged" = "true" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_4" + warn -s "$check" warn " * Container running in Privileged mode: $c" - privileged_containers="$privileged_containers $c" + privileged_containers="$privileged_containers $c" fail=1 - else - warn " * Container running in Privileged mode: $c" - privileged_containers="$privileged_containers $c" + continue fi + warn " * Container running in Privileged mode: $c" + privileged_containers="$privileged_containers $c" fi done # We went through all the containers and found no privileged containers if [ $fail -eq 0 ]; then - pass "$check_5_4" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers running in privileged mode" "$privileged_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers running in privileged mode" "$privileged_containers" } -# 5.5 check_5_5() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.5" + local desc="Ensure sensitive host system directories are not mounted on containers (Scored)" + local remediation="You should not mount directories which are security sensitive on the host within containers, especially in read-write mode." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" # 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. @@ -219,10 +204,9 @@ check_5_5() { fail=0 sensitive_mount_containers="" for c in $containers; do + volumes=$(docker inspect --format '{{ .Mounts }}' "$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 @@ -233,40 +217,37 @@ check_5_5() { if [ $sensitive -eq 1 ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_5" + warn -s "$check" warn " * Sensitive directory $v mounted in: $c" - sensitive_mount_containers="$sensitive_mount_containers $c:$v" + 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" + continue fi + warn " * Sensitive directory $v mounted in: $c" + sensitive_mount_containers="$sensitive_mount_containers $c:$v" fi done done # We went through all the containers and found none with sensitive mounts if [ $fail -eq 0 ]; then - pass "$check_5_5" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers with sensitive directories mounted" "$sensitive_mount_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with sensitive directories mounted" "$sensitive_mount_containers" } -# 5.6 check_5_6() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.6" + local desc="Ensure sshd is not run within containers (Scored)" + local remediation="Uninstall the SSH daemon from the container and use docker exec to enter a container on the remote host." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 ssh_exec_containers="" @@ -277,21 +258,21 @@ check_5_6() { if [ "$processes" -ge 1 ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_6" + warn -s "$check" warn " * Container running sshd: $c" - ssh_exec_containers="$ssh_exec_containers $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" + ssh_exec_containers="$ssh_exec_containers $c" fi fi exec_check=$(docker exec "$c" ps -el 2>/dev/null) if [ $? -eq 255 ]; then if [ $printcheck -eq 0 ]; then - warn "$check_5_6" + warn -s "$check" printcheck=1 fi warn " * Docker exec fails: $c" @@ -302,27 +283,24 @@ check_5_6() { done # We went through all the containers and found none with sshd if [ $fail -eq 0 ]; then - pass "$check_5_6" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers with sshd/docker exec failures" "$ssh_exec_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with sshd/docker exec failures" "$ssh_exec_containers" } -# 5.7 check_5_7() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.7" + local desc="Ensure privileged ports are not mapped within containers (Scored)" + local remediation="You should not map container ports to privileged host ports when starting a container. You should also, ensure that there is no such container to host privileged port mapping declarations in the Dockerfile." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 privileged_port_containers="" @@ -332,60 +310,56 @@ check_5_7() { # iterate through port range (line delimited) for port in $ports; do - if [ -n "$port" ] && [ "$port" -lt 1024 ]; 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_7" + warn -s "$check" warn " * Privileged Port in use: $port in $c" - privileged_port_containers="$privileged_port_containers $c:$port" + 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" + continue fi + warn " * Privileged Port in use: $port in $c" + privileged_port_containers="$privileged_port_containers $c:$port" fi done done # We went through all the containers and found no privileged ports if [ $fail -eq 0 ]; then - pass "$check_5_7" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers using privileged ports" "$privileged_port_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers using privileged ports" "$privileged_port_containers" } -# 5.8 check_5_8() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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" + local id="5.8" + local desc="Ensure that only needed ports are open on the container (Not Scored)" + local remediation="You should ensure that the Dockerfile for each container image only exposes needed ports." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_5_8" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 5.9 check_5_9() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.9" + local desc="Ensure that the host's network namespace is not shared (Scored)" + local remediation="You should not pass the --net=host option when starting any container." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 net_host_containers="" @@ -395,131 +369,120 @@ check_5_9() { if [ "$mode" = "NetworkMode=host" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_9" + warn -s "$check" warn " * Container running with networking mode 'host': $c" - net_host_containers="$net_host_containers $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" + continue fi + warn " * Container running with networking mode 'host': $c" + net_host_containers="$net_host_containers $c" fi done # We went through all the containers and found no Network Mode host if [ $fail -eq 0 ]; then - pass "$check_5_9" - resulttestjson "PASS" - currentScore=$((currentScore + 0)) - else - resulttestjson "WARN" "Containers running with networking mode 'host'" "$net_host_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers running with networking mode 'host'" "$net_host_containers" } -# 5.10 check_5_10() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.10" + local desc="Ensure that the memory usage for containers is limited (Scored)" + local remediation="You should run the container with only as much memory as it requires by using the --memory argument." + local remediationImpact="If correct memory limits are not set on each container, one process can expand its usage and cause other containers to run out of resources." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 mem_unlimited_containers="" for c in $containers; do + memory=$(docker inspect --format '{{ .HostConfig.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_10" - warn " * Container running without memory restrictions: $c" - mem_unlimited_containers="$mem_unlimited_containers $c" + warn -s "$check" + 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" + continue fi + warn " * Container running without memory restrictions: $c" + mem_unlimited_containers="$mem_unlimited_containers $c" fi done # We went through all the containers and found no lack of Memory restrictions if [ $fail -eq 0 ]; then - pass "$check_5_10" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Container running without memory restrictions" "$mem_unlimited_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Container running without memory restrictions" "$mem_unlimited_containers" } -# 5.11 check_5_11() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.11" + local desc="Ensure that CPU priority is set appropriately on containers (Scored)" + local remediation="You should manage the CPU runtime between your containers dependent on their priority within your organization. To do so start the container using the --cpu-shares argument." + local remediationImpact="If you do not correctly assign CPU thresholds, the container process may run out of resources and become unresponsive. If CPU resources on the host are not constrainted, CPU shares do not place any restrictions on individual resources." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 cpu_unlimited_containers="" for c in $containers; do + shares=$(docker inspect --format '{{ .HostConfig.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_11" - warn " * Container running without CPU restrictions: $c" + warn -s "$check" + 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" + continue fi + warn " * Container running without CPU restrictions: $c" + cpu_unlimited_containers="$cpu_unlimited_containers $c" fi done # We went through all the containers and found no lack of CPUShare restrictions if [ $fail -eq 0 ]; then - pass "$check_5_11" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers running without CPU restrictions" "$cpu_unlimited_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers running without CPU restrictions" "$cpu_unlimited_containers" } -# 5.12 check_5_12() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.12" + local desc="Ensure that the container's root filesystem is mounted as read only (Scored)" + local remediation="You should add a --read-only flag at a container's runtime to enforce the container's root filesystem being mounted as read only." + local remediationImpact="Enabling --read-only at container runtime may break some container OS packages if a data writing strategy is not defined. You should define what the container's data should and should not persist at runtime in order to decide which strategy to use." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 fsroot_mount_containers="" @@ -529,39 +492,36 @@ check_5_12() { if [ "$read_status" = "false" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_12" - warn " * Container running with root FS mounted R/W: $c" - fsroot_mount_containers="$fsroot_mount_containers $c" + warn -s "$check" + 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" + continue fi + warn " * Container running with root FS mounted R/W: $c" + fsroot_mount_containers="$fsroot_mount_containers $c" fi done # We went through all the containers and found no R/W FS mounts if [ $fail -eq 0 ]; then - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers running with root FS mounted R/W" "$fsroot_mount_containers" } -# 5.13 check_5_13() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.13" + local desc="Ensure that incoming container traffic is bound to a specific host interface (Scored)" + local remediation="You should bind the container port to a specific host interface on the desired host port. Example: docker run --detach --publish 10.2.3.4:49153:80 nginx In this example, the container port 80 is bound to the host port on 49153 and would accept incoming connection only from the 10.2.3.4 external interface." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 incoming_unbound_containers="" @@ -570,40 +530,37 @@ check_5_13() { 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" + warn -s "$check" + 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" + continue fi + warn " * Port being bound to wildcard IP: $ip in $c" + incoming_unbound_containers="$incoming_unbound_containers $c:$ip" 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_13" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers with port bound to wildcard IP" "$incoming_unbound_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with port bound to wildcard IP" "$incoming_unbound_containers" } -# 5.14 check_5_14() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.14" + local desc="Ensure that the 'on-failure' container restart policy is set to '5' (Scored)" + local remediation="If you wish a container to be automatically restarted, a sample command is docker run --detach --restart=on-failure:5 nginx" + local remediationImpact="If this option is set, a container will only attempt to restart itself 5 times." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 maxretry_unset_containers="" @@ -613,39 +570,36 @@ check_5_14() { if [ "$policy" != "MaximumRetryCount=5" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_14" - warn " * MaximumRetryCount is not set to 5: $c" - maxretry_unset_containers="$maxretry_unset_containers $c" + warn -s "$check" + warn " * MaximumRetryCount is not set to 5: $c" + maxretry_unset_containers="$maxretry_unset_containers $c" fail=1 - else - warn " * MaximumRetryCount is not set to 5: $c" - maxretry_unset_containers="$maxretry_unset_containers $c" + continue fi + warn " * MaximumRetryCount is not set to 5: $c" + maxretry_unset_containers="$maxretry_unset_containers $c" fi done # We went through all the containers and they all had MaximumRetryCount=5 if [ $fail -eq 0 ]; then - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with MaximumRetryCount not set to 5" "$maxretry_unset_containers" } -# 5.15 check_5_15() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.15" + local desc="Ensure that the host's process namespace is not shared (Scored)" + local remediation="You should not start a container with the --pid=host argument." + local remediationImpact="Container processes cannot see processes on the host system." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 pidns_shared_containers="" @@ -655,39 +609,36 @@ check_5_15() { if [ "$mode" = "PidMode=host" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_15" - warn " * Host PID namespace being shared with: $c" + warn -s "$check" + 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" + continue fi + warn " * Host PID namespace being shared with: $c" + pidns_shared_containers="$pidns_shared_containers $c" fi done # We went through all the containers and found none with PidMode as host if [ $fail -eq 0 ]; then - pass "$check_5_15" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers sharing host PID namespace" "$pidns_shared_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers sharing host PID namespace" "$pidns_shared_containers" } -# 5.16 check_5_16() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.16" + local desc="Ensure that the host's IPC namespace is not shared (Scored)" + local remediation="You should not start a container with the --ipc=host argument." + local remediationImpact="Shared memory segments are used in order to accelerate interprocess communications, commonly in high-performance applications. If this type of application is containerized into multiple containers, you might need to share the IPC namespace of the containers in order to achieve high performance. Under these circumstances, you should still only share container specific IPC namespaces and not the host IPC namespace." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 ipcns_shared_containers="" @@ -697,39 +648,36 @@ check_5_16() { if [ "$mode" = "IpcMode=host" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - warn "$check_5_16" - warn " * Host IPC namespace being shared with: $c" + warn -s "$check" + 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" + continue fi + warn " * Host IPC namespace being shared with: $c" + ipcns_shared_containers="$ipcns_shared_containers $c" fi done # We went through all the containers and found none with IPCMode as host if [ $fail -eq 0 ]; then - pass "$check_5_16" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "WARN" "Containers sharing host IPC namespace" "$ipcns_shared_containers" - currentScore=$((currentScore - 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers sharing host IPC namespace" "$ipcns_shared_containers" } -# 5.17 check_5_17() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.17" + local desc="Ensure that host devices are not directly exposed to containers (Not Scored)" + local remediation="You should not directly expose host devices to containers. If you do need to expose host devices to containers, you should use granular permissions as appropriate to your organization." + local remediationImpact="You would not be able to use host devices directly within containers." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 hostdev_exposed_containers="" @@ -739,39 +687,36 @@ check_5_17() { if [ "$devices" != "Devices=" ] && [ "$devices" != "Devices=[]" ] && [ "$devices" != "Devices=" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - info "$check_5_17" - info " * Container has devices exposed directly: $c" + info -c "$check" + 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" + continue fi + info " * Container has devices exposed directly: $c" + hostdev_exposed_containers="$hostdev_exposed_containers $c" fi done # We went through all the containers and found none with devices if [ $fail -eq 0 ]; then - pass "$check_5_17" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "INFO" "Containers with host devices exposed directly" "$hostdev_exposed_containers" - currentScore=$((currentScore + 0)) + pass -c "$check" + logcheckresult "PASS" + return fi + logcheckresult "INFO" "Containers with host devices exposed directly" "$hostdev_exposed_containers" } -# 5.18 check_5_18() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; then return fi - 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)) + local id="5.18" + local desc="Ensure that the default ulimit is overwritten at runtime if needed (Not Scored)" + local remediation="You should only override the default ulimit settings if needed in a specific case." + local remediationImpact="If ulimits are not set correctly, overutilization by individual containers could make the host system unusable." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 no_ulimit_containers="" @@ -781,39 +726,36 @@ check_5_18() { if [ "$ulimits" = "Ulimits=" ] || [ "$ulimits" = "Ulimits=[]" ] || [ "$ulimits" = "Ulimits=" ]; then # If it's the first container, fail the test if [ $fail -eq 0 ]; then - info "$check_5_18" - info " * Container no default ulimit override: $c" + info -c "$check" + 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" + continue fi + info " * Container no default ulimit override: $c" + no_ulimit_containers="$no_ulimit_containers $c" fi done # We went through all the containers and found none without Ulimits if [ $fail -eq 0 ]; then - pass "$check_5_18" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) - else - resulttestjson "INFO" "Containers with no default ulimit override" "$no_ulimit_containers" - currentScore=$((currentScore + 0)) + pass -c "$check" + logcheckresult "PASS" + return fi + logcheckresult "INFO" "Containers with no default ulimit override" "$no_ulimit_containers" } -# 5.19 check_5_19() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.19" + local desc="Ensure mount propagation mode is not set to shared (Scored)" + local remediation="Do not mount volumes in shared mode propagation." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 mountprop_shared_containers="" @@ -822,39 +764,36 @@ check_5_19() { 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" + warn -s "$check" + 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" + continue fi + warn " * Mount propagation mode is shared: $c" + mountprop_shared_containers="$mountprop_shared_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with shared mount propagation" "$mountprop_shared_containers" } -# 5.20 check_5_20() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.20" + local desc="Ensure that the host's UTS namespace is not shared (Scored)" + local remediation="You should not start a container with the --uts=host argument." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 utcns_shared_containers="" @@ -864,39 +803,36 @@ check_5_20() { 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" + warn -s "$check" + 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" + continue fi + warn " * Host UTS namespace being shared with: $c" + utcns_shared_containers="$utcns_shared_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers sharing host UTS namespace" "$utcns_shared_containers" } -# 5.21 check_5_21() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.21" + local desc="Ensurethe default seccomp profile is not Disabled (Scored)" + local remediation="By default, seccomp profiles are enabled. You do not need to do anything unless you want to modify and use a modified seccomp profile." + local remediationImpact="With Docker 1.10 and greater, the default seccomp profile blocks syscalls, regardless of -- cap-add passed to the container." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 seccomp_disabled_containers="" @@ -905,73 +841,68 @@ check_5_21() { 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" + warn -s "$check" + warn " * Default seccomp profile disabled: $c" seccomp_disabled_containers="$seccomp_disabled_containers $c" fail=1 else - warn " * Default seccomp profile disabled: $c" + 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers with default seccomp profile disabled" "$seccomp_disabled_containers" } -# 5.22 check_5_22() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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" + local id="5.22" + local desc="Ensure that docker exec commands are not used with the privileged option (Scored)" + local remediation="You should not use the --privileged option in docker exec commands." + local remediationImpact="If you need enhanced capabilities within a container, then run it with all the permissions it requires. These should be specified individually." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_5_22" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 5.23 check_5_23() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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" + local id="5.23" + local desc="Ensure that docker exec commands are not used with the user=root option (Not Scored)" + local remediation="You should not use the --user=root option in docker exec commands." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_5_23" - resulttestjson "NOTE" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "NOTE" } -# 5.24 check_5_24() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.24" + local desc="Ensure that cgroup usage is confirmed (Scored)" + local remediation="You should not use the --cgroup-parent option within the docker run command unless strictly required." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 unexpected_cgroup_containers="" @@ -981,38 +912,35 @@ check_5_24() { 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" + warn -s "$check" + 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" + continue fi + warn " * Confirm cgroup usage: $c" + unexpected_cgroup_containers="$unexpected_cgroup_containers $c" 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 + pass -s "$check" + logcheckresult "PASS" 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" + logcheckresult "WARN" "Containers using unexpected cgroup" "$unexpected_cgroup_containers" +} - totalChecks=$((totalChecks + 1)) +check_5_25() { + if [ -z "$containers" ]; then + return + fi + local id="5.25" + local desc="Ensure that the container is restricted from acquiring additional privileges (Scored)" + local remediation="You should start your container with the options: docker run --rm -it --security-opt=no-new-privileges ubuntu bash" + local remediationImpact="The no_new_priv option prevents LSMs like SELinux from allowing processes to acquire new privileges." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 addprivs_containers="" @@ -1020,94 +948,87 @@ check_5_25() { 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" + warn -s "$check" + warn " * Privileges not restricted: $c" addprivs_containers="$addprivs_containers $c" fail=1 - else - warn " * Privileges not restricted: $c" - addprivs_containers="$addprivs_containers $c" + continue fi + warn " * Privileges not restricted: $c" + addprivs_containers="$addprivs_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers without restricted privileges" "$addprivs_containers" } -# 5.26 check_5_26() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.26" + local desc="Ensure that container health is checked at runtime (Scored)" + local remediation="You should run the container using the --health-cmd parameter." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" 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" + warn -s "$check" + 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" + continue fi + warn " * Health check not set: $c" + nohealthcheck_containers="$nohealthcheck_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers without health check" "$nohealthcheck_containers" } -# 5.27 check_5_27() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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" + local id="5.27" + local desc="Ensure that Docker commands always make use of the latest version of their image (Not Scored)" + local remediation="You should use proper version pinning mechanisms (the tag which is assigned by default is still vulnerable to caching attacks) to avoid extracting cached older versions. Version pinning mechanisms should be used for base images, packages, and entire images. You can customize version pinning rules according to your requirements." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - info "$check_5_27" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + info -c "$check" + logcheckresult "INFO" } -# 5.28 check_5_28() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.28" + local desc="Ensure that the PIDs cgroup limit is used (Scored)" + local remediation="Use --pids-limit flag with an appropriate value when launching the container." + local remediationImpact="Set the PIDs limit value as appropriate. Incorrect values might leave containers unusable." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 nopids_limit_containers="" @@ -1117,39 +1038,36 @@ check_5_28() { if [ "$pidslimit" = "0" ] || [ "$pidslimit" = "" ] || [ "$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" + warn -s "$check" + 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" + continue fi + warn " * PIDs limit not set: $c" + nopids_limit_containers="$nopids_limit_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers without PIDs cgroup limit" "$nopids_limit_containers" } -# 5.29 check_5_29() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.29" + local desc="Ensure that Docker's default bridge "docker0" is not used (Not Scored)" + local remediation="You should follow the Docker documentation and set up a user-defined network. All the containers should be run in this network." + local remediationImpact="User-defined networks need to be configured and managed in line with organizational security policy." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 docker_network_containers="" @@ -1159,50 +1077,46 @@ check_5_29() { 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 + if [ -n "$docker0Containers" ]; then + if [ $fail -eq 0 ]; then + info -c "$check" + fail=1 fi - currentScore=$((currentScore + 0)) + 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 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)) + pass -c "$check" + logcheckresult "PASS" + return fi + logcheckresult "INFO" "Containers using docker0 network" "$docker_network_containers" } -# 5.30 check_5_30() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.30" + local desc="Ensure that the host's user namespaces are not shared (Scored)" + local remediation="You should not share user namespaces between host and containers." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 hostns_shared_containers="" @@ -1210,39 +1124,36 @@ check_5_30() { 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" + warn -s "$check" + 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" + continue fi + warn " * Namespace shared: $c" + hostns_shared_containers="$hostns_shared_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers sharing host user namespace" "$hostns_shared_containers" } -# 5.31 check_5_31() { - if [ "$running_containers" -ne 1 ]; then + if [ -z "$containers" ]; 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)) + local id="5.31" + local desc="Ensure that the Docker socket is not mounted inside any containers (Scored)" + local remediation="You should ensure that no containers mount docker.sock as a volume." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" fail=0 docker_sock_containers="" @@ -1250,25 +1161,23 @@ check_5_31() { 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" + warn -s "$check" + 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" + continue fi + warn " * Docker socket shared: $c" + docker_sock_containers="$docker_sock_containers $c" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Containers sharing docker socket" "$docker_sock_containers" } check_5_end() { diff --git a/tests/6_docker_security_operations.sh b/tests/6_docker_security_operations.sh index ce9b257..00514f8 100644 --- a/tests/6_docker_security_operations.sh +++ b/tests/6_docker_security_operations.sh @@ -1,22 +1,22 @@ #!/bin/sh 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" + logit "" + local id="6" + local desc="Docker Security Operations" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } -# 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" + local id="6.1" + local desc="Ensure that image sprawl is avoided (Not Scored)" + local remediation="You should keep only the images that you actually need and establish a workflow to remove old or stale images from the host. Additionally, you should use features such as pull-by-digest to get specific images from the registry." + local remediationImpact="docker system prune -a removes all exited containers as well as all images and volumes that are not referenced by running containers, including for UCP and DTR." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) images=$(docker images -q | sort -u | wc -l | awk '{print $1}') active_images=0 @@ -26,37 +26,33 @@ check_6_1() { fi done - info "$check_6_1" - info " * There are currently: $images images" + info -c "$check" + info " * There are currently: $images images" if [ "$active_images" -lt "$((images / 2))" ]; then info " * Only $active_images out of $images are in use" fi - resulttestjson "INFO" "$active_images active/$images in use" - currentScore=$((currentScore + 0)) + logcheckresult "INFO" "$active_images active/$images in use" } -# 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" + local id="6.2" + local desc="Ensure that container sprawl is avoided (Not Scored)" + local remediation="You should periodically check your container inventory on each host and clean up containers which are not in active use with the command: docker container prune" + local remediationImpact="You should retain containers that are actively in use, and delete ones which are no longer needed." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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))" + info -c "$check" 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)) + logcheckresult "INFO" "$total_containers total/$running_containers running" } check_6_end() { diff --git a/tests/7_docker_swarm_configuration.sh b/tests/7_docker_swarm_configuration.sh index c3b9187..f7faab6 100644 --- a/tests/7_docker_swarm_configuration.sh +++ b/tests/7_docker_swarm_configuration.sh @@ -1,93 +1,85 @@ #!/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" + logit "" + local id="7" + local desc="Docker Swarm Configuration" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } -# 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" + local id="7.1" + local desc="Ensure swarm mode is not Enabled, if not needed (Scored)" + local remediation="If swarm mode has been enabled on a system in error, you should run the command: docker swarm leave" + local remediationImpact="Disabling swarm mode will impact the operation of Docker Enterprise components if these are in use." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + warn -s "$check" + logcheckresult "WARN" } -# 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" + local id="7.2" + local desc="Ensure that the minimum number of manager nodes have been created in a swarm (Scored)" + local remediation="If an excessive number of managers is configured, the excess nodes can be demoted to workers using command: docker node demote " + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - pass "$check_7_2 (Swarm mode not enabled)" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + warn -s "$check" + logcheckresult "WARN" + return fi + pass -s "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.3" + local desc="Ensure that swarm services are bound to a specific host interface (Scored)" + local remediation="Resolving this issues requires re-initialization of the swarm, specifying a specific interface for the --listen-addr parameter." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - pass "$check_7_3 (Swarm mode not enabled)" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + warn -s "$check" + logcheckresult "WARN" + return fi + pass -s "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.4" + local desc="Ensure that all Docker swarm overlay networks are encrypted (Scored)" + local remediation="You should create overlay networks the with --opt encrypted flag." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) fail=0 unencrypted_networks="" for encnet in $(docker network ls --filter driver=overlay --quiet); do @@ -95,7 +87,7 @@ check_7_4() { 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" + warn -s "$check" fail=1 fi warn " * Unencrypted overlay network: $(docker network inspect --format '{{ .Name }} ({{ .Scope }})' "$encnet")" @@ -104,145 +96,128 @@ check_7_4() { 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)) + pass -s "$check" + logcheckresult "PASS" + return fi + logcheckresult "WARN" "Unencrypted overlay networks:" "$unencrypted_networks" } -# 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" + local id="7.5" + local desc="Ensure that Docker's secret management commands are used for managing secrets in a swarm cluster (Not Scored)" + local remediation="You should follow the docker secret documentation and use it to manage secrets effectively." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -c "$check" + logcheckresult "PASS" + return fi - else - pass "$check_7_5 (Swarm mode not enabled)" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + info -c "$check" + logcheckresult "INFO" + return fi + pass -c "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.6" + local desc="Ensure that swarm manager is run in auto-lock mode (Scored)" + local remediation="If you are initializing a swarm, use the command: docker swarm init --autolock. If you want to set --autolock on an existing swarm manager node, use the command: docker swarm update --autolock." + local remediationImpact="A swarm in auto-lock mode will not recover from a restart without manual intervention from an administrator to enter the unlock key. This may not always be desirable, and should be reviewed at a policy level." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + warn -s "$check" + logcheckresult "WARN" + return fi - else - pass "$check_7_6 (Swarm mode not enabled)" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + pass -s "$check" + logcheckresult "PASS" + return fi + pass -s "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.7" + local desc="Ensure that the swarm manager auto-lock key is rotated periodically (Not Scored)" + local remediation="You should run the command docker swarm unlock-key --rotate to rotate the keys. To facilitate auditing of this recommendation, you should maintain key rotation records and ensure that you establish a pre-defined frequency for key rotation." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + note -c "$check" + logcheckresult "NOTE" + return fi + pass -c "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.8" + local desc="Ensure that node certificates are rotated as appropriate (Not Scored)" + local remediation="You should run the command docker swarm update --cert-expiry 48h to set the desired expiry time on the node certificate." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + pass -c "$check" + logcheckresult "PASS" + return fi - else - pass "$check_7_8 (Swarm mode not enabled)" - resulttestjson "PASS" - currentScore=$((currentScore + 1)) + info -c "$check" + logcheckresult "INFO" + return fi + pass -c "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.9" + local desc="Ensure that CA certificates are rotated as appropriate (Not Scored)" + local remediation="You should run the command docker swarm ca --rotate to rotate a certificate." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + info -c "$check" + logcheckresult "INFO" + return fi + pass -c "$check (Swarm mode not enabled)" + logcheckresult "PASS" } -# 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" + local id="7.10" + local desc="Ensure that management plane traffic is separated from data plane traffic (Not Scored)" + local remediation="You should initialize the swarm with dedicated interfaces for management and data planes respectively. Example: docker swarm init --advertise-addr=192.168.0.1 --data-path-addr=17.1.0.3" + local remediationImpact="This requires two network interfaces per node." + local check="$id - $desc" + starttestjson "$id" "$desc" - 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)) + info -c "$check" + logcheckresult "INFO" + return fi + pass -c "$check (Swarm mode not enabled)" + logcheckresult "PASS" } check_7_end() { diff --git a/tests/8_docker_enterprise_configuration.sh b/tests/8_docker_enterprise_configuration.sh index 9fce978..aef9d23 100644 --- a/tests/8_docker_enterprise_configuration.sh +++ b/tests/8_docker_enterprise_configuration.sh @@ -1,20 +1,19 @@ #!/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" + logit "" + local id="8" + local desc="Docker Enterprise Configuration" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } check_product_license() { + enterprise_license=1 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 } @@ -23,129 +22,120 @@ check_8_1() { 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" + local id="8.1" + local desc="Universal Control Plane Configuration" + local check="$id - $desc" + info "$check" } -# 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" + local id="8.1.1" + local desc="Configure the LDAP authentication service (Scored)" + local remediation="You can configure LDAP integration via the UCP Admin Settings UI. LDAP integration can also be enabled via a configuration file" + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_1" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="8.1.2" + local desc="Use external certificates (Scored)" + local remediation="You can configure your own certificates for UCP either during installation or after installation via the UCP Admin Settings user interface." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_2" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="8.1.3" + local desc="Enforce the use of client certificate bundles for unprivileged users (Not Scored)" + local remediation="Client certificate bundles can be created in one of two ways. User Management UI: UCP Administrators can provision client certificate bundles on behalf of users. Self-Provision: Users with access to the UCP console can create client certificate bundles themselves." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_3" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="8.1.4" + local desc="Configure applicable cluster role-based access control policies (Not Scored)" + local remediation="UCP RBAC components can be configured as required via the UCP User Management UI." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_4" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="8.1.5" + local desc="Enable signed image enforcement (Scored)" + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_5" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="8.1.6" + local desc="Set the Per-User Session Limit to a value of '3' or lower (Scored)" + local remediation="Retrieve a UCP API token. Retrieve and save UCP config. Open the ucp-config.toml file, set the per_user_limit entry under the [auth.sessions] section to a value of 3 or lower, but greater than 0. Update UCP with the new configuration." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_6" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } -# 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" + local id="8.1.7" + local desc="Set the 'Lifetime Minutes' and 'Renewal Threshold Minutes' values to '15' or lower and '0' respectively (Scored)" + local remediation="Retrieve a UCP API token. Retrieve and save UCP config. Open the ucp-config.toml file, set the lifetime_minutes and renewal_threshold_minutes entries under the [auth.sessions] section to values of 15 or lower and 0 respectively. Update UCP with the new configuration." + local remediationImpact="Setting the Lifetime Minutes setting to a value that is too lower would result in users having to constantly re-authenticate to their Docker Enterprise cluster." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_1_7" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } check_8_2() { @@ -153,11 +143,10 @@ check_8_2() { 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" + local id="8.2" + local desc="Docker Trusted Registry Configuration" + local check="$id - $desc" + info "$check" } check_8_2_1() { @@ -165,15 +154,15 @@ check_8_2_1() { 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" + local id="8.2.1" + local desc="Enable image vulnerability scanning (Scored)" + local remediation="You can navigate to DTR Settings UI and select the Security tab to access the image scanning configuration. Select the Enable Scanning slider to enable this functionality." + local remediationImpact="None." + local check="$id - $desc" + starttestjson "$id" "$desc" - totalChecks=$((totalChecks + 1)) - note "$check_8_2_1" - resulttestjson "INFO" - currentScore=$((currentScore + 0)) + note -c "$check" + logcheckresult "INFO" } check_8_end() { diff --git a/tests/99_community_checks.sh b/tests/99_community_checks.sh index 9ad1774..e0ad438 100644 --- a/tests/99_community_checks.sh +++ b/tests/99_community_checks.sh @@ -1,57 +1,87 @@ #!/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" + logit "" + local id="99" + local desc="Community contributed checks" + checkHeader="$id - $desc" + info "$checkHeader" + startsectionjson "$id" "$desc" } -# check_c_1 check_c_1() { - check_c_1="C.1 - This is a example check" - totalChecks=$((totalChecks + 1)) + local id="C.1" + local desc="This is a example check for a Scored check" + local remediation="This is an example remediation measure for a Scored check" + local remediationImpact="This is an example remediation impact for a Scored check" + local check="$id - $desc" + starttestjson "$id" "$desc" + 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" + pass -s "$check" + logcheckresult "PASS" + return fi + if docker info --format='{{ .Architecture }}' | grep 'aarch64' 2>/dev/null 1>&2; then + info -c "$check" + logcheckresult "INFO" + return + fi + warn -s "$check" + logcheckresult "WARN" +} + +check_c_1_1() { + local id="C.1.1" + local desc="This is a example check for a Not Scored check" + local remediation="This is an example remediation measure for a Not Scored check" + local remediationImpact="This is an example remediation impact for a Not Scored check" + local check="$id - $desc" + starttestjson "$id" "$desc" + + if docker info --format='{{ .Architecture }}' | grep 'x86_64' 2>/dev/null 1>&2; then + pass -c "$check" + logcheckresult "PASS" + return + fi + if docker info --format='{{ .Architecture }}' | grep 'aarch64' 2>/dev/null 1>&2; then + info -c "$check" + logcheckresult "INFO" + return + fi + warn -c "$check" + logcheckresult "WARN" } -# 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" + local id="C.2" + local desc="Ensure operations on legacy registry (v1) are Disabled" + local remediation="Start docker daemon with --disable-legacy-registry=false flag. Starting with Docker 17.12, support for V1 registries has been removed, and the --disable-legacy-registry flag can no longer be used." + local remediationImpact="Prevents the docker daemon from pull, push, and login operations against v1 registries." + local check="$id - $desc" + starttestjson "$id" "$desc" 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)) + pass -s "$check" + logcheckresult "PASS" + return fi - else - desc_c_2="$desc_c_2 (Deprecated)" - check_c_2="$id_c_2 - $desc_c_2" - info "$check_c_2" - resulttestjson "INFO" + if get_docker_effective_command_line_args '--disable-legacy-registry' | grep "disable-legacy-registry" >/dev/null 2>&1; then + pass -s "$check" + logcheckresult "PASS" + return + fi + warn -s "$check" + logcheckresult "WARN" + return fi + local desc="$desc (Deprecated)" + local check="$id - $desc" + info -c "$check" + logcheckresult "INFO" } check_c_end() { diff --git a/tests/TESTS.md b/tests/TESTS.md new file mode 100644 index 0000000..fe2d799 --- /dev/null +++ b/tests/TESTS.md @@ -0,0 +1,266 @@ +# Available Checks +Check ID | Category | Subcategory | Check Name +------------ | ------------ | ------------ | ------------ +`host_configuration` | Host Configuration +`host_general_configuration` | | General Configuration +`check_1_1_1` | | | Ensure the container host has been Hardened (Not Scored) +`check_1_1_2` | | | Ensure that the version of Docker is up to date (Not Scored) +`linux_hosts_specific_configuration` | | Linux Hosts Specific Configuration +`check_1_2_1` | | | Ensure a separate partition for containers has been created (Scored) +`check_1_2_2` | | | Ensure only trusted users are allowed to control Docker daemon (Scored) +`check_1_2_3` | | | Ensure auditing is configured for the Docker daemon (Scored) +`check_1_2_4` | | | Ensure auditing is configured for Docker files and directories - /var/lib/docker (Scored) +`check_1_2_5` | | | Ensure auditing is configured for Docker files and directories - /etc/docker (Scored) +`check_1_2_6` | | | Ensure auditing is configured for Docker files and directories - docker.service (Scored) +`check_1_2_7` | | | Ensure auditing is configured for Docker files and directories - docker.socket (Scored) +`check_1_2_8` | | | Ensure auditing is configured for Docker files and directories - /etc/default/docker (Scored) +`check_1_2_9` | | | Ensure auditing is configured for Docker files and directories - /etc/sysconfig/docker (Scored) +`check_1_2_10` | | | Ensure auditing is configured for Docker files and directories - /etc/docker/daemon.json (Scored) +`check_1_2_11` | | | Ensure auditing is configured for Docker files and directories - /usr/bin/containerd (Scored) +`check_1_2_12` | | | Ensure auditing is configured for Docker files and directories - /usr/sbin/runc (Scored) +`docker_daemon_configuration` | Docker daemon configuration +`check_2_1` | | Ensure network traffic is restricted between containers on the default bridge (Scored) +`check_2_2` | | Ensure the logging level is set to 'info' (Scored) +`check_2_3` | | Ensure Docker is allowed to make changes to iptables (Scored) +`check_2_4` | | Ensure insecure registries are not used (Scored) +`check_2_5` | | Ensure aufs storage driver is not used (Scored) +`check_2_6` | | Ensure TLS authentication for Docker daemon is configured (Scored) +`check_2_7` | | Ensure the default ulimit is configured appropriately (Not Scored) +`check_2_8` | | Enable user namespace support (Scored) +`check_2_9` | | Ensure the default cgroup usage has been confirmed (Scored) +`check_2_10` | | Ensure base device size is not changed until needed (Scored) +`check_2_11` | | Ensure that authorization for Docker client commands is enabled (Scored) +`check_2_12` | | Ensure centralized and remote logging is configured (Scored) +`check_2_13` | | Ensure live restore is enabled (Scored) +`check_2_14` | | Ensure Userland Proxy is Disabled (Scored) +`check_2_15` | | Ensure that a daemon-wide custom seccomp profile is applied if appropriate (Not Scored) +`check_2_16` | | Ensure that experimental features are not implemented in production (Scored) +`check_2_17` | | Ensure containers are restricted from acquiring new privileges (Scored) +`docker_daemon_files` | Docker daemon configuration files +`check_3_1` | | Ensure that the docker.service file ownership is set to root:root (Scored) +`check_3_2` | | Ensure that docker.service file permissions are appropriately set (Scored) +`check_3_3` | | Ensure that docker.socket file ownership is set to root:root (Scored) +`check_3_4` | | Ensure that docker.socket file permissions are set to 644 or more restrictive (Scored) +`check_3_5` | | Ensure that the /etc/docker directory ownership is set to root:root (Scored) +`check_3_6` | | Ensure that /etc/docker directory permissions are set to 755 or more restrictively (Scored) +`check_3_7` | | Ensure that registry certificate file ownership is set to root:root (Scored) +`check_3_8` | | Ensure that registry certificate file permissions are set to 444 or more restrictively (Scored) +`check_3_9` | | Ensure that TLS CA certificate file ownership is set to root:root (Scored) +`check_3_10` | | Ensure that TLS CA certificate file permissions are set to 444 or more restrictively (Scored) +`check_3_11` | | Ensure that Docker server certificate file ownership is set to root:root (Scored) +`check_3_12` | | Ensure that the Docker server certificate file permissions are set to 444 or more restrictively (Scored) +`check_3_13` | | Ensure that the Docker server certificate key file ownership is set to root:root (Scored) +`check_3_14` | | Ensure that the Docker server certificate key file permissions are set to 400 (Scored) +`check_3_15` | | Ensure that the Docker socket file ownership is set to root:docker (Scored) +`check_3_16` | | Ensure that the Docker socket file permissions are set to 660 or more restrictively (Scored) +`check_3_17` | | Ensure that the daemon.json file ownership is set to root:root (Scored) +`check_3_18` | | Ensure that daemon.json file permissions are set to 644 or more restrictive (Scored) +`check_3_19` | | Ensure that the /etc/default/docker file ownership is set to root:root (Scored) +`check_3_20` | | Ensure that the /etc/sysconfig/docker file ownership is set to root:root (Scored) +`check_3_21` | | Ensure that the /etc/sysconfig/docker file permissions are set to 644 or more restrictively (Scored) +`check_3_22` | | Ensure that the /etc/default/docker file permissions are set to 644 or more restrictively (Scored) +`container_images` | Container Images and Build File +`check_4.1` | | Ensure that a user for the container has been created (Scored) +`check_4.2` | | Ensure that containers use only trusted base images (Not Scored) +`check_4.3` | | Ensure that unnecessary packages are not installed in the container (Not Scored) +`check_4.4` | | Ensure images are scanned and rebuilt to include security patches (Not Scored) +`check_4.5` | | Ensure Content trust for Docker is Enabled (Scored) +`check_4.6` | | Ensure that HEALTHCHECK instructions have been added to container images (Scored) +`check_4.7` | | Ensure update instructions are not used alone in the Dockerfile (Not Scored) +`check_4.8` | | Ensure setuid and setgid permissions are removed (Not Scored) +`check_4.9` | | Ensure that COPY is used instead of ADD in Dockerfiles (Not Scored) +`check_4.10` | | Ensure secrets are not stored in Dockerfiles (Not Scored) +`check_4.11` | | Ensure only verified packages are are installed (Not Scored) +`container_runtime` | Container Runtime +`check_running_containers` | | Check if exists running containers +`check_5_1` | | Ensure that, if applicable, an AppArmor Profile is enabled (Scored) +`check_5_2` | | Ensure that, if applicable, SELinux security options are set (Scored) +`check_5_3` | | Ensure that Linux kernel capabilities are restricted within containers (Scored) +`check_5_4` | | Ensure that privileged containers are not used (Scored) +`check_5_5` | | Ensure sensitive host system directories are not mounted on containers (Scored) +`check_5_6` | | Ensure sshd is not run within containers (Scored) +`check_5_7` | | Ensure privileged ports are not mapped within containers (Scored) +`check_5_8` | | Ensure that only needed ports are open on the container (Not Scored) +`check_5_9` | | Ensure that the host's network namespace is not shared (Scored) +`check_5_10` | | Ensure that the memory usage for containers is limited (Scored) +`check_5_11` | | Ensure that CPU priority is set appropriately on containers (Scored) +`check_5_12` | | Ensure that the container's root filesystem is mounted as read only (Scored) +`check_5_13` | | Ensure that incoming container traffic is bound to a specific host interface (Scored) +`check_5_14` | | Ensure that the 'on-failure' container restart policy is set to '5' (Scored) +`check_5_15` | | Ensure that the host's process namespace is not shared (Scored) +`check_5_16` | | Ensure that the host's IPC namespace is not shared (Scored) +`check_5_17` | | Ensure that host devices are not directly exposed to containers (Not Scored) +`check_5_18` | | Ensure that the default ulimit is overwritten at runtime if needed (Not Scored) +`check_5_19` | | Ensure mount propagation mode is not set to shared (Scored) +`check_5_20` | | Ensure that the host's UTS namespace is not shared (Scored) +`check_5_21` | | Ensurethe default seccomp profile is not Disabled (Scored) +`check_5_22` | | Ensure that docker exec commands are not used with the privileged option (Scored) +`check_5_23` | | Ensure that docker exec commands are not used with the user=root option (Not Scored) +`check_5_24` | | Ensure that cgroup usage is confirmed (Scored) +`check_5_25` | | Ensure that the container is restricted from acquiring additional privileges (Scored) +`check_5_26` | | Ensure that container health is checked at runtime (Scored) +`check_5_27` | | Ensure that Docker commands always make use of the latest version of their image (Not Scored) +`check_5_28` | | Ensure that the PIDs cgroup limit is used (Scored) +`check_5_29` | | Ensure that Docker's default bridge docker0 is not used (Not Scored) +`check_5_30` | | Ensure that the host's user namespaces are not shared (Scored) +`check_5_31` | | Ensure that the Docker socket is not mounted inside any containers (Scored) +`docker_security_operations` | Docker Security Operations +`check_6.1` | | Ensure that image sprawl is avoided (Not Scored) +`check_6.2` | | Ensure that container sprawl is avoided (Not Scored) +`docker_swarm_configuration` | Docker Swarm Configuration +`check_7.1` | | Ensure swarm mode is not Enabled, if not needed (Scored) +`check_7.2` | | Ensure that the minimum number of manager nodes have been created in a swarm (Scored) (Swarm mode not enabled) +`check_7.3` | | Ensure that swarm services are bound to a specific host interface (Scored) (Swarm mode not enabled) +`check_7.4` | | Ensure that all Docker swarm overlay networks are encrypted (Scored) +`check_7.5` | | Ensure that Docker's secret management commands are used for managing secrets in a swarm cluster (Not Scored) (Swarm mode not enabled) +`check_7.6` | | Ensure that swarm manager is run in auto-lock mode (Scored) (Swarm mode not enabled) +`check_7.7` | | Ensure that the swarm manager auto-lock key is rotated periodically (Not Scored) (Swarm mode not enabled) +`check_7.8` | | Ensure that node certificates are rotated as appropriate (Not Scored) (Swarm mode not enabled) +`check_7.9` | | Ensure that CA certificates are rotated as appropriate (Not Scored) (Swarm mode not enabled) +`check_7.10` | | Ensure that management plane traffic is separated from data plane traffic (Not Scored) (Swarm mode not enabled) +`docker_enterprise_configuration` | Docker Enterprise Configuration +`check_product_license` | | Check Docker license +`universal_control_plane_configuration` | | Universal Control Plane Configuration +`check_8.1.1` | | | Configure the LDAP authentication service (Scored) +`check_8.1.2` | | | Use external certificates (Scored) +`check_8.1.3` | | | Enforce the use of client certificate bundles for unprivileged users (Not Scored) +`check_8.1.4` | | | Configure applicable cluster role-based access control policies (Not Scored) +`check_8.1.5` | | | Enable signed image enforcement (Scored) +`check_8.1.6` | | | Set the Per-User Session Limit to a value of '3' or lower (Scored) +`check_8.1.7` | | | Set the 'Lifetime Minutes' and 'Renewal Threshold Minutes' values to '15' or lower and '0' respectively (Scored) +`docker_trusted_registry_configuration` | | Docker Trusted Registry Configuration +`check_8.2.1` | | | Enable image vulnerability scanning (Scored) +`community_checks` | Community contributed checks +`check_c_1` | | This is a example check +`check_c_2` | | Ensure operations on legacy registry (v1) are Disabled (Deprecated) + +## Another Check ID are: +- `community_checks` -> Run all community checks +- `community` -> Is an alias for `community_checks` +- `cis` -> Run all bellow checks category: + - `host_configuration` + - `docker_daemon_configuration` + - `docker_daemon_files` + - `container_images` + - `container_runtime` + - `docker_security_operations` + - `docker_swarm_configuration` + - `docker_enterprise_configuration` +- `all` -> Run all bellow checks category: + - `cis` + - `community` +- `cis_level1` -> Run all bellow checks: + - `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` +- `host_configuration_level1` -> Run all bellow checks: + - `check_1_1_1` + - `check_1_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` +- `docker_daemon_configuration_level1` -> Run all bellow checks: + - `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` +- `docker_daemon_files_level1` -> Run all bellow checks: + - `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` +- `container_images_level1` -> Run all bellow checks: + - `check_4_1` + - `check_4_2` + - `check_4_3` + - `check_4_4` + - `check_4_6` + - `check_4_7` + - `check_4_9` + - `check_4_10` +- `container_runtime_level1` -> Run all bellow checks: + - `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` +- `docker_security_operations_level1` -> Run all bellow checks: + - `check_6_1` + - `check_6_2` +- `docker_swarm_configuration_level1` -> Run all bellow checks: + - `check_7_1` + - `check_7_2` + - `check_7_3` + - `check_7_4` + - `check_7_7` +- `docker_enterprise_configuration_level1` -> Run all bellow checks: + - `check_product_license` + - `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_1`