At OpenMeter, we use Dagger to run our programmable CI/CD pipelines in containers. The process includes building and publishing various types of artifacts like Docker images to OCI-compliant registries (GitHub Container registry, etc.). Although Dagger has built-in support for publishing container images, it does not have a universal way to authenticate against registries that's compatible with all tools interacting with OCI registries.
How does OCI registry authentication work?
When interacting with an OCI registry, particularly when pushing artifacts to
it, you need to authenticate yourself. I'm fairly confident that most of you
have already encountered this in some form, most likely when using
docker login
to authenticate against Docker Hub.
Although we won't cover all the details here, here is a high-level overview of the authentication flow:
- You run
docker login
- You enter your credentials
- Docker CLI sends a request to the registry
- Finally, Docker stores your credentials
The authentication process is similar for other registries as well.
One of the main challenges during authenticating is to securely store the
credentials. In the early days, Docker simply stored the credentials in
plaintext in the ~/.docker/config.json
file. Today it uses
credential helpers to
store credentials securely in your OS's keychain.
Since Docker is the original tool for interacting with OCI registries, most tools have opted to use the same (or a very similar) mechanism for storing credentials.
(If you are interested in more details about how registry authentication works, I gave a talk at the Open Source Summit EU '23. You can find the record here and the slides here.)
How does Dagger handle registry authentication?
Dagger's core abstraction revolves around containers. It has built-in support for building and publishing container images.
Here is an example of how you can publish a container image using Dagger's programmable interface:
Dagger safely handles the credentials during the entire lifecycle of the pipeline (ie. they don't get written to the filesystem). Unfortunately, this security guarantee in Dagger is unique to container images, and it's not available for other types of artifacts like Helm charts for example.
OCI registry authentication for non-container artifacts
Let's take a look at how we can authenticate against an OCI registry when publishing non-container artifacts.
We will use Helm (OCI-compatible Kubernetes package manager) as an example:
In this example, we use the helm registry login
command to authenticate
against the Helm registry and we pass the password as a command-line argument.
Although the credentials are injected into the container using Dagger's secret
primitive, we still have a security risk.
This risk lies in Helm writing the credentials to
~/.config/helm/registry/config.json
in plaintext (no OS keychain is available
in this case), making it possible for someone to extract them from Dagger's
layer cache.
The actual exposure depends on how you run Dagger, but it's an undeniable fact that the credentials are stored in plaintext somewhere on the filesystem.
The solution
The solution lies within the problem itself: if the tool (in this case Helm) writes the credentials to a file, it needs to read them from it as well. If the file can be created outside the container and mounted safely for Helm to read, we can avoid storing the credentials on the filesystem.
Fortunately, the registry config format is quite simple:
You can easily create the contents of this file in a Dagger function.
Here is an example of how you can use it:
This solution effectively mitigates the risk of the credentials being exposed by
avoiding writing them to the filesystem. One caveat is that we can no longer use
the tool's native way of authenticating against the registry (ie.
helm registry login
), but that's a small price to pay for security.
Taking it a step further
This is a fairly simple solution, but it's not the most comfortable to work with, especially when building reusable components (for example: a Helm module for Dagger).
Wouldn't it be nice if we could use an API similar to Dagger's built-in one for containers?
I thought so too, so I created a Dagger module for this purpose, called registry-config. It takes care of creating the registry config file and mounting it into the container.
Here is how you can integrate it into your module:
First, install registry-config
:
Next, add the registry config module to your module (and make sure it's initialized in the module constructor):
Next, expose the registry config API in your module:
Finally, use the registry config module in your container:
Using Helm as an example again, here is how you can use the module after implementing registry config support:
That's it! Now you have a secure way to authenticate against OCI registries for tools like Helm that write credentials to a file.
If you'd like to see the registry config module used in a real module, you can check out my Helm module.
Conclusion
OCI tools like Helm store credentials on the filesystem in plaintext. It poses a security risk that is easy to overlook, especially in CI/CD environments where multiple users have access to the same machine.
Fortunately, with Dagger and the registry-config module, it is easy to create a secure way to authenticate against OCI registries.