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.
0 Answers