Using SSH private keys securely when building Docker images
Use case: SSH keys required to access private GitHub repository
Couple of days ago, I needed to build image for Ethereum (this had to be custom build that included code from our private repository).
Accessing private GitHub repositories is easy: all you need is to generate RSA key, and add its public part as deploy
key to GitHub repository of interest (see
Repository Settings).
Then on client side, you need to load private part of the key and…well, that’s it - you will be able to clone from the private repository.
Problem
The problem is that if you ADD
private key to your Docker image (in Dockerfile
), it will be persisted as a layer. And even if you then
remove the private key (once you are done pulling from the repository), your private key will still be available (that’s
how layers work - key will be missing from the latest layer, but still available in the original layer it was added to).
And that is a huge no-no!
There are number of solutions available:
- pull the code before building, so that you do now need to use SSH key at all
- squashing layers, so that temporary layer, which was created when SSH key was added, is merged (and thus is wiped out)
- forward SSH agent (sounds nice, but is hard to accomplish)
- go as far as to use Habitus
- make a note that Habitus solution is very nice, but a bit of an overkill, and proceed from there to..
My solution
Which is pretty simple: expose any required artifacts using local web-server, and fetch them into the image using wget
or similar.
Without further ado, here is Dockerfile:
1FROM alpine:3.4
2
3RUN \
4 apk add --update git openssh-client && \
5
6 # add SSH key
7 wget -O /tmp/id_rsa http://192.168.99.1:8080/some_id_rsa && \
8 chmod 600 /tmp/id_rsa && \
9 eval $(ssh-agent) && \
10 echo -e "StrictHostKeyChecking no" >> /etc/ssh/ssh_config && \
11 ssh-add /tmp/id_rsa && \
12
13 # clone some private repo
14 git clone --depth 1 --branch develop git@github.com:farazdagi/some-repo.git && \
15
16 # install and cleanup
17 # ..some installation instructions
18 apk del git openssh-client && \
19 rm /tmp/id_rsa
20
21EXPOSE 8545
22
23ENTRYPOINT ["/foo"]
- As you see, everything is within a single
RUN
instruction - which means single layer added for all the cloning, installing and cleaning up. - Another point of interest is that I rely on
wget
to obtainsome_id_rsa
(which is the private key). And IP I use is default VM IP address of Mac that runsdocker-machine
(I am on OSX). On Linux box, it can be Docker network IP address of host machine.
Now, the last thing before we docker build
is to make sure that RSA keys are exposed indeed.
You can use Python’s SimpleHTTPServer or PHP’s bundled server, or whatever you have experience with.
I’ve used PHP’s internal server:
1cd ~/folder-that-has-your-deploy-id-rsa
2php -S 192.168.99.1:8080 # that's it, all files in the directory are exposed
The benefits are immense:
- solution is trivial to setup and easy to use (it will work with other “secrets” too)
- you do not need to post-process your images (to squash layers)
- you are still using conventional Dockerfile - no magic here, just
wget
command to obtain artifacts
References
- Sajadi, Khash Using SSH Private keys securely in Docker build