[Docker security] An Overview of Docker Security Essentials

Takahiro Oda
6 min readJan 1, 2022

--

In this article, we will see the best practices for Docker host security based on 6 scenarios.

Best Practices for Docker Host Security

  1. Secure and harden your host OS.
  2. Ensure your host is kept updated.
  3. Ensure you have the latest version of Docker running.
  4. Consider the use of a minimal Linux distribution such as Alpine that offers a much smaller threat surface.
  5. Add your host and containers to a robust vulnerability management plan and constantly scan your host and containers for vulnerabilities.
  6. Only run the services you need to run.
  7. Ensure your kernel is up to date.
  8. Keep up with the latest vulnerability news for the Linux kernel and the Docker platform.

Tutorial prerequisites

ubuntu@Docker-Clair:~$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.6 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bioni

Setup (if you do it locally, just skip this setup)

Install Docker

Update the apt package index and install packages to allow apt to use a repository over HTTPS:

sudo apt-get update
sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release

Add Docker’s official GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Use the following command to set up the stable repository. To add the nightly or test repository, add the word nightly or test (or both) after the word stable in the commands below.

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker Engine

Update the apt package index, and install the latest version of Docker Engine and containerd, or go to the next step to install a specific version:

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

List the versions available in your repo:

apt-cache madison docker-ce

Install a specific version using the version string from the second column, for example,

root@Docker-host:~# sudo apt-get install docker-ce=5:20.10.12~3-0~ubuntu-bionic docker-ce-cli=5:20.10.12~3-0~ubuntu-bionic containerd.io
Reading package lists... Done
Building dependency tree
Reading state information... Done
containerd.io is already the newest version (1.4.12-1).
docker-ce-cli is already the newest version (5:20.10.12~3-0~ubuntu-bionic).
docker-ce is already the newest version (5:20.10.12~3-0~ubuntu-bionic).
0 upgraded, 0 newly installed, 0 to remove and 13 not upgraded.

Verify that Docker Engine is installed correctly by running the hello-world image.

sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:2498fce14358aa50ead0cc6c19990fc6ff866ce72aeb5546e1d59caac3d0d60f
Status: Downloaded newer image for hello-world:latestHello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/For more examples and ideas, visit:
https://docs.docker.com/get-started/

check the status

ubuntu@jenkins:~$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2021-12-31 23:40:27 UTC; 4min 55s ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 6261 (dockerd)
Tasks: 9
Memory: 34.4M
CGroup: /system.slice/docker.service
└─6261 /usr/bin/docke

Scenario based demos

1:Running Docker Containers with an Unprivileged User

Running Docker containers with an unprivileged user instead of the default “root” user prevents privilege escalation attacks.

As you can see above, it allows a user to run as root.

So countermeasures are

1:reconfigure and build your own Docker images

2:Prior to building your Docker image, specify an unprivileged user in your Dockerfile by adding the following command, replacing <USER> with your username and <GROUP> with a non-sudo group

RUN groupadd -r <USER> && useradd -r -g <GROUP> <USER>

This is an example of Dockerfile.

root@Docker-host:~# vi Dockerfile

Inside

FROM ubuntu:18.04LABEL maintainer="Takahiro Oda"RUN groupadd -r taka && useradd -r -g taka taka# Environment Variables
ENV HOME /home/taka
ENV DEBIAN_FRONTEND=noninteractive

Lets build the secure version of Docker image

docker build . -t taka

You can see the new image listed

We can see that the unprivileged user logged in

root@Docker-host:~# docker run -u taka -it --rm 0d54dc8a72d7 /bin/bash
taka@a877f0da0d2e:/$

You can delete the image

root@Docker-host:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
taka latest 0d54dc8a72d7 7 minutes ago 63.5MB
ubuntu 18.04 5a214d77f5d7 3 months ago 63.1MB
hello-world latest feb5d9fea6a5 3 months ago 13.3kB
root@Docker-host:~# docker rmi 0d54dc8a72d7
Untagged: taka:latest
Deleted: sha256:0d54dc8a72d75abd4def95a2aee06b6c5be889cf1504c3a65154a385bfb8c0ca
Deleted: sha256:39969fe4cb7d136b0c25e030c78fe096e2bbe0df1f0f8c770a8388ebc31b97a5
Deleted: sha256:060cf2ed1ad45963e1ff957c9317c5290e094aa53542921d8939d273c0dc31a6
Deleted: sha256:0621ab0cb71e31febef7dbbd60fd2524b00d4dd004edd780fdb5b45981e9ca19
Deleted: sha256:d009197e55453607831eea0821bb43a54436c754fb3c1e232dadb77b33054846

2:Disabling the Docker Container “root” User

You can disable the “root” user by changing the default shell from /bin/bash to /usr/sbin/nologin. This prevents any user on the container from accessing the “root” account irregardless of whether they have the “root” password.

FROM ubuntu:18.04
LABEL maintainer="Takahiro Oda"
RUN groupadd -r taka && useradd -r -g taka taka
RUN chsh -s /usr/sbin/nologin root
# Environment Variables
ENV HOME /home/taka
ENV DEBIAN_FRONTEND=noninteractive
~
~

We can see that it fails to become root user. This configuration is only applicable if you want to disable the “root” account completely.

3:Preventing Privilege Escalation Attacks

It is recommended to run your containers with specific permissions and ensure that they cannot escalate their privileges.

docker run --security-opt=no-new-privileges <IMAGE-ID>

4 Limiting Docker Container Kernel Capabilities

it is always recommended to not run containers with the --privileged flag as it overrides any other user permission and security restrictions you have set.

Countermeasures

1:Drop all kernel capabilities

root@Docker-host:~# docker run  -u taka -it   --cap-drop all 697ce6d07386  /bin/bash
taka@412505d3efba:/$

For example, specify a particular kernel capabilities to be used.

root@Docker-host:~# docker run  -u taka -it   --cap-drop all  --cap-add NET_ADMIN  697ce6d07386  /bin/bash
taka@8c64eb91a58c:/$

5:File System Permissions and Access

The ability to specify file system permissions and access allows you to set up containers with a read only file system or a temporary file system.

1: Run a Docker container with a read-only file system

root@Docker-host:~# docker run  -u taka -it   --read-only  697ce6d07386  /bin/bash
taka@28436b13aec8:/$

6:Disabling Inter-Container Communication

It is possible to isolate Docker containers from one another which prevents them from communicating with each other. This can be helpful if you want to isolate a particular Docker container. By default, Docker does not isolate containers, allowing them to communicate with each other.

1: In order to disable inter-container communication, create a new Docker network with the enable_icc option set to false and replacing <NETWORK-NAME> with any desired name.

root@Docker-host:~# docker network create --driver bridge -o "com.docker.network.bridge.enable_icc"="false" taka-network# new network 
336bcb061bc6e86405d5d3e89a21ff50b38958461583b569f0d2a334b20cfd78
#show the networks
root@Docker-host:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
2334de9e5a40 bridge bridge local
82e9ac2e3980 host host local
8538813c01f6 none null local
336bcb061bc6 taka-network bridge local

You can see the details

root@Docker-host:~# docker network inspect taka-network
[
{
"Name": "taka-network",
"Id": "336bcb061bc6e86405d5d3e89a21ff50b38958461583b569f0d2a334b20cfd78",
"Created": "2022-01-01T22:58:41.820967913Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {
"com.docker.network.bridge.enable_icc": "false"
},
"Labels": {}
}
]
root@Docker-host:~#

2:You can now run an isolated container by including the --network flag

root@Docker-host:~# docker run -it -u taka --rm  --network taka-network taka /bin/bash

--

--

No responses yet