Use your ssh key with a passphrase inside a docker container

Old timey hotel keys in pigeon holes made of wood.

The number of articles, stack overflow questions, and GitHub gists I had to go through only to figure out why each and every one of them did not work is just astounding only to find the solution at the “oh my god, obviously” place...

Background!

“Why are you even spending time on this?...”

At Work™ there are a bunch of processes and whatnot, docker containers, build steps, etc. Your work probably has similar things, it’s about as controversial as saying you use a monitor. Hardly specific to any one company.

One of these steps is supposed to clone a private repository from a VCS (version control system). For the sake of argument, let’s use GitHub.

The container does not have its own ssh key, because that would be leaking the keys to your kingdom to the world, should the image ever get published, and rotating a key across an enterprise is a Pain™. Don’t get into that position. Anyways, the expectation is that that container, once run, is going to use your own ssh key to clone the repository, so if you have access to the repo it works, if you don’t have access to the repo, it doesn’t work. Makes sense, keep secrets outside, and have proper security around our codebase.

The mechanism it did that was weird though. It mounted the ~/.ssh folder, where your ssh config file is and all your keys with your private and public parts by default, and then just used that. The documentation also mentioned something about starting an ssh-agent locally and adding the identity, as in your ssh key, to the agent, so docker can then make use of it. Still sounds logical.

It quickly fell apart the moment I touched that system though, because, being a good security conscious person, I also have a passphrase on my ssh key. Upon running that particular make command, which runs the docker container with some extra arguments, the container asked for my passphrase. I pasted it in, hit enter, and then docker froze. Or got stuck. It did not keep on going.

It also revealed my passphrase, which is not something it should have done because it’s a password input.

All of that led me thinking that it wasn’t actually using the ssh-agent, it was simply trying to use the keys present in the mounted filesystem. If an ssh key does not have a passphrase, this works, accidentally.

Onwards to the rabbit hole!

Bad info on the internet

Docker does not work like that

I’ll dedicate this section on listing the questions and some of the answers I found and then writing about why they are wrong. Or not necessarily wrong, just not usable for me. If you want to skip to the solution, head to the next heading.

The goal I wanted is to reuse the launched and active ssh agent running on the host machine (a mac) from within the docker container. I know the ssh agent is running, because if I run ssh-add -l (list all the identities added to the ssh-agent) I get the following output:

⏱  11:10:24 💀 95%  ➜
ssh-add -l
256 SHA256:OgHhY8UxynWzjVEOlalYDU5qIIpWfbmYTDez3LwOBOQ id_ed25519 (ED25519)

If I run ssh-add -l from within the docker container, I should get the same output.

Here are the results that did not work:

results for “share ssh agent docker”

James Ridgway: Sharing an SSH Agent between a host machine and a Docker container

Link to the article, it’s from September 2020.

The idea in this article is to mount the value of the host machine's SSH_AUTH_SOCK environment variable value, a path to where the ssh-agent socket is, to a known path inside the container, and setting the same environment variable inside the docker container to that known path.

The problem here is that sockets, a type of file descriptor, cannot be mounted into a container at the end of 2023. It’s a docker engine limitation. Proper files and directories are good to go, but not sockets. I am also suspect about symbolic links.

A lot of the solutions I read are trying to use the same method.

GitHub gist by d11wtq

Link to the gist, it’s from August 2014.

It suffers from the same limitation, sockets cannot be mounted.

Stack Overflow: Using SSH agent with Docker Compose and Dockerfile

Link to the question, it’s from May 2022, so quite recent.

Same issue about mounting the socket, plus they delve into using the build step, which is not something I wanted to do.

results for “ssh key within docker container”

Medium: Use Your local SSH Keys Inside a Docker Container by David Barrall

Link to article, it’s from October 2020.

New info, but of the four different ways he solves this, all of them rely on changing the build step, and the last one requires using Docker Secrets, which requires using Docker Swarm, or Compose. Neither of which I have, only a container.

Plus he also seems to just mount the .ssh directory into the image. I don’t think a passphrase protected key would actually work. By “work”, I mean the container can use the key to clone something without having to enter the passphrase.

FastRuby.io: Securely using SSH keys in Docker to access private Github repositories by Mike Toppa

Link to article, it’s from September 2022.

Lists four ways of achieving this. All of them involve modifying the build step, so automatically a no-go. The fourth one seems promising though, because it seems to rely on some built in Docker functionality for it with the --mount=type=ssh argument, but that’s a build only argument, so can’t use it for anything docker run.

Reddit: A docker container need an ssh key to access server. Do you build the image with the key or do you pass the key on docker run? by /u/rrzibot

Link to the thread, it’s from October 2022.

Answers generally tend to favour the “mount the keys as is to the container, all good”, which doesn’t work if you have passphrase.

Other answers are detailing why including keys in build are a bad idea.

Yet another says “use a secret management vault”, and then it’s the usual Reddit offtopic responses.

Solution

I got the ssh agent forwarded to the docker container

The solution is so simple, so easy to access, but so badly optimized for SEO, it’s kind of weird. It actually came from the 1password forums about SSH Commit signing inside Docker.

💡
Heh, funnily enough before I bumped into the 1Password forum, another member in one of the Discord servers I’m in, nanovad, already linked me to the same solution. At the time I ignored it because I didn’t read the actual content of the link and thought they were just linking to another “mount a socket” one, which doesn’t work. Turns out they were linking to the run services one 😅

In that post Floris, a 1Password employee, essentially says: “Oh yeah Docker has run services, and the ssh auth sock is one of them.”

What...

And there it is. SSH agent forwarding on the Docker Desktop Networking services page.

docker run \
    --rm \
    --mount type=bind,src=/run/host-services/ssh-auth.sock,target=/run/host-services/ssh-auth.sock
    -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock" \
    your_docker_image \
    cmd_to_run

And voilá, I have the desired output. There are some other considerations, like the user inside the container needs to be the same user as your host’s user, and group, otherwise you’ll get a permission denied error from the agent, but otherwise I now have a usable shared ssh agent that I can use to clone a private repository from within the container.

There are some complications where 1Password is the ssh agent, but if you start yours with eval $(ssh-agent -s), this works.

Photo by Fernando Santander on Unsplash