Using Mutual TLS authentication with Amazon API Gateway
On-boarding trusted clients and partners on Amazon API Gateway gets more secure with Mutual TLS
Mutual TLS or MTLS is the de-facto transport layer security standard used in critical Business-to-Business (B2B) and Internet of Things (IoT) integrations. Essentially Mutual TLS establishes a two-way trust in a client-server communication channel. So, it’s not just the client that verifies identity of server (which happens to be the case with standard HTTPS based communication via browser), but the server also verifies identity of client. In short, MTLS is used to authenticate a trusted client/partner based on X.509 certificates. Amazon API Gateway supports MTLS authentication and therefore we can leverage this feature to authenticate trusted clients/partners and grant them access to APIs published on API Gateway
For a relevant introduction to mutual TLS, refer to this post by Benjamin Porter : https://freedomben.medium.com/what-is-mtls-and-how-does-it-work-9dcdbf6c1e41
The preamble
In order to configure API Gateway to use MTLS, we need to create a custom domain name with mutual TLS authentication enabled. However, before we proceed any further, we will make a couple of assumptions. These assumptions are essentially associated with the prerequisites of setting up a custom domain name. Here are the relevant ones:
- Hosted zone in Amazon Route 53: We will assume that a configured domain exists in Route 53 (say, ‘example.com’)
- AWS ACM certificate: A verified AWS Certificate Manager (ACM) issued public certificate exists that is associated with sub-domain ‘api.example.com’. This certificate will be used as the identity of the custom domain name
- Amazon S3 bucket: A bucket exists in Amazon S3 (say ‘partners-trust-store’) with appropriate bucket policies, that will be used to host a trust store bundle for partner certificates
- Deployed REST API on Amazon API Gateway: A REST API with at least one resource and method has been deployed with a staging label (say ‘DEV’) and is accessible with the default ‘execute-api’ endpoint. Let’s consider ‘customer’ API with a GET endpoint: ‘/customer/{customer-id}’
Finally, this post will use AWS CLI (version 2.2.13) throughout, to perform necessary actions with AdministratorAccess IAM permission configured (for sake of simplicity) with default profile. Here’s a link that describes how to install and configure AWS CLI: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html
Establish the trust
Amazon API Gateway must trust client/partner certificate to establish MTLS connection. In order to set up the trust, partners must share their public certificates which will have to be configured in API Gateway’s trust-store.
Generate certificate
For purpose of this post, we will use self-signed certificates and generate these using OpenSSL commands, on behalf of a partner.
In reality a client/partner will use a CA signed certificate and share the same with API Gateway
Here are the commands: the first one generates a RSA 2048-bit private key (‘partner.key’) and a certificate signing request or CSR. The second one generates a self-signed X.509 certificate (‘partner.crt’) in PEM format, based on private key and CSR file.
openssl req -newkey rsa:2048 \
-nodes -keyout partner.key \
-out partner.csropenssl x509 -signkey partner1.key \
-in partner.csr -req -days 365 \
-out partner.crt
A client/partner will never share the private key with API Gateway. Only public certificate needs to be shared so that API Gateway can trust the certificate
Create the trust store
The trust store (or trust store bundle) in API Gateway, is basically an Amazon S3 based file that contains PEM format public certificates of all trusted partners appended one after another. This file could be versioned by leveraging versioning capabilities of Amazon S3. A sample file with two certificates appended would look somewhat like:
-----BEGIN CERTIFICATE-----
<Certificate Content>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<Certificate Content>
-----END CERTIFICATE-----
With X.509 certificate generated for the partner, we must add the contents of this certificate to trust-store file (say, ‘trusted_partners.pem’) and push it into the existing S3 bucket named ‘partners-trust-store’. We can use following command to do so:
aws s3api put-object --bucket partners-trust-store \
--acl private --key trusted_partners.pem \
--body ./trusted_partners.pem
At this point, our Amazon S3 based trust store is ready to be configured with API Gateway custom domain name and we note S3 URI of the trust-store file: ‘s3://partners-trust-store/trusted_partners.pem’
Configure custom domain name
Amazon API Gateway’s ‘Custom Domain Name’ feature not only provides a friendly and intuitive domain name for APIs, it also achieves transparency for API clients in the event of a change in default ‘execute-api’ based endpoint (say, due to endpoint versioning, change in stage labels, etc.). Here’s what is required to configure a custom domain name with MTLS authentication enabled.
Create custom domain name
In order to create a custom domain name, we will use following command and pass domain name associated with the ACM certificate (‘api.example.com’), regional ACM certificate ARN and Amazon S3 URI of the trust-store for mutual TLS authentication. Note that, to configure MTLS for REST APIs, we will have to create a regional custom domain name and use a minimum TLS version of 1.2.
aws apigateway create-domain-name --region ap-south-1 \
--domain-name "api.example.com" \
--regional-certificate-arn arn:aws:acm:ap-south-1:<acct-id>:certificate/<cert-UUID> \
--endpoint-configuration types=REGIONAL \
--security-policy TLS_1_2 \
--mutual-tls-authentication truststoreUri=s3://partners-trust-store/trusted_partners.pem
There are two specific details which will have to be noted in output of the above command- ‘regionalDomainName’ and ‘regionalHostedZoneId’. These are actually Amazon API Gateway regional domain name and hosted zone identifier. These will be used subsequently. Here’s a sample output of the command:
{
"domainName": "api.example.com",
"certificateUploadDate": "2021-06-19T10:47:19+05:30",
"regionalDomainName": "d-l2wcahxeij.execute-api.ap-south-1.amazonaws.com",
"regionalHostedZoneId": "Z3VO1THU9YC4UF",
"regionalCertificateArn": "arn:aws:acm:ap-south-1:<acct-id>:certificate/<cert-UUID>",
"domainNameStatus": "UPDATING",
...
}
Create API mappings
Now its time to create API mappings for the deployed API identified by API ID. Once this mapping is set, API resource endpoints will be accessible through the mapped path. In order to create mappings, we will use following command and pass the domain name (‘api.example.com’), our chosen base path (say, ‘c360’), stage (‘DEV’) and API ID.
aws apigateway create-base-path-mapping \
--domain-name "api.example.com" \
--base-path c360 \
--rest-api-id <api-id> \
--stage DEV
The deployed API (and its resources) should now be available at the following endpoint:
- https://api.example.com/c360/customer/<sub-resources>
Disable default API endpoint
Since we intend the API to be accessible only through custom domain name endpoint using MTLS connection, the default ‘execute-api’ endpoint for this API should be disabled. Once disabled, the API will have to be re-deployed. Here are the commands to perform these operations.
aws apigateway update-rest-api \
--rest-api-id <api-id> \
--patch-operations op=replace,path=/disableExecuteApiEndpoint,value='True'aws apigateway create-deployment \
--rest-api-id <api-id> \
--stage-name DEV
Create ‘alias’ record in Route 53
Given that we already have an existing Route 53 hosted zone, it’s time to create an alias record which will point to the Amazon API Gateway regional domain name (along with regional hosted zone identifier) that we noted as part of the response to ‘create-domain-name’ command. Essentially this means that ‘api.example.com’ will be resolved to an alias record pointing to the regional domain name of API Gateway.
First we need to create a JSON file (say, ‘api-gw-domain-name.json’ ) that carries the change record as shown below:
{
"Comment": "UPSERT API gateway regional domain name record ",
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "<regionalHostedZoneId>",
"DNSName": "<regionalDomainName>",
"EvaluateTargetHealth": true
}
}
}]
}
Subsequently, we have to use following command and pass our Route 53 hosted zone identifier and the JSON file to create/update alias record
aws route53 change-resource-record-sets --hosted-zone-id <hosted-zone-id> --change-batch file://api-gw-domain-name.json
The above command will show PENDING status as response which indicates that changes in this request are not yet propagated to all Route 53 DNS servers. However once propagated, status will change to INSYNC.
Test the setup
We will use Postman to test MTLS setup on API Gateway. If we try to access the REST API endpoint without configuring partner’s trusted certificate, then MTLS connection will not get established (Refer: Figure-1)
Partner certificate and key could be configured from Settings > Certificates tab (Refer: Figure-2)
Once the partner certificate is configured, we should be able to get response from the deployed API endpoint (Refer: Figure-3)
Furthermore, if some other certificate (not trusted by API Gateway) is used, we will receive HTTP 403 (Forbidden) from API Gateway.
Conclusion
Amazon API Gateway’s custom domain name feature with MTLS authentication enabled, provides a very powerful solution to establish transport layer trust between clients/partners and API Gateway. This allows the clients/partners to call API endpoints only if they can present a trusted X.509 certificate and possess the corresponding private key.