mTLS auth with AWS API Gateway
A game changer for Open Banking and the wider CDR
Recently, AWS announced that API Gateway now supports Mutual TLS authentication. It is a game changer for Open Banking and the wider CDR.
Why is this a big deal? And now that it is finally here, how to use it? Let’s have a look in this part 1. In the next part, we will take a look at how we can check for certificate validity using a Lambda custom authoriser.
What is mTLS?
Normally when you visit a secure website such as your bank, your browser verifies the identity of the banks server using the certificate it presents to the browser. In mutual TLS, the client also presents its certificate to the server allowing the server to verify the identity of the client. This type of authentication is commonly used in machine to machine communications such as IoT and highly secure APIs like financial services. There are many excellent articles available on the internet if you want to understand mTLS in detail.
So why is AWS API Gateway announcing support for mTLS a big deal?
Well this plugs a gap in AWS capabilities. Till now if you wanted to implement mTLS APIs on AWS, you had to reach for third party solutions.
For example, one of the largest use cases being implemented all over Australia today is the Australian Consumer Data Rights. This legislation gives consumers control over their data held by banks and other organisations. This includes opt-in right to authorise third parties to access data and offer the consumer enhanced services such as recommendations and comparisons.
As part of CDR, first Banks, followed by Energy and then Telcos must publish a set of standard APIs. Most of these APIs are secured by mTLS. For example some of the Open Banking endpoints with their security requirements are shown below.
At Contino, I have been working on CDR Open Banking implementations of some of Australia’s largest banks for the last couple of years, and we implemented Open Banking in AWS using mostly cloud native technologies such as AWS Lambda and DynamoDB.
But we had to rely on third party solution to provide us with mTLS capabilities. Which brought in its own challenges such as different licensing, cost accounting mechanisms and its own deployment & management overheads.
Now our architecture can be simplified and made completely pay per use. And we can manage it all within our familiar AWS environment and with our favourite Infrastructure as Code tools & CICD pipelines.
So how do I use mTLS with AWS API Gateway?
Let’s set up a simplified Open Banking Get Accounts API with its backend implementation stubbed out to see how to set up and test mTLS on AWS API Gateway.
Note: To test mTLS with AWS API gateway, you need a custom domain and a SSL/TLS X.509 certificate for it. I use AWS Route 53 for DNS, AWS ACM for server certificates and openssl to generate private Root CA and client certificates in this example.
Step 1- create a Lambda function to handle request:
Log into your AWS console and create a Lambda function. You can use below code or bring your own.
Step 2 - create a HTTP API:
Navigate to API Gateway. Select Create API -> HTTP API and hit import. You can use below Open API definition or create your own. Remember to replace your region and your Lambda function arn if you use the below Open API definition.
Once the API is created, Add a stage to the API. I am calling mine dev
and just for convenience enable automatic deployment:
Grab your stage invoke url and lets test our new api:
curl https://{your api url}/{your stage name}/v1/banking/accounts
{"data":{"accounts":[{"accountId":"string","creationDate":"string","displayName":"string","nickname":"string","openStatus":"OPEN","isOwned":true,"maskedNumber":"string","productCategory":"TRANS_AND_SAVINGS_ACCOUNTS","productName":"string"}]},"links":{"self":"string","first":"string","prev":"string","next":"string","last":"string"},"meta":{"totalRecords":1,"totalPages":1}}
Step 3 - We will need client certificates to test mTLS. Lets create those:
Create private & public keys for a private Root CA :
openssl genrsa -out MyRootCA.key 4096
openssl req -new -x509 -days 365 -key MyRootCA.key -out MyRootCA.pem
Create client certificate private key and certificate signing request (CSR):
openssl genrsa -out MyClient.key 2048
openssl req -new -key MyClient.key -out MyClient.csr
Sign the newly created client cert by using the CA created above:
openssl x509 -req -in MyClient.csr -CA MyRootCA.pem -CAkey MyRootCA.key -set_serial 01 -out MyClient.pem -days 365 -sha256
You should now have below files in your folder:
- MyRootCA.key (root CA private key)
- MyRootCA.pem (root CA public key)
- MyClient.csr (client certificate signing request)
- MyClient.key (client certificate private key)
- MyClient.pem (client certificate public key)
Copy the root CA public key to a trust store file for uploading to API Gateway.
cp MyRootCA.pem truststore.pem
We will need to upload this truststore.pem
file to an S3 bucket. API Gateway will use this trust store to verify the certificate client submits in API requests.
aws s3 cp truststore.pem s3://{your-s3-bucket}/truststore.pem
Step 4 - create a custom domain name for our API:
Once your custom domain is created in API Gateway, select it and go to API Mappings:
Configure a API mapping as shown below:
Note: In the final url with path, the
cds-au
path set above will replace thedev
stage name in the path giving us the full url as:
https://{your custom domain}/cds-au/v1/banking/accounts
Step 5 - configure a DNS record to point to the API gateway domain for this API:
I use Route 53 for this:
Once your new DNS record has propagated after a few minutes, you should be able to hit your new API with mTLS:
curl --key MyClient.key --cert MyClient.pem https://{your custom domain}/cds-au/v1/banking/accounts{"data":{"accounts":[{"accountId":"string","creationDate":"string","displayName":"string","nickname":"string","openStatus":"OPEN","isOwned":true,"maskedNumber":"string","productCategory":"TRANS_AND_SAVINGS_ACCOUNTS","productName":"string"}]},"links":{"self":"string","first":"string","prev":"string","next":"string","last":"string"},"meta":{"totalRecords":1,"totalPages":1}}
Hurray! You got mTLS on your API!!
If you don’t believe me, try hitting the API without the ` — key MyClient.key — cert MyClient.pem` params:
curl https://{your custom domain}/cds-au/v1/banking/accountscurl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to {your custom domain}:443
Step 6 - disable non-mTLS access to the API:
One last thing, let’s ensure our API can’t be accessed without mTLS auth.
Now if you try to hit your API Gateway endpoint for the API (not your custom domain), you will get:
curl https://{your api url}/{your stage name}/v1/banking/accounts
{"message":"Not Found"}%
So now we have ensured that our API can only be accessed by clients whose certificate we trust using mutual TLS.
Next:
AWS is planning further enhancements to this feature such as supporting native certificate revocation verification capabilities. In the meanwhile we can use Lambda custom authorisers to validate certificates either against a certificate revocation list (CRL) or by using the Online Certificate Status Protocol (OCSP). In the next part of this blog post, I will show how we can check for certificate validity using a Lambda custom authoriser.