Skip to content

Commit 76b5c0d

Browse files
authored
fix(route53): support multiple cross account DNS delegations (#17837)
the custom resource lambda function's role is only created once. To support multiple zone delegations the role creation and policy management needs to be decoupled so each CrossAccountZoneDelegationRecord instance can add an individual policy to the role. Fixes #17836 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent e1a041f commit 76b5c0d

File tree

3 files changed

+241
-23
lines changed

3 files changed

+241
-23
lines changed

packages/@aws-cdk/aws-route53/lib/record-set.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -683,15 +683,22 @@ export class CrossAccountZoneDelegationRecord extends CoreConstruct {
683683
throw Error('Only one of parentHostedZoneName and parentHostedZoneId is supported');
684684
}
685685

686-
const serviceToken = CustomResourceProvider.getOrCreate(this, CROSS_ACCOUNT_ZONE_DELEGATION_RESOURCE_TYPE, {
686+
const provider = CustomResourceProvider.getOrCreateProvider(this, CROSS_ACCOUNT_ZONE_DELEGATION_RESOURCE_TYPE, {
687687
codeDirectory: path.join(__dirname, 'cross-account-zone-delegation-handler'),
688688
runtime: CustomResourceProviderRuntime.NODEJS_12_X,
689-
policyStatements: [{ Effect: 'Allow', Action: 'sts:AssumeRole', Resource: props.delegationRole.roleArn }],
690689
});
691690

691+
const role = iam.Role.fromRoleArn(this, 'cross-account-zone-delegation-handler-role', provider.roleArn);
692+
693+
role.addToPrincipalPolicy(new iam.PolicyStatement({
694+
effect: iam.Effect.ALLOW,
695+
actions: ['sts:AssumeRole'],
696+
resources: [props.delegationRole.roleArn],
697+
}));
698+
692699
new CustomResource(this, 'CrossAccountZoneDelegationCustomResource', {
693700
resourceType: CROSS_ACCOUNT_ZONE_DELEGATION_RESOURCE_TYPE,
694-
serviceToken,
701+
serviceToken: provider.serviceToken,
695702
removalPolicy: props.removalPolicy,
696703
properties: {
697704
AssumeRoleArn: props.delegationRole.roleArn,

packages/@aws-cdk/aws-route53/test/integ.cross-account-zone-delegation.expected.json

+98-20
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,55 @@
7878
"Name": "sub.myzone.com."
7979
}
8080
},
81+
"DelegationWithZoneIdcrossaccountzonedelegationhandlerrolePolicy5170A69B": {
82+
"Type": "AWS::IAM::Policy",
83+
"Properties": {
84+
"PolicyDocument": {
85+
"Statement": [
86+
{
87+
"Action": "sts:AssumeRole",
88+
"Effect": "Allow",
89+
"Resource": {
90+
"Fn::GetAtt": [
91+
"ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E",
92+
"Arn"
93+
]
94+
}
95+
}
96+
],
97+
"Version": "2012-10-17"
98+
},
99+
"PolicyName": "DelegationWithZoneIdcrossaccountzonedelegationhandlerrolePolicy5170A69B",
100+
"Roles": [
101+
{
102+
"Fn::Select": [
103+
1,
104+
{
105+
"Fn::Split": [
106+
"/",
107+
{
108+
"Fn::Select": [
109+
5,
110+
{
111+
"Fn::Split": [
112+
":",
113+
{
114+
"Fn::GetAtt": [
115+
"CustomCrossAccountZoneDelegationCustomResourceProviderRoleED64687B",
116+
"Arn"
117+
]
118+
}
119+
]
120+
}
121+
]
122+
}
123+
]
124+
}
125+
]
126+
}
127+
]
128+
}
129+
},
81130
"DelegationWithZoneIdCrossAccountZoneDelegationCustomResourceFFD766E7": {
82131
"Type": "Custom::CrossAccountZoneDelegation",
83132
"Properties": {
@@ -127,26 +176,6 @@
127176
{
128177
"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
129178
}
130-
],
131-
"Policies": [
132-
{
133-
"PolicyName": "Inline",
134-
"PolicyDocument": {
135-
"Version": "2012-10-17",
136-
"Statement": [
137-
{
138-
"Effect": "Allow",
139-
"Action": "sts:AssumeRole",
140-
"Resource": {
141-
"Fn::GetAtt": [
142-
"ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E",
143-
"Arn"
144-
]
145-
}
146-
}
147-
]
148-
}
149-
}
150179
]
151180
}
152181
},
@@ -212,6 +241,55 @@
212241
"Name": "anothersub.myzone.com."
213242
}
214243
},
244+
"DelegationWithZoneNamecrossaccountzonedelegationhandlerrolePolicy86996882": {
245+
"Type": "AWS::IAM::Policy",
246+
"Properties": {
247+
"PolicyDocument": {
248+
"Statement": [
249+
{
250+
"Action": "sts:AssumeRole",
251+
"Effect": "Allow",
252+
"Resource": {
253+
"Fn::GetAtt": [
254+
"ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E",
255+
"Arn"
256+
]
257+
}
258+
}
259+
],
260+
"Version": "2012-10-17"
261+
},
262+
"PolicyName": "DelegationWithZoneNamecrossaccountzonedelegationhandlerrolePolicy86996882",
263+
"Roles": [
264+
{
265+
"Fn::Select": [
266+
1,
267+
{
268+
"Fn::Split": [
269+
"/",
270+
{
271+
"Fn::Select": [
272+
5,
273+
{
274+
"Fn::Split": [
275+
":",
276+
{
277+
"Fn::GetAtt": [
278+
"CustomCrossAccountZoneDelegationCustomResourceProviderRoleED64687B",
279+
"Arn"
280+
]
281+
}
282+
]
283+
}
284+
]
285+
}
286+
]
287+
}
288+
]
289+
}
290+
]
291+
}
292+
},
215293
"DelegationWithZoneNameCrossAccountZoneDelegationCustomResourceA1A1C94A": {
216294
"Type": "Custom::CrossAccountZoneDelegation",
217295
"Properties": {

packages/@aws-cdk/aws-route53/test/record-set.test.ts

+133
Original file line numberDiff line numberDiff line change
@@ -732,4 +732,137 @@ describe('record set', () => {
732732

733733

734734
});
735+
736+
test('Multiple cross account zone delegation records', () => {
737+
// GIVEN
738+
const stack = new Stack();
739+
const parentZone = new route53.PublicHostedZone(stack, 'ParentHostedZone', {
740+
zoneName: 'myzone.com',
741+
crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal('123456789012'),
742+
});
743+
744+
// WHEN
745+
const childZone = new route53.PublicHostedZone(stack, 'ChildHostedZone', {
746+
zoneName: 'sub.myzone.com',
747+
});
748+
new route53.CrossAccountZoneDelegationRecord(stack, 'Delegation', {
749+
delegatedZone: childZone,
750+
parentHostedZoneName: 'myzone.com',
751+
delegationRole: parentZone.crossAccountZoneDelegationRole!,
752+
ttl: Duration.seconds(60),
753+
});
754+
const childZone2 = new route53.PublicHostedZone(stack, 'ChildHostedZone2', {
755+
zoneName: 'anothersub.myzone.com',
756+
});
757+
new route53.CrossAccountZoneDelegationRecord(stack, 'Delegation2', {
758+
delegatedZone: childZone2,
759+
parentHostedZoneName: 'myzone.com',
760+
delegationRole: parentZone.crossAccountZoneDelegationRole!,
761+
ttl: Duration.seconds(60),
762+
});
763+
764+
// THEN
765+
const childHostedZones = [
766+
{ name: 'sub.myzone.com', id: 'ChildHostedZone4B14AC71' },
767+
{ name: 'anothersub.myzone.com', id: 'ChildHostedZone2A37198F0' },
768+
];
769+
770+
for (var childHostedZone of childHostedZones) {
771+
expect(stack).toHaveResource('Custom::CrossAccountZoneDelegation', {
772+
ServiceToken: {
773+
'Fn::GetAtt': [
774+
'CustomCrossAccountZoneDelegationCustomResourceProviderHandler44A84265',
775+
'Arn',
776+
],
777+
},
778+
AssumeRoleArn: {
779+
'Fn::GetAtt': [
780+
'ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E',
781+
'Arn',
782+
],
783+
},
784+
ParentZoneName: 'myzone.com',
785+
DelegatedZoneName: childHostedZone.name,
786+
DelegatedZoneNameServers: {
787+
'Fn::GetAtt': [
788+
childHostedZone.id,
789+
'NameServers',
790+
],
791+
},
792+
TTL: 60,
793+
});
794+
}
795+
});
796+
797+
test('Cross account zone delegation policies', () => {
798+
// GIVEN
799+
const stack = new Stack();
800+
const parentZone = new route53.PublicHostedZone(stack, 'ParentHostedZone', {
801+
zoneName: 'myzone.com',
802+
crossAccountZoneDelegationPrincipal: new iam.AccountPrincipal('123456789012'),
803+
});
804+
805+
// WHEN
806+
const childZone = new route53.PublicHostedZone(stack, 'ChildHostedZone', {
807+
zoneName: 'sub.myzone.com',
808+
});
809+
new route53.CrossAccountZoneDelegationRecord(stack, 'Delegation', {
810+
delegatedZone: childZone,
811+
parentHostedZoneName: 'myzone.com',
812+
delegationRole: parentZone.crossAccountZoneDelegationRole!,
813+
ttl: Duration.seconds(60),
814+
});
815+
const childZone2 = new route53.PublicHostedZone(stack, 'ChildHostedZone2', {
816+
zoneName: 'anothersub.myzone.com',
817+
});
818+
new route53.CrossAccountZoneDelegationRecord(stack, 'Delegation2', {
819+
delegatedZone: childZone2,
820+
parentHostedZoneName: 'myzone.com',
821+
delegationRole: parentZone.crossAccountZoneDelegationRole!,
822+
ttl: Duration.seconds(60),
823+
});
824+
825+
// THEN
826+
const policyNames = [
827+
'DelegationcrossaccountzonedelegationhandlerrolePolicy1E157602',
828+
'Delegation2crossaccountzonedelegationhandlerrolePolicy713BEAC3',
829+
];
830+
831+
for (var policyName of policyNames) {
832+
expect(stack).toHaveResource('AWS::IAM::Policy', {
833+
PolicyName: policyName,
834+
PolicyDocument: {
835+
Version: '2012-10-17',
836+
Statement: [
837+
{
838+
Action: 'sts:AssumeRole',
839+
Effect: 'Allow',
840+
Resource: {
841+
'Fn::GetAtt': [
842+
'ParentHostedZoneCrossAccountZoneDelegationRole95B1C36E',
843+
'Arn',
844+
],
845+
},
846+
},
847+
],
848+
},
849+
Roles: [
850+
{
851+
'Fn::Select': [1, {
852+
'Fn::Split': ['/', {
853+
'Fn::Select': [5, {
854+
'Fn::Split': [':', {
855+
'Fn::GetAtt': [
856+
'CustomCrossAccountZoneDelegationCustomResourceProviderRoleED64687B',
857+
'Arn',
858+
],
859+
}],
860+
}],
861+
}],
862+
}],
863+
},
864+
],
865+
});
866+
}
867+
});
735868
});

0 commit comments

Comments
 (0)