Configuring mTLS
The Control Plane supports Mutual Transport Layer Security (mTLS) to encrypt and authenticate client and inter-server communications with user-supplied certificates. In this configuration, the Control Plane serves HTTPS rather than HTTP.
Enabling mTLS on the Control Plane Servers
Each Control Plane server is both an HTTP(S) server and an HTTP(S) client of other Control Plane servers in the cluster. This means that each server needs both a server certificate and a client certificate.
Important
With mTLS enabled, clients will verify that the server they're connecting to (by IP address or DNS name) is listed in the Common Name (CN) or Subject Alternative Name (SAN) of the server's certificate. It's important that the server's certificate include every address and DNS name that you will use to connect to it. Note that, by default, inter-server communication uses the IPv4 address configured for the server (see configuration.md. If no address is configured, the server will automatically detect the host machine's IP address. You can see which IP address it will automatically detect by running ip route get 1 on the host machine.
You'll need to place the following files in a directory on the host machine for each Control Plane server:
- The Certificate Authority (CA) certificate
- A server certificate
- A server key
- A client certificate
- A client key
Tip
If your organization allows it, you can use the same certificate for both the client and server certificates, provided that the certificate includes both 'Server Authentication' and 'Client Authentication' in the Extended Key Usage (EKU). We demonstrate this in our tutorial.
Then you can use the certificates by adding the directory to the volumes section for each service in the Control Plane stack definition file and setting these variables in the environment sections:
PGEDGE_HTTP__CA_CERT: the path to the CA certificatePGEDGE_HTTP__SERVER_CERT: the path to the server certificatePGEDGE_HTTP__SERVER_KEY: the path to the server keyPGEDGE_HTTP__CLIENT_CERT: the path to the client certificatePGEDGE_HTTP__CLIENT_KEY: the path to the client key
For example, if you've placed the certificates in a /opt/pgedge/control-plane on each host machine your stack definition might look like:
services:
host-1:
image: ghcr.io/pgedge/control-plane:v0.5.1
command: run
environment:
- PGEDGE_HOST_ID=host-1
- PGEDGE_DATA_DIR=/data/pgedge/control-plane
- PGEDGE_HTTP__CA_CERT=/opt/pgedge/control-plane/ca.crt
- PGEDGE_HTTP__SERVER_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__SERVER_KEY=/opt/pgedge/control-plane/server.key
- PGEDGE_HTTP__CLIENT_CERT=/opt/pgedge/control-plane/client.crt
- PGEDGE_HTTP__CLIENT_KEY=/opt/pgedge/control-plane/client.key
volumes:
- /data/pgedge/control-plane:/data/pgedge/control-plane
- /var/run/docker.sock:/var/run/docker.sock
- /opt/pgedge/control-plane:/opt/pgedge/control-plane
networks:
- host
deploy:
placement:
constraints:
- node.id==vzou89zyd4n3xz6p6jvoohqxx
host-2:
# Some lines omitted for brevity
environment:
# ...
- PGEDGE_HTTP__CA_CERT=/opt/pgedge/control-plane/ca.crt
- PGEDGE_HTTP__SERVER_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__SERVER_KEY=/opt/pgedge/control-plane/server.key
- PGEDGE_HTTP__CLIENT_CERT=/opt/pgedge/control-plane/client.crt
- PGEDGE_HTTP__CLIENT_KEY=/opt/pgedge/control-plane/client.key
volumes:
# ...
- /opt/pgedge/control-plane:/opt/pgedge/control-plane
# ...
host-3:
# ...
environment:
# ...
- PGEDGE_HTTP__CA_CERT=/opt/pgedge/control-plane/ca.crt
- PGEDGE_HTTP__SERVER_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__SERVER_KEY=/opt/pgedge/control-plane/server.key
- PGEDGE_HTTP__CLIENT_CERT=/opt/pgedge/control-plane/client.crt
- PGEDGE_HTTP__CLIENT_KEY=/opt/pgedge/control-plane/client.key
volumes:
# ...
- /opt/pgedge/control-plane:/opt/pgedge/control-plane
# ...
Enabling mTLS on Clients
Clients need the following files to communicate with a Control Plane server that has mTLS enabled:
- The Certificate Authority (CA) certificate
- A client certificate
- A client key
How the files are provided to the client will depend on the application. With cURL, for example, you would use the --cacert, --cert, and --key options to provide the path of each file:
curl 'https://host-1:3000/v1/version' \
--cacert ./ca.crt \
--cert ./client.crt \
--key ./client.key
Tutorial
This tutorial demonstrates the process of generating and using certificates for mTLS with the Control Plane. For the purpose of the tutorial, we'll assume three host machines with these attributes:
- Hostname:
host-1IP Address:192.168.105.2 - Hostname:
host-2IP Address:192.168.105.3 - Hostname:
host-3IP Address:192.168.105.4
Generating Certificates
In a production setup, we would use our organization's Public Key Infrastructure (PKI) to create and rotate certificates. However, to keep this tutorial self-contained, we're going to use the step CLI with a local certificate authority. See the step CLI installation page for installation instructions.
# Generate a root certificate authority. This command will prompt for a password
# to encrypt the CA key.
step certificate create tutorial ./ca.crt ./ca.key \
--profile=root-ca \
--not-after 8760h
# Generate certificates for each server. These commands will prompt for the CA
# key, but each new key is passwordless. Note that we're using the hostname as
# the Common Name (CN) and including the IP address as a Subject Alternative
# Name (SAN). The step CLI's 'leaf' profile includes both server and client
# authentication in the Extended Key Usage (EKU).
step certificate create host-1 host-1.crt host-1.key \
--san '192.168.105.2' \
--no-password \
--insecure \
--profile leaf \
--not-after 2160h \
--ca ./ca.crt \
--ca-key ./ca.key
step certificate create host-2 host-2.crt host-2.key \
--san '192.168.105.3' \
--no-password \
--insecure \
--profile leaf \
--not-after 2160h \
--ca ./ca.crt \
--ca-key ./ca.key
step certificate create host-3 host-3.crt host-3.key \
--san '192.168.105.4' \
--no-password \
--insecure \
--profile leaf \
--not-after 2160h \
--ca ./ca.crt \
--ca-key ./ca.key
# Generate the client certificate. We're also using a passwordless key here for
# simplicity, but this will depend on your use case.
step certificate create client client.crt client.key \
--no-password \
--insecure \
--profile leaf \
--not-after 2160h \
--ca ./ca.crt \
--ca-key ./ca.key
Now, we can distribute our certificates to each Control Plane host machine:
# Make new directories for our certificates
ssh host-1 'sudo mkdir -p /opt/pgedge/control-plane'
ssh host-2 'sudo mkdir -p /opt/pgedge/control-plane'
ssh host-3 'sudo mkdir -p /opt/pgedge/control-plane'
# Copy the server certificates and keys to each machine. We're reading them from
# stdin rather than using scp to keep the tutorial short.
ssh host-1 'sudo tee /opt/pgedge/control-plane/ca.crt > /dev/null' < ./ca.crt
ssh host-1 'sudo tee /opt/pgedge/control-plane/server.crt > /dev/null' < ./host-1.crt
ssh host-1 'sudo tee /opt/pgedge/control-plane/server.key > /dev/null' < ./host-1.key
ssh host-2 'sudo tee /opt/pgedge/control-plane/ca.crt > /dev/null' < ./ca.crt
ssh host-2 'sudo tee /opt/pgedge/control-plane/server.crt > /dev/null' < ./host-2.crt
ssh host-2 'sudo tee /opt/pgedge/control-plane/server.key > /dev/null' < ./host-2.key
ssh host-3 'sudo tee /opt/pgedge/control-plane/ca.crt > /dev/null' < ./ca.crt
ssh host-3 'sudo tee /opt/pgedge/control-plane/server.crt > /dev/null' < ./host-3.crt
ssh host-3 'sudo tee /opt/pgedge/control-plane/server.key > /dev/null' < ./host-3.key
Configuring the Control Plane
Now that our certificates are available on each machine, we can update our stack YAML to use them. Note that the node IDs and directories may be different in your installation:
services:
host-1:
image: ghcr.io/pgedge/control-plane:v0.5.1
command: run
environment:
- PGEDGE_HOST_ID=host-1
- PGEDGE_DATA_DIR=/data/pgedge/control-plane
- PGEDGE_HTTP__CA_CERT=/opt/pgedge/control-plane/ca.crt
- PGEDGE_HTTP__SERVER_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__SERVER_KEY=/opt/pgedge/control-plane/server.key
# We're able to use the same certificate as the server's client
# certificate because it includes 'Client Authentication' in its Extended
# Key Usage.
- PGEDGE_HTTP__CLIENT_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__CLIENT_KEY=/opt/pgedge/control-plane/server.key
volumes:
- /data/pgedge/control-plane:/data/pgedge/control-plane
- /var/run/docker.sock:/var/run/docker.sock
- /opt/pgedge/control-plane:/opt/pgedge/control-plane
networks:
- host
deploy:
placement:
constraints:
- node.id==81kw1zwmh9y8hk4rd7igylry0
host-2:
image: ghcr.io/pgedge/control-plane:v0.5.1
command: run
environment:
- PGEDGE_HOST_ID=host-2
- PGEDGE_DATA_DIR=/data/pgedge/control-plane
- PGEDGE_HTTP__CA_CERT=/opt/pgedge/control-plane/ca.crt
- PGEDGE_HTTP__SERVER_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__SERVER_KEY=/opt/pgedge/control-plane/server.key
- PGEDGE_HTTP__CLIENT_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__CLIENT_KEY=/opt/pgedge/control-plane/server.key
volumes:
- /data/pgedge/control-plane:/data/pgedge/control-plane
- /var/run/docker.sock:/var/run/docker.sock
- /opt/pgedge/control-plane:/opt/pgedge/control-plane
networks:
- host
deploy:
placement:
constraints:
- node.id==xz7069ytbdq7uzd2tvrj2wlf2
host-3:
image: ghcr.io/pgedge/control-plane:v0.5.1
command: run
environment:
- PGEDGE_HOST_ID=host-3
- PGEDGE_DATA_DIR=/data/pgedge/control-plane
- PGEDGE_HTTP__CA_CERT=/opt/pgedge/control-plane/ca.crt
- PGEDGE_HTTP__SERVER_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__SERVER_KEY=/opt/pgedge/control-plane/server.key
# We're able to use the same certificate as the server's client
# certificate because it includes 'Client Authentication' in its Extended
# Key Usage.
- PGEDGE_HTTP__CLIENT_CERT=/opt/pgedge/control-plane/server.crt
- PGEDGE_HTTP__CLIENT_KEY=/opt/pgedge/control-plane/server.key
volumes:
- /data/pgedge/control-plane:/data/pgedge/control-plane
- /var/run/docker.sock:/var/run/docker.sock
- /opt/pgedge/control-plane:/opt/pgedge/control-plane
networks:
- host
deploy:
placement:
constraints:
- node.id==gf3ny9opk0idbq0t2c7lrxpks
networks:
host:
name: host
external: true
Now, we're able to deploy our stack YAML from any of the Docker Swarm manager hosts:
docker stack deploy -c control-plane.yaml control-plane
Connecting With mTLS
Once the deploy is complete, we'll need to use our client certificate to access the Control Plane API. With the cURL command line, for example, this looks like:
# Note that we need to use https now
curl https://host-1:3000/v1/version \
--cacert ./ca.crt \
--cert ./client.crt \
--key ./client.key