0

Introduction

I have some TypeScript code that uses CDK to create an API Gateway and a Lambda. It works and deploys to a standard AWS URL. So far so good.

I now need to transfer the API Gateway so that it operates on a custom domain, so that it can set a cookie in a web app. This is proving far harder, and I suspect I am having difficulty because I am new to TypeScript, AWS, and CDK all at the same time. There are a number of documentation resources on the web, but most would require me to rewrite the precious little working code I have, which I am reluctant to do.

I have created a certificate manually, because that requires validation and thus it does not make sense to create it in code. Other than that I want all other resources to be created by CDK code in a Stack. In my view, it defeats the purpose of CDK if I have to configure things manually.

Problem

The below code deploys everything I need to gatekeeper.d.aws.example.com - a HostedZone, an ARecord, a LambdaRestApi and a Function (lambda). However it does not work because the NS records newly assigned to gatekeeper.d.aws.example.com do not match the ones in the parent d.aws.example.com.

I think this means that although d.aws.example.com is "known", the gateway subdomain cannot delegate to it.

Here is my working code:

// Create the lambda resource
const referrerLambda = new lambda.Function(this, 'EisReferrerLambda', {
    runtime: lambda.Runtime.NODEJS_14_X,
    handler: 'index.handler',
    code: lambda.Code.fromAsset(path.join(__dirname, '../../src/lambda')),
    environment: env
});

// Set up the domain name on which the API should appear
const domainName = 'gatekeeper.d.aws.example.com';

// TODO need to fetch it with an env var? Or read from environment?
const certificateArn = 'arn:aws:acm:us-east-1:xxx:certificate/yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy';

const certificate = acm.Certificate.fromCertificateArn(this, 'SslCertificate', certificateArn);

const hostedZone = new route53.HostedZone(this, 'EisReferrerHostedZone', {
    zoneName: domainName
});

// Add an A record
new route53.ARecord(this, 'DnsRecord', {
    zone: hostedZone,
    target: route53.RecordTarget.fromAlias(new targets.ApiGateway(apiGateway)),
});

// I think I need a DomainNameOptions object
const dno : DomainNameOptions = { certificate, domainName };

// Create the APIG resource
// See https://intro-to-cdk.workshop.aws/the-workshop/4-create-apigateway.html
const apiGateway = new apigw.LambdaRestApi(this, "EisReferrerApi", {
    handler: referrerLambda,
    // proxy = on means that the lambda handles all requests to the APIG,
    // instead of just explicit resource endpoints
    proxy: false,
    // deploy = on means that we get a default stage of "prod", I don't want
    // that - I'm creating a custom Deployment anyway
    deploy: false,
    // Point to a domain name options object
    domainName: dno
});

// Create an endpoint in the APIG
// https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigateway-readme.html#defining-apis
const items = apiGateway.root.addResource('gatekeeper');
items.addMethod('GET');  // GET /default/gatekeeper

// The deployment resource is just needed by the Stage system
const deployment = new apigw.Deployment(
    this,
    'EisReferrerDeployment',
    { api: apiGateway }
);

// Create a Stage (this affects the first component in the path
const stageName = 'default';
apiGateway.deploymentStage = new apigw.Stage(
    this,
    stageName,
    { deployment, stageName }
);

Question

As you can see from the code, I've found how to create an A record, but creating/modifying NS records seems harder. For a start, there does not seem to be an NSRecord class, at least based on exploring the class structure from my IDE autocomplete.

A rudimentary solution would allow me to create NS records with the fixed values that are set up elsewhere (in the AWS account that "owns" the domain). A better solution would be to read what those records are, and then use them.

Update

To see if my thinking is on the right track, I have run this deployment code, and manually modified the automatically assigned NS records in the HostedZone to match the records in the parent (in the other account). I think I have to wait for this change to seep into the DNS system, and I will update with the result.

Update 2

My manual adjustment did not work. I have therefore found a new thing to try (see "To add a NS record to a HostedZone in different account"):

// Commented out from earlier code
// const hostedZone = new route53.HostedZone(this, 'EisReferrerHostedZone', {
//     zoneName: domainName
// });

// In the account containing the HostedZone
const parentZone = new route53.PublicHostedZone(this, 'HostedZone', {
    zoneName: 'd.aws.example.com',
    crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal('12345678012')
});

// In this account
const subZone = new route53.PublicHostedZone(this, 'SubZone', {
    zoneName: domainName
});

new route53.CrossAccountZoneDelegationRecord(this, 'delegate', {
    delegatedZone: subZone,
    parentHostedZoneId: parentZone.hostedZoneId,
    delegationRole: parentZone.crossAccountDelegationRole
});

This sounds exactly what I need, but I fear the AWS documentation is out of date here - crossAccountDelegationRole is rendered in red in my IDE, and it crashes due to being undefined when cdk diff is run.

Update 3

I am assuming the property mentioned above is a typo or a reference to an outdated version of the library. I am now doing this:

new route53.CrossAccountZoneDelegationRecord(this, 'delegate', {
    delegatedZone: subZone,
    parentHostedZoneId: parentZone.hostedZoneId,
    delegationRole: parentZone.crossAccountZoneDelegationRole
});

This feel tantalisingly close, but it crashes:

Failed to create resource. AccessDenied: User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/CustomCrossAccountZoneDelegationC-xxx is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::yyyyyyyyyyyy:role/HostedZoneCrossAccountZoneDelegat-yyy

I wonder if I need to declare the IAM creds for the other account? I do have them.

I am not sure why permissions are needed, anyway - could it not just read the NS records in the other account and copy them to the local account? The DNS in the other account is public anyway.

I am willing to research fixing the IAM error, but this doesn't half feel like shooting in the dark. I might spend another two hours inching towards solving that sub-problem, only to find that the whole thing will fail for another reason.

halfer
  • 161
  • 1
  • 5
  • 25
  • (I could not find a tag here for aws-cdk - please let me know if this is "too programming" to be on-topic). – halfer Mar 13 '21 at 23:56
  • For full disclosure, I have [cross-posted this from Stack Overflow](https://stackoverflow.com/questions/66616710/how-can-i-set-up-my-hostedzone-so-that-it-delegates-to-a-parent-dns-record). I wonder if it is rather an esoteric mix of requirements - setting up a custom domain _and_ in CDK _and_ with cross-account DNS! – halfer Mar 13 '21 at 23:57

0 Answers0