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(cloudfront): add grantCreateInvalidation to Distribution #22575

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,16 @@ const distribution = cloudfront.Distribution.fromDistributionAttributes(this, 'I
});
```

### Permissions

Use the `grantCreateInvalidation()` method to allow to create invalidations on the distribution.

```ts
declare const distribution: cloudfront.Distribution;
declare const lambdaFn: lambda.Function;
distribution.grantCreateInvalidation(lambdaFn);
```

## Migrating from the original CloudFrontWebDistribution to the newer Distribution construct

It's possible to migrate a distribution from the original to the modern API.
Expand Down
33 changes: 33 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/distribution.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as s3 from '@aws-cdk/aws-s3';
import { ArnFormat, IResource, Lazy, Resource, Stack, Token, Duration, Names, FeatureFlags } from '@aws-cdk/core';
Expand All @@ -12,6 +13,7 @@ import { IKeyGroup } from './key-group';
import { IOrigin, OriginBindConfig, OriginBindOptions } from './origin';
import { IOriginRequestPolicy } from './origin-request-policy';
import { CacheBehavior } from './private/cache-behavior';
import { formatDistributionArn, grantCreateInvalidation } from './private/utils';
import { IResponseHeadersPolicy } from './response-headers-policy';

/**
Expand Down Expand Up @@ -39,6 +41,20 @@ export interface IDistribution extends IResource {
* @attribute
*/
readonly distributionId: string;

/**
* The distribution ARN for this distribution.
*
* @attribute
*/
readonly distributionArn: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I get why this is being added in this context. It's not included in Fn::GetAtt in the resource per https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html. How is this being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's useful when grant other than CreateInvalidation:

declare const role: iam.Role;

// before
role.addToPrincipalPolicy(new iam.PolicyStatement({
  actions: ['cloudfront:ListInvalidations'],
  // region is needed here
  resources: [this.formatArn({ service: 'cloudfront', region: '', resource: 'distribution', resourceName: dist.distributionId })],
}));

// after
role.addToPrincipalPolicy(new iam.PolicyStatement({
  actions: ['cloudfront:ListInvalidations'],
  resources: [dist.distributionArn],
}));

Can be removed if it's excessive.


/**
* Grant to create invalidations for this bucket to an IAM principal (Role/Group/User).
*
* @param identity The principal
*/
grantCreateInvalidation(identity: iam.IGrantable): iam.Grant;
}

/**
Expand Down Expand Up @@ -250,19 +266,26 @@ export class Distribution extends Resource implements IDistribution {
public readonly domainName: string;
public readonly distributionDomainName: string;
public readonly distributionId: string;
public readonly distributionArn: string;

constructor() {
super(scope, id);
this.domainName = attrs.domainName;
this.distributionDomainName = attrs.domainName;
this.distributionId = attrs.distributionId;
this.distributionArn = formatDistributionArn(this, this.distributionId);
}

grantCreateInvalidation(identity: iam.IGrantable): iam.Grant {
return grantCreateInvalidation(this, identity);
}
}();
}

public readonly domainName: string;
public readonly distributionDomainName: string;
public readonly distributionId: string;
public readonly distributionArn: string;

private readonly defaultBehavior: CacheBehavior;
private readonly additionalBehaviors: CacheBehavior[] = [];
Expand Down Expand Up @@ -328,6 +351,7 @@ export class Distribution extends Resource implements IDistribution {
this.domainName = distribution.attrDomainName;
this.distributionDomainName = distribution.attrDomainName;
this.distributionId = distribution.ref;
this.distributionArn = formatDistributionArn(this, this.distributionId);
}

/**
Expand All @@ -345,6 +369,15 @@ export class Distribution extends Resource implements IDistribution {
this.additionalBehaviors.push(new CacheBehavior(originId, { pathPattern, ...behaviorOptions }));
}

/**
* Grant to create invalidations for this bucket to an IAM principal (Role/Group/User).
*
* @param identity The principal
*/
grantCreateInvalidation(identity: iam.IGrantable): iam.Grant {
return grantCreateInvalidation(this, identity);
}

private addOrigin(origin: IOrigin, isFailoverOrigin: boolean = false): string {
const ORIGIN_ID_MAX_LENGTH = 128;

Expand Down
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/private/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as iam from '@aws-cdk/aws-iam';
import { Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { IDistribution } from '..';

/**
* Format distribution ARN from stack and distribution ID.
*/
export function formatDistributionArn(scope: Construct, distributionId: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing the benefit of creating this file instead of just putting these in the distribution file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These functions are shared by distribution.ts and web-distribution.ts.

return Stack.of(scope).formatArn({
service: 'cloudfront',
region: '',
resource: 'distribution',
resourceName: distributionId,
});
}

export function grantCreateInvalidation(distribution: IDistribution, grantee: iam.IGrantable) {
return iam.Grant.addToPrincipal({
grantee,
actions: ['cloudfront:CreateInvalidation'],
resourceArns: [distribution.distributionArn],
});
}
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FunctionAssociation } from './function';
import { GeoRestriction } from './geo-restriction';
import { IKeyGroup } from './key-group';
import { IOriginAccessIdentity } from './origin-access-identity';
import { formatDistributionArn, grantCreateInvalidation } from './private/utils';

/**
* HTTP status code to failover to second origin
Expand Down Expand Up @@ -751,12 +752,18 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu
public readonly domainName: string;
public readonly distributionDomainName: string;
public readonly distributionId: string;
public readonly distributionArn: string;

constructor() {
super(scope, id);
this.domainName = attrs.domainName;
this.distributionDomainName = attrs.domainName;
this.distributionId = attrs.distributionId;
this.distributionArn = formatDistributionArn(this, this.distributionId);
}

grantCreateInvalidation(identity: iam.IGrantable): iam.Grant {
return grantCreateInvalidation(this, identity);
}
}();
}
Expand Down Expand Up @@ -788,6 +795,11 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu
*/
public readonly distributionId: string;

/**
* The distribution ARN for this distribution.
*/
public readonly distributionArn: string;

/**
* Maps our methods to the string arrays they are
*/
Expand Down Expand Up @@ -981,6 +993,16 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu
this.domainName = distribution.attrDomainName;
this.distributionDomainName = distribution.attrDomainName;
this.distributionId = distribution.ref;
this.distributionArn = formatDistributionArn(this, this.distributionId);
}

/**
* Grant to create invalidations for this bucket to an IAM principal (Role/Group/User).
*
* @param identity The principal
*/
grantCreateInvalidation(identity: iam.IGrantable): iam.Grant {
return grantCreateInvalidation(this, identity);
}

private toBehavior(input: BehaviorWithOrigin, protoPolicy?: ViewerProtocolPolicy) {
Expand Down
38 changes: 38 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Match, Template } from '@aws-cdk/assertions';
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import * as s3 from '@aws-cdk/aws-s3';
import { App, Duration, Stack } from '@aws-cdk/core';
Expand Down Expand Up @@ -1025,3 +1026,40 @@ describe('supported HTTP versions', () => {
});
});
});

test('returns valid distribution ARN', () => {
const distribution = new Distribution(stack, 'Distribution', {
defaultBehavior: { origin: defaultOrigin() },
});

expect(stack.resolve(distribution.distributionArn))
.toEqual(stack.resolve(`arn:${stack.partition}:cloudfront::${stack.account}:distribution/${distribution.distributionId}`));
});

test('grants createInvalidation', () => {
const distribution = new Distribution(stack, 'Distribution', {
defaultBehavior: { origin: defaultOrigin() },
});
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.AccountRootPrincipal(),
});
distribution.grantCreateInvalidation(role);

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'cloudfront:CreateInvalidation',
Resource: {
'Fn::Join': [
'', [
'arn:', { Ref: 'AWS::Partition' }, ':cloudfront::1234:distribution/',
{ Ref: 'Distribution830FAC52' },
],
],
},
},
],
},
});
});
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"20.0.0"}
{"version":"21.0.0"}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "20.0.0",
"version": "21.0.0",
"files": {
"e56a9eb81eb88d748b8062f87488a5f2d9ec1137d330b9d5e0eb33f3ea9de5c7": {
"feb9d110131b95db8f916c66a3b8e62b8f66fec046bb499a952921f9fea2db03": {
"source": {
"path": "integ-distribution-basic.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "e56a9eb81eb88d748b8062f87488a5f2d9ec1137d330b9d5e0eb33f3ea9de5c7.json",
"objectKey": "feb9d110131b95db8f916c66a3b8e62b8f66fec046bb499a952921f9fea2db03.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,77 @@
]
}
}
},
"Role1ABCC5F0": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::",
{
"Ref": "AWS::AccountId"
},
":root"
]
]
}
}
}
],
"Version": "2012-10-17"
}
}
},
"RoleDefaultPolicy5FFB7DAB": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "cloudfront:CreateInvalidation",
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":cloudfront::",
{
"Ref": "AWS::AccountId"
},
":distribution/",
{
"Ref": "DistB3B78991"
}
]
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "RoleDefaultPolicy5FFB7DAB",
"Roles": [
{
"Ref": "Role1ABCC5F0"
}
]
}
}
},
"Parameters": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
{
"version": "20.0.0",
"version": "21.0.0",
"testCases": {
"integ.distribution-basic": {
"integ-distribution-basic/distribution-basic-test/DefaultTest": {
"stacks": [
"integ-distribution-basic"
],
"diffAssets": false,
"stackUpdateWorkflow": true
"assertionStack": "integ-distribution-basic/distribution-basic-test/DefaultTest/DeployAssert",
"assertionStackName": "integdistributionbasicdistributionbasictestDefaultTestDeployAssert2D53EBF0"
}
},
"synthContext": {},
"enableLookups": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "21.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "integdistributionbasicdistributionbasictestDefaultTestDeployAssert2D53EBF0.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading