Secure your API Gateway APIs mutual TLS
Tags: aws, security, serverless
Published: 2022-01-17 17:55:03

---
title: "Secure your API Gateway APIs mutual TLS"
cover_image: "https://jimmydqv.com/assets/img/post_mtls_api_thumb.png"
tags: aws, security, serverless
published: true
---

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][auth0-post-link].
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. 

<span class="text-red-500">I will use the console and CLI to do the entire setup. As normal everything exists as CloudFormation and is available on [GitHub][github-link]</span>


## Create certificates

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][openssl-link] 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.

``` bash

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.

``` bash

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.

``` bash

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:

``` bash

PrivateCA.key
PrivateCA.pem
client.key
client.csr
client.pem

```

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.

``` bash

cp PrivateCA.pem truststore.pem

```

Then let's create the bucket and upload the file.

``` bash

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 <span class="font-mono text-blue-500">api-hello-world</span>, set the runtime to <span class="font-mono text-blue-500">python 3.8</span>, leave rest as default anc click <span class="font-mono text-blue-500">Create Function</span>  

![image](https://jimmydqv.com/assets/img/post_mtls_hello_world_lambda_step_1.png)

In the next step update the code and hit <span class="font-mono text-blue-500">Deploy</span>

``` python

import json

def lambda_handler(event, context):
    
    print("Hello world from Lambda!")
    
    return {
        'statusCode': 200,
        '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 <span class="font-mono text-blue-500">Create API</span>.
In the selection screen click <span class="font-mono text-blue-500">Build</span> for the HTTP API.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_create_apigw_step1.png)

We create one integration for the Lambda function and name the API, I will call mine <span class="font-mono text-blue-500">mtls-hello-world</span> and click <span class="font-mono text-blue-500">Next</span>.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_create_apigw_step2.png)

Now we need to configure the route. Set the method to <span class="font-mono text-blue-500">GET</span> and add a resource path, point the route to the corresponding integration.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_create_apigw_step3.png)

Move on to the next part of the configuration, to setup the stages. You can leave this with default settings with a <span class="font-mono text-blue-500">$default</span> stage with auto-deploy on.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_create_apigw_step4.png)

Final step is to review and create the API. Your configuration should look something like this. Click <span class="font-mono text-blue-500">Create</span> to create the API.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_create_apigw_step5.png)

Now let's test it all out before moving to the next part of the configuration. Select you newly created API and find the <span class="font-mono text-blue-500">Invoke URL</span>. Copy and paste the URL into a browser and don't forget to add the resources path, e.g. <span class="font-mono text-blue-500">/api-hello-world</span> 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.
![image](https://jimmydqv.com/assets/img/post_mtls_create_acm_cert.png)

Next navigate to the <span class="font-mono text-blue-500">Custom Domain Name</span> 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.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_custom_domain_step1.png)

Select the <span class="font-mono text-blue-500">API Mappings Tab</span> and configure the API mappings, select API and stage.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_custom_domain_step2.png)

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://<your domain>/api-hello-world or run CURL command:

``` bash

curl https://<your domain>/api-hello-world

```

You should get a <span class="font-mono text-blue-500">Hello World</span> 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 <span class="font-mono text-blue-500">Save</span>.

![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_enable_mtls.png)

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 <span class="font-mono text-blue-500">Edit</span>.
![image](https://jimmydqv.com/assets/img/post_mtls_api_gateway_disable_default_endpoint.png)

Select <span class="font-mono text-blue-500">Disable</span> on the default endpoint and click <span class="font-mono text-blue-500">Save</span>.

Trying to access the API via the default endpoint should now give a message <span class="font-mono text-blue-500">"Not Found"</span>

``` bash

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.

## Gotchas

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.


## Series

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.][auth0-post-link]

[auth0-post-link]: https://jimmydqv.com/secure-api-gw-with-auth0/
[openssl-link]: https://www.openssl.org/
[github-link]: https://github.com/JimmyDqv/blogs-and-sessions-code/tree/main/Http-api-mutual-tls