Using Vault to Protect the Docker Daemon Socket

Andreas Hug
4 min readFeb 27, 2019
(Photo by Steve Buissinne on Pixabay)

The Docker daemon can be configured to allow remote access over HTTP by providing a TCP socket. Encryption and authorization can (or must!) be done by using TLS and the documentation contains a full page on how to do the setup. However, the OpenSSL command line tools are quite complex and far from intuitive if you don’t use them regularly. Or as the documentation states:

Using TLS and managing a CA is an advanced topic. Please familiarize yourself with OpenSSL, x509 and TLS before using it in production.

This is where Hashicorp’s Vault can shine. In a nutshell, Vault is a tool for managing secrets with a powerful API. One of the various secret backends is PKI which generates X.509 certificates dynamically based on configured roles, and that’s precisely what we need to protect the Docker daemon.

There are however two issues you should be aware of:

  • The Docker client does not support password-protected TLS keys.
  • The Docker daemon does not support certificate revocation or OCSP.

Running Vault

I won’t go into detail on how to set up Vault because this is well covered by the documentation and a good deal of tutorials. For a quickstart, here is how to run Vault in a Docker container. This starts a Vault server with in-memory storage, which is useful for testing but should not be used in production.

docker run -d -e VAULT_ADDR="http://localhost:8200" --cap-add IPC_LOCK --name dev-vault vault

Get your root token from docker logs dev-vault and log in with docker exec -ti dev-vault vault login <token> . Now you can access Vault with docker exec -ti dev-vault vault <command>. For the sake of simplicity, I’ll just write vault <command> for the rest of this article.

Mounting the PKI backend

To use the PKI backend, it has to be mounted first.

vault secrets enable -path=pki/docker pki

Note: I’m mounting the backend to a custom path since Vault only allows one CA per backend, and I want to keep the certificates for Docker separate from everything else.

You can increase the maximum TTL to allow certificates with a longer lifetime. The actual TTL will be set later when generating the certificates.

vault secrets tune -max-lease-ttl=87600h pki/docker

Creating a CA

Docker doesn’t rely on the list of trusted root certificates of your operating system, so it’s totally fine to create your own certificate authority with Vault.

vault write -field certificate pki/docker/root/generate/internal common_name="Docker Root CA" ttl=87600h

This will return your new root certificate. Save it to docker-ca.crt.

Roles

The PKI backend uses roles to define the certificate settings. We’ll need two roles: one we name server for the Docker daemon and one we name client for the Docker clients. All options are described in Vault’s API documentation. Adjust the configuration to your needs.

vault write pki/docker/roles/server \
max_ttl=26280h \
ttl=8760h \
allowed_domains=example.com \
allow_localhost=true \
allow_ip_sans=true \
allow_bare_domains=true \
allow_subdomains=true \
server_flag=true \
client_flag=false \
key_usage=DigitalSignature,KeyEncipherment \
ou="Docker Daemon"
vault write pki/docker/roles/client \
max_ttl=8760h \
ttl=720h \
allow_any_name=true \
enforce_hostnames=false \
server_flag=false \
client_flag=true \
key_usage=DigitalSignature \
ou="Docker Client" \
no_store=true

Daemon

Now it’s time to create the certificate for the Docker daemon:

vault write pki/docker/issue/server \
common_name=example.com \
alt_names=localhost \
ip_sans="127.0.0.1"\
ttl=8760h

Note: Allow localhost and 127.0.0.1 to use the client on the host system. All options are explained in Vault’s API documentation.

You’ll receive the certificate and the RSA key. Save both to example_com.crt / example_com.key and copy both to your server, along with docker_ca.crt. Make sure to protect your key file with restrictive permissions (e.g. chmod 0600 example_com.key).

Edit you Docker daemon configuration (usually /etc/docker/daemon.json) to enable TLS and restart the Docker daemon.

{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
"tls": true,
"tlscacert": "/path/to/docker_ca.crt",
"tlscert": "/path/to/example_com.crt",
"tlskey": "/path/to/example_com.key",
"tlsverify": true
}

Note: If you are using systemd to run the Docker daemon, you’ll probably have to override the configuration, because defining hosts in daemon.json clashes with the default parameters. Just follow the documentation.

Clients

The client certificates are generated the same way, except the role client will be used this time:

vault write pki/docker/issue/client \
common_name=jane.doe \
ttl=240h

Note: A low time to live value improves security, and you can easily create new certificates.

Again, save the output to files docker_jane_doe.crt / docker_jane_doe.crt and remember to set permissions on the key file.

Now you’re ready to test the connection:

docker \
--tlsverify \
--tlscacert=docker_ca.crt \
--tlscert=docker_jane_doe.crt \
--tlskey=docker_jane_doe.key \
--host=example.com:2376 \
version

To use the TLS connection by default, copy the client certificate files to ~/.docker/ and rename them to ca.pem/ cert.pem / key.pem and add these lines to your .profile:

export DOCKER_HOST=tcp://example.com:2376
export DOCKER_TLS_VERIFY=1

That’s pretty much it! To me, using Vault is a great alternative to the OpenSSL command line tools, and with it’s HTTP API and several client libraries, you can automate the process pretty well.

--

--