
Docker containers are the most popular containerization technology. Initially, it was used mainly for dev and test environments, and in time passed into production. Docker containers began to breed in a production environment like mushrooms after rain, but few of those who use this technology thought about how to safely publish Docker containers.
Based on
OWASP , we have prepared a list of rules, the implementation of which will significantly protect your environment, built on Docker containers.
Rule 0
Host Machine and Docker should contain all current updates.
To protect against known vulnerabilities leading to a container environment out of the container’s host system, which usually ends with privilege escalation on the host system, installing all patches for the host OS, Docker Engine and Docker Machine is extremely important.
')
In addition, containers (unlike virtual machines) share a kernel with a host, so a kernel exploit running inside the container is directly executed in the host core. For example, an exploit of elevating privileges in the kernel (for example, Dirty COW) running inside a well-isolated container will lead to root access on the host.
Rule 1
Do not give access to the Docker daemon socket
Docker service (daemon) uses UNIX socket /var/run/docker.sock for incoming API connections.
The owner of this resource must be the root user. And nothing else. Changing access rights to this socket is essentially the same as granting root access to the host system.
Also, you should not fumble the socket /var/run/docker.sock with containers, where you can do without it, because in this case, compromising the service in the container will lead to full control over the host system. If you have containers that use something like this:
-v /var/run/docker.sock://var/run/docker.sock
or for docker-compose:
volumes: - "/var/run/docker.sock:/var/run/docker.sock"
It is urgent to change this.
And last - never, hear,
never use a Docker TCP socket without the absolute certainty that you need it, especially without using additional protection methods (at least authorization). By default, the Docker TCP socket opens a port on the external interface 0.0.0.0:2375 (2376, in the case of HTTPs) and allows you to completely control the containers, and with it the host system.
Rule 2
Configure an unprivileged user inside the container
Setting up a container to use an unprivileged user is the best way to avoid an attack on privilege escalation. This can be done in various ways:
1. Using the “-u” option of the “docker run” command:
docker run -u 4000 alpine
2. During image build:
FROM alpine RUN groupadd -r myuser && useradd -r -g myuser myuser < root-, , > USER myuser
3. Enable “user namespace” support in the Docker daemon:
--userns-remap=default
More on this in the
official documentation .
In Kubernetes, the latter is configured in the
Security Context via the runAsNonRoot option:
kind: ... apiVersion: ... metadata: name: ... spec: ... containers: - name: ... image: .... securityContext: ... runAsNonRoot: true ...
Rule 3
Limit container capabilities
In Linux, starting with kernel 2.2, a way to control the capabilities of privileged processes called
Linux Kernel Capabilities (details by reference) has appeared.
Docker by default uses a pre-installed set of these kernel features. And allows you to change this set using the commands:
--cap-drop — --cap-add —
The best security setting is to first disable all features (--cap-drop all), and then connect only the necessary ones. For example:
docker run --cap-drop all --cap-add CHOWN alpine
And the most important (!): Avoid launching containers with the —privileged flag !!!
In Kubernetes, the Linux Kernel Capabilities limit is configured in the Security Context via the options option:
kind: ... apiVersion: ... metadata: name: ... spec: ... containers: - name: ... image: .... securityContext: ... capabilities: drop: - all add: - CHOWN ...
Rule 4
Use the no-new-privileges flag
When launching a container it is useful to use the --security-opt = no-new-privileges flag which prevents elevation of privileges inside the container.
In Kubernetes, the Linux Kernel Capabilities restriction is configured in the Security Context through the option allowPrivilegeEscalation:
kind: ... apiVersion: ... metadata: name: ... spec: ... containers: - name: ... image: .... securityContext: ... allowPrivilegeEscalation: false ...
Rule 5
Disable interconnect interactions
By default Docker has inter-container interaction enabled, which means that all containers can interact with each other (using the docker0 network). This feature can be disabled by starting the Docker service with the –icc = false flag.
Rule 6
Use Linux security modules (Linux Security Module - seccomp, AppArmor, SELinux)
By default, Docker already uses profiles for Linux security modules. Therefore,
never disable security profiles! The maximum that can be done with them is to tighten the rules.
The seccomp default profile is available
here .
Docker also uses AppArmor for protection, and the Docker Engine itself generates a default profile for AppArmor when the container is started. In other words, instead of:
$ docker run --rm -it hello-world
starts:
$ docker run --rm -it --security-opt apparmor=docker-default hello-world
Also in the
documentation is an example of the AppArmor profile for nginx, which is quite possible (necessary!) To use:
Rule 7
Limit container resources
This rule is quite simple: in the name of preventing containers from devouring all server resources during the next DoS / DDoS attack, we can set memory usage limits for each container separately. You can limit: the amount of memory, CPU, the number of container restarts.
So let's go in order.
MemoryOption -m or --memoryThe maximum amount of memory a container can use. The minimum value is 4m (4 megabytes).
Option --memory-swapOption to configure swap (swap file). Configured slyly:
- If --memory-swap> 0, then it is necessary that the –memory flag be set. In this case, memory-swap shows how much memory is available to the container along with the swap.
- Easier by example. If - memory = "300m", and - memory-swap = "1g", then the container can use 300 MB of memory and 700 MB of swap (1g - 300m).
- If - memory-swap = 0, the setting is ignored.
- If --memory-swap is set to the same value as --memory, then the container will not have a swap.
- If the value of --memory-swap is not set, but --memory is set, then the number of swap will be equal to twice the amount of specified memory. For example, if - memory = "300m", and - memory-swap is not specified, then the container will use 300 MB of memory and 600 MB of swap.
- If - memory-swap = -1, then the container will use the entire swap, which is possible on the host system.
Hostess note: the free utility running inside the container does not show the actual value of the available swap for the container, but the number of the host swap.
Option --oom-kill-disableAllows you to enable or disable OOM (Out of memory) killer.
Attention! You can turn off OOM Killer only with the --memory option specified, otherwise it may happen that when out-of-memory inside the container, the kernel starts killing host system processes.
The rest of the memory management options, such as - memory-swappiness, memory, reservation and kernel-memory, are more used to tune container performance.
CPUOption --cpusThe option sets how many available processor resources a container can use. For example, if we have a host with two CPUs and we specify --cpus = "1.5", then the container is guaranteed to use one and a half processor.
Option --cpuset-cpusCustomizes the use of specific cores or CPUs. The value can be set with a hyphen or a comma. In the first case, the range of allowed nuclei will be indicated, in the second case - specific nuclei.
Number of container restarts --restart=on-failure:<number_of_restarts>
This setting sets how many times Docker will attempt to restart the container if it unexpectedly crashes. The counter is reset if the state of the container has changed to “running”.
It is recommended to set a small positive number, for example, 5, which will allow you to avoid endless restarts of the service not working.
Rule 8
Use read-only file systems and volume
If the container does not have to write anything anywhere, then you need to use the read-only filesystem as much as possible. This greatly complicates the life of a potential offender.
An example of launching a container with a read-only file system:
docker run --read-only alpine
Example of connecting volume in read-only mode:
docker run -v volume-name:/path/in/container:ro alpine
Rule 9
Use container security analysis tools
It is necessary to use tools to detect containers with known vulnerabilities. There are not many of them yet, but they are:
• Free:
• Commercial:
And for Kubernetes, there are tools for identifying configuration errors: