cfn-api-gateway-custom-domain icon indicating copy to clipboard operation
cfn-api-gateway-custom-domain copied to clipboard

API Gateway custom domains as CloudFormation resources, backed by Let's Encrypt

cfn-api-gateway-custom-domain

[Ed note: in an amazing turn of events, this library was released mere hours before AWS basically made it obsolete.]

This is a CloudFormation custom resource for API Gateway custom domains. It runs Certbot on Lambda to create certificates, and automatically creates Route53 DNS records to respond to Let's Encrypt domain ownership challenges.

It's basically a prollyfill for the conspicuously missing AWS::ApiGateway::DomainName resource type, which will likely land if/when AWS Certificate Manager supports API Gateway.

If you need to renew your certificates or would like to just use Route53 to create Let's Encrypt certificates, check out certbot-route53.sh.

Features

  • Fast: Certificates are installed and enabled in minutes
  • Free: Certificates cost nothing (but you can donate)
  • Easy: Certificates need only 14 lines in a CloudFormation template
  • Safe: Certificates never touch your email or machine

Setup

Before you get started, you'll need to:

  1. create a Route53 public hosted zone for the domain, and
  2. point the domain at your zone's nameservers.

Since Let's Encrypt needs to be able to contact Route53, your DNS settings must be in effect already.

Usage

  1. First, make sure you have a AWS::Route53::HostedZone in the Resources section of your template:

      MyHostedZone:
        Type: AWS::Route53::HostedZone
        Properties:
          Name: jedschmidt.com
    
  2. Then, add an API Gateway Custom Domain stack to your template:

      ApiGatewayCustomDomain:
        Type: AWS::CloudFormation::Stack
        Properties:
          TemplateURL: https://s3.amazonaws.com/api-gateway-custom-domain/stack.template
          Parameters:
            LetsEncryptAccountEmail: [email protected]
            LetsEncryptAgreeTOS: Yes
            LetsEncryptManualPublicIpLoggingOk: Yes
    

    You'll need to specify three things:

    • LetsEncryptAccountEmail: The email address associated with your Let's Encrypt account
    • LetsEncryptAgreeTOS: That you agree to the Let's Encrypt Terms of Service. This must be Yes.
    • LetsEncryptManualPublicIpLoggingOk: That you're okay with Let's Encrypt logging the IP address of the Lambda used to run certbot. This must be Yes.

    This stack has only one output: ServiceToken. This can be accessed using !GetAtt {your-logical-stack-name}.Outputs.ServiceToken.

  3. Finally, add a custom domain to your template:

      MyDomain:
        Type: Custom::ApiGatewayCustomDomain
        Properties:
          ServiceToken: !GetAtt ApiGatewayCustomDomain.Outputs.ServiceToken
          HostedZoneId: !Ref MyHostedZone
          Subdomain: www
    

    You'll need to specify two things:

    • Service Token: The Service token output by your API Gateway Custom Domain stack
    • HostedZoneId: A reference to the existing AWS::Route53::HostedZone resource for which you're creating a certificate.

    You can also optionally specify:

    • Subdomain: The subdomain prefix for which you're creating a certificate, such as www. This is concatenated with the Name of the hosted zone, to create the full domain name. If this is omitted, the bare apex domain is used.

    This resource returns the results of the createDomainName function.

At this point, you've done all you need to create/update/deploy your stack and get your certificate installed into API Gateway, but to user the domain you'll need to add an alias DNS record that resolves your domain to the CloudFront distribution created with your custom domain name, and then map the domain to a stage of your rest API:

  MyDNSRecord:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Ref MyHostedZone
      RecordSets:
      - Type: A
        Name: !GetAtt MyDomain.domainName
        AliasTarget:
          HostedZoneId: Z2FDTNDATAQYW2 # (hardcoded for all CloudFormation templates)
          DNSName: !GetAtt MyDomain.distributionDomainName

  MyPathMapping:
    Type: AWS::ApiGateway::BasePathMapping
    Properties:
      DomainName: !GetAtt MyDomain.domainName
      RestApiId: !Ref MyRestAPI
      Stage: prod

Example

See the included example for a simple website redirect app configured entirely with CloudFormation.

How it works

When a custom domain name is first created in your stack, CloudFormation calls a node.js function in a Lambda-backed custom resource, which in turn launches Certbot in a Python subprocess. Certbot then contacts Let's Encrypt to get a challenge string, which is placed in a TXT record on Route53. Once the record is live, Certbot tells Let's Encrypt to verify it, and once it's verified, Let's Encrypt sends the certificate back to Certbot and then to API Gateway, where it's used to create a custom domain.

Todo

  • Automate certificate renewal
  • Revoke certificates on deletion

Thanks