8

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?

scubbo
  • 4,969
  • 7
  • 40
  • 71
  • can you try `curl https://api.test.example.org` ? by default curl is going to hit port 80 http – Balu Vyamajala Feb 01 '21 at 04:48
  • I solved the `curl https://..execute-api.us-east-1.amazonaws.com` returning `Forbidden` by adding `-H "Host: api.test.example.org"`. Still getting "Could not resolve host" when curling `https://api.test.example.org`. Reference: https://stackoverflow.com/a/44387484/899470 – juniper- Jun 25 '23 at 02:01

1 Answers1

14

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)
      ),
    });
  }
}
Captain Jack Sparrow
  • 971
  • 1
  • 11
  • 28
Balu Vyamajala
  • 9,287
  • 1
  • 20
  • 42
  • 1
    Got it, thanks! I can confirm that when I tried `curl`ing `https://api.test.example.org` with the `ARecord` Construct present in CDK, the response was as expected - and when I removed the `ARecord` and re-deployed, the `curl` failed with `Could not resolve host`. Follow-on question - why do I need to define the domain name twice, once in the `LambdaRestApi` and once in an `ARecord`? Why does the `LambdaRestApi` accept a `domainName` argument if doesn't set up the DNS routing for that name? What _does_ that `domainName` argument do? – scubbo Feb 01 '21 at 15:21
  • 1
    First one creates a customDomain with in API Gateway which can point to a stage. This just won't resolve within DNS, still need to create an entry in domain provider for it to be resolved which in this case is Route53. [Here](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html) are more details. – Balu Vyamajala Feb 01 '21 at 21:32
  • For the life of me I can't see the functional difference between the CDK in this post and the CDK in the original post. Both have the domain name in the LambdaRestApi and both have an ARecord. Any help on what I'm missing? I'm running into the exact same issue as OP and have the same CDK constructs. And I am trying to curl with https:// – juniper- Jun 25 '23 at 01:51