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.
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.
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
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
-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
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 email@example.com:/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
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.