So in P3 of the Harden Docker with CIS series, I’ll continue with the hardening process of the Docker installation which we setup in the P1. We’ll start with the module two of the benchmark (CIS Docker Benchmark v1.2.0) i.e. Docker daemon configuration. There are seventeen items in total out of which one is “Not scored”, thus it will be not be entertained in detail in this post. In this port we’ll cover eight out of the total sixteen scored items, and the others will be covered in the next part. So let’s begin.
Not Scored
CIS Control | Description |
---|---|
2.7 | Ensure the default ulimit is configured appropriately |
Scored
CIS Control | Description |
---|---|
2.1 | Ensure network traffic is restricted between containers on the default bridge |
2.2 | Ensure the logging level is set to ‘info’ |
2.3 | Ensure Docker is allowed to make changes to iptables |
2.4 | Ensure insecure registries are not used |
2.5 | Ensure aufs storage driver is not used |
2.6 | Ensure TLS authentication for Docker daemon is configured |
2.8 | Enable user namespace support |
2.9 | Ensure the default cgroup usage has been confirmed |
2.10 | Ensure base device size is not changed until needed |
2.11 | Ensure that authorization for Docker client commands is enabled |
2.12 | Ensure centralized and remote logging is configured |
2.13 | Ensure live restore is enabled |
2.14 | Ensure Userland Proxy is Disabled |
2.15 | Ensure that a daemon-wide custom seccomp profile is applied if appropriate |
2.16 | Ensure that experimental features are not implemented in production |
2.17 | Ensure containers are restricted from acquiring new privileges |
2.1 Ensure network traffic is restricted between containers on the default bridge
By default, all the containers in the default bridge can communicate with each other, without any restrictions. However this can be dangerous configuration if a malicious container is running on the default bridge as the other privileged containers. Thus, ensuring that “Inter container communication” (ICC) is turned off.
To verify if the ICC is turned off/on your Docker installation
[email protected]:~$ docker network ls --quiet | xargs docker network inspect --format '{{ .Name }}: {{ .Options }}'
bridge: map[com.docker.network.bridge.default_bridge:true <strong><em>com.docker.network.bridge.enable_icc:true</em></strong> com.docker.network.bridge.enable_ip_masquerade:true com.docker.network.bridge.host_binding_ipv4:0.0.0.0 com.docker.network.bridge.name:docker0 com.docker.network.driver.mtu:1500]
Code language: Bash (bash)
As it is evident, in my Docker installation, ICC is turned on, thus containers can talk to each other, let’s see how this works.
# All the running containers
[email protected]:~$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2dff2170ca2 nginx:alpine "/docker-entrypoint.…" 47 minutes ago Up 46 minutes 80/tcp eager_blackwell
b709bf22a35c nginx:alpine "/docker-entrypoint.…" 47 minutes ago Up 46 minutes 80/tcp hardcore_bhabha
# IP address and hostname of containers
[email protected]:~$ docker exec -it b2dff2170ca2 sh
$ hostname
b2dff2170ca2
$ ip addr
1: lo: mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
24: [email protected]: mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[email protected]:~$ docker exec -it b709bf22a35c sh
$ hostname
b709bf22a35c
$ ip addr
1: lo: mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
26: [email protected]: mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# Now let's ping and curl one box from the other
# Curl 172.17.0.2 from 172.17.0.3
$ curl 172.17.0.2
Welcome to nginx!
$ ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.058 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.067 ms
Code language: Bash (bash)
As shown in the command line snippet above, we can ping, as well as curl the other container running nginx. Now let’s turn off ICC and try to do the same.
We can add our settings for the Docker daemon in the /etc/docker/daemon.json
settings file, and then restart the Docker daemon with sudo systemctl restart docker
and we can verify that if the settings took effect.
To turn off ICC, create daemon.json
file it already doesn’t exist and then add the following content
{
"icc":false
}
Code language: JSON / JSON with Comments (json)
After restarting the Docker daemon, let’s verify the settings.
[email protected]:~$ docker network ls --quiet | xargs docker network inspect --format '{{ .Name }}: {{ .Options }}'
bridge: map[com.docker.network.bridge.default_bridge:true <strong><em>com.docker.network.bridge.enable_icc:false</em></strong> com.docker.network.bridge.enable_ip_masquerade:true com.docker.network.bridge.host_binding_ipv4:0.0.0.0 com.docker.network.bridge.name:docker0 com.docker.network.driver.mtu:1500]
Code language: Bash (bash)
So as ICC has been turned off, now let’s try and ping one container from the other.
NOTE: As we have restarted the Docker daemon, all the containers would have been stopped. So start all the containers (docker start $(docker ps -a -q)
) before attempting anything.
Code language: Bash (bash)$ curl 172.17.0.2 curl: (28) Failed to connect to 172.17.0.2 port 80: Operation timed out $ ping 172.17.0.2 PING 172.17.0.2 (172.17.0.2): 56 data bytes --- 172.17.0.2 ping statistics --- 41 packets transmitted, 0 packets received, 100% packet loss
So now after turning off ICC, containers can no longer communicate with each other.
2.2 Ensure the logging level is set to ‘info’
By default Docker daemon logging level is set to ‘info, however, we’ll be explicit and enable this setting in the /etc/docker/daemon,json
file as well.
Add the following to change the logging level to ‘info’
"logging-level" : 'info'
Code language: JavaScript (javascript)
2.3 Ensure Docker is allowed to make changes to iptables
Docker uses iptables
to manage networking and other network related configurations, thus Docker daemon should be allowed to make changes to the iptables
. By default, this setting is enabled, however, following the suit, we’ll be explicit.
"iptables" : true
Code language: JavaScript (javascript)
2.4 Ensure insecure registries are not used
Insecure registries should not be used as they present a risk of traffic interception and modification. This can be mitigated using TLS communication, and ensuring that only secure registries are used to pull/push the images. By default Docker considers or looks for every registry to be trusted except the local one. The output of the following command should always only list one single registry i.e. local one.
$ docker info --format 'Insecure Registries: {{.RegistryConfig.InsecureRegistryCIDRs}}'
Insecure Registries: [127.0.0.0/8]
Code language: Bash (bash)
If there are any registries other than the local one, then that registry should be removed.
2.5 Ensure aufs storage driver is not used
This is an obsolete setting, as these days only overlay2
is used for Docker images and containers. However, it is still important to know how to look for this setting.
$ docker info --format 'Storage Driver: {{ .Driver }}'
Storage Driver: overlay2
Code language: Bash (bash)
This is the expected output, if there is anything except overlay2, it should be changed to any of the available ones except aufs
such as devicemapper
, btrfs
, zfs
, overlay
, overlay2
, and fuse-overlayfs
2.6 Ensure TLS authentication for Docker daemon is configured
To avoid confusion and to ensure best suggested practices, I haven’t delved deep into this topic. Docker‘s official documentation about the topic should serve the purpose and provide up to date information as to how to setup certificates for Docker daemon.
Reference: https://docs.docker.com/engine/security/https/
2.8 Enable user namespace support
The best way to prevent privilege-escalation attacks from within a container is to configure your container’s applications to run as unprivileged users. This can be achieved using user-namespace remapping in Docker. There are a few prerequisites to enable this feature for Docker daemon, else default options can also be used to achieve the same. We’ll use the default option and for detailed process and other intricacies official document can be followed.
Add the following line to the /etc/docker/daemon.json
file to enable user namespace remapping.
"userns-remap": "default"
Code language: JavaScript (javascript)
This will create a default (dockremap user) mapping and will be utilized to run containers. We can verify if this mapping was created by running a container (docker run hello-world
) and verifying the contents of the /var/lib/docker
directory.
Code language: Bash (bash)~$ sudo ls -la /var/lib/docker/ drwx--x--x 16 root root 4096 Dec 13 11:31 . drwxr-xr-x 24 root root 4096 Dec 13 09:55 .. drwx------ 14 231072 231072 4096 Dec 13 11:30 231072.231072 drwx------ 2 root root 4096 Nov 22 14:30 builder drwx--x--x 4 root root 4096 Nov 22 14:30 buildkit drwx------ 8 root root 4096 Dec 13 11:24 containers drwx------ 3 root root 4096 Nov 22 14:30 image drwxr-x--- 3 root root 4096 Nov 22 14:30 network drwx------ 21 root root 4096 Dec 13 11:30 overlay2 drwx------ 4 root root 4096 Nov 22 14:30 plugins drwx------ 2 root root 4096 Dec 13 11:30 runtimes drwx------ 2 root root 4096 Nov 22 14:30 swarm drwx------ 2 root root 4096 Dec 13 11:30 tmp drwx------ 2 root root 4096 Nov 22 14:30 trust drwx------ 2 root root 4096 Nov 22 14:30 volumes
As it is evident, the files in the folder 231072.231072
is owned by the 231072
user thus limiting escalation attacks from within the containers.
There are a few limitation to this approach as well, and ways to exclude running containers from this mapping. This is particularly true for containers that need to run as root. This can be referred to in the official documentation.
2.9 Ensure the default cgroup usage has been confirmed
Cgroups can be utilized to ration a lot of resources within the host operating system. Thus ensuring that Docker containers are running under a specific cgroup is important. By default Docker utilizes /docker
for cgroup driver and system.slice
for systemd cgroup driver.
These settings can be changed, on container to container basis using --cgroup-parent
flag while initiating a container, or can be changed globally by setting up the values in the /etc/docker/daemon.json
file.
#cgroup driver
"cgroup-parent": "/foobar"
#systemd cgroup driver
"cgroup-parent": "a-b-c.slice"
Code language: PHP (php)
This completes the part 1 of our Docker daemon configuration section of the CIS Docker Benchmarks. We’ll continue with other controls in the next post.
If you have questions or need help setting things up, reach out to me @jtnydv