Skip to content

Commit

Permalink
feat(route53): Convenience API for creating zone delegations (#1853)
Browse files Browse the repository at this point in the history
When creating delegation relationship between two `PublicHostedZone`s,
one can now use `zone.addDelegation(otherZone)` instead of manually
creating the `ZoneDelegationRecord` insteance. This reduces the risk of
passing the incorrect name server, or hosting the record on the wrong
end of the relationship (DNS is hard!)

Additionally, fixes a bug in which it was not possible to create a zone
delegation record using a `IHostedZone.hostedZoneNameServers` property
as the array was mapped, which caused the `Fn::GetAtt` stringified list
token to become corrupted.

Fixes #1847
  • Loading branch information
RomainMuller authored Feb 28, 2019
1 parent 58025c0 commit f974531
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 21 deletions.
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-route53/lib/hosted-zone.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ec2 = require('@aws-cdk/aws-ec2');
import cdk = require('@aws-cdk/cdk');
import { HostedZoneImportProps, IHostedZone } from './hosted-zone-ref';
import { ZoneDelegationRecord } from './records';
import { CfnHostedZone } from './route53.generated';
import { validateZoneName } from './util';

Expand Down Expand Up @@ -112,6 +113,41 @@ export class PublicHostedZone extends HostedZone {
public addVpc(_vpc: ec2.IVpcNetwork) {
throw new Error('Cannot associate public hosted zones with a VPC');
}

/**
* Adds a delegation from this zone to a designated zone.
*
* @param delegate the zone being delegated to.
* @param opts options for creating the DNS record, if any.
*/
public addDelegation(delegate: PublicHostedZone, opts: ZoneDelegationOptions = {}): void {
new ZoneDelegationRecord(this, `${this.zoneName} -> ${delegate.zoneName}`, {
zone: this,
delegatedZoneName: delegate.zoneName,
nameServers: delegate.hostedZoneNameServers!, // PublicHostedZones always have name servers!
comment: opts.comment,
ttl: opts.ttl,
});
}
}

/**
* Options available when creating a delegation relationship from one PublicHostedZone to another.
*/
export interface ZoneDelegationOptions {
/**
* A comment to add on the DNS record created to incorporate the delegation.
*
* @default none
*/
comment?: string;

/**
* The TTL (Time To Live) of the DNS delegation record in DNS caches.
*
* @default 172800
*/
ttl?: number;
}

export interface PrivateHostedZoneProps extends CommonHostedZoneProps {
Expand Down
28 changes: 9 additions & 19 deletions packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Construct } from '@aws-cdk/cdk';
import cdk = require('@aws-cdk/cdk');
import { ZoneDelegationOptions } from '../hosted-zone';
import { IHostedZone } from '../hosted-zone-ref';
import { CfnRecordSet } from '../route53.generated';
import { determineFullyQualifiedDomainName } from './_util';

export interface ZoneDelegationRecordProps {
export interface ZoneDelegationRecordProps extends ZoneDelegationOptions {
/**
* The zone in which this delegate is defined.
*/
Expand All @@ -17,38 +18,27 @@ export interface ZoneDelegationRecordProps {
* The name servers to report in the delegation records.
*/
nameServers: string[];

/**
* The TTL of the zone delegation records.
*
* @default 172800 seconds.
*/
ttl?: number;

/**
* Any comments that you want to include about the zone delegation records.
*
* @default no comment.
*/
comment?: string;
}

/**
* A record to delegate further lookups to a different set of name servers
*/
export class ZoneDelegationRecord extends Construct {
constructor(scope: Construct, id: string, props: ZoneDelegationRecordProps) {
export class ZoneDelegationRecord extends cdk.Construct {
constructor(scope: cdk.Construct, id: string, props: ZoneDelegationRecordProps) {
super(scope, id);

const ttl = props.ttl === undefined ? 172_800 : props.ttl;
const resourceRecords = cdk.unresolved(props.nameServers)
? props.nameServers // Can't map a string-array token!
: props.nameServers.map(ns => (cdk.unresolved(ns) || ns.endsWith('.')) ? ns : `${ns}.`);

new CfnRecordSet(this, 'Resource', {
hostedZoneId: props.zone.hostedZoneId,
name: determineFullyQualifiedDomainName(props.delegatedZoneName, props.zone),
type: 'NS',
ttl: ttl.toString(),
comment: props.comment,
resourceRecords: props.nameServers.map(ns => ns.endsWith('.') ? ns : `${ns}.`)
resourceRecords,
});
}
}
25 changes: 24 additions & 1 deletion packages/@aws-cdk/aws-route53/test/integ.route53.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,29 @@
"Name": "cdk.test."
}
},
"PublicZonecdktestsubcdktest83558650": {
"Type": "AWS::Route53::RecordSet",
"Properties": {
"Name": "sub.cdk.test.",
"Type": "NS",
"HostedZoneId": {
"Ref": "PublicZone2E1C4E34"
},
"ResourceRecords": {
"Fn::GetAtt": [
"PublicSubZoneDBD26A0A",
"NameServers"
]
},
"TTL": "172800"
}
},
"PublicSubZoneDBD26A0A": {
"Type": "AWS::Route53::HostedZone",
"Properties": {
"Name": "sub.cdk.test."
}
},
"CNAMEC70A2D52": {
"Type": "AWS::Route53::RecordSet",
"Properties": {
Expand Down Expand Up @@ -559,4 +582,4 @@
}
}
}
}
}
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-route53/test/integ.route53.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const privateZone = new PrivateHostedZone(stack, 'PrivateZone', {
const publicZone = new PublicHostedZone(stack, 'PublicZone', {
zoneName: 'cdk.test'
});
const publicSubZone = new PublicHostedZone(stack, 'PublicSubZone', {
zoneName: 'sub.cdk.test'
});
publicZone.addDelegation(publicSubZone);

new TxtRecord(privateZone, 'TXT', {
zone: privateZone,
Expand Down
22 changes: 21 additions & 1 deletion packages/@aws-cdk/aws-route53/test/test.route53.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,27 @@ export = {
// THEN
test.throws(() => zone.addVpc(vpc), /Cannot associate public hosted zones with a VPC/);
test.done();
}
},

'setting up zone delegation'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const zone = new PublicHostedZone(stack, 'TopZone', { zoneName: 'top.test' });
const delegate = new PublicHostedZone(stack, 'SubZone', { zoneName: 'sub.top.test' });

// WHEN
zone.addDelegation(delegate, { ttl: 1337 });

// THEN
expect(stack).to(haveResource('AWS::Route53::RecordSet', {
Type: 'NS',
Name: 'sub.top.test.',
HostedZoneId: zone.node.resolve(zone.hostedZoneId),
ResourceRecords: zone.node.resolve(delegate.hostedZoneNameServers),
TTL: '1337',
}));
test.done();
},
};

class TestApp {
Expand Down

0 comments on commit f974531

Please sign in to comment.