Skip to content

Commit b5bd39e

Browse files
authored
fix(route53): IHostedZone cannot be used for ses.Identity.publicHostedZone anymore (#26888)
`Identity.publicHostedZone` takes an `IPublicHostedZone`, but because of TypeScript structural typing it would also accept an `IHostedZone`. When in [this PR](#26333) the `grantDelegation` method was added to the `IPublicHostedZone` interface, this passing was no longer allowed and code that used to work on accident, no longer works. For example: ``` const zone = HostedZone.fromHostedZoneId(stack, 'Zone', 'hosted-id'); const sesIdentity = ses.Identity.publicHostedZone(zone); ``` It raises an error because the imported `zone` does not implement the `grantDelegation` method. This fix moves the `grantDelegation` method declaration into the `IHostedZone` interface and makes it available to all imported zones. Closes #26872. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent c67da83 commit b5bd39e

File tree

6 files changed

+178
-31
lines changed

6 files changed

+178
-31
lines changed

Diff for: packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.cross-account-zone-delegation.js.snapshot/aws-cdk-route53-cross-account-integ.template.json

+38-12
Original file line numberDiff line numberDiff line change
@@ -354,18 +354,44 @@
354354
}
355355
},
356356
"Effect": "Allow",
357-
"Resource": {
358-
"Fn::Join": [
359-
"",
360-
[
361-
"arn:",
362-
{
363-
"Ref": "AWS::Partition"
364-
},
365-
":route53:::hostedzone/imported-public-zone-id"
366-
]
367-
]
368-
}
357+
"Resource": [
358+
{
359+
"Fn::Join": [
360+
"",
361+
[
362+
"arn:",
363+
{
364+
"Ref": "AWS::Partition"
365+
},
366+
":route53:::hostedzone/imported-private-zone-id"
367+
]
368+
]
369+
},
370+
{
371+
"Fn::Join": [
372+
"",
373+
[
374+
"arn:",
375+
{
376+
"Ref": "AWS::Partition"
377+
},
378+
":route53:::hostedzone/imported-public-zone-id"
379+
]
380+
]
381+
},
382+
{
383+
"Fn::Join": [
384+
"",
385+
[
386+
"arn:",
387+
{
388+
"Ref": "AWS::Partition"
389+
},
390+
":route53:::hostedzone/imported-zone-id"
391+
]
392+
]
393+
}
394+
]
369395
},
370396
{
371397
"Action": "route53:ListHostedZonesByName",

Diff for: packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.cross-account-zone-delegation.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as iam from 'aws-cdk-lib/aws-iam';
22
import * as cdk from 'aws-cdk-lib';
3-
import { PublicHostedZone, CrossAccountZoneDelegationRecord } from 'aws-cdk-lib/aws-route53';
3+
import { PublicHostedZone, CrossAccountZoneDelegationRecord, PrivateHostedZone, HostedZone } from 'aws-cdk-lib/aws-route53';
44
import { IntegTest } from '@aws-cdk/integ-tests-alpha';
55

66
const app = new cdk.App();
@@ -36,9 +36,15 @@ const role = new iam.Role(stack, 'Role', {
3636
assumedBy: new iam.AccountRootPrincipal(),
3737
});
3838

39+
const importedZone = HostedZone.fromHostedZoneId(stack, 'ImportedZone', 'imported-zone-id');
40+
importedZone.grantDelegation(role);
41+
3942
const importedPublicZone = PublicHostedZone.fromPublicHostedZoneId(stack, 'ImportedPublicZone', 'imported-public-zone-id');
4043
importedPublicZone.grantDelegation(role);
4144

45+
const importedPrivateZone = PrivateHostedZone.fromPrivateHostedZoneId(stack, 'ImportedPrivateZone', 'imported-private-zone-id');
46+
importedPrivateZone.grantDelegation(role);
47+
4248
new IntegTest(app, 'Route53CrossAccountInteg', {
4349
testCases: [stack],
4450
diffAssets: true,

Diff for: packages/aws-cdk-lib/aws-route53/README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ const zoneFromAttributes = route53.PublicHostedZone.fromPublicHostedZoneAttribut
289289
const zoneFromId = route53.PublicHostedZone.fromPublicHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ');
290290
```
291291

292-
You can use `CrossAccountZoneDelegationRecord` on imported Public Hosted Zones with the `grantDelegation` method:
292+
You can use `CrossAccountZoneDelegationRecord` on imported Hosted Zones with the `grantDelegation` method:
293293

294294
```ts
295295
const crossAccountRole = new iam.Role(this, 'CrossAccountRole', {
@@ -299,8 +299,14 @@ const crossAccountRole = new iam.Role(this, 'CrossAccountRole', {
299299
assumedBy: new iam.AccountPrincipal('12345678901'),
300300
});
301301

302-
const zoneFromId = route53.PublicHostedZone.fromPublicHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ');
302+
const zoneFromId = route53.HostedZone.fromHostedZoneId(this, 'MyZone', 'zone-id');
303303
zoneFromId.grantDelegation(crossAccountRole);
304+
305+
const publicZoneFromId = route53.PublicHostedZone.fromPublicHostedZoneId(this, 'MyPublicZone', 'public-zone-id');
306+
publicZoneFromId.grantDelegation(crossAccountRole);
307+
308+
const privateZoneFromId = route53.PrivateHostedZone.fromPrivateHostedZoneId(this, 'MyPrivateZone', 'private-zone-id');
309+
privateZoneFromId.grantDelegation(crossAccountRole);
304310
```
305311

306312
## VPC Endpoint Service Private DNS

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

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as iam from '../../aws-iam';
12
import { IResource } from '../../core';
23

34
/**
@@ -32,6 +33,11 @@ export interface IHostedZone extends IResource {
3233
* @attribute
3334
*/
3435
readonly hostedZoneNameServers?: string[];
36+
37+
/**
38+
* Grant permissions to add delegation records to this zone
39+
*/
40+
grantDelegation(grantee: iam.IGrantable): iam.Grant;
3541
}
3642

3743
/**

Diff for: packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts

+16-12
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ export class HostedZone extends Resource implements IHostedZone {
8484
public get hostedZoneArn(): string {
8585
return makeHostedZoneArn(this, this.hostedZoneId);
8686
}
87+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
88+
return makeGrantDelegation(grantee, this.hostedZoneArn);
89+
}
8790
}
8891

8992
return new Import(scope, id);
@@ -105,6 +108,9 @@ export class HostedZone extends Resource implements IHostedZone {
105108
public get hostedZoneArn(): string {
106109
return makeHostedZoneArn(this, this.hostedZoneId);
107110
}
111+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
112+
return makeGrantDelegation(grantee, this.hostedZoneArn);
113+
}
108114
}
109115

110116
return new Import(scope, id);
@@ -192,6 +198,10 @@ export class HostedZone extends Resource implements IHostedZone {
192198
public addVpc(vpc: ec2.IVpc) {
193199
this.vpcs.push({ vpcId: vpc.vpcId, vpcRegion: vpc.env.region ?? Stack.of(vpc).region });
194200
}
201+
202+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
203+
return makeGrantDelegation(grantee, this.hostedZoneArn);
204+
}
195205
}
196206

197207
/**
@@ -238,12 +248,7 @@ export interface PublicHostedZoneProps extends CommonHostedZoneProps {
238248
/**
239249
* Represents a Route 53 public hosted zone
240250
*/
241-
export interface IPublicHostedZone extends IHostedZone {
242-
/**
243-
* Grant permissions to add delegation records to this zone
244-
*/
245-
grantDelegation(grantee: iam.IGrantable): iam.Grant;
246-
}
251+
export interface IPublicHostedZone extends IHostedZone {}
247252

248253
/**
249254
* Create a Route53 public hosted zone.
@@ -271,7 +276,7 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
271276
}
272277
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
273278
return makeGrantDelegation(grantee, this.hostedZoneArn);
274-
};
279+
}
275280
}
276281
return new Import(scope, id);
277282
}
@@ -294,7 +299,7 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
294299
}
295300
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
296301
return makeGrantDelegation(grantee, this.hostedZoneArn);
297-
};
302+
}
298303
}
299304
return new Import(scope, id);
300305
}
@@ -364,10 +369,6 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
364369
ttl: opts.ttl,
365370
});
366371
}
367-
368-
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
369-
return makeGrantDelegation(grantee, this.hostedZoneArn);
370-
}
371372
}
372373

373374
/**
@@ -434,6 +435,9 @@ export class PrivateHostedZone extends HostedZone implements IPrivateHostedZone
434435
public get hostedZoneArn(): string {
435436
return makeHostedZoneArn(this, this.hostedZoneId);
436437
}
438+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
439+
return makeGrantDelegation(grantee, this.hostedZoneArn);
440+
}
437441
}
438442
return new Import(scope, id);
439443
}

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

+103-4
Original file line numberDiff line numberDiff line change
@@ -288,19 +288,18 @@ test('grantDelegation', () => {
288288
});
289289
});
290290

291-
test('grantDelegation on imported public zones', () => {
291+
test('grantDelegation on imported zones', () => {
292292
// GIVEN
293293
const stack = new cdk.Stack(undefined, 'TestStack', {
294294
env: { account: '123456789012', region: 'us-east-1' },
295295
});
296296

297297
const role = new iam.Role(stack, 'Role', {
298-
assumedBy: new iam.AccountPrincipal('22222222222222'),
298+
assumedBy: new iam.AccountRootPrincipal(),
299299
});
300300

301-
const zone = PublicHostedZone.fromPublicHostedZoneId(stack, 'Zone', 'hosted-id');
302-
303301
// WHEN
302+
const zone = HostedZone.fromHostedZoneId(stack, 'Zone', 'hosted-id');
304303
zone.grantDelegation(role);
305304

306305
// THEN
@@ -339,6 +338,106 @@ test('grantDelegation on imported public zones', () => {
339338
});
340339
});
341340

341+
test('grantDelegation on public imported zones', () => {
342+
// GIVEN
343+
const stack = new cdk.Stack(undefined, 'TestStack', {
344+
env: { account: '123456789012', region: 'us-east-1' },
345+
});
346+
347+
const role = new iam.Role(stack, 'Role', {
348+
assumedBy: new iam.AccountRootPrincipal(),
349+
});
350+
351+
// WHEN
352+
const publicZone = PublicHostedZone.fromPublicHostedZoneId(stack, 'PublicZone', 'public-hosted-id');
353+
publicZone.grantDelegation(role);
354+
355+
// THEN
356+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
357+
PolicyDocument: {
358+
Statement: [
359+
{
360+
Action: 'route53:ChangeResourceRecordSets',
361+
Effect: 'Allow',
362+
Resource: {
363+
'Fn::Join': [
364+
'',
365+
[
366+
'arn:',
367+
{
368+
Ref: 'AWS::Partition',
369+
},
370+
':route53:::hostedzone/public-hosted-id',
371+
],
372+
],
373+
},
374+
Condition: {
375+
'ForAllValues:StringEquals': {
376+
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
377+
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
378+
},
379+
},
380+
},
381+
{
382+
Action: 'route53:ListHostedZonesByName',
383+
Effect: 'Allow',
384+
Resource: '*',
385+
},
386+
],
387+
},
388+
});
389+
});
390+
391+
test('grantDelegation on private imported zones', () => {
392+
// GIVEN
393+
const stack = new cdk.Stack(undefined, 'TestStack', {
394+
env: { account: '123456789012', region: 'us-east-1' },
395+
});
396+
397+
const role = new iam.Role(stack, 'Role', {
398+
assumedBy: new iam.AccountRootPrincipal(),
399+
});
400+
401+
// WHEN
402+
const privateZone = PrivateHostedZone.fromPrivateHostedZoneId(stack, 'PrivateZone', 'private-hosted-id');
403+
privateZone.grantDelegation(role);
404+
405+
// THEN
406+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
407+
PolicyDocument: {
408+
Statement: [
409+
{
410+
Action: 'route53:ChangeResourceRecordSets',
411+
Effect: 'Allow',
412+
Resource: {
413+
'Fn::Join': [
414+
'',
415+
[
416+
'arn:',
417+
{
418+
Ref: 'AWS::Partition',
419+
},
420+
':route53:::hostedzone/private-hosted-id',
421+
],
422+
],
423+
},
424+
Condition: {
425+
'ForAllValues:StringEquals': {
426+
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
427+
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
428+
},
429+
},
430+
},
431+
{
432+
Action: 'route53:ListHostedZonesByName',
433+
Effect: 'Allow',
434+
Resource: '*',
435+
},
436+
],
437+
},
438+
});
439+
});
440+
342441
describe('Hosted Zone with dot', () => {
343442
test('Hosted Zone constructs without trailing dot by default', () => {
344443
// GIVEN

0 commit comments

Comments
 (0)