By Fran Lauriano

Gone are the days when a developer did his job without thinking about information security as a fundamental goal to any project. The developer needs to know that a security flaw can compromise, if not the image of a company, its own business. The possibility of leaks of sensitive information, such as customers’ credit card numbers, is now one of the biggest concerns of business managers. Besides, of course, the fraud of direct financial loss to the companies themselves. Precisely, for this very reason, security must be considered at all levels of a project, from code development to the infrastructure where it will run, which makes the team’s work even more thorough.

There are many methodologies and tools available to assist the developer’s work. And Docker is one of them. Its modularity methodology makes both changes and version control more efficient and secure. Since containerization allows you to disable a part of an application, either for repair or upgrade, without interrupting it entirely.

However, it is necessary to use Docker combined with good security practices, especially because when installing Docker and using its images on the server, another window opens for exploiting vulnerabilities. Thus, our goal here is to detail 8 (eight) measures to mitigate the exploitation of vulnerabilities using Docker. Among them, we present from good practices in the creation of images, to the execution of containers. It’s worth mentioning that this post is intended for people who already use Dockeror who at least have some knowledge about it – since it deprives itself of more basic explanations. For initial information about what Docker is and what it’s for, we suggest reading the topic on the RedHat blog.

 

  1. Use reliable images

One of the great advantages of using Docker is the creation of images from an existing one. Therefore, it’s necessary to be careful with the guide images used, as many of them may be poorly configured, or worse, they may contain malware. To reduce this possibility, it’s essential to use authentic images, available on the Docker Hub. It’s also crucial to enable the Docker Content Trust feature, responsible for validating the images, recognizing their authenticity; what should be done through the following command:

$ export DOCKER_CONTENT_TRUST=1

 

This way, when you try to download an unsigned image, the following message appears:

$ docker pull mongoclient/mongoclient

Using default tag: latest

Error: remote trust data does not exist for docker.io/mongoclient/mongoclient: notary.docker.io does not have trust data for docker.io/mongoclient/mongoclient

 

  1. Keep the containers updated

Keeping Docker containers updated should avoid security vulnerabilities, since the updates can include essential patches. However, the simple use of the “latest” tag is not a good practice, once it will always download the latest version available at that time, which can cause your application to “crash”. Therefore, it’s better to make a routine to review your Dockerfile and update it with specific versions.

 

  1. Do not use privileged mode

The container configured in privileged mode has root access to host machine resources. Therefore, if an attacker can gain access to the container, they can access the host machine with root user. In practice, you don’t have to worry about this, because by default, this setting is disabled. However, it’s important to make it clear that when you’re creating the container, you should avoid the use of the flag –privileged, that activates the mentioned feature. If you want to check if a container is in privileged mode, use the following command:

$ docker inspect --format='{{.HostConfig.Privileged}}' [container_name|container_id]

 

To run a container without privilege:

$ docker run -d --name noprivileged -it alpine
$ docker inspect --format='{{.HostConfig.Privileged}}' noprivileged

false

 

To enable privileged mode:

$ docker run -d --privileged --name privileged -it alpine

$ docker inspect --format='{{.HostConfig.Privileged}}' privileged




true

 

  1. Do not run processes in containers as root

The use of containers allows the isolation of applications, but the docker can offer incomplete isolation, since the resources of the host machine are shared with the container. Thus, running applications as root inside the container, can make it possible to exploit vulnerabilities in the kernel or Docker daemon, making room for an attacker to scale access from inside the container to outside, allowing access to the host machine. For this reason, it’s recommended to specify a non-root user to execute the processes inside the container. This can be done using the USER command. Here’s an example based on an alpine image, where a user is created who writes the message “Hello World!” in a file and displays it on the screen:

FROM alpine:3.13

RUN adduser -D alpine

USER alpine

RUN echo "Hello World!" > /tmp/hello.txt

CMD ["cat", "/tmp/hello.txt"]

 

  1. Limit the capabilities of a container

Docker containers come with some capabilities enabled. Linux capabilities allow processes to perform privileged operations that should be limited only to the root user. This way, controlling a user’s access privilege level is not enough. For that reason, you must disable these features. To exemplify this, you can observe the capabilities of an alpine distribution using the following Dockerfile:

FROM alpine:3.13

RUN adduser -D alpine

RUN apk update && apk add libcap

CMD ["getpcaps", "1"]

 

Even running with a non-root user, the container still has permissions on several capabilities:

$ docker run --rm --user alpine -it capabilities

1: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=i

 

To disable these capabilities, the –cap-drop ALL parameter is used:

docker run --cap-drop ALL --user alpine --rm -i -t capabilities

1: =

 

  1. Do not put passwords in Dockerfile

Putting a password in the Dockerfile or in source code used in the generation of images, besides not being a good security practice, makes the Dockerfile recipe non-reusable. To better handle this sensitive information, you can use Docker Secrets or Kubernetes Secrets. Using these resources, passwords are kept in memory only at run time; thus, as soon as the container is stopped, this information disappears from memory.

 

  1. Combine the use of lightweight images with multiple build stages

The use of small images, in addition to speeding up the image creation step, brings a security gain, since they reduce the vulnerability exploitation windows. In addition, it’s possible to combine light images with multiple Dockerfile build stages, using the tag FROM. With that, you can copy an artifact from one stage to another according to its usefulness, leaving the final image with only what is necessary for it to function. See, in the following example, a Dockerfile recipe for compiling and running an application in golang:

FROM golang:1.15.7-alpine3.13 as builder

RUN adduser -D deployer

WORKDIR /go/src/my-app

COPY api .

RUN go mod download

RUN CGO_ENABLED=0 go build -o build//my-app cmd/main.go




FROM scratch as /my-app

COPY --from=builder /etc/passwd /etc/passwd

COPY --from=builder /go/src//my-app/build//my-app .

USER deployer

CMD [".//my-app"]

 

  1. Make use of audit tools

Auditing tools can be great allies to identify bad configurations in Docker. Docker Bench for Security is one of those tools, and follows the CIS Docker Benchmark recommendations. This application has 3 alert levels: WARN, INFO and PASS. The former is critical and must be corrected; the second, while not critical, should not be ignored; the latter, in turn, signal that everything is right. Here’s an example:

Conclusion

A good way to stay secure with the use of Docker is, therefore, not to forget these 8 safe practices. Remember:

  1. Use reliable images;
  2. Keep the containers updated;
  3. Do not use the privileged mode;
  4. Do not run processes in containers as root;
  5. Limit the capabilities of a container;
  6. Do not put passwords in Dockerfile or source code;
  7. Combine the use of lightweight images with multiple build stages;
  8. Make use of audit tools.

 

References

https://www.cisecurity.org/benchmark/docker/

https://docs.docker.com/engine/security/rootless/

https://docs.docker.com/engine/security/trust/

https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/

https://docs.docker.com/engine/swarm/secrets/

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

https://man7.org/linux/man-pages/man7/capabilities.7.html

https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups

https://github.com/docker/docker-bench-security

https://docs.docker.com/engine/security/