-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Expand file tree
/
Copy pathhosted-zone.ts
More file actions
614 lines (557 loc) · 22 KB
/
hosted-zone.ts
File metadata and controls
614 lines (557 loc) · 22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
import type { Construct } from 'constructs';
import { HostedZoneGrants } from './hosted-zone-grants';
import type { HostedZoneProviderProps } from './hosted-zone-provider';
import type { GrantDelegationOptions, HostedZoneAttributes, IHostedZone, PublicHostedZoneAttributes, PrivateHostedZoneAttributes } from './hosted-zone-ref';
import type { IKeySigningKey } from './key-signing-key';
import { KeySigningKey } from './key-signing-key';
import { CaaAmazonRecord, ZoneDelegationRecord } from './record-set';
import type { CfnKeySigningKey, HostedZoneReference } from './route53.generated';
import { CfnHostedZone, CfnDNSSEC } from './route53.generated';
import { makeHostedZoneArn, validateZoneName } from './util';
import type * as ec2 from '../../aws-ec2';
import * as iam from '../../aws-iam';
import type * as kms from '../../aws-kms';
import * as cxschema from '../../cloud-assembly-schema';
import type { Duration } from '../../core';
import { ContextProvider, Lazy, Resource, Stack } from '../../core';
import { ValidationError } from '../../core/lib/errors';
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
import { propertyInjectable } from '../../core/lib/prop-injectable';
/**
* Common properties to create a Route 53 hosted zone
*/
export interface CommonHostedZoneProps {
/**
* The name of the domain. For resource record types that include a domain
* name, specify a fully qualified domain name.
*/
readonly zoneName: string;
/**
* Whether to add a trailing dot to the zone name.
*
* @default true
*/
readonly addTrailingDot?: boolean;
/**
* Any comments that you want to include about the hosted zone.
*
* @default none
*/
readonly comment?: string;
/**
* The Amazon Resource Name (ARN) for the log group that you want Amazon Route 53 to send query logs to.
*
* @default disabled
*/
readonly queryLogsLogGroupArn?: string;
}
/**
* Properties of a new hosted zone
*/
export interface HostedZoneProps extends CommonHostedZoneProps {
/**
* A VPC that you want to associate with this hosted zone. When you specify
* this property, a private hosted zone will be created.
*
* You can associate additional VPCs to this private zone using `addVpc(vpc)`.
*
* @default public (no VPCs associated)
*/
readonly vpcs?: ec2.IVpc[];
}
/**
* Options for enabling key signing from a hosted zone.
*/
export interface ZoneSigningOptions {
/**
* The customer-managed KMS key that will be used to sign the records.
*
* The KMS Key must be unique for each KSK within a hosted zone. Additionally, the
* KMS key must be an asymetric customer-managed key using the ECC_NIST_P256 algorithm.
*
* @see https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-configuring-dnssec-cmk-requirements.html
*/
readonly kmsKey: kms.IKey;
/**
* The name for the key signing key.
*
* This name must be unique within a hosted zone.
*
* @default an autogenerated name
*/
readonly keySigningKeyName?: string;
}
/**
* Container for records, and records contain information about how to route traffic for a
* specific domain, such as example.com and its subdomains (acme.example.com, zenith.example.com)
*/
@propertyInjectable
export class HostedZone extends Resource implements IHostedZone {
/** Uniquely identifies this class. */
public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-route53.HostedZone';
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
/**
* FQDN of this hosted zone
*/
public get name(): string {
return this.zoneName;
}
/**
* Import a Route 53 hosted zone defined either outside the CDK, or in a different CDK stack
*
* Use when hosted zone ID is known. If a HostedZone is imported with this method the zoneName cannot be referenced.
* If the zoneName is needed then the HostedZone should be imported with `fromHostedZoneAttributes()` or `fromLookup()`
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param hostedZoneId the ID of the hosted zone to import
*/
public static fromHostedZoneId(scope: Construct, id: string, hostedZoneId: string): IHostedZone {
class Import extends Resource implements IHostedZone {
public readonly hostedZoneId = hostedZoneId;
public get name(): string { return this.zoneName; }
public get zoneName(): string {
throw new ValidationError('CannotReferenceZoneNameHosted', 'Cannot reference `zoneName` when using `HostedZone.fromHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`. If this is the case, use `fromHostedZoneAttributes()` or `fromLookup()` instead.', this);
}
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.hostedZoneId,
};
}
}
return new Import(scope, id);
}
/**
* Imports a hosted zone from another stack.
*
* Use when both hosted zone ID and hosted zone name are known.
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param attrs the HostedZoneAttributes (hosted zone ID and hosted zone name)
*/
public static fromHostedZoneAttributes(scope: Construct, id: string, attrs: HostedZoneAttributes): IHostedZone {
class Import extends Resource implements IHostedZone {
public readonly hostedZoneId = attrs.hostedZoneId;
public readonly zoneName = attrs.zoneName;
public readonly name = attrs.zoneName;
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.hostedZoneId,
};
}
}
return new Import(scope, id);
}
/**
* Lookup a hosted zone in the current account/region based on query parameters.
* Requires environment, you must specify env for the stack.
*
* Use to easily query hosted zones.
*
* @see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
*/
public static fromLookup(scope: Construct, id: string, query: HostedZoneProviderProps): IHostedZone {
if (!query.domainName) {
throw new ValidationError('CannotUndefinedValueAttributeDomain', 'Cannot use undefined value for attribute `domainName`', scope);
}
const DEFAULT_HOSTED_ZONE: HostedZoneContextResponse = {
Id: 'DUMMY',
Name: query.domainName,
};
interface HostedZoneContextResponse {
Id: string;
Name: string;
}
const response: HostedZoneContextResponse = ContextProvider.getValue(scope, {
provider: cxschema.ContextProvider.HOSTED_ZONE_PROVIDER,
dummyValue: DEFAULT_HOSTED_ZONE,
props: query,
}).value;
// CDK handles the '.' at the end, so remove it here
if (response.Name.endsWith('.')) {
response.Name = response.Name.substring(0, response.Name.length - 1);
}
response.Id = response.Id.replace('/hostedzone/', '');
return HostedZone.fromHostedZoneAttributes(scope, id, {
hostedZoneId: response.Id,
zoneName: response.Name,
});
}
public readonly hostedZoneId: string;
public readonly zoneName: string;
public readonly hostedZoneNameServers?: string[];
/**
* VPCs to which this hosted zone will be added
*/
protected readonly vpcs = new Array<CfnHostedZone.VPCProperty>();
/**
* The key signing key used to sign the hosted zone.
*/
private keySigningKey?: IKeySigningKey;
/**
* Grants helper for this hosted zone
*/
public readonly grants = HostedZoneGrants.fromHostedZone(this);
constructor(scope: Construct, id: string, props: HostedZoneProps) {
super(scope, id);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
validateZoneName(props.zoneName);
// Add a dot at the end if the addTrailingDot property is not false.
const zoneName = (props.addTrailingDot === false || props.zoneName.endsWith('.')) ? props.zoneName : `${props.zoneName}.`;
const resource = new CfnHostedZone(this, 'Resource', {
name: zoneName,
hostedZoneConfig: props.comment ? { comment: props.comment } : undefined,
queryLoggingConfig: props.queryLogsLogGroupArn ? { cloudWatchLogsLogGroupArn: props.queryLogsLogGroupArn } : undefined,
vpcs: Lazy.any({ produce: () => this.vpcs.length === 0 ? undefined : this.vpcs }),
});
this.hostedZoneId = resource.ref;
this.hostedZoneNameServers = resource.attrNameServers;
this.zoneName = props.zoneName;
for (const vpc of props.vpcs || []) {
this.addVpc(vpc);
}
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.hostedZoneId,
};
}
/**
* Add another VPC to this private hosted zone.
*
* @param vpc the other VPC to add.
*/
@MethodMetadata()
public addVpc(vpc: ec2.IVpc) {
this.vpcs.push({ vpcId: vpc.vpcId, vpcRegion: vpc.env.region ?? Stack.of(vpc).region });
}
/**
* [disable-awslint:no-grants]
*/
@MethodMetadata()
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return this.grants.delegation(grantee, options);
}
/**
* Enable DNSSEC for this hosted zone.
*
* This will create a key signing key with the given options and enable DNSSEC signing
* for the hosted zone.
*/
@MethodMetadata()
public enableDnssec(options: ZoneSigningOptions): IKeySigningKey {
if (this.keySigningKey) {
throw new ValidationError('AlreadyEnabledHostedZone', 'DNSSEC is already enabled for this hosted zone', this);
}
this.keySigningKey = new KeySigningKey(this, 'KeySigningKey', {
hostedZone: this,
keySigningKeyName: options.keySigningKeyName,
kmsKey: options.kmsKey,
});
const dnssec = new CfnDNSSEC(this, 'DNSSEC', {
hostedZoneId: this.hostedZoneId,
});
// The KSK must exist and be in an 'ACTIVE' status before DNSSEC can be enabled.
dnssec.addDependency(this.keySigningKey.node.defaultChild as CfnKeySigningKey);
return this.keySigningKey;
}
}
/**
* Construction properties for a PublicHostedZone.
*/
export interface PublicHostedZoneProps extends CommonHostedZoneProps {
/**
* Whether to create a CAA record to restrict certificate authorities allowed
* to issue certificates for this domain to Amazon only.
*
* @default false
*/
readonly caaAmazon?: boolean;
/**
* A principal which is trusted to assume a role for zone delegation
*
* If supplied, this will create a Role in the same account as the Hosted
* Zone, which can be assumed by the `CrossAccountZoneDelegationRecord` to
* create a delegation record to a zone in a different account.
*
* Be sure to indicate the account(s) that you trust to create delegation
* records, using either `iam.AccountPrincipal` or `iam.OrganizationPrincipal`.
*
* If you are planning to use `iam.ServicePrincipal`s here, be sure to include
* region-specific service principals for every opt-in region you are going to
* be delegating to; or don't use this feature and create separate roles
* with appropriate permissions for every opt-in region instead.
*
* @default - No delegation configuration
* @deprecated Create the Role yourself and call `hostedZone.grantDelegation()`.
*/
readonly crossAccountZoneDelegationPrincipal?: iam.IPrincipal;
/**
* The name of the role created for cross account delegation
*
* @default - A role name is generated automatically
* @deprecated Create the Role yourself and call `hostedZone.grantDelegation()`.
*/
readonly crossAccountZoneDelegationRoleName?: string;
}
/**
* Represents a Route 53 public hosted zone
*/
export interface IPublicHostedZone extends IHostedZone {}
/**
* Create a Route53 public hosted zone.
*
* @resource AWS::Route53::HostedZone
*/
@propertyInjectable
export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
/**
* Uniquely identifies this class.
*/
public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-route53.PublicHostedZone';
/**
* Import a Route 53 public hosted zone defined either outside the CDK, or in a different CDK stack
*
* Use when hosted zone ID is known. If a PublicHostedZone is imported with this method the zoneName cannot be referenced.
* If the zoneName is needed then the PublicHostedZone should be imported with `fromPublicHostedZoneAttributes()`.
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param publicHostedZoneId the ID of the public hosted zone to import
*/
public static fromPublicHostedZoneId(scope: Construct, id: string, publicHostedZoneId: string): IPublicHostedZone {
class Import extends Resource implements IPublicHostedZone {
public readonly hostedZoneId = publicHostedZoneId;
public get name(): string { return this.zoneName; }
public get zoneName(): string { throw new ValidationError('CannotReferenceZoneNamePublic', 'Cannot reference `zoneName` when using `PublicHostedZone.fromPublicHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`. If this is the case, use `fromPublicHostedZoneAttributes()` instead', this); }
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.hostedZoneId,
};
}
}
return new Import(scope, id);
}
/**
* Imports a public hosted zone from another stack.
*
* Use when both hosted zone ID and hosted zone name are known.
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param attrs the PublicHostedZoneAttributes (hosted zone ID and hosted zone name)
*/
public static fromPublicHostedZoneAttributes(scope: Construct, id: string, attrs: PublicHostedZoneAttributes): IPublicHostedZone {
class Import extends Resource implements IPublicHostedZone {
public readonly hostedZoneId = attrs.hostedZoneId;
public readonly zoneName = attrs.zoneName;
public readonly name = attrs.zoneName;
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.zoneName,
};
}
}
return new Import(scope, id);
}
/**
* Role for cross account zone delegation
*/
public readonly crossAccountZoneDelegationRole?: iam.Role;
constructor(scope: Construct, id: string, props: PublicHostedZoneProps) {
super(scope, id, props);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
if (props.caaAmazon) {
new CaaAmazonRecord(this, 'CaaAmazon', {
zone: this,
});
}
if (!props.crossAccountZoneDelegationPrincipal && props.crossAccountZoneDelegationRoleName) {
throw Error('crossAccountZoneDelegationRoleName property is not supported without crossAccountZoneDelegationPrincipal');
}
if (props.crossAccountZoneDelegationPrincipal) {
this.crossAccountZoneDelegationRole = new iam.Role(this, 'CrossAccountZoneDelegationRole', {
roleName: props.crossAccountZoneDelegationRoleName,
assumedBy: props.crossAccountZoneDelegationPrincipal,
inlinePolicies: {
delegation: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ['route53:ChangeResourceRecordSets'],
resources: [this.hostedZoneArn],
conditions: {
'ForAllValues:StringEquals': {
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
},
},
}),
new iam.PolicyStatement({
actions: ['route53:ListHostedZonesByName'],
resources: ['*'],
}),
],
}),
},
});
}
}
@MethodMetadata()
public addVpc(_vpc: ec2.IVpc) {
throw new ValidationError('CannotAssociatePublicHostedZones', 'Cannot associate public hosted zones with a VPC', this);
}
/**
* 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.
*/
@MethodMetadata()
public addDelegation(delegate: IPublicHostedZone, opts: ZoneDelegationOptions = {}): void {
new ZoneDelegationRecord(this, `${this.zoneName} -> ${delegate.zoneName}`, {
zone: this,
recordName: 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
*/
readonly comment?: string;
/**
* The TTL (Time To Live) of the DNS delegation record in DNS caches.
*
* @default 172800
*/
readonly ttl?: Duration;
}
/**
* Properties to create a Route 53 private hosted zone
*/
export interface PrivateHostedZoneProps extends CommonHostedZoneProps {
/**
* A VPC that you want to associate with this hosted zone.
*
* Private hosted zones must be associated with at least one VPC. You can
* associated additional VPCs using `addVpc(vpc)`.
*/
readonly vpc: ec2.IVpc;
}
/**
* Represents a Route 53 private hosted zone
*/
export interface IPrivateHostedZone extends IHostedZone {}
/**
* Create a Route53 private hosted zone for use in one or more VPCs.
*
* Note that `enableDnsHostnames` and `enableDnsSupport` must have been enabled
* for the VPC you're configuring for private hosted zones.
*
* @resource AWS::Route53::HostedZone
*/
@propertyInjectable
export class PrivateHostedZone extends HostedZone implements IPrivateHostedZone {
/** Uniquely identifies this class. */
public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-route53.PrivateHostedZone';
/**
* Import a Route 53 private hosted zone defined either outside the CDK, or in a different CDK stack
*
* Use when hosted zone ID is known. If a HostedZone is imported with this method the zoneName cannot be referenced.
* If the zoneName is needed then you cannot import a PrivateHostedZone.
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param privateHostedZoneId the ID of the private hosted zone to import
*/
public static fromPrivateHostedZoneId(scope: Construct, id: string, privateHostedZoneId: string): IPrivateHostedZone {
class Import extends Resource implements IPrivateHostedZone {
public readonly hostedZoneId = privateHostedZoneId;
public get name(): string { return this.zoneName; }
public get zoneName(): string { throw new ValidationError('CannotReferenceZoneNamePrivate', 'Cannot reference `zoneName` when using `PrivateHostedZone.fromPrivateHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`', this); }
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.hostedZoneId,
};
}
}
return new Import(scope, id);
}
/**
* Imports a private hosted zone from another stack.
*
* Use when both hosted zone ID and hosted zone name are known.
*
* @param scope the parent Construct for this Construct
* @param id the logical name of this Construct
* @param attrs the PrivateHostedZoneAttributes (hosted zone ID and hosted zone name)
*/
public static fromPrivateHostedZoneAttributes(scope: Construct, id: string, attrs: PrivateHostedZoneAttributes): IPrivateHostedZone {
class Import extends Resource implements IPrivateHostedZone {
public readonly hostedZoneId = attrs.hostedZoneId;
public readonly zoneName = attrs.zoneName;
public get name(): string { return this.zoneName; }
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
}
public get hostedZoneRef(): HostedZoneReference {
return {
hostedZoneId: this.hostedZoneId,
};
}
}
return new Import(scope, id);
}
constructor(scope: Construct, id: string, props: PrivateHostedZoneProps) {
super(scope, id, props);
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
this.addVpc(props.vpc);
}
}