Skip to Content

Docker TLS - secure client and daemon

In production setup it is very important to secure communication between client and server. In this post I will provide simple to follow guide to make your environment more secure!

Share on:

By default after installing docker on our host we have client and server(daemon) locally speaking to each other by IPC socket. On Linux - /var/run/docker.sock.

So what we should do when our daemon is in remote location? How to make connection secure?

Example environment

Role Server IP
Client docker-host1.lukas.int 10.10.10.20
CA docker-host2.lukas.int 10.10.10.21
Daemon docker-host3.lukas.int 10.10.10.22

All hosts can reach each other by hostname.

TLS

Docker uses TLS for securing connection over network between client and daemon.
We will set this from both sides:

  • Daemon will want to talk only to clients with proper certificate
  • Client will want to talk only to daemon with proper certificate also

Self-signed cert - if we don’t have cert from CA

  • Private key for CA
[root@docker-host2 ~]# 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:
  • Public key
[root@docker-host2 ~]# openssl  req -new -x509 -days 730 -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) [XX]:PL
State or Province Name (full name) []:Maz
Locality Name (eg, city) [Default City]:Warsaw
Organization Name (eg, company) [Default Company Ltd]:knowledgepill.it
Organizational Unit Name (eg, section) []:BlogSite
Common Name (eg, your name or your server's hostname) []:docker-host2.lukas.int

Generate key and certificate for daemon

  • Generate private key for daemon
[root@docker-host2 ~]# openssl genrsa -out daemon-key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
...................................................................................................++++
........................................................++++
e is 65537 (0x010001)
  • Request CA to sign certificate for daemon
[root@docker-host2 ~]# openssl req -subj "/CN=docker-host3" -sha256 -new -key daemon-key.pem -out daemon.csr
  • Add info to cert
[root@docker-host2 ~]# vi extfile.cnf

subjectAltName = DNS:docker-host3,IP:10.10.10.22
extendedKeyUsage = serverAuth
  • Generate daemon certificate
[root@docker-host2 ~]# openssl x509 -req -days 730 -sha256 -in daemon.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out daemon-cert.pem -extfile extfile.cnf
Signature ok
subject=CN = docker-host3
Getting CA Private Key
Enter pass phrase for ca-key.pem:

Generate key and cert for client

  • Generate private key for client
[root@docker-host2 ~]# openssl genrsa -out client-key.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
......................++++
....++++
e is 65537 (0x010001)
  • Request CA to sign certificate for client
[root@docker-host2 ~]# openssl req -subj "/CN=docker-host1" -sha256 -new -key client-key.pem -out client.csr
  • Add info to cert
[root@docker-host2 ~]# vi extfile.cnf

extendedKeyUsage = clientAuth
  • Generate client certificate
[root@docker-host2 ~]# openssl x509 -req -days 730 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile extfile.cnf
Signature ok
subject=CN = docker-host1
Getting CA Private Key
Enter pass phrase for ca-key.pem:

Send generated keys and certificates

I moved generated files to correct machines:

  • client - docker-host1
    • ca.pem -> /home/lukas/.docker/ca.pem
    • client-cert.pem -> /home/lukas/.docker/cert.pem
    • client-key.pem -> /home/lukas/.docker/key.pem
  • daemon - docker-host3
    • ca.pem -> /home/lukas/.docker/ca.pem
    • daemon-cert.pem -> /home/lukas/.docker/cert.pem
    • daemon-key.pem -> /home/lukas/.docker/key.pem

It is important to change names properly as shown above.

Configure TLS

Daemon

On docker-host3 that will be daemon host - set TLS parameters in /etc/docker/daemon.json config file

[root@docker-host3 ~]# vi /etc/docker/daemon.json

{
    "hosts": ["tcp://docker-host3:2376"],
    "tls": true,
    "tlsverify": true,
    "tlscacert": "/home/lukas/.docker/ca.pem",
    "tlscert": "/home/lukas/.docker/cert.pem",
    "tlskey": "/home/lukas/.docker/key.pem"
}

Important!

hosts tells docker daemon to listen on IP docker-host3 on port 2376.
This option cant’t be use in docker config file if we have on host systemd.
If so, we have to edit docker.service with command: systemctl edit docker

In editor we have to add and save:

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://docker-host3:2376

Restart Docker

[root@docker-host3 ~]# systemctl restart docker

Check that docker listen on correct IP:Port

[root@docker-host3 ~]# ps aux | grep docker
root      1729  0.7  4.6 922864 87940 ?        Ssl  22:38   0:00 /usr/bin/dockerd -H tcp://docker-host3:2376
root      1895  0.0  0.0   9180   968 pts/0    S+   22:39   0:00 grep --color=auto docker

From now at docker-host3 we can’t just use docker cli - IPC communication is now forbidden.
We can communicate by tcp - daemon insist on using TLS at client side:

[root@docker-host3 ~]# docker -H tcp://docker-host3:2376 version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b
 Built:             Wed Mar 11 01:27:04 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Error response from daemon: Client sent an HTTP request to an HTTPS server.

Client

Set remote daemon location and check that without TLS we can’t connect

[lukas@docker-host1 ~]# export DOCKER_HOST=tcp://docker-host3:2376

[lukas@docker-host1 ~]$ docker version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b
 Built:             Wed Mar 11 01:27:04 2020
 OS/Arch:           linux/amd64
 Experimental:      false
Error response from daemon: Client sent an HTTP request to an HTTPS server.

Activate TLS verification and check again that now we can connect securely

[lukas@docker-host1 ~]# export DOCKER_TLS_VERIFY=1

[lukas@docker-host1 ~]$ docker version
Client: Docker Engine - Community
 Version:           19.03.8
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        afacb8b
 Built:             Wed Mar 11 01:27:04 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.8
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       afacb8b
  Built:            Wed Mar 11 01:25:42 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Remember to set exported variables in for example .bashrc for persistence.

Optionally we can change path to cert and key by setting variable DOCKER_CERT_PATH