SSH with Docker and BuildKit (with SPM)
Posted Saturday, April 3, 2021.In a previous post, Docker with private SPM dependencies we discussed how to inject an SSH key into the Dockerfile for private dependency resolution, and also how to make this work in GitHub actions.
With the new Docker BuildKit (requires Engine 18.09 or higher), there is a new option to forward SSH agent connections. BuildKit is on by default in Engine 20.10
and in the action docker/build-push-action@v2
. Because it is now the default option, it doesn't require the build to use DOCKER_BUILDKIT=1 build .
when building, or have # syntax = docker/dockerfile:1.2
at the top of your Dockerfile to get access to these new features.
While we would always recommend using multi-stage builds (future post coming soon) so that the stage with the SSH key is not available to the public and only for the build, it seems that during the build the old version would leak the private key to the logs in the step echo "$GITHUB_SSH" ...
to stdout
. For local builds this isn't much of a risk. However on GitHub actions, anyone with access to the actions tab - and therefore the history of all the action's logs - would be able to see all or part of the key.
Using the new --ssh
option in the build will stop this from being a possibility. Here is how to set this up for both local builds and on GitHub actions.
Dockerfile Changes
Original
From Docker with private SPM dependencies, our Dockerfile looked as such:
And this could be built using docker build --build-arg GITHUB_SSH="$(cat ~/.ssh/github_rsa)" -t hiimtmac/cool-app .
Updated
Using the new --ssh
option, we can adjust our Dockerfile to look like this:
And this could be built using docker build --ssh github=$HOME/.ssh/github_rsa -t hiimtmac/cool-app .
. Any RUN
command that needs SSH access (in this case the RUN swift build ...
command) needs to have --mount=type=ssh,id=...
in the beginning.
You can use the default SSH_AUTH_SOCK by pre-pending your
RUN
commands with just--mount=type=ssh
and then building withdocker build --ssh default -t hiimtmac/cool-app .
, but I like to use a separate key because this is the key that GitHub actions will have access to, and then at no point is my personal private key ever involved in the build. Additionally, if you needed separate keys for github/bitbucket etc you would need to id/name them to differentiate and use them for differentRUN
commands.
Results
When building the old way with the original Dockerfile, you can see part of the private key leaking to stdout
:
docker build --build-arg GITHUB_SSH="$(cat ~/.ssh/github_rsa)" -t hiimtmac/test .
However building the new way with the new BuildKit and the updated Dockerfile, you can see that the private key is not in the stdout
logs at all:
docker build --ssh github=$HOME/.ssh/github_rsa -t hiimtmac/test .
Actions Changes
Original
From GitHub actions with private SPM dependencies, our actions workflow looked as such:
Updates
Using the new --ssh
option, we can adjust our actions workflow to look like this:
Result
Similar to when building locally, the SSH key (or part of it) is no longer present in the logs!
Secrets Changes
The original format of the private key has to be preserved in the GitHub secret for this to work. In GitHub actions with private SPM dependencies we changed:
to:
before uploading to GitHub as a secret. In this new workflow, we would want the original representation of the private key in the GitHub secret, not the one with all the newlines removed. Adjust this in GitHub before running the action.
Bonus
Here are a couple of additional steps you can take:
SPM Cache
Specifically for an SPM project you can cache dependencies so that between builds it will only re-fetch dependencies if your Package.resolved
has changed.
Note that the
--mount=type=ssh,id=github
part has been moved from theswift build
command to theswift package resolve
command as that is the one that needs it to resolve private dependencies.
GitHub Actions Tags
If you want to tag multiple versions based on the SEMVAR git tag, you can do so below like this:
For tag 2.4.5
this would create the following Docker images:
- hiimtmac/fake-repo:2.4.5
- hiimtmac/fake-repo:2.4
- hiimtmac/fake-repo:2
- hiimtmac/fake-repo:latest
- hiimtmac/fake-repo:develop
In Closing
Some further reading about ssh and secrets in the new BuildKit can be found here, I used these resources to put together this solution:
- https://docs.docker.com/develop/develop-images/build_enhancements/
- https://medium.com/@tonistiigi/build-secrets-and-ssh-forwarding-in-docker-18-09-ae8161d066
- https://www.webfactory.de/blog/use-ssh-key-for-private-repositories-in-github-actions
- http://blog.oddbit.com/post/2019-02-24-docker-build-learns-about-secr/
As always, if you know of a better way to do something, please let me know!
Tagged With: