Using SSH Private Keys Securely in Docker Build
Secrets are almost always needed during a build. In majority of cases, we need to provide a private SSH key to pull our code from a private git repository.
Join the DZone community and get the full member experience.Join For Free
Secrets, including private SSH keys, are almost always needed during a build. In majority of cases, we need to provide a private SSH key to pull our code from a private git repository.
Prior to Docker days, we had our private keys in our home directly
~/.ssh and could pull git repositories without sharing our secrets. We could also use SSH forwarding to pull private git repositories on remote servers. With Docker this is not possible easily.
I've seen different approaches being suggested. Here's a list and why I think they're not adequate:
Pull the Code From Private Repos Before Starting the Docker Build
This doesn't work for many dependency management systems like Gems, Go packages or npms as they're part of the build process.
Start the Docker daemon With SSH Forwarding
This is a good solution but difficult to get working and doesn't work well on build servers.
Add and Remove SSH Keys to Images
This method is guaranteed to work but has two major drawbacks: you need to copy the key from
~/.ssh to your local folder (Build context), which makes it exposed to other users on your machine as well as accidental commit to your repository, let alone accidental publishing of your image with your keys if the delete part doesn't work (delete has to be a squash for this to work).
Using a Local Web Server
Habitus is an open source Docker build flow tool that supports complex builds. From version 0.4 it also supports exposing secrets to the build process in a secure way. Here's how it works:
Habitus comes with an internal web server that runs for the duration of the build process. This web server exposes your defined and selected secrets (like your private SSH keys), to the container being built. This way there's no need to move the SSH key out of its secure home, while making it possible to use them and remove them in a single Dockerfile instruction.
Imagine a Dockerfile like this:
FROM ubuntu # ... usual apt-get steps RUN ssh -T email@example.com
This will fail due to authentication issues.
With Habitus you can do this:
FROM ubuntu ... usual apt-get steps + adding github to known_hosts RUN wget -O ~/.ssh/id_rsa http://192.168.99.1:8080/api/v1/secrets/id_rsa && ssh -T firstname.lastname@example.org && rm ~/.ssh/id_rsa
So what's going on?
Habitus is a single executable that runs on your machine. It connects to your Docker daemon and starts a Docker build as you would normally. Using a yaml file called
build.yml you can run complex builds consisting of multiple Dockerfiles as well as controlling which secrets are exposed to the build. Here's an example of what
build.yml can look like:
build: version: 2016-03-14 steps: builder: name: builder dockerfile: Dockerfile.builder secrets: id_rsa: type: file value: _env(HOME)/.ssh/my_private_key
This will expose the contents of
~/.ssh/my_private_key as a secret to the caller of the Habitus API web server through this URL:
The IP address here is the default VM IP address of your Mac running Docker Machine. On a Linux box, it can be the Docker network IP address of your machine. This can be made configurable with Dockerfile Build Args:
FROM ubuntu ... ARG host RUN wget -O ~/.ssh/id_rsa http://$host:8080/api/v1/secrets/id_rsa && ssh -T email@example.com && rm ~/.ssh/id_rsa
You can pass in the host IP into Habitus:
$ habitus --build host=10.0.99.1
This method has many advantages:
- It doesn't leave any traces of your secrets on the images
- The same Dockerfile works for different developers and build servers without sharing secrets
- The web server is exposed only to the internal Docker network of your machine and only for the duration of the build
- You control which files are available through the service and can map their names for consistency
- The same Dockerfile can run without Habitus (this part needs running a local web server on your machine or wrapping the
RUNstep in error/timeout protection so it won't fail the build, but is better than hacking Docker to include new commands that won't work with a normal Docker setup)
Published at DZone with permission of Khash Sajadi, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.