This post is courtesy of Pankaj Agrawal, Solutions Architect.

In September 2020, Amazon API Gateway announced support for mutual Transport Layer Security (TLS) authentication. This is a new method for client-to-server authentication that can be used with API Gateway’s existing authorization options. Mutual TLS (mTLS) is an extension of Transport Layer Security(TLS), requiring both the server and client to verify each other.

Mutual TLS is commonly used for business-to-business (B2B) applications. It’s used in standards such as Open Banking, which enables secure open API integrations for financial institutions. It’s also common for Internet of Things (IoT) applications to authenticate devices using digital certificates.

This post covers automating the mTLS setup for API Gateway HTTP APIs, but the same steps can also be used for REST APIs as well. Download the code used in this walkthrough from the project’s GitHub repo.

Overview

To enable mutual TLS, you must create an API with a valid custom domain name. Mutual TLS is available for both regional REST APIs and the newer HTTP APIs. To set up mutual TLS with API Gateway, you must upload a certificate authority (CA) public key certificate to Amazon S3. This is called a truststore and is used for validating client certificates.

Reference architecture

The AWS Certificate Manager Private Certificate Authority (ACM Private CA) is a highly available private CA service. I am using the ACM Private CA as a certificate authority to configure HTTP APIs and to distribute certificates to clients.

Deploying the solution

To deploy the application, the solution uses the AWS Serverless Application Model (AWS SAM). AWS SAM provides shorthand syntax to define functions, APIs, databases, and event source mappings. As a prerequisite, you must have AWS SAM CLI and Java 8 installed. You must also have the AWS CLI configured.

To deploy the solution:

  1. Clone the GitHub repository and build the application with the AWS SAM CLI. Run the following commands in a terminal:
    git clone https://github.com/aws-samples/api-gateway-auth.git
    cd api-gateway-auth
    sam build

    Console output

  2. Deploy the application:
    sam deploy --guided

Provide a stack name and preferred AWS Region for the deployment process. The template requires three parameters:

  1. HostedZoneId: The template uses an Amazon Route 53 public hosted zone to configure the custom domain. Provide the hosted zone ID where the record set must be created.
  2. DomainName: The custom domain name for the API Gateway HTTP API.
  3. TruststoreKey: The name for the trust store file in S3 bucket, which is used by API Gateway for mTLS. By default its truststore.pem.

SAM deployment configuration

After deployment, the stack outputs the ARN of a test client certificate (ClientOneCertArn). This is used to validate the setup later. The API Gateway HTTP API endpoint is also provided as output.

SAM deployment output

You have now created an API Gateway HTTP APIs endpoint using mTLS.

Setting up the ACM Private CA

The AWS SAM template starts with setting up the ACM Private CA. This enables you to create a hierarchy of certificate authorities with up to five levels. A well-designed CA hierarchy offers benefits such as granular security controls and division of administrative tasks. To learn more about the CA hierarchy, visit designing a CA hierarchy. The ACM Private CA is used to configure HTTP APIs and to distribute certificates to clients.

First, a root CA is created and activated, followed by a subordinate CA following best practices. The subordinate CA is used to configure mTLS for the API and distribute the client certificates.

 PrivateCA: Type: AWS::ACMPCA::CertificateAuthority Properties: KeyAlgorithm: RSA_2048 SigningAlgorithm: SHA256WITHRSA Subject: CommonName: !Sub "${AWS::StackName}-rootca" Type: ROOT PrivateCACertificate: Type: AWS::ACMPCA::Certificate Properties: CertificateAuthorityArn: !Ref PrivateCA CertificateSigningRequest: !GetAtt PrivateCA.CertificateSigningRequest SigningAlgorithm: SHA256WITHRSA TemplateArn: 'arn:aws:acm-pca:::template/RootCACertificate/V1' Validity: Type: YEARS Value: 10 PrivateCAActivation: Type: AWS::ACMPCA::CertificateAuthorityActivation Properties: Certificate: !GetAtt - PrivateCACertificate - Certificate CertificateAuthorityArn: !Ref PrivateCA Status: ACTIVE MtlsCA: Type: AWS::ACMPCA::CertificateAuthority Properties: Type: SUBORDINATE KeyAlgorithm: RSA_2048 SigningAlgorithm: SHA256WITHRSA Subject: CommonName: !Sub "${AWS::StackName}-mtlsca" MtlsCertificate: DependsOn: PrivateCAActivation Type: AWS::ACMPCA::Certificate Properties: CertificateAuthorityArn: !Ref PrivateCA CertificateSigningRequest: !GetAtt - MtlsCA - CertificateSigningRequest SigningAlgorithm: SHA256WITHRSA TemplateArn: 'arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen3/V1' Validity: Type: YEARS Value: 3 MtlsActivation: Type: AWS::ACMPCA::CertificateAuthorityActivation Properties: CertificateAuthorityArn: !Ref MtlsCA Certificate: !GetAtt - MtlsCertificate - Certificate CertificateChain: !GetAtt - PrivateCAActivation - CompleteCertificateChain Status: ACTIVE

Issuing client certificate from ACM Private CA

Create a client certificate, which is used as a test certificate to validate the mTLS setup:

ClientOneCert: DependsOn: MtlsActivation Type: AWS::CertificateManager::Certificate Properties: CertificateAuthorityArn: !Ref MtlsCA CertificateTransparencyLoggingPreference: ENABLED DomainName: !Ref DomainName Tags: - Key: Name Value: ClientOneCert

Setting up a truststore in Amazon S3

