Podman - Encrypted Images

The world of containers consists of images, which are made out of layers. Normally, everybody with access to the image can investigate it, check out what's in there and how it works. Encrypting your images can prevent others to see the content of your images, even if they get access to it.

Podman - Encrypted Images
Photo by Fer Troulik / Unsplash

The world of containers consists of images, which are made out of layers. Normally, everybody with access to the image can investigate it, check out what's in there and how it works. Encrypting your images can prevent others to see the content of your images, even if they get access to it.

Let's see how you can encrypt images, push them to a registry and start a container from it. Shall we?

Podman

Podman is a rootless and daemonless drop-in replacement for Docker. You can start and stop container, build and push images, and basically everything you can do with Docker.

There are some huge benefits, when it comes to Podman. It is a systemd native, which means you can control your containers with systemd services easily. It has various options to run containers as root or user. Not only that, but it also provides features that don't even exist in Docker, like auto-updates, running Pods and even Kubernetes deployments.

You can find a couple of articles in my blog, too.

Why is encrypting needed?

Having container images in a registry like Docker hub or quay.io has its benefits, for sure. You can also create private registries, that are protected, and the transport will be encrypted via TLS, too. Why even bother about image encryption?

Well, what happens if you have a couple of unattended machines like IoT devices? Downloading images from a private registry also requires you to login to said registry. Furthermore, the image will be unprotected on your servers, too.

And lastly, another layer of security for your intellectual property or most valuable code won't hurt, right? Protecting your images in case they are stolen might make the difference between long nights and a well deserved sleep.

Encrypting Images

Let's encrypt something. It's so easy, you might not even look back afterward.

Pre-Requisites

You will need to have Podman installed to follow the below guide.

If you need a kick-start for related topics, I have linked you articles from my Blog explaining "How to get started".

Podman - Getting Started
Podman is a daemonless container engine to manage, run and develop OCI Containers on your Linux System. It supports rootfull and rootless mode for your containers and brings some features, which are not present in Docker.
GPG - Getting Started 1/2
The world is full of signing and encryption methods. There is one, that is especially useful in Open Source software. GPG can be used to sign mails, Git commits, container image encryption and much more. Let’s set it up!

In addition, you will need access to a container registry. This can be a public one like Docker Hub, GitHub Container Registry or Quay, but you can also run your own registry locally, for testing.

Project directory

We will start with a super simple Dockerfile/Containerfile. But, we will put in some "super secure information". First, create a little project directory, looking like the below:

encrypted_container/
|
|- Containerfile
|- files/
    |- index.html
Directory Layout

Next, we will put our secure information in the index.html file. Yeah, I know, "Why should we put something secure in a web file?". Because it's an example, a really trivial one, and we only want to ensure that nobody can see the information by stealing the image.

<h1>Super Secure Site</h1>
<p>The secret passphrase is: "P0dM4n"</p>
files/index.html

Next, we will need a Containerfile for the new image.

FROM docker.io/library/nginx:1.23

LABEL org.opencontainers.image.authors="somebody@example.com"

COPY files/ /usr/share/nginx/html/
Containerfile

That's it. Everything normal so far. You can basically build and work with the content as usual.

Build & Push

Now, let's build and push our image. It's super easy. The build works exactly the same way as always. If you never build an image, you may take a look at "Podman - Images", first.

# Build image
$ podman build -t docker.io/dschier/wtdexamples:unencrypted .

# Check the image
$ podman image ls
REPOSITORY                         TAG          IMAGE ID      CREATED         SIZE
docker.io/dschier/wtdexamples      unencrypted  972a95e09895  51 seconds ago  147 MB

# Run the image for testing
podman run -dt -P --name example docker.io/dschier/wtdexamples:unencrypted 
9a5e3abf42c2b66bfb5478e7646d19bc796cf83c241b78aad9da8a7a6ec26f35

# Check for state
$ podman ps -a
CONTAINER ID  IMAGE                                      COMMAND               CREATED        STATUS        PORTS                  NAMES
9a5e3abf42c2  docker.io/dschier/wtdexamples:unencrypted  nginx -g daemon o...  3 seconds ago  Up 3 seconds  0.0.0.0:36227->80/tcp  example

# Check if its working
$ curl localhost:36227
<h1>Super Secure Site</h1>
<p>The secret passphrase is: "P0dM4n"</p>

This worked, so far. But what about encryption? Well, this is happening, if you want to share the image by pushing it to a registry. So, let's do this. I am using Docker Hub here, but this will work with all major container registries.

