How To Secure Docker Images With Encryption Through Containerd

Docker image encryption with containerd

Docker has exploded into popularity over the past decade for DevOps. Its massive adoption rates make it the first choice for container-based orchestration. It is used by small implementations and large-scale enterprises to launch and deploy applications using Linux containers — which in a nutshell is a form of OS-level virtualization.

In contrast to a VM, a container just contains the required files. Docker is open-sourced and is a container engine that uses Linux Kernel features to create containers on top of an operating system. This means that it is easy for a developer to efficiently shift an application over from a laptop to a test environment.

Docker by design is small, lightweight, portable, fast to launch, highly scalable, and great for continuous integration (CI) and continuous deployment (CD). But how secure is it?

By default, Docker container images are unencrypted. These container images often contain code and sensitive data such as private and API keys that are used by the application. This means that if a malicious user gains access to the Docker container, they also gain access to your sensitive data.

How do we prevent this? The easiest solution is to encrypt your Docker containers.

How to encrypt a Docker container image

For this tutorial, we will be using containerd to encrypt your Docker image. What is containerd?  containerd is an industry-standard for container runtimes that is available as a daemon for Linux and Windows and is designed to be embedded into a larger system. It manages the complete container lifecycle of its host system, from image transfer and storage, container execution and supervision, to low-level storage and network attachments.

To start using containerd, you will need Go Go 1.9.x or above on your Linux host. To install containerd, you can do using the wget command or go directly to the download page. The current latest version is 1.5.2 and here is the command for installing the binaries for cotnainerd.

If a configuration file doesn’t exist, you can generate a default one using the following command:

To connect to containerd, create a new main.go file and import containerd as a root package that contains the client. Here is the sample code:

The above will create a new client with a default containerd socket path. To change this, create a context for calls to client methods.

Now it’s time to pull in the redis image from DockerHub.

Here is the entire main.go code you need in one space:

Now you can build your main.go

If you run sudo ./main, you will get the following returned result (or something similar):

Now that we have containerd working, how exactly do we encrypt a Docker image?

First, we need to generate some keys. Here are the commands for generating RSA keys with openssl.

Let’s pull in an image so that we can encrypt it.

To view your encryption information on the image, you can use the ctr-enc image layerinfo command. As we haven’t encrypted our image yet, here is what it can look like:

Now it’s time to encrypt our Docker image. To do this by using the ctr-enc images encrypt command. This will encrypt the existing image to a new tag. ctr-enc images encrypt takes five arguments.

The first argument is –recipient jwe:mypubkey.pem. This portion of the command tells containerd that we want to encrypt the image using the public key mypubkey.pem. It is prefixed with jwe: to indicate that the encryption scheme is JSON web encryption scheme.

The second argument is –platform linux/amd64. This flag tells containerd to only encrypt the linux/amd64 image.

The third argument is, which points to the image we want to encrypt.

The fourth argument is bash.enc:latest, which is the tag of the encrypted image to be created.

And finally, you can also decide which layer you want to encrypt using the –layer tag. This argument is optional and can be omitted if you want to encrypt the entire image and not just parts of the image.

Here is an example of how to use it with our public key.

To push your encrypted image to the registry, you can just use sudo docker run. It’s good to note that only the Docker registry version 2.7.1 and above supports encrypted OCI images.

Here is the full command for it:

You can now tag and push the image, and then delete the local copy using the following command:

Now if we attempt to run the encrypted container, the image will fail if the keys for the encrypted image is not provided. You can pass in the keys using the –key flag. Here is an example of how to do so:

That is basically it for encrypting a Docker image, pushing it to a registry, and running the decrypted image.

Where to from here?

When it comes to security, using the default settings is one of the biggest risks that any production-level application can experience. Encryption is one methodology for securing your Docker. Other methods include setting resource limits for your container, and implementing Docker bench security to check host, docker daemon configuration, and configuration files, in addition to container images, build files, and container runtimes.

Another standard security protocol for Docker is to never run a container as a root user. If you do not specify a user when starting a container, it defaults the user set in the image — which is often the root user.

Always scan and rebuild images to include security patches, so that your deployments are always up to date. You can also enable Docker Content Trust (DCT), which uses digital signatures to validate the integrity of images pulled from remote Docker registries.

Alfrick Opidi / About Author

Alfrick is an experienced full-stack web developer with a deep interest in taking technical information and converting it into easy to understand content. See him as a technology enthusiast who explores the latest developments in the industry and presents them in a relatable, concise, and decipherable manner.