The ACM Private CA is ready for configuring mTLS on the API. The configuration uses an S3 object as its truststore to validate client certificates. To automate this, an AWS Lambda backed custom resource copies the public certificate chain of the ACM Private CA to the S3 bucket:

 TrustStoreBucket: Type: AWS::S3::Bucket Properties: VersioningConfiguration: Status: Enabled TrustedStoreCustomResourceFunction: Type: AWS::Serverless::Function Properties: FunctionName: TrustedStoreCustomResourceFunction Handler: com.auth.TrustedStoreCustomResourceHandler::handleRequest Timeout: 120 Policies: - S3CrudPolicy: BucketName: !Ref TrustStoreBucket

The example custom resource is written in Java but it could also be written in another language runtime. The custom resource is invoked with the public certificate details of the private root CA, subordinate CAs, and the target S3 bucket. The Lambda function then concatenates the certificate chain and stores the object in the S3 bucket.

TrustedStoreCustomResource: Type: Custom::TrustedStore Properties: ServiceToken: !GetAtt TrustedStoreCustomResourceFunction.Arn TrustStoreBucket: !Ref TrustStoreBucket TrustStoreKey: !Ref TruststoreKey Certs: - !GetAtt MtlsCertificate.Certificate - !GetAtt PrivateCACertificate.Certificate

You can view and download the handler code for the Lambda-backed custom resource from the repo.

Configuring Amazon API Gateway HTTP APIs with mTLS

With a valid truststore object in the S3 bucket, you can set up the API. A valid custom domain must be configured for API Gateway to enable mTLS. The following code creates and sets up a custom domain for HTTP APIs. See template.yaml for a complete example.

CustomDomainCert: Type: AWS::CertificateManager::Certificate Properties: CertificateTransparencyLoggingPreference: ENABLED DomainName: !Ref DomainName DomainValidationOptions: - DomainName: !Ref DomainName HostedZoneId: !Ref HostedZoneId ValidationMethod: DNS SampleHttpApi: Type: AWS::Serverless::HttpApi DependsOn: TrustedStoreCustomResource Properties: CorsConfiguration: AllowMethods: - GET AllowOrigins: - http://localhost:8080 Domain: CertificateArn: !Ref CustomDomainCert DomainName: !Ref DomainName EndpointConfiguration: REGIONAL SecurityPolicy: TLS_1_2 MutualTlsAuthentication: TruststoreUri: !GetAtt TrustedStoreCustomResource.TrustStoreUri TruststoreVersion: !GetAtt TrustedStoreCustomResource.ObjectVersion Route53: EvaluateTargetHealth: False HostedZoneId: !Ref HostedZoneId DisableExecuteApiEndpoint: true 

An Amazon Route 53 public hosted zone is used to configure the custom domain. This must be set up in your AWS account separately and you must provide the hosted zone ID as a parameter to the template.

Since the HTTP APIs default endpoint does not require mutual TLS, it is disabled via DisableExecuteApiEndpoint. This helps to ensure that mTLS authentication is enforced for all traffic to the API.

The sample API invokes a Lambda function and returns the request payload as the response.

Testing and validating the setup

To validate the setup, first export the client certificate created earlier. You can export the certificate by using the AWS Management Console or AWS CLI. This example uses the AWS CLI to export the certificate. To learn how to do this via the console, see exporting a private certificate using the console.

  1. Export the base64 PEM-encoded certificate to a local file, client.pem.aws acm export-certificate --certificate-arn <<Certificat ARN from stack output>>
    --passphrase $(echo -n 'your paraphrase' | base64) --region us-east-2 | jq -r '"\(.Certificate)"' > client.pem
  2. Export the encrypted private key associated with the public key in the certificate and save it to a local file client.encrypted.key. You must provide a passphrase to associate with the encrypted private key. This is used to decrypt the exported private key.aws acm export-certificate --certificate-arn <<Certificat ARN from stack output>>
    --passphrase $(echo -n 'your paraphrase' | base64) --region us-east-2| jq -r '"\(.PrivateKey)"' > client.encrypted.key
  3. Decrypt the exported private key using passphrase and OpenSSL:openssl rsa -in client.encrypted.key -out client.decrypted.key
  4. Access the API using mutual TLS:curl -v --cert client.pem  --key client.decrypted.key https://demo-api.example.com

Adding a certificate revocation list

AWS Certificate Manager Private Certificate Authority (ACM Private CA) can be natively configured with an optional certificate revocation list (CRL).

CRL is a way for certificate authority (CA) to make it known that one or more of their digital certificates is no longer trustworthy. When they revoke a certificate, they invalidate the certificate ahead of its expiration date. The certificate authority can revoke an issued certificate for several reasons, the most common one being that the certificate’s private key are compromised.

API Gateway HTTP APIs mTLS setup can be used along with all existing API Gateway authorizer options. You can further extend validation to AWS Lambda authorizers, which can be configured to validate the client certificates against this certificate revocation list (CRL). For example:

Certificate revocation architecture

For Lambda authorizer blueprint examples, refer to aws-apigateway-lambda-authorizer-blueprints.

Conclusion

Mutual TLS (mTLS) for API Gateway is now generally available at no additional cost. This post shows how to automate mutual TLS for Amazon API Gateway HTTP APIs using the AWS Certificate Manager Private Certificate Authority as a private CA. Using infrastructure as code (IaC) enables you to develop, deploy, and scale cloud applications, often with greater speed, less risk, and reduced cost.

Download the complete working example for deploying mTLS with API Gateway at this GitHub repo. To learn more about Amazon API Gateway, visit the API Gateway developer guide documentation.

For more serverless learning resources, visit Serverless Land.