This is my second post, in the series, on the topic around authentication and authorization for Amazon API Gateway. In my last post I discussed how to use Auth0 and JWT Authorizer with API Gateway. In this post we will take a closer look at using Mutual TLS together with API Gateway. The reason to use Mutual TLS is often for service-2-service integration scenarios where one service run on-premesis or in an other Public Cloud Vendor making IAM Authorization not an option.
I will use the console and CLI to do the entire setup. As normal everything exists as CloudFormation and is available on GitHub
First of all we are going to create the client certificate and the private certificate authority (CA) that we need. I will be using OpenSSL on Mac to create this.
The first step is to create the private CA public and private keys, that we then use to create the client certificates. To do this run the following commands and supply the requested input.
openssl genrsa -out PrivateCA.key 4096
openssl req -new -x509 -days 3650 -key PrivateCA.key -out PrivateCA.pem
Next step is to create the client private key and a certificate signing request (CSR), run the following commands and supply the requested input.
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
Last step is to sign the client certificate with our private CA. Run the following command to do that.
openssl x509 -req -in client.csr -CA PrivateCA.pem -CAkey PrivateCA.key -set_serial 01 -out client.pem -days 3650 -sha256
After this you should have the following files created:
Now we need to upload the PrivateCA.pem file to an Amazon S3 Bucket, so we can configure it as TrustStore for API Gateway. To make things clearer let's copy PrivateCA.pem to truststore.pem.
cp PrivateCA.pem truststore.pem
Then let's create the bucket and upload the file.
aws s3 mb s3://<bucket_name> --region <region>
aws s3 cp truststore.pem s3://<bucket_name>/truststore.pem
Create API Gateway & Lambda
With the truststore in place in S3 we can start to create the API. We start by creating a simple Lambda function to use as integration.
Create the Lambda function
Jump into the Lambda part of the console and start authoring a function from scratch. Name the function api-hello-world, set the runtime to python 3.8, leave rest as default anc click Create Function
In the next step update the code and hit Deploy
def lambda_handler(event, context):
print("Hello world from Lambda!")
'body': json.dumps('Hello World!')
Create API Gateway
Time to start setting up API Gateway. Navigate to API gateway part of the console and click Create API. In the selection screen click Build for the HTTP API.
We create one integration for the Lambda function and name the API, I will call mine mtls-hello-world and click Next.
Now we need to configure the route. Set the method to GET and add a resource path, point the route to the corresponding integration.
Move on to the next part of the configuration, to setup the stages. You can leave this with default settings with a $default stage with auto-deploy on.
Final step is to review and create the API. Your configuration should look something like this. Click Create to create the API.
Now let's test it all out before moving to the next part of the configuration. Select you newly created API and find the Invoke URL. Copy and paste the URL into a browser and don't forget to add the resources path, e.g. /api-hello-world if everything is working you should now see result in the browser window.
Create Custom Domain
Mutal TLS requires a custom domain name. First of all we must create a certificate in ACM to use. Navigate to ACM and create a certificate for your domain.
Next navigate to the Custom Domain Name section in API Gateway and create a domain name that match the certificate that we just created, make sure to select the certificate in the dropdown menu as well.
Select the API Mappings Tab and configure the API mappings, select API and stage.
Make sure that you configure your DNS to point to the API Gateway Domain name, I use Route53 and add a new A record.
Now let's test that we can call our API using our custom domain name. Paste the URL, including route in a browser e.g https://
curl https://<your domain>/api-hello-world
You should get a Hello World reply back from API Gateway.
Configure Mutual TLS
Now it's time to configure our Mutual TLS authentication. This is done on our custom domain name. Select the custom domain name and click edit on domain details. Enable Mutual TLS authentication and fill in the S3 path to the truststore and click Save.
Since Mutal TLS is configured on our custom domain name we must ensure that a user can't circumvent the authorization by calling the default API endpoint. To do that we should disable it, this will require all calls to come in via the custom domain name. Navigate to the API and Edit.
Select Disable on the default endpoint and click Save.
Trying to access the API via the default endpoint should now give a message "Not Found"
curl https://<default endpoint>/api-hello-world
Final wrap up
Now we have an API setup on a custom domain name with Mutual TLS for authentication. A setup like this is really powerful when doing service-2-service or machine-2-machine integrations where it's not possible to use IAM. As an example a service running on-premises where you don't want to bundle long lived IAM access tokens, or in the case that it's a legacy service that already use Mutual TLS today.
What is important to remember is that Mutal TLS is configured on the custom domain name and not on the API it self. Therefor the default endpoint for the API in API Gateway must be disabled to force the calls to come in via the custom domain name so it's not possible to circumvent the authentication.
Disabling the default endpoint is a good practice if you use custom domain name even if you don't use Mutual TLS.
This was the second post in the series on how to setup different Authentication and Authorization on API gateway. Don't miss my previous post about Auth0 and built in JWT Authorizer.