ssh auth forwarding for containers.
# don't just trust me
docker run --rm dobbs/docker-agent
# invoke it
eval "$(docker run --rm dobbs/docker-agent)"
# this is leveraging ssh-agent, so maybe this too
ssh-add
# enjoy your new-found, key-based ssh access
IMAGE_OF_YOUR_CHOICE=buildpack-deps:xenial
docker run \
--volume docker-agent-tmp:/tmp \
--env SSH_AUTH_SOCK=/tmp/ssh-auth-sock \
$IMAGE_OF_YOUR_CHOICE \
ssh -T [email protected]
# another case of look first
docker-agent-destroy
# and then leap
eval "$(docker-agent-destroy)"
I have been experimenting with a pattern: I package a small collection of shell scripts into container images for use as shell-based consoles for supporting DevOps operations. If you have used a rails console, or IRB or pry in ruby, or use ipython, or lisp REPL, you'll have the general idea of the UX.
Most of these collections need SSH access from inside the container.
An often suggested (but risky!) example:
docker run -v $HOME/.ssh:/home/app/.ssh SOME-UNTRUSTED-IMAGE ssh -T [email protected]
The risk: this untrusted image might have layers which, in addition to their advertized function also helpfully scrape the .ssh folder to harvest private keys and ship them to untrusted servers.
Cautious or especially security-minded developers may read the Dockerfiles of the containers before running them, but most of us don't really have time to audit All The Things for security.
I hope this container will become irrelevant when this issue closes: docker/for-mac#410
In the meantime, I will recommend people reduce the risk of exposing private keys by useing this container or something like it.
There are 3 essential parts:
- sshd running inside the image
- the /tmp folder which is exposed as a volume
- a script to be eval'd on your mac
When you invoke the image with no other arguments, it shows you the script.
When you eval that script:
- a collection of bash functions are added to your shell
- any previous containers named
docker-agent
are removed - a new container named
docker-agent
launchessshd
- docker will create (or reuse) a volume named
docker-agent-tmp
- an ssh tunnel is opened to the
sshd
with auth forwarding enabled, and the auth socket inside the container is symlinked to/tmp/ssh-auth-sock
Step 5 there is a special piece of shell magic. We establish a persistent connection from MacOS into the sshd running inside the container. On the inside of that tunnel, that is, inside the container we create a symlink of the SSH_AUTH_SOCK into the volume-mounted /tmp folder.
Because the image exposes /tmp
as a volume other containers can
mount that shared volume and thereby gain access to the symlink to
SSH_AUTH_SOCK for their own auth forwarding for as long as the tunnel
remains open.
For containers that want to use ssh, add these flags to your docker run
:
--volume docker-agent-tmp:/tmp
and --env SSH_AUTH_SOCK=/tmp/ssh-auth-sock
ssh auth forwarding needs all three of these things just right in order to work. If any one of them breaks, the ssh access will also break. That can make it feel a bit fragile. The reader will be rewarded for taking time to understand how the parts interact.
check all three of the following status commands.
Detects if docker-agent
container is running. Try
docker-agent-sshd-start
if you find it is not running. Might also
both docker-agent-sshd-stop
and then start.
Detects if the ssh tunnel is still running. Try
docker-agent-tunnel-start
if you find the tunnel is not running.
Lists the contents of the shared /tmp volume which can help diagnose file permissions problems (see below).
This one shows the commands that will tear down all the things. Run
eval "$(docker-agent-destroy)"
to tear it down. The simplest way to
reset is maybe to nuke it from orbit and start over.
If you're doing The Right Thing, you've created an application user in
your container rather than run everything as root
. Sadly, The Right
Thing is always more work.
The ssh tunnel destribed in step 5 above creates a socket owned by
root
. Your container's app user doesn't have read access. There
are ways to fix it. Here are some examples:
-
kill the tunnel and create your own
# make sure you're looking at the right tunnel: ps -x | grep SSH_AUTH_SOCK | grep -v grep # and then kill it: ps -x | grep SSH_AUTH_SOCK | grep -v grep \ | head -1 | awk '{print $1}' | xargs kill # figure out your app user's UID: UID=$(docker run --rm \ --entrypoint=/bin/sh YOUR_CONTAINER \ id -u THE_APP_USER 2>/dev/null) # figure out which port to use PORT=$(docker port docker-agent 22/tcp | grep -oE '[[:digit:]]+$') # create your own tunnel which controls directory ownership ssh -f -A -p $PORT root@localhost \ 'ln -fs \$SSH_AUTH_SOCK /tmp/ssh-auth-sock; chown -R $UID \$(dirname \$SSH_AUTH_SOCK); tail -f /dev/null'"
-
create a different tunnel and different socket for this container
# figure out your app user's UID: UID=$(docker run --rm \ --entrypoint=/bin/sh YOUR_CONTAINER \ id -u THE_APP_USER 2>/dev/null) # figure out which port to use PORT=$(docker port docker-agent 22/tcp | grep -oE '[[:digit:]]+$') # create your own tunnel which controls directory ownership # and creates its own symlink ssh -f -A -p $PORT root@localhost \ 'ln -fs \$SSH_AUTH_SOCK /tmp/app-$UID-sock; chown -R $UID \$(dirname \$SSH_AUTH_SOCK); tail -f /dev/null'" # user the new symlink when you launch your container docker run \ --volume docker-agent-tmp:/tmp \ --env SSH_AUTH_SOCK=/tmp/app-$UID-sock \ YOUR_CONTAINER \ ssh -T [email protected]
A hacker can learn a lot trying to build tools around ssh auth forwarding. Here are a couple of fine sources for understanding how all these parts work.
http://www.unixwiz.net/techtips/ssh-agent-forwarding.html
https://stackoverflow.com/a/26470428/1074208
A hacker can also learn a ton from the ways other hackers try to use the tools. Thanks to my coworkers for suffering through the rough edges with me.