# Log in to your registry
$ podman login docker.io

# Push the unencrypted image (for reference)
$ podman push docker.io/dschier/wtdexamples:unencrypted

So far, nothing unusual. But, the above allows everybody in the world to pull the image and see the above password. Furthermore, one can inspect the image and see the layers:

# Inspect the image
$ podman inspect docker.io/dschier/wtdexamples:unencrypted

One can also run the image, as we have done above, without any kind of security measures. Therefore, we want to upload the image in an encrypted way.

Encrypting

Podman supports a multitude of encryption options. I will stick to JWE (JSON Web Encryption) for this article. It provides a strong algorithm and is easy to share across machines.

The easiest method to encrypt an image is JSON Web Encryption (JWE). To do this, you will need a TLS key pair. We can create one without installing any dependencies.

# Create a directory
$ mkdir certs
$ cd certs/

# Create the private key
$ podman run -it -v $PWD:/work:z docker.io/library/nginx openssl genrsa -out /work/wtdexample_encrypt_private.pem

# Create the public key
$ podman run -it -v $PWD:/work:z docker.io/library/nginx openssl rsa -in /work/wtdexample_encrypt_private.pem -pubout -out /work/wtdexample_encrypt_public.pem

# Check the keys
$ ls
wtdexample_encrypt_private.pem  wtdexample_encrypt_public.pem

Well, now we can use these keys to encrypt and decrypt our image. I will create a new tag, so we can compare each of the images afterward.

# Tag a new image
$ podman tag docker.io/dschier/wtdexamples:unencrypted docker.io/dschier/wtdexamples:jwe

# Push the image, but encrypt it
$ podman push --encryption-key jwe:wtdexample_encrypt_public.pem docker.io/dschier/wtdexamples:jwe

To validate, that this worked, let's delete and pull the image.

# delete the tagged image
$ podman image rm docker.io/dschier/wtdexamples:jwe
Untagged: docker.io/dschier/wtdexamples:jwe

# delete other, related images
$ podman image rm docker.io/dschier/wtdexamples:unencrypted 
Untagged: docker.io/dschier/wtdexamples:unencrypted
Deleted: c9d7871a629ac0790ebf0de7f3ede5f73bc53ce7a8146b6fcac15a924c600663

# Pull the image again
$ podman pull docker.io/dschier/wtdexamples:jwe
Trying to pull docker.io/dschier/wtdexamples:jwe...
Getting image source signatures

...

Error: writing blob: adding layer with blob "sha256:649ec709baac4a5e31a098e28723de907b5e7e16f755ec31c105df9983ccd609": processing tar file(archive/tar: invalid tar header): exit status 1

As you can see, this wasn't working. So, let's try pulling it with our private key.

# Pull the image and provide a key
$ podman pull --decryption-key wtdexample_encrypt_private.pem docker.io/dschier/wtdexamples:jwe

Trying to pull docker.io/dschier/wtdexamples:jwe...
Getting image source signatures

...

WARN[0008] Compressor for blob with digest sha256:c47b86cfcc03bce8dbf4e8684582dc4fc80c10bde85da272d87294c8405396c6 previously recorded as uncompressed, now gzip 
Copying config c9d7871a62 done  
Writing manifest to image destination
Storing signatures
c9d7871a629ac0790ebf0de7f3ede5f73bc53ce7a8146b6fcac15a924c600663

temp/encrypted-containers/certs took 8s 

$ podman image ls
REPOSITORY                     TAG         IMAGE ID      CREATED      SIZE
docker.io/dschier/wtdexamples  jwe         c9d7871a629a  5 days ago   147 MB

Well, this worked fine. Now, we can start it, as usual.

The feature seems to be super new, but is already supported from Podman, Buildah and Skopeo. Therefore, let me link to the related documentation.

buildah/buildah-push.1.md at main · containers/buildah
A tool that facilitates building OCI images. Contribute to containers/buildah development by creating an account on GitHub.
skopeo/skopeo-copy.1.md at main · containers/skopeo
Work with remote images registries - retrieving information, images, signing content - skopeo/skopeo-copy.1.md at main · containers/skopeo

Conclusion

Well, well, finally we can encrypt images, push them to a public registry and still protect the content.

What is the use case? Managing access to images, based on roles/users and permissions on a registry, can be cumbersome and annoying. Sharing one secret file, to get access to exactly one (or multiple) images, is pretty easy and can be used on many devices without a huge management overhead.

What do you think? Is this useful for you? Let me know!