← All Articles

Using SSH Private keys securely in Docker build

Khash SajadiKhash Sajadi
Mar 15th 16Updated Jun 13th 19
Open Source
Security

using-ssh-private-keys-securely-in-docker-build

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).

My solution: Using a local web server

Habitus is an open source build flow tool for Docker 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 git@github.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/secrets/file/id_rsa && ssh -T git@github.com && 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:

http://192.168.99.1/secrets/file/id_rsa

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/secrets/file/id_rsa && ssh -T git@github.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 RUN step 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)

You can read more about Habitus and how to use it as well as contributing to it on the Habitus Github page.


Related blog post: Pulling Git into a Docker image without leaving SSH keys behind -->

Watch our talk at the DigitalOcean Meetup about Habitus-->



Try Cloud 66 for Free, No credit card required