Using Docker Engine API securely

Martín Lamas
Trabe
Published in
4 min readAug 19, 2019

--

Photo by Florian Klauer on Unsplash

In this story we’ll learn how to use the Docker Engine API through the network in a secure way. The Engine API is an HTTP API served by the Docker Engine. It’s the API the Docker client uses to communicate with the Engine, so everything the Docker client can do can also be done with the API.

Enabling the API

In order to use the Docker Engine API, a TCP socket must be enabled when the engine daemon starts. By default, a unix domain socket (or IPC socket) is created at /var/run/docker.sock. However, we can configure the daemon to listen to multiple sockets at the same time using multiple -H options:

$ dockerd -H unix:///var/run/docker.sock -H tcp://192.168.1.100:2375

In this example, the daemon will open two sockets: a unix domain socket, at /var/run/docker.sock, and a TCP socket listening on the 2375 TCP port for the 192.168.1.100 interface. By convention, 2375 TCP and 2376 TCP ports should be used for un-encrypted and encrypted connections, respectively.

On systemd based GNU/Linux distributions we can enable the engine API by creating the /etc/systemd/system/docker.service.d/override.conf file with the following contents:

With -H fd:// we enable the support to establish the communication with the daemon via Systemd socket activation. Also, with -H tcp://0.0.0.0:2375, the daemon will open a TCP socket listening on 2375 TCP port for all system interfaces. Moreover, we had to explicitly clear ExecStart before setting it again, as it is an additive setting and can have multiple entries.

Then, we must execute the following command to reload the configuration of the systemd units:

$ sudo systemctl daemon-reload

As the last step, the service must be restarted:

$ sudo systemctl restart docker.service

And that’s all. Now we can retrieve the containers list using any http client:

$ curl -X GET http://localhost:2375/containers/json?all=1

Securing the API

At this point the API is public and available to anyone. This is not a good idea, especially if we want to expose this API to the internet. Fortunately, we can configure a secured TLS connection using self-signed certificates. Let’s go!

First, you must check that the openssl package is installed in your system.

Then, on the Docker daemon’s host machine, we need to generate a CA certificate:

$ openssl genrsa -aes256 -out ca-key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.........++++
...............................................................................++++
e is 65537 (0x010001)
Enter pass phrase for ca-key.pem:
Verifying - Enter pass phrase for ca-key.pem:
$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 \
-out ca.pem
Enter pass phrase for ca-key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:ES
State or Province Name (full name) [Some-State]:A Coruna
Locality Name (eg, city) []:A Coruna
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Trabe
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:docker.acme.com
Email Address []:

We must fill the Common Name field with the FQDN of the docker host machine.

Now that we have a CA, we must create a server key and a certificate signing request (CSR):

$ openssl genrsa -out server-key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.............................................................................................................++++
....................++++
e is 65537 (0x010001)
$ openssl req -subj "/CN=docker.acme.com" -sha256 -new \
-key server-key.pem -out server.csr

Next, we’ll sign the server CSR with the CA:

$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem \
-CAkey ca-key.pem -CAcreateserial -out server-cert.pem
Signature ok
subject=CN = docker.acme.com
Getting CA Private Key
Enter pass phrase for ca-key.pem:

If we need to establish the SSL connection through the IP address of the server then we must create a certificate extensions file before generating the server certificate:

$ echo subjectAltName = \
DNS:docker.acme.com,IP:10.0.0.100,IP:127.0.0.1 >> extfile.cnf
$ echo extendedKeyUsage = serverAuth >> extfile.cnf$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem \
-CAkey ca-key.pem -CAcreateserial -out server-cert.pem \
-extfile extfile.cnf
Signature ok
subject=CN = docker.acme.com
Getting CA Private Key
Enter pass phrase for ca-key.pem:

Now, we must generate a client certificate:

$ openssl genrsa -out key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
............++++
...................................................................................................++++
e is 65537 (0x010001)
$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr$ echo extendedKeyUsage = clientAuth > extfile-client.cnf$ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem \
-CAkey ca-key.pem -CAcreateserial -out cert.pem \
-extfile extfile-client.cnf
Signature ok
subject=CN = client
Getting CA Private Key
Enter pass phrase for ca-key.pem:

As the last step, we must configure the daemon to enable TLS using these certificates. Place them under /etc/ssl and update the /etc/systemd/system/docker.service.d/override.conf file content:

Then, reload the configuration of the systemd units and restart the service as we did before.

Configuring the client to consume the API

With everything in place, we can start making secure calls to consume the API, using the generated certificates to set the authentication in the applications.

For example, you may want to consume the API using Python and the requests library. In the next snippet we’ll make a request to fetch the list of containers available in the docker host. We must include the certificates in the library calls:

As we said at the beginning, you can execute any docker command through this API. You can read the documentation to get more info about the available API methods.

--

--