This gist provides a complete example of client-side authentication for Apache APISIX.
It includes the process of:
- generating a certificate
- configuring the certificate in APISIX
- configuring the route in APISIX
- testing
It also demonstrates how to pass some information about the client certificate upstream, which includes serial
, fingerprint
, and common name
.
Just follow these commands, it will generate certificates by OpenSSL.
# For ROOT CA
openssl genrsa -out ca.key 2048
openssl req -new -sha256 -key ca.key -out ca.csr -subj "/CN=ROOTCA"
openssl x509 -req -days 36500 -sha256 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.cer
# For server certificate
openssl genrsa -out server.key 2048
openssl req -new -sha256 -key server.key -out server.csr -subj "/CN=test.com"
openssl x509 -req -days 36500 -sha256 -extensions v3_req -CA ca.cer -CAkey ca.key -CAserial ca.srl -CAcreateserial -in server.csr -out server.cer
# For client certificate
openssl genrsa -out client.key 2048
openssl req -new -sha256 -key client.key -out client.csr -subj "/CN=CLIENT"
openssl x509 -req -days 36500 -sha256 -extensions v3_req -CA ca.cer -CAkey ca.key -CAserial ca.srl -CAcreateserial -in client.csr -out client.cer
# Convert client certificate to pkcs12 for Windows usage
openssl pkcs12 -export -clcerts -in client.cer -inkey client.key -out client.p12
Use curl
to request APISIX Admin API to setting up SSL for specify SNI. Note that line feeds in the certificate need to be replaced with \n
.
curl -XPUT 'http://127.0.0.1:9180/apisix/admin/ssls/1' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: application/json' \
--data-raw '{
"sni": "test.com",
"cert": "<content of server.cer>",
"key": "<content of server.key>",
"client": {
"ca": "<content of ca.cer>"
}
}'
The sni
field specifies the domain name (CN) of the certificate, when the client tries to handshake with APISIX via TLS, APISIX will match the SNI data in ClientHello with this field to find the corresponding server certificate for handshake.
The cert
and the key
fields are the public and private keys of the server certificate.
The client.ca
is the CA public key of the client certification certificate, which in theory can use a different public key from the server certificate, and this example uses the same CA for the convenience of demonstration purposes.
Next, let's create a route.
curl -XPUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: application/json' \
--data-raw '{
"uri": "/anything",
"plugins": {
"proxy-rewrite": {
"headers": {
"X-Ssl-Client-Fingerprint": "$ssl_client_fingerprint",
"X-Ssl-Client-Serial": "$ssl_client_serial",
"X-Ssl-Client-S-DN": "$ssl_client_s_dn"
}
}
},
"upstream": {
"nodes": {
"httpbin.org":1
},
"type":"roundrobin"
}
}'
As you can see, we don't need to specify the hostname in the route (you can of course set it if you need to), APISIX will automatically handle the TLS handshake based on the SNI and the ssl resource created in the previous step.
In addition to this, you will see that we have set up the proxy-rewrite
plugin, which will dynamically set the headers for the request, and the source of their values are Nginx variables, which you can see here, http://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables. We will see the effect of this plugin later.
If you are using Windows, and try it in broswer, you will first need to install the client certificate into the system keyring. Double click on the client.p12
file to install it.
If you use curl
, then you need to specify the location of the client certificate and key at request time.
Since we will be using test.com as the domain name for testing, you will have to add them to your DNS or local hosts file.
When everything is done, let's open a Chromium-like (it prefers to use system certificates keyring) browser and try to access https://test.com:9443/anything.
Firstly, you will see this, just click Advanced
and Proceed to test.com (unsafe)
to continue.
Then, if you have just installed the certificate correctly, you will see this. You need to check the certificate and confirm it.
Finally, you will see these things, the API return JSON data from httpbin.org
(we have just configured the upstream).
The response contains the three request headers we just configured via the proxy-rewrite
plugin. Where we can determine from the last two images that the serial
value of the client certificate was indeed received correctly upstream.
Use this command:
curl https://test.com:9443/anything -k --cert ./client.cer --key ./client.key
Then, you will recieve response:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "test.com",
"User-Agent": "curl/7.81.0",
"X-Amzn-Trace-Id": "Root=1-63256343-17e870ca1d8f72dc40b2c5a9",
"X-Forwarded-Host": "test.com",
"X-Ssl-Client-Fingerprint": "c1626ce3bca723f187d04e3757f1d000ca62d651",
"X-Ssl-Client-S-Dn": "CN=CLIENT",
"X-Ssl-Client-Serial": "5141CC6F5E2B4BA31746D7DBFE9BA81F069CF970"
},
"json": null,
"method": "GET",
"origin": "127.0.0.1",
"url": "http://test.com/anything"
}
As you can see, the response body also contains the request body received upstream, which contains the correct data.
Apache APISIX does a good job of mTLS for clients to APISIX and sends key information from the certificate to the upstream service.
Also, implementing APISIX with mTLS for upstream services is very simple, and I will describe it later.