The Talent500 Blog

Top 9 Docker Security Best Practices For 2023

Top 9 Docker Security Best Practices For 2023

 

Over the past decade, Docker has emerged as one of the most popular choices for software virtualization across the DevOps community. It helps control development costs, is simple and quick to configure, efficient for CI, easy to maintain and offers high compatibility – so much so that the word “container” has almost become interchangeable with “Docker.” However, as a DevOps engineer, it is important to understand that container image/docker security is a complicated issue that needs to be addressed proactively. Failing to do so may expose your project to unknown vulnerabilities and compromise overall security. To help our fellow readers, the DevOps experts at Talent500 have compiled a list of the top nine Docker security best practices that you must follow religiously in 2023. 

Let’s get started!

#1 Make Sure To Not Expose The Docker Daemon Socket

All communications with the Docker API are enabled by the Docker socket /var/run/docker.sock, a Unix network socket that is owned by the root user by default. Leaving it exposed will allow any user to gain root access to the host, as the permissions granted to them will be at par with the root user. Running docker with -H tcp://0.0.0.0:XXX leads to serious vulnerabilities including that from public internet actors, as malicious users may gain direct, unauthenticated, and unencrypted access to the Docker daemon. Thus, you must ensure that tcp Docker daemon socket is disabled and that /var/run/docker.sock is not exposed to any other container as well. You may also use SSH and TLS for secure access to Docker even when dealing with remote connections.

As an additional step, mount the socket read-only to further reduce the chances of exploitation:

docker run –read-only alpine sh -c -echo “running as read only” > /tmp’

You may use the below snippet to look out for containers running in similar configurations:

docker inspect –format=’{{.HostConfig.Binds}}’ [container id]

#2 Be Mindful Of Base Image Selection

It is observed that DevOps engineers aren’t picky when it comes to selecting the node image, and they conveniently choose a proper OS regardless of their requirements. A full-fledged OS isn’t the best choice when viewed from a security point of view since they have tens and hundreds of known vulnerabilities. Generally, DevOps engineers use base images with full Debian Stretch distribution like docker pull node because it is commonly utilized even if the project scope doesn’t require general operating system libraries and utilities.  Such methods only expand the attack surface, so make sure that your base image is minimal by conforming to your actual requirements and dependencies.

#3 Avoid Rootless Containers

An overwhelming number of images run container entrypoint as root (UID 0), causing severe security threats. In fact, very few cases require you to execute a container as root, while your execution environment may also block you from doing so. In such cases, you might use su-exec or gosu for rebounding to a standard user.

In order to run a non-root container, you will need to make the following changes in your Dockerfile:

Make the user (as mentioned in the USER instruction) exit inside the container:

FROM alpine:3.12

# Create user and set ownership and permissions as required

RUN adduser -D myuser && chown -R myuser /myapp-data

# … copy application files

USER myuser

ENTRYPOINT [“/myapp”]

Also, you must enable appropriate file system permission at locations where the process will read/write.

#4 Leverage Multistage Builds

Using multistage builds is one of the best Docker security best practices, given the fact that it has operational benefits apart from improving security. In this method, the artifacts generated from the intermediate container are copied to the final image with minimum binary files and dependencies. Since the build tools and intermediate files aren’t a part of the final image, the attack surface is significantly smaller. 

Here’s an example of a multistage build:

#This is the “builder” stage

FROM golang:1.15 as builder

WORKDIR /my-go-app

COPY app-src .

RUN GOOS=linux GOARCH=amd64 go build ./cmd/app-service

#This is the final stage, and we copy artifacts from “builder”

FROM gcr.io/distroless/static-debian10

COPY –from=builder /my-go-app/app-service /bin/app-service

ENTRYPOINT [“/bin/app-service”]

Next, use the use the golang:1.15 container:

FROM golang:1.15 as builder

Next, copy the source code to continue the build:

WORKDIR /my-go-app

COPY app-src .

RUN GOOS=linux GOARCH=amd64 go build ./cmd/app-service

