How to provide a custom domain name for a Lambda-based APIGateway in CDK?

Viewed 5753

For the purposes of this question, assume that I already have an example.org Hosted Zone in Route53 (my actual zone is, of course, different)

With the following CDK app:

export class MyExampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    const backend = new Function(this, 'backendLambda', {
      code: new AssetCode("lambda/"),
      handler: "index.handler",
      runtime: Runtime.PYTHON_3_8
    });

    apiDomainName = 'api.test.example.org'
    const api = new LambdaRestApi(this, 'api', {
      handler: backend,
      proxy: true,
      deploy: true,
      domainName: { 
        domainName: apiDomainName,
        certificate: new Certificate(this, 'apiCertificate', {
          domainName: apiDomainName
        })
      }
    });

  }
}

, when I run cdk deploy, part of the output reads:

Outputs:
MyExampleStack.apiEndpoint0F54D2EA = https://<alphanumericId>.execute-api.us-east-1.amazonaws.com/prod/

, and, indeed, when I curl that url, I see the response I would expect from my Lambda code. I would expect curling api.test.example.org to give the same result - however, instead it gives curl: (6) Could not resolve host: api.test.example.org.

Based on this documentation, I tried:

export class MyExampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const rootDomain = 'example.org'
    
    const zone = HostedZone.fromLookup(this, 'baseZone', {
      domainName: rootDomain
    });
    
    const backend = new Function(...);

    const api = new LambdaRestApi(...);
    new ARecord(this, 'apiDNS', {
      zone: zone,
      recordName: 'api.test',
      target: RecordTarget.fromAlias(new ApiGateway(api))
    });

  }
}

which did give a Route53 entry:

$ aws route53 list-hosted-zones
{
    "HostedZones": [
        {
            "Id": "/hostedzone/ZO3B2N6W70PDD",
            "Name": "example.org.",
            "CallerReference": "598D71AB-4A98-EC5A-A170-D51CB243A2EA",
            "Config": {
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 8
        }
    ]
}
$ aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/ZO3B2N6W70PDD --query 'ResourceRecordSets[?Name==`api.test.example.org.`]'
[
    {
        "Name": "api.test.example.org.",
        "Type": "A",
        "AliasTarget": {
            "HostedZoneId": "Z1UJRXOUMOOFQ9",
            "DNSName": "<alphanumericId2>.execute-api.us-east-1.amazonaws.com.",
            "EvaluateTargetHealth": false
        }
    }
]

But this still doesn't work:

  • curl api.test.example.org still gives "Could not resolve host"
  • curl <alphanumericId2>.execute-api.us-east-1.amazonaws.com gives curl: (7) Failed to connect to <alphanumericId2>.execute-api.us-east-1.amazonaws.com port 80: Connection refused
  • curl https://<alphanumericId2>..execute-api.us-east-1.amazonaws.com gives {"message":"Forbidden"}
  • curl https://<alphanumericId>.[...] (i.e. the output from cdk deploy) still gives the expected response from the Lambda

How can I define a custom name in Route53 to route to my Lambda-backed APIGateway API?

1 Answers

Overall code LambdaRestApi with Route53 A Record, will create

  • Custom domain pointing to a particular stage prod i.e api.test.example.org domain to stage `prod'(example)
  • Route 53 A record for api.test.example.org pointing to Api Gateway hosted zone.

These are two combinations that will work

  • https://api.test.example.org will work pointing directly to stage prod.
  • CDK Output https://abcdef1234.execute-api.us-east-1.amazonaws.com/prod/ will work as stage is appended to it.

These are few combinations that will not work

  • Two other tests you did with http://
  • With no protocol, defaults to http, will not work, as we api gateway by default gives TLS 1.0 (ssl-https) and no http listener.
  • One other attempt you did with https:// without a stage name at the end, will return 403 forbidden, as stage name is missing.

Here is full CDK code.

import * as cdk from "@aws-cdk/core";
import * as apigw from "@aws-cdk/aws-apigateway";
import * as acm from "@aws-cdk/aws-certificatemanager";
import * as route53 from "@aws-cdk/aws-route53";
import * as route53Targets from "@aws-cdk/aws-route53-targets";
import * as lambda from "@aws-cdk/aws-lambda";

export class HelloCdkStack extends cdk.Stack {

constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    this.buildLambdaApiGateway();
  }

    buildLambdaApiGateway() {
    const rootDomain = "example.org";

    const zone = route53.HostedZone.fromLookup(this, "baseZone", {
      domainName: rootDomain,
    });

    const backend = new lambda.Function(this, "MyLayeredLambda", {
      code: new lambda.InlineCode("foo"),
      handler: "index.handler",
      runtime: lambda.Runtime.NODEJS_10_X,
    });

    const restApi = new apigw.LambdaRestApi(this, "myapi", {
      handler: backend,
      domainName: {
        domainName: `api-test.${rootDomain}`,
        certificate: acm.Certificate.fromCertificateArn(
          this,
          "my-cert",
          "arn:aws:acm:us-east-1:111112222333:certificate/abcd6805-1234-4159-ac38-761acdc700ef"
        ),
        endpointType: apigw.EndpointType.REGIONAL,
      },
    });

    new route53.ARecord(this, "apiDNS", {
      zone: zone,
      recordName: "api-test",
      target: route53.RecordTarget.fromAlias(
        new route53Targets.ApiGateway(restApi)
      ),
    });
  }
}
Related