Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(route53): add support for grantDelegation on imported PublicHostedZone #26333

Merged
merged 10 commits into from
Aug 18, 2023
12 changes: 12 additions & 0 deletions packages/aws-cdk-lib/aws-route53/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ const zoneFromAttributes = route53.PublicHostedZone.fromPublicHostedZoneAttribut
const zoneFromId = route53.PublicHostedZone.fromPublicHostedZoneId(this, 'MyZone', 'ZOJJZC49E0EPZ');
```

You can use `CrossAccountZoneDelegationRecord` on imported Public Hosted Zones with the `grantDelegation` method:

```ts
const crossAccountRole = new iam.Role(this, 'CrossAccountRole', {
// The role name must be predictable
roleName: 'MyDelegationRole',
// The other account
assumedBy: new iam.AccountPrincipal('12345678901'),
});
zoneFromId.grantDelegation(crossAccountRole);
```

## VPC Endpoint Service Private DNS

When you create a VPC endpoint service, AWS generates endpoint-specific DNS hostnames that consumers use to communicate with the service.
Expand Down
37 changes: 14 additions & 23 deletions packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HostedZoneProviderProps } from './hosted-zone-provider';
import { HostedZoneAttributes, IHostedZone, PublicHostedZoneAttributes } from './hosted-zone-ref';
import { CaaAmazonRecord, ZoneDelegationRecord } from './record-set';
import { CfnHostedZone } from './route53.generated';
import { makeHostedZoneArn, validateZoneName } from './util';
import { makeGrantDelegation, makeHostedZoneArn, validateZoneName } from './util';
import * as ec2 from '../../aws-ec2';
import * as iam from '../../aws-iam';
import * as cxschema from '../../cloud-assembly-schema';
Expand Down Expand Up @@ -238,7 +238,12 @@ export interface PublicHostedZoneProps extends CommonHostedZoneProps {
/**
* Represents a Route 53 public hosted zone
*/
export interface IPublicHostedZone extends IHostedZone { }
export interface IPublicHostedZone extends IHostedZone {
/**
* Grant permissions to add delegation records to this zone
*/
grantDelegation(grantee: iam.IGrantable): void;
}

/**
* Create a Route53 public hosted zone.
Expand All @@ -264,6 +269,9 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable) {
makeGrantDelegation(grantee, this.hostedZoneArn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you need to return makeGrantDelegation

};
}
return new Import(scope, id);
}
Expand All @@ -284,6 +292,9 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
public get hostedZoneArn(): string {
return makeHostedZoneArn(this, this.hostedZoneId);
}
public grantDelegation(grantee: iam.IGrantable) {
makeGrantDelegation(grantee, this.hostedZoneArn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

};
}
return new Import(scope, id);
}
Expand Down Expand Up @@ -354,28 +365,8 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone {
});
}

/**
* Grant permissions to add delegation records to this zone
*/
public grantDelegation(grantee: iam.IGrantable) {
const g1 = iam.Grant.addToPrincipal({
grantee,
actions: ['route53:ChangeResourceRecordSets'],
resourceArns: [this.hostedZoneArn],
conditions: {
'ForAllValues:StringEquals': {
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
},
},
});
const g2 = iam.Grant.addToPrincipal({
grantee,
actions: ['route53:ListHostedZonesByName'],
resourceArns: ['*'],
});

return g1.combine(g2);
makeGrantDelegation(grantee, this.hostedZoneArn);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here.

}
}

Expand Down
22 changes: 22 additions & 0 deletions packages/aws-cdk-lib/aws-route53/lib/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Construct } from 'constructs';
import { IHostedZone } from './hosted-zone-ref';
import * as iam from '../../aws-iam';
import { Stack } from '../../core';

/**
Expand Down Expand Up @@ -69,3 +70,24 @@ export function makeHostedZoneArn(construct: Construct, hostedZoneId: string): s
resourceName: hostedZoneId,
});
}

export function makeGrantDelegation(grantee: iam.IGrantable, hostedZoneArn: string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify the return type in the function definition (looks like iam.Grant or iam.IGrantable)

const g1 = iam.Grant.addToPrincipal({
grantee,
actions: ['route53:ChangeResourceRecordSets'],
resourceArns: [hostedZoneArn],
conditions: {
'ForAllValues:StringEquals': {
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
},
},
});
const g2 = iam.Grant.addToPrincipal({
grantee,
actions: ['route53:ListHostedZonesByName'],
resourceArns: ['*'],
});

return g1.combine(g2);
}
51 changes: 51 additions & 0 deletions packages/aws-cdk-lib/aws-route53/test/hosted-zone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,57 @@ test('grantDelegation', () => {
});
});

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

const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.AccountPrincipal('22222222222222'),
});

const zone = PublicHostedZone.fromPublicHostedZoneId(stack, 'Zone', 'hosted-id');

// WHEN
zone.grantDelegation(role);

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'route53:ChangeResourceRecordSets',
Effect: 'Allow',
Resource: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':route53:::hostedzone/hosted-id',
],
],
},
Condition: {
'ForAllValues:StringEquals': {
'route53:ChangeResourceRecordSetsRecordTypes': ['NS'],
'route53:ChangeResourceRecordSetsActions': ['UPSERT', 'DELETE'],
},
},
},
{
Action: 'route53:ListHostedZonesByName',
Effect: 'Allow',
Resource: '*',
},
],
},
});
});

describe('Hosted Zone with dot', () => {
test('Hosted Zone constructs without trailing dot by default', () => {
// GIVEN
Expand Down