This commit is contained in:
Alexei Ledenev 2017-03-24 17:50:33 +00:00 committed by GitHub
commit dac8422579
17 changed files with 997 additions and 0 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
*.log
config/*_config.sh

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "bats_tests/test_helper/bats-assert"]
path = bats_tests/test_helper/bats-assert
url = https://github.com/ztombol/bats-assert
[submodule "bats_tests/test_helper/bats-support"]
path = bats_tests/test_helper/bats-support
url = https://github.com/ztombol/bats-support

View file

@ -45,6 +45,61 @@ The [distribution specific Dockerfiles](https://github.com/docker/docker-bench-s
may also help if the distribution you're using haven't yet shipped Docker
version 1.10.0 or later.
## Running Docker Bench Bats tests
[Bats](https://github.com/sstephenson/bats) is a [TAP](http://testanything.org/)-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected.
All Docker Bench scipts are also available as Bats tests. Also container level (and image level) tests are automatically generated for all containers available on host. It's possible to run all or only selected test(s), if you like.
By default TAP test results are reported, but it's possible to produce a "pretty" printed output too.
Use the following command to run Docker Bench Bats tests:
```
Help documentation for run_tests.sh
Basic usage: run_tests.sh [-c] [-p|-t] [-o path] <test> [<test> ...]
Command line switches are optional. The following switches are recognized.
-c --Displays number of tests. No further functions are performed.
-g --Generates all CIS Bats tests without execution. No further functions are performed.
-p --Show results in pretty format.
-t --Show results in TAP format. This is the default format.
-r --Create test results files: tests_<timestamp>.tap in test result folder.
-o --Specify test result folder. Default to /var/docker-bench/results.
-h --Displays this help message. No further functions are performed.
Example: run_tests.sh -t -o /var/docker-bench/results
```
**Note:**: You need to run `run_tests.sh` on Docker host as `root` user.
### Running Docker Bench Bats tests from Docker image
First, clone and compile your `docker-bench-tests` Docker image.
```sh
git clone https://github.com/gaia-adm/docker-bench-security.git
cd docker-bench-security
docker build -t docker-bench-tests -f bats.Dockerfile .
```
Then run `docker-bench-tests` container (as bellow). Test results will be saved into `/var/docker-bench` folder in TAP format. Test results file is named accoring to the `test_<timestamp>.tap` pattern.
```sh
docker run -it --net host --pid host --cap-add audit_control \
-v /var/lib:/var/lib \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/lib/systemd:/usr/lib/systemd \
-v /var/docker-bench:/var/docker-bench \
-v /etc/fstab:/etc/fstab \
-v /etc/docker:/etc/docker \
-v /etc/default/docker:/etc/default/docker \
-v /etc/group:/etc/group \
--label docker_bench_security \
docker-bench-tests
```
## Building Docker Bench for Security
If you wish to build and run this container yourself, you can follow the

42
bats.Dockerfile Normal file
View file

@ -0,0 +1,42 @@
# REPOSITORY https://github.com/docker/docker-bench-securit
FROM alpine:3.3
MAINTAINER dockerbench.com
MAINTAINER Alexei Ledenev <alexei.led@gmail.com>
ENV VERSION 1.11.1
ENV BATS_VERSION 0.4.0
ENV BATS_SHA_256 480d8d64f1681eee78d1002527f3f06e1ac01e173b761bc73d0cf33f4dc1d8d7
LABEL docker_bench_security=true
RUN apk --update add curl bash ncurses \
&& rm -rf /var/lib/apt/lists/* \
&& rm /var/cache/apk/*
RUN curl -o "/tmp/v${BATS_VERSION}.tar.gz" -LS "https://github.com/sstephenson/bats/archive/v${BATS_VERSION}.tar.gz" && \
echo "${BATS_SHA_256} v${BATS_VERSION}.tar.gz" > /tmp/v${BATS_VERSION}.tar.gz.sha256 && \
cd /tmp && sha256sum -c v${BATS_VERSION}.tar.gz.sha256 && \
tar -xvzf "/tmp/v${BATS_VERSION}.tar.gz" -C /tmp/ && \
bash "/tmp/bats-${BATS_VERSION}/install.sh" /usr/local && \
rm -rf /tmp/*
RUN curl -o "/tmp/docker-$VERSION.tgz" -LS "https://get.docker.com/builds/Linux/x86_64/docker-$VERSION.tgz" && \
curl -o "/tmp/docker-$VERSION.tgz.sha256" -LS "https://get.docker.com/builds/Linux/x86_64/docker-$VERSION.tgz.sha256" && \
cd /tmp && sha256sum -c docker-$VERSION.tgz.sha256 && \
tar -xvzf "/tmp/docker-$VERSION.tgz" -C /tmp/ && \
chmod u+x /tmp/docker/docker && mv /tmp/docker/docker /usr/bin/ && \
rm -rf /tmp/*
RUN mkdir /docker-bench-security
COPY . /docker-bench-security
RUN chmod +x /docker-bench-security/run_tests.sh
WORKDIR /docker-bench-security
VOLUME /var/docker-bench
CMD ["-r"]
ENTRYPOINT ["./run_tests.sh"]

View file

@ -0,0 +1,128 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
load "$BATS_TEST_DIRNAME/../helper_lib.sh"
# 1.1
@test "1.1 - Create a separate partition for containers" {
run grep /var/lib/docker /etc/fstab
assert_success
}
# 1.2
@test "1.2 - Use an updated Linux Kernel" {
kernel_version=$(uname -r | cut -d "-" -f 1)
run do_version_check 3.10 "$kernel_version"
assert [ $status -eq 9 -o $status -eq 10 ]
}
# 1.4
@test "1.4 - Remove all non-essential services from the host - Network" {
# Check for listening network services.
listening_services=$(netstat -na | grep -v tcp6 | grep -v unix | grep -c LISTEN)
if [ "$listening_services" -eq 0 ]; then
fail "Failed to get listening services for check: $BATS_TEST_NAME"
else
if [ "$listening_services" -gt 5 ]; then
fail "Host listening on: $listening_services ports"
fi
fi
}
# 1.5
@test "1.5 - Keep Docker up to date" {
docker_version=$(docker version | grep -i -A1 '^server' | grep -i 'version:' \
| awk '{print $NF; exit}' | tr -d '[:alpha:]-,')
docker_current_version="1.11.1"
docker_current_date="2016-04-27"
run do_version_check "$docker_current_version" "$docker_version"
if [ $status -eq 11 ]; then
fail "Using $docker_version, when $docker_current_version is current as of $docker_current_date. Your operating system vendor may provide support and security maintenance for docker."
fi
assert [ $status -eq 9 -o $status -eq 10 ]
}
# 1.6
@test "1.6 - Only allow trusted users to control Docker daemon" {
users_string=$(awk -F':' '/^docker/{print $4}' /etc/group)
docker_users=(${users_string//,/ })
for u in "${docker_users[@]}"; do
local found=1
for tu in "${config_trusted_users[@]}"; do
if [ "$u" = "$tu" ]; then
found=0
fi
done
if [ $found -eq 1 ]; then
fail "User $u is not a trusted user!"
fi
done
}
# 1.7
@test "1.7 - Audit docker daemon - /usr/bin/docker" {
file="/usr/bin/docker"
run command -v auditctl
assert_success
run auditctl -l | grep "$file"
assert_success
}
test_audit_directory() {
local directory="$1"
assert [ -d "$directory" ]
run command -v auditctl >/dev/null
assert_success
run auditctl -l | grep "$directory"
assert_success
}
test_audit_file() {
file="$1"
assert [ -f "$file" ]
run command -v auditctl
assert_success
run auditctl -l | grep "$file"
assert_success
}
# 1.8
@test "1.8 - Audit Docker files and directories - /var/lib/docker" {
test_audit_directory "/var/lib/docker"
}
# 1.9
@test "1.9 - Audit Docker files and directories - /etc/docker" {
test_audit_directory "/etc/docker"
}
# 1.10
@test "1.10 - Audit Docker files and directories - docker.service" {
test_audit_file "$(get_systemd_service_file docker.service)"
}
# 1.11
@test "1.11 - Audit Docker files and directories - docker.socket" {
test_audit_file "$(get_systemd_service_file docker.socket)"
}
# 1.12
@test "1.12 - Audit Docker files and directories - /etc/default/docker" {
test_audit_file "/etc/default/docker"
}
# 1.13
@test "1.13 - Audit Docker files and directories - /etc/docker/daemon.json" {
test_audit_file "/etc/docker/daemon.json"
}
# 1.14
@test "1.14 - Audit Docker files and directories - /usr/bin/docker-containerd" {
test_audit_file "/usr/bin/docker-containerd"
}
# 1.15
@test "1.15 - Audit Docker files and directories - /usr/bin/docker-runc" {
test_audit_file "/usr/bin/docker-runc"
}

View file

@ -0,0 +1,102 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
load "$BATS_TEST_DIRNAME/../helper_lib.sh"
# 2.1
@test "2.1 - Restrict network traffic between containers" {
result=$(get_docker_effective_command_line_args '--icc')
run grep "false" <<< "$result"
assert_success
}
# 2.2
@test "2.2 - Set the logging level" {
result=$(get_docker_effective_command_line_args '-l')
run grep 'debug' <<< "$result"
assert_failure
}
# 2.3
@test "2.3 - Allow Docker to make changes to iptables" {
result=$(get_docker_effective_command_line_args '--iptables')
run grep "false" <<< "$result"
assert_failure
}
# 2.4
@test "2.4 - Do not use insecure registries" {
result=$(get_docker_effective_command_line_args '--insecure-registry')
run grep "insecure-registry" <<< "$result"
assert_failure
}
# 2.5
@test "2.5 - Do not use the aufs storage driver" {
result=$(docker info 2>/dev/null)
run grep -e "^Storage Driver:\s*aufs\s*$" <<< "$result"
assert_failure
}
# 2.6
@test "2.6 - Configure TLS authentication for Docker daemon" {
result=$(get_docker_cumulative_command_line_args '-H')
run grep -vE '(unix|fd)://' <<< "$result"
if [ $status -eq 0 ]; then
result=$(get_command_line_args docker)
run $(grep "tlsverify" <<< "$result" | grep "tlskey")
assert_success
fi
}
# 2.7
@test "2.7 - Set default ulimit as appropriate" {
result=$(get_docker_effective_command_line_args '--default-ulimit')
run grep "default-ulimit" <<< "$result"
assert_success
}
# 2.8
@test "2.8 - Enable user namespace support" {
result=$(get_docker_effective_command_line_args '--userns-remap')
run grep "userns-remap" <<< "$result"
assert_success
}
# 2.9
@test "2.9 - Confirm default cgroup usage" {
result=$(get_docker_effective_command_line_args '--cgroup-parent')
run grep "cgroup-parent" <<< "$result"
if [ $status -eq 0 ]; then
assert_output_contains "docker"
fi
}
# 2.10
@test "2.10 - Do not change base device size until needed" {
result=$(get_docker_effective_command_line_args '--storage-opt')
run grep "dm.basesize" <<< "$result"
assert_failure
}
# 2.11
@test "2.11 - Use authorization plugin" {
result=$(get_docker_effective_command_line_args '--authorization-plugin')
run grep "authorization-plugin" <<< "$result"
assert_success
}
# 2.12
@test "2.12 - Configure centralized and remote logging" {
result=$(get_docker_effective_command_line_args '--log-driver')
run grep "log-driver" <<< "$result"
assert_success
}
# 2.13
@test "2.13 - Disable operations on legacy registry (v1)" {
result=$(get_docker_effective_command_line_args '--disable-legacy-registry')
run grep "disable-legacy-registry" <<< "$result"
assert_success
}

View file

@ -0,0 +1,223 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
load "$BATS_TEST_DIRNAME/../helper_lib.sh"
# 3.1
@test "3.1 - Verify that docker.service file ownership is set to root:root" {
file="$(get_systemd_service_file docker.service)"
if [ -f "$file" ]; then
if [ "$(stat -c %u%g "$file")" -ne 00 ]; then
fail "Wrong ownership for $file"
fi
fi
}
# 3.2
@test "3.2 - Verify that docker.service file permissions are set to 644" {
file="$(get_systemd_service_file docker.service)"
if [ -f "$file" ]; then
if [ "$(stat -c %a "$file")" -ne 644 ]; then
fail "Wrong permissions for $file"
fi
fi
}
# 3.3
@test "3.3 - Verify that docker.socket file ownership is set to root:root" {
file="$(get_systemd_service_file docker.socket)"
if [ -f "$file" ]; then
if [ "$(stat -c %u%g "$file")" -ne 00 ]; then
fail "Wrong ownership for $file"
fi
fi
}
# 3.4
@test "3.4 - Verify that docker.socket file permissions are set to 644" {
file="$(get_systemd_service_file docker.socket)"
if [ -f "$file" ]; then
if [ "$(stat -c %a "$file")" -ne 644 ]; then
fail "Wrong permissions for $file"
fi
fi
}
# 3.5
@test "3.5 - Verify that /etc/docker directory ownership is set to root:root" {
directory="/etc/docker"
if [ -d "$directory" ]; then
if [ "$(stat -c %u%g $directory)" -ne 00 ]; then
fail "Wrong ownership for $directory"
fi
fi
}
# 3.6
@test "3.6 - Verify that /etc/docker directory permissions are set to 755 or 700" {
directory="/etc/docker"
if [ -d "$directory" ]; then
if [ "$(stat -c %a $directory)" -ne 755 ] && [ "$(stat -c %a $directory)" -ne 700 ]; then
fail "Wrong permissions for $directory : $(stat -c %a $directory)"
fi
fi
}
# 3.7
@test "3.7 - Verify that registry certificate file ownership is set to root:root" {
directory="/etc/docker/certs.d/"
if [ -d "$directory" ]; then
fail=0
owners=$(ls -lL $directory | grep ".crt" | awk '{print $3, $4}')
for p in $owners; do
printf "%s" "$p" | grep "root" >/dev/null 2>&1
if [ $? -ne 0 ]; then
fail=1
fi
done
if [ $fail -eq 1 ]; then
fail "Wrong ownership for $directory"
fi
fi
}
# 3.8
@test "3.8 - Verify that registry certificate file permissions are set to 444" {
directory="/etc/docker/certs.d/"
if [ -d "$directory" ]; then
fail=0
perms=$(ls -lL $directory | grep ".crt" | awk '{print $1}')
for p in $perms; do
if [ "$p" != "-r--r--r--." ] && [ "$p" = "-r--------." ]; then
fail=1
fi
done
if [ $fail -eq 1 ]; then
fail "Wrong permissions for $directory"
fi
fi
}
# 3.9
@test "3.9 - Verify that TLS CA certificate file ownership is set to root:root" {
tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1)
if [ -f "$tlscacert" ]; then
if [ "$(stat -c %u%g "$tlscacert")" -ne 00 ]; then
fail "Wrong ownership for $tlscacert : $(stat -c %u%g "$tlscacert")"
fi
fi
}
# 3.10
@test "3.10 - Verify that TLS CA certificate file permissions are set to 444" {
tlscacert=$(get_docker_effective_command_line_args '--tlscacert' | sed -n 's/.*tlscacert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1)
if [ -f "$tlscacert" ]; then
perms=$(ls -ld "$tlscacert" | awk '{print $1}')
if [ "$perms" != "-r--r--r--" ]; then
fail "Wrong permissions for $tlscacert"
fi
fi
}
# 3.11
@test "3.11 - Verify that Docker server certificate file ownership is set to root:root" {
tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1)
if [ -f "$tlscert" ]; then
if [ "$(stat -c %u%g "$tlscert")" -ne 00 ]; then
fail "Wrong ownership for $tlscert : $(stat -c %u%g "$tlscert")"
fi
fi
}
# 3.12
@test "3.12 - Verify that Docker server certificate file permissions are set to 444" {
tlscert=$(get_docker_effective_command_line_args '--tlscert' | sed -n 's/.*tlscert=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1)
if [ -f "$tlscert" ]; then
perms=$(ls -ld "$tlscert" | awk '{print $1}')
if [ "$perms" != "-r--r--r--" ]; then
fail "Wrong permissions for $tlscert"
fi
fi
}
# 3.13
@test "3.13 - Verify that Docker server key file ownership is set to root:root" {
tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1)
if [ -f "$tlskey" ]; then
if [ "$(stat -c %u%g "$tlskey")" -ne 00 ]; then
fail "Wrong ownership for $tlskey : $(stat -c %u%g "$tlskey")"
fi
fi
}
# 3.14
@test "3.14 - Verify that Docker server key file permissions are set to 400" {
tlskey=$(get_docker_effective_command_line_args '--tlskey' | sed -n 's/.*tlskey=\([^s]\)/\1/p' | sed 's/--/ --/g' | cut -d " " -f 1)
if [ -f "$tlskey" ]; then
perms=$(ls -ld "$tlskey" | awk '{print $1}')
if [ "$perms" != "-r--------" ]; then
fail "Wrong permissions for $tlskey"
fi
fi
}
# 3.15
@test "3.15 - Verify that Docker socket file ownership is set to root:docker" {
file="/var/run/docker.sock"
if [ -S "$file" ]; then
if [ "$(stat -c %U:%G $file)" != 'root:docker' ]; then
fail "Wrong ownership for $file"
fi
fi
}
# 3.16
@test "3.16 - Verify that Docker socket file permissions are set to 660" {
file="/var/run/docker.sock"
if [ -S "$file" ]; then
if [ "$(stat -c %a $file)" -ne 660 ]; then
fail "Wrong permissions for $file"
fi
fi
}
# 3.17
@test "3.17 - Verify that daemon.json file ownership is set to root:root" {
file="/etc/docker/daemon.json"
if [ -f "$file" ]; then
if [ "$(stat -c %U:%G $file)" != 'root:root' ]; then
fail "Wrong ownership for $file"
fi
fi
}
# 3.18
@test "3.18 - Verify that daemon.json file permissions are set to 644" {
file="/etc/docker/daemon.json"
if [ -f "$file" ]; then
if [ "$(stat -c %a $file)" -ne 644 ]; then
fail "Wrong permissions for $file"
fi
fi
}
# 3.19
@test "3.19 - Verify that /etc/default/docker file ownership is set to root:root" {
file="/etc/default/docker"
if [ -f "$file" ]; then
if [ "$(stat -c %U:%G $file)" != 'root:root' ]; then
fail "Wrong ownership for $file"
fi
fi
}
# 3.20
@test "3.20 - Verify that /etc/default/docker file permissions are set to 644" {
file="/etc/default/docker"
if [ -f "$file" ]; then
if [ "$(stat -c %a $file)" -ne 644 ]; then
fail "Wrong permissions for $file"
fi
fi
}

View file

@ -0,0 +1,18 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
load "$BATS_TEST_DIRNAME/../helper_lib.sh"
# 4.1
@test "4.1 - Create a user for the container: {{c}}" {
user=$(docker inspect --format 'User={{.Config.User}}' "{{c}}")
if [ "$user" = "User=" ] || [ "$user" = "User=[]" ] || [ "$user" = "User=<no value>" ]; then
# get PID 1 and check if it's running as root (uid=0)
uid=$(docker exec {{c}} awk '/^Uid:/{print $2}' /proc/1/status)
if [ $uid -eq 0 ]; then
fail "Running as root: {{c}}"
fi
fi
}

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
# 4.5
@test "4.5 - Enable Content trust for Docker" {
assert [ "x$DOCKER_CONTENT_TRUST" = "x1" ]
}

View file

@ -0,0 +1,202 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
load "$BATS_TEST_DIRNAME/../helper_lib.sh"
# 5.1
@test "5.1 - Verify AppArmor Profile, if applicable: {{c}}" {
policy=$(docker inspect --format 'AppArmorProfile={{ .AppArmorProfile }}' "{{c}}")
if [ "$policy" = "AppArmorProfile=" ] || [ "$policy" = "AppArmorProfile=[]" ] || [ "$policy" = "AppArmorProfile=<no value>" ]; then
fail "No AppArmorProfile Found: {{c}}"
fi
}
# 5.2
@test "5.2 - Verify SELinux security options, if applicable: {{c}}" {
policy=$(docker inspect --format 'SecurityOpt={{ .HostConfig.SecurityOpt }}' "{{c}}")
if [ "$policy" = "SecurityOpt=" ] || [ "$policy" = "SecurityOpt=[]" ] || [ "$policy" = "SecurityOpt=<no value>" ]; then
fail "No SecurityOptions Found: {{c}}"
fi
}
# 5.3
@test "5.3 - Restrict Linux Kernel Capabilities within containers: {{c}}" {
caps=$(docker inspect --format 'CapAdd={{ .HostConfig.CapAdd}}' "{{c}}")
if [ "$caps" != 'CapAdd=' ] && [ "$caps" != 'CapAdd=[]' ] && [ "$caps" != 'CapAdd=<no value>' ] && [ "$caps" != 'CapAdd=<nil>' ]; then
fail "Capabilities added: $caps to {{c}}"
fi
}
# 5.4
@test "5.4 - Do not use privileged containers: {{c}}" {
privileged=$(docker inspect --format '{{ .HostConfig.Privileged }}' "{{c}}")
if [ "$privileged" = "true" ]; then
fail "Container running in Privileged mode: {{c}}"
fi
}
# 5.5
@test "5.5 - Do not mount sensitive host system directories on containers: {{c}}" {
# List of sensitive directories to test for. Script uses new-lines as a separator.
# Note the lack of identation. It needs it for the substring comparison.
sensitive_dirs=(/boot /dev /etc /lib /proc /sys /usr)
run docker inspect --format '{{ .VolumesRW }}' "{{c}}"
if [ $status -eq 0 ]; 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
run contains "$volumes" "$v"
if [ $status -eq 0 ]; then
fail "Sensitive directory $v mounted in: {{c}}"
fi
done
}
# 5.7
@test "5.7 - Do not map privileged ports within containers: {{c}}" {
# Port format is private port -> ip: public port
ports=$(docker port "{{c}}" | awk '{print $3}' | cut -d ':' -f2 | tr "\n" " ")
# iterate through port range (line delimited)
for port in $ports; do
if [ ! -z "$port" ] && [ "0$port" -lt 1024 ]; then
fail "Privileged Port in use: $port in {{c}}"
fi
done
}
# 5.9
@test "5.9 - Do not share the host's network namespace: {{c}}" {
mode=$(docker inspect --format 'NetworkMode={{ .HostConfig.NetworkMode }}' "{{c}}")
if [ "$mode" = "NetworkMode=host" ]; then
fail "Container running with networking mode 'host': {{c}}"
fi
}
# 5.10
@test "5.10 - Limit memory usage for container: {{c}}" {
run docker inspect --format '{{ .Config.Memory }}' "{{c}}"
if [ $status -eq 0 ]; then
memory=$(docker inspect --format '{{ .Config.Memory }}' "{{c}}")
else
memory=$(docker inspect --format '{{ .HostConfig.Memory }}' "{{c}}")
fi
if [ "$memory" = "0" ]; then
fail "Container running without memory restrictions: {{c}}"
fi
}
# 5.11
@test "5.11 - Set container CPU priority appropriately: {{c}}" {
run docker inspect --format '{{ .Config.CpuShares }}' "{{c}}"
if [ $status -eq 0 ]; then
shares=$(docker inspect --format '{{ .Config.CpuShares }}' "{{c}}")
else
shares=$(docker inspect --format '{{ .HostConfig.CpuShares }}' "{{c}}")
fi
if [ "$shares" = "0" ]; then
fail "Container running without CPU restrictions: {{c}}"
fi
}
# 5.12
@test "5.12 - Mount container's root filesystem as read only: {{c}}" {
read_status=$(docker inspect --format '{{ .HostConfig.ReadonlyRootfs }}' "{{c}}")
if [ "$read_status" = "false" ]; then
fail "Container running with root FS mounted R/W: {{c}}"
fi
}
# 5.13
@test "5.13 - Bind incoming container traffic to a specific host interface: {{c}}" {
for ip in $(docker port "{{c}}" | awk '{print $3}' | cut -d ':' -f1 | tr "\n" " "); do
if [ "$ip" = "0.0.0.0" ]; then
fail "Port being bound to wildcard IP: $ip in {{c}}"
fi
done
}
# 5.14
@test "5.14 - Set the 'on-failure' container restart policy to 5: {{c}}" {
policy=$(docker inspect --format MaximumRetryCount='{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' "{{c}}")
if [ "$policy" != "MaximumRetryCount=5" ]; then
fail "MaximumRetryCount is not set to 5: {{c}}"
fi
}
# 5.15
@test "5.15 - Do not share the host's process namespace: {{c}}" {
mode=$(docker inspect --format 'PidMode={{.HostConfig.PidMode }}' "{{c}}")
if [ "$mode" = "PidMode=host" ]; then
fail "Host PID namespace being shared with: {{c}}"
fi
}
# 5.16
@test "5.16 - Do not share the host's IPC namespace: {{c}}" {
mode=$(docker inspect --format 'IpcMode={{.HostConfig.IpcMode }}' "{{c}}")
if [ "$mode" = "IpcMode=host" ]; then
fail "Host IPC namespace being shared with: {{c}}"
fi
}
# 5.17
@test "5.17 - Do not directly expose host devices to containers: {{c}}" {
devices=$(docker inspect --format 'Devices={{ .HostConfig.Devices }}' "{{c}}")
if [ "$devices" != "Devices=" ] && [ "$devices" != "Devices=[]" ] && [ "$devices" != "Devices=<no value>" ]; then
fail "Container has devices exposed directly: {{c}}"
fi
}
# 5.18
@test "5.18 - Override default ulimit at runtime only if needed: {{c}}" {
ulimits=$(docker inspect --format 'Ulimits={{ .HostConfig.Ulimits }}' "{{c}}")
if [ "$ulimits" = "Ulimits=" ] || [ "$ulimits" = "Ulimits=[]" ] || [ "$ulimits" = "Ulimits=<no value>" ]; then
fail "Container no default ulimit override: {{c}}"
fi
}
# 5.19
@test "5.19 - Do not set mount propagation mode to shared: {{c}}" {
mode=$(docker inspect --format 'Propagation={{range $mnt := .Mounts}} {{json $mnt.Propagation}} {{end}}' "{{c}}")
if [ "$mode" = "Propagation=shared" ]; then
fail "Mount propagation mode is shared: {{c}}"
fi
}
# 5.20
@test "5.20 - Do not share the host's UTS namespace: {{c}}" {
mode=$(docker inspect --format 'UTSMode={{.HostConfig.UTSMode }}' "{{c}}")
if [ "$mode" = "UTSMode=host" ]; then
fail "Host UTS namespace being shared with: {{c}}"
fi
}
# 5.21
@test "5.21 - Do not disable default seccomp profile: {{c}}" {
result=$(docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "{{c}}")
run grep "seccomp:unconfined" <<< "$result"
if [ $status -eq 0 ]; then
fail "Default seccomp profile disabled: {{c}}"
fi
}
# 5.24
@test "5.24 - Confirm cgroup usage: {{c}}" {
mode=$(docker inspect --format 'CgroupParent={{.HostConfig.CgroupParent }}x' "{{c}}")
if [ "$mode" != "CgroupParent=x" ]; then
fail "Confirm cgroup usage: {{c}}"
fi
}
# 5.25
@test "5.25 - Restrict container from acquiring additional privileges: {{c}}" {
result=$(docker inspect --format 'SecurityOpt={{.HostConfig.SecurityOpt }}' "{{c}}")
run grep "no-new-privileges" <<< "$result"
if [ $status -ne 0 ]; then
fail "Privileges not restricted: {{c}}"
fi
}

View file

@ -0,0 +1,36 @@
#!/usr/bin/env bats
load "test_helper/bats-support/load"
load "test_helper/bats-assert/load"
load "$BATS_TEST_DIRNAME/../helper_lib.sh"
# 6.4
@test "6.4 - Avoid image sprawl" {
images=$(docker images -q | sort -u | wc -l | awk '{print $1}')
active_images=0
for c in $(docker inspect -f "{{.Image}}" $(docker ps -qa)); do
if docker images --no-trunc -a | grep "$c" > /dev/null ; then
active_images=$(( active_images += 1 ))
fi
done
if [ "$images" -gt 100 ]; then
fail "There are currently: $images images"
fi
if [ "$active_images" -lt "$((images / 2))" ]; then
fail "Only $active_images out of $images are in use"
fi
}
# 6.5
@test "6.5 - Avoid container sprawl" {
total_containers=$(docker info 2>/dev/null | grep "Containers" | awk '{print $2}')
running_containers=$(docker ps -q | wc -l | awk '{print $1}')
diff="$((total_containers - running_containers))"
if [ "$diff" -gt 25 ]; then
fail "There are currently a total of $total_containers containers, with only $running_containers of them currently running"
fi
}

@ -0,0 +1 @@
Subproject commit 9f88b4207da750093baabc4e3f41bf68f0dd3630

@ -0,0 +1 @@
Subproject commit d0a131831c487a1f1141e76d3ab386c89642cdff

4
config/0_config.sh Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
# trusted users that can control Docker daemon
config_trusted_users=(vagrant docker ubuntu)

View file

@ -0,0 +1,4 @@
#!/bin/bash
# trusted users that can control Docker daemon
config_trusted_users=(vagrant docker ubuntu)

46
generate_tests.sh Normal file
View file

@ -0,0 +1,46 @@
#!/bin/bash
. ./helper_lib.sh
TEST_SRC=./bats_tests
BENCH_ROOT=/var/docker-bench
TEST_ROOT=$BENCH_ROOT/bats_tests
prepare_tests_directory()
{
mkdir -p $BENCH_ROOT
if [ -d "$TEST_ROOT" ]; then
rm -rf $TEST_ROOT
fi
if [ ! -f "$BENCH_ROOT/helper_lib.sh" ]; then
cp helper_lib.sh $BENCH_ROOT
fi
cp -r $TEST_SRC $TEST_ROOT
}
list_running_containers() {
# List all running containers
containers=($(docker ps | sed '1d' | awk '{print $NF}' | tr "\n" " "))
# If there is a container with label docker_bench_security, memorize it:
local benchcont="nil"
for c in "${containers[@]}"; do
labels=$(docker inspect --format '{{ .Config.Labels }}' "$c")
contains "$labels" "docker_bench_security" && benchcont="$c"
done
# List all running containers except docker-bench (use names to improve readability in logs)
docker ps -aq --format="{{.Names}}" | grep -v "$benchcont" | tr "\n" " "
}
generate_all_tests() {
# prepare test direcory: copy tests and templates
prepare_tests_directory
# generate tests from templates for running containers
containers=($(list_running_containers))
( cd $TEST_ROOT || exit 1
for c in "${containers[@]}"; do
for t in *.bats.template; do
sed -e "s/{{c}}/$c/g" "${t}" > "${t%.*.*}_${c}.bats"
done
done
)
}

119
run_tests.sh Executable file
View file

@ -0,0 +1,119 @@
#!/bin/bash
. ./generate_tests.sh
. ./config/0_config.sh
TEST_RESULTS=$BENCH_ROOT/results
# make result folder (inside VOLUME)
# mkdir -p $TEST_RESULTS
# generate all tests: copy host and daemon level tests and generate container level tests for running containers
# generate_all_tests
# run bats with all tests or passed tests
# bats $TEST_ROOT >
#Set Script Name variable
SCRIPT="run_tests.sh"
#Initialize variables to default values.
OPT_FORMAT="t"
OPT_OUTPUT=$TEST_RESULTS
OPT_RESULTS=1
#Set fonts for Help.
if [ -e "/usr/bin/tput" ]; then
BOLD=$(tput bold)
REV=$(tput smso)
NORM=$(tput sgr0)
else
BOLD=""
REV=""
NORM=""
fi
#Help function
HELP() {
echo -e \\n"Help documentation for ${BOLD}${SCRIPT}${NORM}"\\n
echo -e "Basic usage: ${BOLD}$SCRIPT [-c] [-p|-t] [-o path] <test> [<test> ...]${NORM}"\\n
echo -e "Command line switches are optional. The following switches are recognized."
echo -e "${REV}-c${NORM} --Displays number of tests. No further functions are performed."
echo -e "${REV}-g${NORM} --Generates all CIS Bats tests without execution. No further functions are performed."
echo -e "${REV}-p${NORM} --Show results in pretty format."
echo -e "${REV}-t${NORM} --Show results in TAP format. This is the default format."
echo -e "${REV}-r${NORM} --Create test results files: ${BOLD}tests_<timestamp>.tap${NORM} in test result folder."
echo -e "${REV}-o${NORM} --Specify test result folder. Default to ${BOLD}$TEST_RESULTS${NORM}."
echo -e "${REV}-h${NORM} --Displays this help message. No further functions are performed."\\n
echo -e "Example: ${BOLD}$SCRIPT -t -o $TEST_RESULTS${NORM}"\\n
exit 1
}
#Check the number of arguments. If none are passed, print help and exit.
NUMARGS=$#
if [ "$NUMARGS" -eq 0 ]; then
HELP
fi
### Start getopts code ###
#Parse command line flags
while getopts o:rptcgh FLAG; do
case $FLAG in
o) # output test results into specified folder
OPT_OUTPUT=$OPTARG
;;
p) # output test results in TAP format
OPT_FORMAT="p"
;;
t) # output test results in pretty format
OPT_FORMAT="t"
;;
r) # save test results into file
OPT_RESULTS=0
;;
c) # count tests
if [ -d "$TEST_ROOT" ]; then
echo -e "There are ${BOLD}$(bats "${TEST_ROOT}" -c)${NORM} tests in ${BOLD}${TEST_ROOT}${NORM}"
else
echo -e "No tests found, run ${BOLD}${SCRIPT}${NORM} with ${REV}-g${NORM} option first."
fi
exit 1
;;
g) # genetate all Bats tests: copy tests and generate tests (per container) from templates
generate_all_tests
exit 1
;;
h) #show help
HELP
;;
\?) #unrecognized option - show help
echo -e \\n"Option -${BOLD}$OPTARG${NORM} not allowed."
HELP
;;
esac
done
shift $((OPTIND-1)) #This tells getopts to move on to the next argument.
### End getopts code ###
### Run Bats tests ###
TESTS="${TEST_ROOT}"
if [ ! -d "${TEST_ROOT}" ]; then # generate tests if needed
generate_all_tests
fi
if [ $# -ne 0 ]; then # get tests from command line
TESTS=$*
fi
if [ ${OPT_RESULTS} -eq 0 ]; then # run tests and [create test result file]
if [ ! -d "$OPT_OUTPUT" ]; then
mkdir -p "$OPT_OUTPUT"
fi
bats "${TESTS}" -${OPT_FORMAT} > "${OPT_OUTPUT}/tests_$(date +%s).tap"
else
bats "${TESTS}" -${OPT_FORMAT}
fi
exit 0