Notes on CIS hardening of Docker
Skipping stuff that should be secure by default, stuff that seems to be trumped by availability concerns, operational policy-based controls, and probably some other things…
Host configuration
- Separate
/var/lib/docker
partition - Host hardening
- Only trusted users in
docker
group - Auditing:
/usr/bin/docker
/var/lib/docker
/etc/docker
/usr/lib/systemd/system/docker.service
/usr/lib/systemd/system/docker.socket
/etc/default/docker
/etc/docker/daemon.json
/usr/bin/docker-containerd
/usr/bin/containerd-shim-runc-v1
/usr/bin/containerd-shim-runc-v2
/usr/bin/docker-runc
Daemon configuration (daemon.json)
- Run daemon rootless if possible (not supported with swarm)
- Disable inter-container networking (
"icc": false
) - Set default ulimit (
"default-ulimits": {"nofile": {"Soft": 100, "Hard": 200}, "nproc": {"Soft": 1024, "Hard": 2048}}
) - [L2] Enable user namespace support (
"userns-remap": "default"
) - [L2] User authorization plugin (ex: authz)
- [L2] Configure centralized+remote logging (
"log-driver": "syslog", "log-opts": {"syslog-address": "tcp://192.0.2.1"}
) - Restrict containers from acquiring new privs (
"no-new-privileges": true
) - Enable live restore (
"live-restore": true
) (not supported with swarm) - Disable userland proxy (
"userland-proxy": false
)
Docker daemon config files (ownership/privs)
docker.service
/docker.socket
:root:root
644
/etc/docker
:root:root
755
/etc/docker/certs.d/*
:root:root
444
/var/run/docker.sock
:root:docker
660
/etc/docker/daemon.json
:root:root
644
/etc/default/docker
:root:root
644
/etc/sysconfig/docker
:root:root
644
/run/containerd/containerd.sock
:root:root
660
Container images and build file configs
- Create a user for the container (USER)
- Do not install unnecessary packages in the container
- Scan+rebuild images for security patches
- Don’t use update instructions alone in Dockerfiles
- “Use update instructions together with install instructions and version pinning for packages while installing them. This will prevent caching and force the extraction of the required versions.”
- Use
COPY
instead ofADD
unless there are specific needs forADD
(extracting an archive / retrieving from a URL) (ADD or COPY) - Don’t store any kind of secrets in Dockerfiles
- [L2] Verify authenticity of packages before installing them into images
Container runtime configuration
- Enable AppArmor/SELinux as appropriate
- Restrict Linux kernel capabilities within containers (cap_add, cap_drop)
- Specifically drop
NEW_RAW
(unless specifically required) as that could allow a Bad Guy in a container to create spoofed network traffic ex:cap_drop: - ALL cap_add: - CHOWN
- Specifically drop
- Don’t map containers to privileged ports (below
1024
) unless absolutely required (like for load balancers/proxies) (ports) - Set memory limits / CPU priority for containers (mem_limit, cpu_shares)
mem_limit: 512m cpu_shares: 512 # 1024 = 100%, 512 = 50%
- Mount container’s root filesystem read-only (read-only, tmpfs)
read_only: true tmpfs: - /run - /tmp volumes: - /opt/app/data:/run/app/data:rw # bind mount for persistence
- Bind to a specific host interface (ports)
ports: - 192.0.2.1:8080:80 # short form - name: web-secured # long form target:443 published: 8443 host_ip: 192.0.2.1
- Don’t share host devices to containers; if required, limit privs (devices)
devices: - "/dev/temp_sda:/dev/temp_sda:r"
- Set PIDs cgroup limit (pids_limit)
pids_limit: 10
- [L2] Don’t use Docker’s default
docker0
bridge (use user-defined networks, default for compose)
Docker swarm configuration
- Minimum number of manager nodes (one, or smallest odd number if management fault tolerance is required) (manager nodes)
- Bind swarm services to specific host interfaces, separating management and data plane traffic (swarm init)
docker swarm init \ --listen-addr 192.0.2.1 \ # inbound management --advertise-addr 192.0.2.1 \ # outbound management --data-path-addr 172.16.0.1 # data plane
- Encrypt overlay network traffic (encrypted)
- Use swarm secrets (secrets)
See also: