Skip to content

Commit a93af2f

Browse files
authored
feat(route53): add support for grantDelegation on imported PublicHostedZone (#26333)
Imported `PublicHostedZone` with `fromPublicHostedZoneId` and `fromPublicHostedZoneAttributes` don't have support for the `grantDelegation` method since they return an instance of type `IPublicHostedZone`. This change adds support for `grantDelegation` to those instances as well. Closes #26240. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4d3ec71 commit a93af2f

File tree

10 files changed

+339
-29
lines changed

10 files changed

+339
-29
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.cross-account-zone-delegation.js.snapshot/aws-cdk-route53-cross-account-integ.assets.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
}
1515
}
1616
},
17-
"3222f491727b0389ac87f972f2443b490ff3cee14d24c28f1527c3f085cab460": {
17+
"52da24cb67101152630cedcc08830f183f595580f8a7f6fcef1e0aac216c7198": {
1818
"source": {
1919
"path": "aws-cdk-route53-cross-account-integ.template.json",
2020
"packaging": "file"
2121
},
2222
"destinations": {
2323
"current_account-current_region": {
2424
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
25-
"objectKey": "3222f491727b0389ac87f972f2443b490ff3cee14d24c28f1527c3f085cab460.json",
25+
"objectKey": "52da24cb67101152630cedcc08830f183f595580f8a7f6fcef1e0aac216c7198.json",
2626
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
2727
}
2828
}

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,86 @@
302302
],
303303
"UpdateReplacePolicy": "Delete",
304304
"DeletionPolicy": "Delete"
305+
},
306+
"Role1ABCC5F0": {
307+
"Type": "AWS::IAM::Role",
308+
"Properties": {
309+
"AssumeRolePolicyDocument": {
310+
"Statement": [
311+
{
312+
"Action": "sts:AssumeRole",
313+
"Effect": "Allow",
314+
"Principal": {
315+
"AWS": {
316+
"Fn::Join": [
317+
"",
318+
[
319+
"arn:",
320+
{
321+
"Ref": "AWS::Partition"
322+
},
323+
":iam::",
324+
{
325+
"Ref": "AWS::AccountId"
326+
},
327+
":root"
328+
]
329+
]
330+
}
331+
}
332+
}
333+
],
334+
"Version": "2012-10-17"
335+
}
336+
}
337+
},
338+
"RoleDefaultPolicy5FFB7DAB": {
339+
"Type": "AWS::IAM::Policy",
340+
"Properties": {
341+
"PolicyDocument": {
342+
"Statement": [
343+
{
344+
"Action": "route53:ChangeResourceRecordSets",
345+
"Condition": {
346+
"ForAllValues:StringEquals": {
347+
"route53:ChangeResourceRecordSetsRecordTypes": [
348+
"NS"
349+
],
350+
"route53:ChangeResourceRecordSetsActions": [
351+
"UPSERT",
352+
"DELETE"
353+
]
354+
}
355+
},
356+
"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+
}
369+
},
370+
{
371+
"Action": "route53:ListHostedZonesByName",
372+
"Effect": "Allow",
373+
"Resource": "*"
374+
}
375+
],
376+
"Version": "2012-10-17"
377+
},
378+
"PolicyName": "RoleDefaultPolicy5FFB7DAB",
379+
"Roles": [
380+
{
381+
"Ref": "Role1ABCC5F0"
382+
}
383+
]
384+
}
305385
}
306386
},
307387
"Parameters": {

packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.cross-account-zone-delegation.js.snapshot/manifest.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3222f491727b0389ac87f972f2443b490ff3cee14d24c28f1527c3f085cab460.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/52da24cb67101152630cedcc08830f183f595580f8a7f6fcef1e0aac216c7198.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [
@@ -93,6 +93,18 @@
9393
"data": "DelegationWithZoneNameCrossAccountZoneDelegationCustomResourceA1A1C94A"
9494
}
9595
],
96+
"/aws-cdk-route53-cross-account-integ/Role/Resource": [
97+
{
98+
"type": "aws:cdk:logicalId",
99+
"data": "Role1ABCC5F0"
100+
}
101+
],
102+
"/aws-cdk-route53-cross-account-integ/Role/DefaultPolicy/Resource": [
103+
{
104+
"type": "aws:cdk:logicalId",
105+
"data": "RoleDefaultPolicy5FFB7DAB"
106+
}
107+
],
96108
"/aws-cdk-route53-cross-account-integ/BootstrapVersion": [
97109
{
98110
"type": "aws:cdk:logicalId",

packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.cross-account-zone-delegation.js.snapshot/tree.json

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,138 @@
427427
"version": "0.0.0"
428428
}
429429
},
430+
"Role": {
431+
"id": "Role",
432+
"path": "aws-cdk-route53-cross-account-integ/Role",
433+
"children": {
434+
"ImportRole": {
435+
"id": "ImportRole",
436+
"path": "aws-cdk-route53-cross-account-integ/Role/ImportRole",
437+
"constructInfo": {
438+
"fqn": "aws-cdk-lib.Resource",
439+
"version": "0.0.0"
440+
}
441+
},
442+
"Resource": {
443+
"id": "Resource",
444+
"path": "aws-cdk-route53-cross-account-integ/Role/Resource",
445+
"attributes": {
446+
"aws:cdk:cloudformation:type": "AWS::IAM::Role",
447+
"aws:cdk:cloudformation:props": {
448+
"assumeRolePolicyDocument": {
449+
"Statement": [
450+
{
451+
"Action": "sts:AssumeRole",
452+
"Effect": "Allow",
453+
"Principal": {
454+
"AWS": {
455+
"Fn::Join": [
456+
"",
457+
[
458+
"arn:",
459+
{
460+
"Ref": "AWS::Partition"
461+
},
462+
":iam::",
463+
{
464+
"Ref": "AWS::AccountId"
465+
},
466+
":root"
467+
]
468+
]
469+
}
470+
}
471+
}
472+
],
473+
"Version": "2012-10-17"
474+
}
475+
}
476+
},
477+
"constructInfo": {
478+
"fqn": "aws-cdk-lib.aws_iam.CfnRole",
479+
"version": "0.0.0"
480+
}
481+
},
482+
"DefaultPolicy": {
483+
"id": "DefaultPolicy",
484+
"path": "aws-cdk-route53-cross-account-integ/Role/DefaultPolicy",
485+
"children": {
486+
"Resource": {
487+
"id": "Resource",
488+
"path": "aws-cdk-route53-cross-account-integ/Role/DefaultPolicy/Resource",
489+
"attributes": {
490+
"aws:cdk:cloudformation:type": "AWS::IAM::Policy",
491+
"aws:cdk:cloudformation:props": {
492+
"policyDocument": {
493+
"Statement": [
494+
{
495+
"Action": "route53:ChangeResourceRecordSets",
496+
"Condition": {
497+
"ForAllValues:StringEquals": {
498+
"route53:ChangeResourceRecordSetsRecordTypes": [
499+
"NS"
500+
],
501+
"route53:ChangeResourceRecordSetsActions": [
502+
"UPSERT",
503+
"DELETE"
504+
]
505+
}
506+
},
507+
"Effect": "Allow",
508+
"Resource": {
509+
"Fn::Join": [
510+
"",
511+
[
512+
"arn:",
513+
{
514+
"Ref": "AWS::Partition"
515+
},
516+
":route53:::hostedzone/imported-public-zone-id"
517+
]
518+
]
519+
}
520+
},
521+
{
522+
"Action": "route53:ListHostedZonesByName",
523+
"Effect": "Allow",
524+
"Resource": "*"
525+
}
526+
],
527+
"Version": "2012-10-17"
528+
},
529+
"policyName": "RoleDefaultPolicy5FFB7DAB",
530+
"roles": [
531+
{
532+
"Ref": "Role1ABCC5F0"
533+
}
534+
]
535+
}
536+
},
537+
"constructInfo": {
538+
"fqn": "aws-cdk-lib.aws_iam.CfnPolicy",
539+
"version": "0.0.0"
540+
}
541+
}
542+
},
543+
"constructInfo": {
544+
"fqn": "aws-cdk-lib.aws_iam.Policy",
545+
"version": "0.0.0"
546+
}
547+
}
548+
},
549+
"constructInfo": {
550+
"fqn": "aws-cdk-lib.aws_iam.Role",
551+
"version": "0.0.0"
552+
}
553+
},
554+
"ImportedPublicZone": {
555+
"id": "ImportedPublicZone",
556+
"path": "aws-cdk-route53-cross-account-integ/ImportedPublicZone",
557+
"constructInfo": {
558+
"fqn": "aws-cdk-lib.Resource",
559+
"version": "0.0.0"
560+
}
561+
},
430562
"BootstrapVersion": {
431563
"id": "BootstrapVersion",
432564
"path": "aws-cdk-route53-cross-account-integ/BootstrapVersion",

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,16 @@ new CrossAccountZoneDelegationRecord(stack, 'DelegationWithZoneName', {
3232
delegationRole: parentZone.crossAccountZoneDelegationRole!,
3333
});
3434

35+
const role = new iam.Role(stack, 'Role', {
36+
assumedBy: new iam.AccountRootPrincipal(),
37+
});
38+
39+
const importedPublicZone = PublicHostedZone.fromPublicHostedZoneId(stack, 'ImportedPublicZone', 'imported-public-zone-id');
40+
importedPublicZone.grantDelegation(role);
41+
3542
new IntegTest(app, 'Route53CrossAccountInteg', {
3643
testCases: [stack],
3744
diffAssets: true,
3845
});
46+
3947
app.synth();

packages/aws-cdk-lib/aws-ec2/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -980,8 +980,8 @@ Endpoint services support private DNS, which makes it easier for clients to conn
980980
You can enable private DNS on an endpoint service like so:
981981

982982
```ts
983-
import { HostedZone, VpcEndpointServiceDomainName } from 'aws-cdk-lib/aws-route53';
984-
declare const zone: HostedZone;
983+
import { PublicHostedZone, VpcEndpointServiceDomainName } from 'aws-cdk-lib/aws-route53';
984+
declare const zone: PublicHostedZone;
985985
declare const vpces: ec2.VpcEndpointService;
986986

987987
new VpcEndpointServiceDomainName(this, 'EndpointDomain', {

packages/aws-cdk-lib/aws-route53/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,20 @@ 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:
293+
294+
```ts
295+
const crossAccountRole = new iam.Role(this, 'CrossAccountRole', {
296+
// The role name must be predictable
297+
roleName: 'MyDelegationRole',
298+
// The other account
299+
assumedBy: new iam.AccountPrincipal('12345678901'),
300+
});
301+
302+
const zoneFromId = route53.PublicHostedZone.fromPublicHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ');
303+
zoneFromId.grantDelegation(crossAccountRole);
304+
```
305+
292306
## VPC Endpoint Service Private DNS
293307

294308
When you create a VPC endpoint service, AWS generates endpoint-specific DNS hostnames that consumers use to communicate with the service.

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

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { HostedZoneProviderProps } from './hosted-zone-provider';
33
import { HostedZoneAttributes, IHostedZone, PublicHostedZoneAttributes } from './hosted-zone-ref';
44
import { CaaAmazonRecord, ZoneDelegationRecord } from './record-set';
55
import { CfnHostedZone } from './route53.generated';
6-
import { makeHostedZoneArn, validateZoneName } from './util';
6+
import { makeGrantDelegation, makeHostedZoneArn, validateZoneName } from './util';
77
import * as ec2 from '../../aws-ec2';
88
import * as iam from '../../aws-iam';
99
import * as cxschema from '../../cloud-assembly-schema';
@@ -238,7 +238,12 @@ export interface PublicHostedZoneProps extends CommonHostedZoneProps {
238238
/**
239239
* Represents a Route 53 public hosted zone
240240
*/
241-
export interface IPublicHostedZone extends IHostedZone { }
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+
}
242247

243248
/**
244249
* Create a Route53 public hosted zone.
@@ -264,6 +269,9 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
264269
public get hostedZoneArn(): string {
265270
return makeHostedZoneArn(this, this.hostedZoneId);
266271
}
272+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
273+
return makeGrantDelegation(grantee, this.hostedZoneArn);
274+
};
267275
}
268276
return new Import(scope, id);
269277
}
@@ -284,6 +292,9 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
284292
public get hostedZoneArn(): string {
285293
return makeHostedZoneArn(this, this.hostedZoneId);
286294
}
295+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
296+
return makeGrantDelegation(grantee, this.hostedZoneArn);
297+
};
287298
}
288299
return new Import(scope, id);
289300
}
@@ -354,28 +365,8 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
354365
});
355366
}
356367

357-
/**
358-
* Grant permissions to add delegation records to this zone
359-
*/
360-
public grantDelegation(grantee: iam.IGrantable) {
361-
const g1 = iam.Grant.addToPrincipal({
362-
grantee,
363-
actions: ['route53:ChangeResourceRecordSets'],
364-
resourceArns: [this.hostedZoneArn],
365-
conditions: {
366-
'ForAllValues:StringEquals': {
367-
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
368-
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
369-
},
370-
},
371-
});
372-
const g2 = iam.Grant.addToPrincipal({
373-
grantee,
374-
actions: ['route53:ListHostedZonesByName'],
375-
resourceArns: ['*'],
376-
});
377-
378-
return g1.combine(g2);
368+
public grantDelegation(grantee: iam.IGrantable): iam.Grant {
369+
return makeGrantDelegation(grantee, this.hostedZoneArn);
379370
}
380371
}
381372

0 commit comments

Comments
 (0)