Downstream mutual TLS (mTLS) with Pomerium
Downstream mutual TLS (mTLS) refers to a requirement that end users must present a trusted client certificate when connecting to services secured by Pomerium.
With ordinary TLS, only the server presents a certificate. This allows the client to verify the identity of the server before proceeding with the connection, ensuring that the connection between the client and server is not only private (encrypted) but also authenticated.
With mTLS, the client must also present a certificate. The server will allow requests only when the client presents a certificate that it recognizes as trusted. This capability can be used to provide an additional layer of security.
Pomerium uses the term "downstream mTLS" when referring to the connection between end users and Pomerium, and "upstream mTLS" when referring to the connection between Pomerium and the services protected by Pomerium. (See Upstream mTLS for more information on the latter.)
Enabling downstream mTLS in Pomerium requires all clients to authenticate themselves by providing a trusted client certificate during the initial connection. Only after Pomerium successfully verifies the client certificate will it permit access to the configured routes.
This guide shows you how to configure Pomerium to enable mTLS using client certificates issued by a private certificate authority.
Before You Begin
To complete this guide, you will need:
- A working Pomerium instance. Complete the Pomerium Core quickstart with Docker for a quick proof of concept to test with this guide.
mkcert
to issue certificates from a locally-trusted certificate authority (CA)
The mkcert
tool is designed for testing: It creates a locally-trusted root certificate for development purposes. This guide uses mkcert
for a proof-of-concept example, but a production deployment will require a more sophisticated certificate management solution.
Configure Pomerium with a server certificate
If your Pomerium instance already has a server certificate configured, you can skip to the Create a client certificate step.
This guide uses the domain localhost.pomerium.io
as Pomerium's root domain (all subdomains under localhost.pomerium.io
resolve to localhost).
Create a root CA
If you haven’t yet, install mkcert
following these instructions.
Create a trusted root CA:
mkcert -install
Create a wildcard TLS certificate
Run the following command to create a wildcard server certificate for *.localhost.pomerium.io
:
mkcert '*.localhost.pomerium.io'
This creates two files in the current working directory:
_wildcard.localhost.pomerium.io.pem
_wildcard.localhost.pomerium.io-key.pem
_wildcard.localhost.pomerium.io.pem
is the certificate, which contains a public key bound to the DNS name *.localhost.pomerium.io
.
_wildcard.localhost.pomerium.io-key.pem
is the corresponding private key.
Update Pomerium configuration
Update the config.yaml
file or environment variables with your wildcard certificate. If running Pomerium in Docker, you will need to bind mount these files or copy them into the container and update the file paths accordingly.
- config.yaml
- Environment Variables
certificate_file: '_wildcard.localhost.pomerium.io.pem'
certificate_key_file: '_wildcard.localhost.pomerium.io-key.pem'
CERTIFICATE_FILE="_wildcard.localhost.pomerium.io.pem"
CERTIFICATE_KEY_FILE="_wildcard.localhost.pomerium.io-key.pem"
Create a client certificate
If you haven’t yet, install mkcert
following these instructions.
Then, to create a client certificate, run the following command:
mkcert -client -pkcs12 'yourUsername@localhost.pomerium.io'
This creates a new file in the current working directory, containing both the client certificate and the corresponding private key:
yourUsername@localhost.pomerium.io-client.p12
(Note that the root CA created by mkcert
does not need to be installed into the system trust store in order to be used as a trusted CA by Pomerium.)
Configure Pomerium to require mTLS
Update the config.yaml
file or environment variables to trust only certificates issued by your mkcert
root CA. To find the path to the root CA certificate created by mkcert
, run the following command:
echo "$(mkcert -CAROOT)/rootCA.pem"
If running Pomerium in Docker, you will need to bind mount this file or copy it into the container (and update the file path accordingly).
- config.yaml
- Environment Variables
downstream_mtls:
ca_file: '/YOUR/MKCERT/CAROOT/rootCA.pem'
DOWNSTREAM_MTLS_CA_FILE="/YOUR/MKCERT/CAROOT/rootCA.pem"
(See the Downstream mTLS Settings reference page for more details about the available mTLS settings.)
Your Pomerium instance should now require a client certificate in order to access any configured routes. If you attempt to access any route from your browser, you should now see a Pomerium error page.
Install your client certificate
Now you'll need to install the client certificate you created earlier. The following instructions are for Chrome on Linux, but client certificates are supported in all major browsers.
-
Go to
chrome://settings/certificates
: -
Click on Import and browse to the directory where you created the certificates above. Choose
_wildcard.localhost.pomerium.io-client.p12
: -
You will be prompted for the certificate password. The default password set by
mkcert
ischangeit
: -
The org-mkcert development certificate should now be in your list of certificates:
Using the client certificate
Visit https://verify.localhost.pomerium.io (or another route you've defined). You should be prompted to choose a client certificate:
After selecting this certificate, Pomerium should now allow you to access this route.