Define the next stage on the basis of Debian distroless image:

FROM gcr.io/distroless/static-debian10

Use the –from=builder flag and COPY the executable from the builder phase: 

COPY –from=builder /my-go-app/app-service /bin/app-service

Good to go!

#5 Make executables owned by root and not writable 

Making the root user the owner of every executable in containers and making them non-world-writable are two simple hacks that go a long way in ensuring Docker security.Hence, you must avoid the following:

WORKDIR $APP_HOME

COPY –chown=app:app app-files/ /app

USER app

ENTRYPOINT /app/my-app-entrypoint.sh

This is due to the fact that –chown app:app/RUN chown … gives the user ownership rights as opposed to only providing execution rights. Making the root user the owner also allows you to enforce container immutability which restricts the containers from updating code at runtime in an automated manner, thereby preventing modification. This will also prevent the user from modifying any scripts/binaries that can potentially invite attacks.

#6 Leverage Linux Security Modules 

Using a Linux security module like SELinux or AppArmor can go a long way in fortifying your Dockers.

For instance, SELinux enables you to enforce policy-based access, which can solve multiple problems at once:

AppArmor, on the other hand, uses program profiles to limit the functionality of specific applications after you load a new profile in it:

apparmor_parser -r -W /path/to/custom_profile

Now, you may run the custom profile:

docker run –rm -it –security-opt apparmor=custom_profile hello-world

You can also use Seccomp to allow or disallow system calls within a container:

docker run –rm -it –security-opt seccomp=./seccomp/profile.json hello-world

However, we would like to tell our readers that while these security measures are extremely useful, do not disable the default security profile!

#7 Put A Limit To Resources

DoS attacks can be daunting for any software professional and while you might go for state-of-the-art solutions, limiting your resources is an excellent fallback strategy.

Begin by limiting the number of restarts (–restart=on-failure:<number_of_restarts>), maximum number of processes (–ulimit nproc=<number>), CPU, memory, and even  maximum file descriptors (–ulimit nofile=<number>).

This way, you have a reliable strategy to face DoS attacks and still be in a tactically better position.

#8 Use Metadata Labels

You must add metadata labels to the images as they allow better utilization of the images.

This is an extremely simple way to communicate your (maintainer’s) details to users, and it can be as simple as adding the below command: LABEL maintainer=”me@abc.com”

This is the “maintainer” label which allows you to designate the name and email address, but you can practically add any metadata that you feel is important to the user. Image labels provide metadata for the image you’re building. This helps users understand how to use the image easily. The most common label is “maintainer,” which specifies the email address and the name of the person maintaining this image. Add metadata with the following LABEL command: In addition to a maintainer contact, add any metadata that is important to you. This metadata could contain: a commit hash, a link to the relevant build, quality status (did all tests pass?), source code, a reference to your SECURITY.TXT file location and so on.

When adding labels, you must also adopt a SECURITY.TXT (RFC5785) file that references your responsible disclosure policy for your Docker label schema: LABEL securitytxt=”https://www.example.com/.well-known/security.txt”

Details like application version number, licensing, the relation of containers to projects or components, and link to your website are also required by users to make sure to drop them too.

#9  Enable Runtime Security For Containers 

Most organizations aren’t capable of detecting exploits in real-time, let alone tackling them, and this is one of the stark realities of the cybersecurity world. The inability to counter ongoing attacks results in exposing workloads which is a huge setback. Thus, you must use Kubernetes tools and processes like policy admission control, network policies, secrets, role-based access control (RBAC), and audit logs to introduce automation components as security analysts work on live vulnerabilities. You must also choose the right container engine runtime settings, like read-only root filesystem settings discussed above. 

Wrap Up

In this article, we discussed some of the most popular low-effort-high-return Docker security best practices, but there’s more to it. Dockers inherently increase the attack surface, but with the right best practices, you can minimize the vulnerabilities while continuing to enjoy its benefits.

Join Talent500 to know more about such DevOps insights and land the most exciting careers in the industry!

 

0