Simplifying TLS And Using It For Docker

Transport Layer Security (TLS) can be a complex beast and it's quite easy to skip trying to understand it when setting up a cluster or VPN. This tutorial will try to explain it whilst showing how to use it in the context of setting up secure connections between you, the client, and a Docker host/server.

Terminology

A certificate authority is a trusted third party. For example, I trust Bob and Bob vouches for Steven (shown by a certificate), so I trust Steven.

A certificate signing request (CSR) is a message sent from an applicant to a certificate authority in order to apply for a digital identity certificate. The CSR contains information identifying the applicant, such as a distinguished name, IP or fully qualified domain name. This information must be signed using the applicant's private key and contains contains the applicant's public key accompanied by other credentials or proofs of identity required by the certificate authority.

File Extensions

Throughout the tutorial below I will use the following file extensions which may or may not be the same as other tutorial's conventions.

  • .crt - a certificate file.
  • .csr - a certificate signing request.
  • .pem- a private key.

Creating a Certificate Authority

In this example, I am going to create a certificate authority for Programster at ca.programster.org, and use it for nodes to communicate with each other using certificates provided by this certificate authority.

First we need to generate a private key for our certificate authority. This will take a passphrase to create and then use so that if anyone else manages to steal this file, they will be unable to use it to issue certificates in the name of the certificate authority.

openssl genrsa -aes256 -out programster-ca-private-key.pem 2048

By default the generated private key is world readable. You may wish to remove this permission so that only you can view and edit it.

chmod 700 programster-ca-private-key.pem

Even though this is key encrypted, I recommend you take care with it and keep it private.

Now we are going to generate a public certificate for this certificate authority using the private key we just generated. We need to make sure to use our certificate authority's server IP or fully qualified domain name when stating the "Common Name" for the certificate.

openssl req -new -x509 -days 365 \
-key programster-ca-private-key.pem \
-sha256 \
-out programster.crt

Setting Up A Server And Client

Next we will generate a private key for a node that wishes to communicate with other nodes that use certificates issued by the Programster certficate authority. This may be one of our servers that host Docker containers, or it may be a client that wishes to send Docker commands to these hosts (e.g. your personal computer). These steps are usually performed on a separate server to the certificate authority (but don't have to be as a shortcut).

openssl genrsa -out node1-private-key.pem 2048
chmod 700 node1-private-key.pem

We didn't use -aes256 in the generation of the private key this time because we want our cluster's processes to be able to use this key, hence we definitely want to change the file permissions to 700 which was "more optional" before.

Now that we have a private key for our server. We need to generate a request to the Programster certificate authority to vouch for us using this key.

openssl req -subj "/CN=node1.programster.org" -new \
-key node1-private-key.pem \
-out node1.csr

Replace node1.programster.org with the fully qualified domain name or IP of the node the certificate is for.

Send this CSR to the certificate authority server for it to approve.

On our certificate authority server, we are going to sign the request we just received in order to give the requester our backing.

If The Requester Is A Docker Host (Server)

If the requester is from a server that has a fixed IP and will be hosting containers, then create an extension file first with the following. This will add the requesters various IPs (public and private) to the certificate so that they can be verified against these.

echo subjectAltName = IP:10.10.10.20,IP:192.168.1.3 > extfile.cnf

If The Requester Is A Client

On the other hand, if the requester is a client with no fixed IP that you wish to authenticate when connecting to a docker host, then use the following instead. This is typical for your laptop that you wish to send commands to your hosts from.

echo extendedKeyUsage = clientAuth > extfile.cnf

Now using the extfile we generated, we can sign the request.

openssl x509 -req \
-days 365 \
-in node1.csr \
-CA programster.crt \
-CAkey programster-ca-private-key.pem \
-CAcreateserial \
-out node1.crt \
-extfile extfile.cnf

We no longer need the certificate sign request (CSR) after having used it to generate a certificate for the requester. Nor do we need the extfile.conf file that was used in building the certificate.

rm node1.csr extfile.conf

Now send the signed certificate, and a copy of the CA certificate back to the requester.

scp node1.crt programster.crt user@node1.programster.org:/path/to/folder

Testing it works.

After performing the steps in Setting Up A Server And Client for both a server and a client, we can set the server to listen on a public port for requests, and authenticate using the certificates we just created.

Set the docker host to use certificates.

For the server, run the command below:

sudo docker daemon \
--tlsverify \
--tlscacert=programster.crt \
--tlscert=node1.crt \
--tlskey=node1-private-key.pem \
-H=0.0.0.0:2376

Client Connect Using Certificates

For the client (your local laptop/computer), use the command below to securely connect to the host using the certificates, and fetch information about the version of docker it is running.

docker \
--tlsverify \
--tlscacert=programster.crt \
--tlscert=client.crt \
--tlskey=client-private-key.pem \
-H=node1.programster.org:2376 \
version

You should get output similar to below:

Client:
 Version:      1.9.1
 API version:  1.21
 Go version:   go1.4.2
 Git commit:   a34a1d5
 Built:        Fri Nov 20 13:12:04 UTC 2015
 OS/Arch:      linux/amd64

Server:
 Version:      1.9.1
 API version:  1.21
 Go version:   go1.4.2
 Git commit:   a34a1d5
 Built:        Fri Nov 20 12:59:02 UTC 2015
 OS/Arch:      linux/amd64

Conclusion

Congratulations! You can now securely send docker commands to your hosts without having to SSH into them. This is a key step for setting up a Docker swarm.

References

Author

Programster

Stuart is a software developer with a passion for Linux and open source projects.

comments powered by Disqus
We are a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for us to earn fees by linking to Amazon.com and affiliated sites. More info.