Skip to content

Commit d24305c

Browse files
authored
feat(route53): add HostedZoneGrants (#36109)
Create a new class, `HostedZoneGrants`, that has all the grants methods, and delegate the `grantDelegation()` method in all implementations of `IHostedZone` to it. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 439495f commit d24305c

File tree

6 files changed

+161
-15
lines changed

6 files changed

+161
-15
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { GrantDelegationOptions, INamedHostedZoneRef } from './hosted-zone-ref';
2+
import { makeGrantDelegation } from './util';
3+
import { IGrantable } from '../../aws-iam';
4+
import { Grant } from '../../aws-iam/lib/grant';
5+
6+
/**
7+
* Collection of grant methods for a INamedHostedZoneRef
8+
*/
9+
export class HostedZoneGrants {
10+
/**
11+
* Creates grants for INamedHostedZoneRef
12+
*
13+
*/
14+
public static fromHostedZone(hostedZone: INamedHostedZoneRef): HostedZoneGrants {
15+
return new HostedZoneGrants(hostedZone);
16+
}
17+
18+
private constructor(private readonly hostedZone: INamedHostedZoneRef) {
19+
}
20+
21+
/**
22+
* Grant permissions to add delegation records to this zone
23+
*/
24+
public delegation(grantee: IGrantable, delegationOptions?: GrantDelegationOptions): Grant {
25+
return makeGrantDelegation(grantee, this.hostedZone, delegationOptions);
26+
}
27+
}

packages/aws-cdk-lib/aws-route53/lib/hosted-zone-ref.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import * as iam from '../../aws-iam';
22
import { IResource } from '../../core';
3+
import { IHostedZoneRef } from '../../interfaces/generated/aws-route53-interfaces.generated';
4+
5+
export interface INamedHostedZoneRef extends IHostedZoneRef {
6+
readonly name: string;
7+
}
38

49
/**
510
* Imported or created hosted zone
611
*/
7-
export interface IHostedZone extends IResource {
12+
export interface IHostedZone extends IResource, INamedHostedZoneRef {
813
/**
914
* ID of this hosted zone, such as "Z23ABC4XYZL05B"
1015
*

packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Construct } from 'constructs';
2+
import { HostedZoneGrants } from './hosted-zone-grants';
23
import { HostedZoneProviderProps } from './hosted-zone-provider';
34
import { GrantDelegationOptions, HostedZoneAttributes, IHostedZone, PublicHostedZoneAttributes, PrivateHostedZoneAttributes } from './hosted-zone-ref';
45
import { IKeySigningKey, KeySigningKey } from './key-signing-key';
56
import { CaaAmazonRecord, ZoneDelegationRecord } from './record-set';
6-
import { CfnHostedZone, CfnDNSSEC, CfnKeySigningKey } from './route53.generated';
7-
import { makeGrantDelegation, makeHostedZoneArn, validateZoneName } from './util';
7+
import { CfnHostedZone, CfnDNSSEC, CfnKeySigningKey, HostedZoneReference } from './route53.generated';
8+
import { makeHostedZoneArn, validateZoneName } from './util';
89
import * as ec2 from '../../aws-ec2';
910
import * as iam from '../../aws-iam';
1011
import * as kms from '../../aws-kms';
@@ -98,6 +99,13 @@ export class HostedZone extends Resource implements IHostedZone {
9899
return makeHostedZoneArn(this, this.hostedZoneId);
99100
}
100101

102+
/**
103+
* FQDN of this hosted zone
104+
*/
105+
public get name(): string {
106+
return this.zoneName;
107+
}
108+
101109
/**
102110
* Import a Route 53 hosted zone defined either outside the CDK, or in a different CDK stack
103111
*
@@ -111,14 +119,20 @@ export class HostedZone extends Resource implements IHostedZone {
111119
public static fromHostedZoneId(scope: Construct, id: string, hostedZoneId: string): IHostedZone {
112120
class Import extends Resource implements IHostedZone {
113121
public readonly hostedZoneId = hostedZoneId;
122+
public get name(): string { return this.zoneName; }
114123
public get zoneName(): string {
115124
throw new ValidationError('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);
116125
}
117126
public get hostedZoneArn(): string {
118127
return makeHostedZoneArn(this, this.hostedZoneId);
119128
}
120129
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
121-
return makeGrantDelegation(grantee, this, options);
130+
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
131+
}
132+
public get hostedZoneRef(): HostedZoneReference {
133+
return {
134+
hostedZoneId: this.hostedZoneId,
135+
};
122136
}
123137
}
124138

@@ -138,11 +152,17 @@ export class HostedZone extends Resource implements IHostedZone {
138152
class Import extends Resource implements IHostedZone {
139153
public readonly hostedZoneId = attrs.hostedZoneId;
140154
public readonly zoneName = attrs.zoneName;
155+
public readonly name = attrs.zoneName;
141156
public get hostedZoneArn(): string {
142157
return makeHostedZoneArn(this, this.hostedZoneId);
143158
}
144159
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
145-
return makeGrantDelegation(grantee, this, options);
160+
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
161+
}
162+
public get hostedZoneRef(): HostedZoneReference {
163+
return {
164+
hostedZoneId: this.hostedZoneId,
165+
};
146166
}
147167
}
148168

@@ -204,6 +224,11 @@ export class HostedZone extends Resource implements IHostedZone {
204224
*/
205225
private keySigningKey?: IKeySigningKey;
206226

227+
/**
228+
* Grants helper for this hosted zone
229+
*/
230+
public readonly grants = HostedZoneGrants.fromHostedZone(this);
231+
207232
constructor(scope: Construct, id: string, props: HostedZoneProps) {
208233
super(scope, id);
209234
// Enhanced CDK Analytics Telemetry
@@ -230,6 +255,12 @@ export class HostedZone extends Resource implements IHostedZone {
230255
}
231256
}
232257

258+
public get hostedZoneRef(): HostedZoneReference {
259+
return {
260+
hostedZoneId: this.hostedZoneId,
261+
};
262+
}
263+
233264
/**
234265
* Add another VPC to this private hosted zone.
235266
*
@@ -242,7 +273,7 @@ export class HostedZone extends Resource implements IHostedZone {
242273

243274
@MethodMetadata()
244275
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
245-
return makeGrantDelegation(grantee, this, options);
276+
return this.grants.delegation(grantee, options);
246277
}
247278

248279
/**
@@ -341,12 +372,18 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
341372
public static fromPublicHostedZoneId(scope: Construct, id: string, publicHostedZoneId: string): IPublicHostedZone {
342373
class Import extends Resource implements IPublicHostedZone {
343374
public readonly hostedZoneId = publicHostedZoneId;
375+
public get name(): string { return this.zoneName; }
344376
public get zoneName(): string { throw new ValidationError('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); }
345377
public get hostedZoneArn(): string {
346378
return makeHostedZoneArn(this, this.hostedZoneId);
347379
}
348380
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
349-
return makeGrantDelegation(grantee, this, options);
381+
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
382+
}
383+
public get hostedZoneRef(): HostedZoneReference {
384+
return {
385+
hostedZoneId: this.hostedZoneId,
386+
};
350387
}
351388
}
352389
return new Import(scope, id);
@@ -365,11 +402,17 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
365402
class Import extends Resource implements IPublicHostedZone {
366403
public readonly hostedZoneId = attrs.hostedZoneId;
367404
public readonly zoneName = attrs.zoneName;
405+
public readonly name = attrs.zoneName;
368406
public get hostedZoneArn(): string {
369407
return makeHostedZoneArn(this, this.hostedZoneId);
370408
}
371409
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
372-
return makeGrantDelegation(grantee, this, options);
410+
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
411+
}
412+
public get hostedZoneRef(): HostedZoneReference {
413+
return {
414+
hostedZoneId: this.zoneName,
415+
};
373416
}
374417
}
375418
return new Import(scope, id);
@@ -509,12 +552,18 @@ export class PrivateHostedZone extends HostedZone implements IPrivateHostedZone
509552
public static fromPrivateHostedZoneId(scope: Construct, id: string, privateHostedZoneId: string): IPrivateHostedZone {
510553
class Import extends Resource implements IPrivateHostedZone {
511554
public readonly hostedZoneId = privateHostedZoneId;
555+
public get name(): string { return this.zoneName; }
512556
public get zoneName(): string { throw new ValidationError('Cannot reference `zoneName` when using `PrivateHostedZone.fromPrivateHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`', this); }
513557
public get hostedZoneArn(): string {
514558
return makeHostedZoneArn(this, this.hostedZoneId);
515559
}
516560
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
517-
return makeGrantDelegation(grantee, this, options);
561+
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
562+
}
563+
public get hostedZoneRef(): HostedZoneReference {
564+
return {
565+
hostedZoneId: this.hostedZoneId,
566+
};
518567
}
519568
}
520569
return new Import(scope, id);
@@ -533,11 +582,17 @@ export class PrivateHostedZone extends HostedZone implements IPrivateHostedZone
533582
class Import extends Resource implements IPrivateHostedZone {
534583
public readonly hostedZoneId = attrs.hostedZoneId;
535584
public readonly zoneName = attrs.zoneName;
585+
public get name(): string { return this.zoneName; }
536586
public get hostedZoneArn(): string {
537587
return makeHostedZoneArn(this, this.hostedZoneId);
538588
}
539589
public grantDelegation(grantee: iam.IGrantable, options?: GrantDelegationOptions): iam.Grant {
540-
return makeGrantDelegation(grantee, this, options);
590+
return HostedZoneGrants.fromHostedZone(this).delegation(grantee, options);
591+
}
592+
public get hostedZoneRef(): HostedZoneReference {
593+
return {
594+
hostedZoneId: this.hostedZoneId,
595+
};
541596
}
542597
}
543598
return new Import(scope, id);

packages/aws-cdk-lib/aws-route53/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './alias-record-target';
22
export * from './hosted-zone';
3+
export * from './hosted-zone-grants';
34
export * from './hosted-zone-provider';
45
export * from './hosted-zone-ref';
56
export * from './key-signing-key';

packages/aws-cdk-lib/aws-route53/lib/util.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Construct } from 'constructs';
2-
import { GrantDelegationOptions, IHostedZone } from './hosted-zone-ref';
2+
import { GrantDelegationOptions, IHostedZone, INamedHostedZoneRef } from './hosted-zone-ref';
33
import * as iam from '../../aws-iam';
44
import { Stack, Token, UnscopedValidationError } from '../../core';
55

@@ -137,15 +137,15 @@ function validateDelegatedZoneName(parentZoneName: string, delegatedZoneName: st
137137
}
138138
}
139139

140-
export function makeGrantDelegation(grantee: iam.IGrantable, hostedZone: IHostedZone, delegationOptions?: GrantDelegationOptions): iam.Grant {
140+
export function makeGrantDelegation(grantee: iam.IGrantable, hostedZone: INamedHostedZoneRef, delegationOptions?: GrantDelegationOptions): iam.Grant {
141141
const delegatedZoneNames = delegationOptions?.delegatedZoneNames?.map(delegatedZoneName => {
142-
validateDelegatedZoneName(hostedZone.zoneName, delegatedZoneName);
142+
validateDelegatedZoneName(hostedZone.name, delegatedZoneName);
143143
return octalEncodeDelegatedZoneName(delegatedZoneName);
144144
});
145145
const g1 = iam.Grant.addToPrincipal({
146146
grantee,
147147
actions: ['route53:ChangeResourceRecordSets'],
148-
resourceArns: [hostedZone.hostedZoneArn],
148+
resourceArns: [makeHostedZoneArn(hostedZone, hostedZone.hostedZoneRef.hostedZoneId)],
149149
conditions: {
150150
'ForAllValues:StringEquals': {
151151
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],

packages/aws-cdk-lib/aws-route53/test/hosted-zone.test.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as ec2 from '../../aws-ec2';
44
import * as iam from '../../aws-iam';
55
import * as kms from '../../aws-kms';
66
import * as cdk from '../../core';
7-
import { HostedZone, PrivateHostedZone, PublicHostedZone, ZoneSigningOptions } from '../lib';
7+
import { CfnHostedZone, HostedZone, PrivateHostedZone, PublicHostedZone } from '../lib';
8+
import { HostedZoneGrants } from '../lib/hosted-zone-grants';
89

910
describe('hosted zone', () => {
1011
describe('Hosted Zone', () => {
@@ -289,6 +290,63 @@ test('grantDelegation', () => {
289290
});
290291
});
291292

293+
test('grantDelegation on L1s', () => {
294+
// GIVEN
295+
const stack = new cdk.Stack(undefined, 'TestStack', {
296+
env: { account: '123456789012', region: 'us-east-1' },
297+
});
298+
299+
const role = new iam.Role(stack, 'Role', {
300+
assumedBy: new iam.AccountPrincipal('22222222222222'),
301+
});
302+
303+
const zone = new CfnHostedZone(stack, 'Zone', {
304+
name: 'banana.com',
305+
});
306+
307+
// WHEN
308+
HostedZoneGrants.fromHostedZone(zone).delegation(role);
309+
310+
// THEN
311+
const template = Template.fromStack(stack);
312+
template.hasResourceProperties('AWS::IAM::Policy', {
313+
PolicyDocument: {
314+
Statement: [
315+
{
316+
Action: 'route53:ChangeResourceRecordSets',
317+
Effect: 'Allow',
318+
Resource: {
319+
'Fn::Join': [
320+
'',
321+
[
322+
'arn:',
323+
{
324+
Ref: 'AWS::Partition',
325+
},
326+
':route53:::hostedzone/',
327+
{
328+
Ref: 'Zone',
329+
},
330+
],
331+
],
332+
},
333+
Condition: {
334+
'ForAllValues:StringEquals': {
335+
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
336+
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
337+
},
338+
},
339+
},
340+
{
341+
Action: 'route53:ListHostedZonesByName',
342+
Effect: 'Allow',
343+
Resource: '*',
344+
},
345+
],
346+
},
347+
});
348+
});
349+
292350
test('grantDelegation on imported zones', () => {
293351
// GIVEN
294352
const stack = new cdk.Stack(undefined, 'TestStack', {

0 commit comments

Comments
 (0)