An application programming interface (API) provides access to the features of a business application, but with the visual elements stripped away. By using APIs, devices like tablets, self-service kiosks, point-of-sale terminals, and robotic sensors can connect up to apps running on servers in a datacenter or in the cloud. Because they give access to the heart of your business applications, it should come as no surprise that there are some APIs that the general public should not have access to. Take for example an API for a warehouse management system or one for a retail point-of-sale system. In such cases, access should be locked down, allowing entry only to authenticated clients that have been preapproved.
Client certificate authentication is a reliable way to restrict API access and in recent years has grown in popularity, perhaps due to being restyled under a new name that better captures its use case: mutual TLS, or mTLS for short. The idea remains the same, however. You can store a digital certificate on a client, which allows the client to unlock access to an API. The server verifies the authenticity of the client’s certificate and, meanwhile, the client can verify that the server is the expected provider of that API. Thus, the client and server verify one another. Authentication and trust are mutual between the two parties.
HAProxy, when placed in front of your servers, enables mTLS. In fact, it supports using client certificates from end-to-end, allowing you to authenticate clients that connect to HAProxy and for HAProxy to authenticate itself to your backend servers. In this blog post, you’ll learn how to set it up.
When a client device needs to make an API call, it first establishes a secure connection to the HAProxy load balancer that’s situated in front of your servers. While making that connection, the client provides its certificate. To validate it, HAProxy checks whether it was digitally signed previously by your organization’s certificate authority.
A certificate authority (CA) is a trusted entity that can vouch for the identity of another entity. In this case of client certificates and mTLS, your organization will use its own CA to sign all client certificates that it will deploy to applications. In essence, your organization becomes its own authority, doling out certificates and then verifying them when presented back to it.
To demonstrate, we’ll create a root certificate authority for our organization. This root CA will sit at the top of our hierarchy of certificates, and as such it would cause widespread problems if it were to ever become compromised. For that reason, we’ll also create an intermediate CA and then use the intermediate CA to sign client certificates.
Create a Root CA
A root CA sits at the top of your certification hierarchy and, because there is nothing above it, it must sign its own certificate. However, it’s a good idea to create a CA that’s subordinate to that one and use the subordinate CA for signing client certificates. That way, there is less risk of the root CA being mishandled and compromised. You will still want to obtain a server SSL certificate from a provider like GoDaddy, DigiCert, Entrust, or Let’s Encrypt because server certificates must be trusted by browsers, and certificates from these providers will have that trust implicitly. The certification hierarchy we’re creating will be for client certificates only.
Continuing with the scenario of creating your own root CA, use the following openssl command:
When run, the command prompts you to enter some additional information about your organization. One crucial question it asks is the name to assign to the CA. The Common Name (CN) should be independent of any server name, and could be something like acme-root-ca. The command will create two files, root-ca.key and root-ca.crt.
The certificate file holds information about the CA, such as its name, expiration date, and issuer. Meanwhile, the key file contains the cryptographic bits associated with the certificate, which you can use to prove that you are the sole owner of the certificate and key. Keep the key private within your organization.
You can inspect the contents of the root-ca.crt file with the following command:
Create an Intermediate CA
Next, let’s create a CA that’s subordinate to the root CA. You can use this intermediate CA to sign client certificates. Use the following command to create its key and certificate signing request:
After asking you the same questions about your organization—for which you should choose a different Common Name—this creates two files, intermediate-ca.key and intermediate-ca.csr. The first file is this CA’s private key and the second is a certificate signing request, which you will sign with the root CA key.
Because the root CA was self-signed, its type was automatically configured as a CA certificate, as opposed to being designated an SSL server certificate or client certificate. With this new, intermediate CA, we’ll need to state explicitly that we want it to be a CA type, which we do by defining an extensions file. First, create a file named ca-cert-extensions.cnf and add extra information to append to the certificate. It should look like this:
Then use the following openssl command to sign the certificate signing request and create the certificate:
This creates the file intermediate-ca.crt. Inspecting the file shows its name, expiration date, and issuer:
Create a client certificate
Now that we have an intermediate certificate, the next step is to create a client certificate request and then sign it to create a certificate. For this example, assume that the certificate will be installed into a “scanner” application running in a warehouse. Create the certificate signing request with the following command:
Next, let’s create an extensions file to designate this certificate as a client certificate. Add a file named client-cert-extensions.cnf with the following contents:
Then use the following command to sign the certificate signing request:
You now have a client certificate that you can use to authenticate our fictitious scanner application and gain access to an API. Use the command below to inspect the contents of the certificate:
Enable client certificate authentication in HAProxy
Enabling client certificate authentication in HAProxy is straightforward. Consider the frontend section below:
bind line listens on port 443 for HTTPS connections and also sets arguments needed for certificate-based authentication:
sslargument enables HTTPS
crtargument specifies the server SSL certificate, which you will typically obtain from a certificate provider like Let’s Encrypt
verify requiredargument requires clients to send a client certificate
ca-fileargument specifies the intermediate certificate with which we will verify that the client’s certificate has been signed with our organization’s CA
ca-verify-fileargument (introduced in HAProxy 2.2) includes the root CA certificate, allowing HAProxy to send a shorter list of CAs to the client in the SERVER HELLO message that will be used for verification, but keeping upper level CAs, such as the root, out of that list. HAProxy requires the root CA to be set with this argument or else included in the intermediate-ca.crt file (compatibility with older versions of HAProxy).
To test it, try the following curl command. Note that curl, acting as the client, will verify the server’s SSL certificate and give an error if it doesn’t trust it. For example, you’ll get a validation error if the SSL certificate is self-signed. That’s the mutual part of mTLS! To disable that check, include the
First, try it without using the client certificate. Note the error, alert certificate required.
Next, try it again with the client certificate. The connection should succeed and you can examine the flow of messages that occur during the SSL handshake, during which the client certificate is requested, sent, and verified:
You may be curious why the client needs to set both its certificate and private key when connecting through curl. Practically speaking, curl will not send the private key to the server, but it does use it to calculate a hash it sends to the server, which proves to the server that the client possesses the key.
Send a client certificate to a backend server
For end-to-end authentication, HAProxy can verify the backend server’s SSL certificate and send a client certificate of its own. Consider the server line in a backend section of the HAProxy configuration below:
The arguments have the following meaning:
sslargument enables HTTPS communication with the server
verify requiredargument requires HAProxy to verify the server’s SSL certificate against the CAs specified with the
ca-fileargument. You can set
ca-fileto a file or directory containing a list of certificates or, if using HAProxy 2.6 or newer, to @system-ca to load the operating system’s list of CAs.
crtargument specifies the client certificate to send to the backend server
In this blog post, you learned how to create certificate authorities within your organization and use them to sign client certificates used for authentication. HAProxy has a straightforward way to enable certificate-based authentication, both between clients and HAProxy, and between HAProxy and backend servers.