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 convenience grant methods to IDistribution #22709

Merged
merged 3 commits into from Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

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

### Permissions

Use the `grant()` method to allow actions on the distribution.
`grantCreateInvalidation()` is a shorthand to allow `CreateInvalidation`.

```ts
declare const distribution: cloudfront.Distribution;
declare const lambdaFn: lambda.Function;
distribution.grant(lambdaFn, 'cloudfront:ListInvalidations', 'cloudfront:GetInvalidation');
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
45 changes: 45 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/distribution.ts
@@ -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 } from './private/utils';
import { IResponseHeadersPolicy } from './response-headers-policy';

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

/**
* Adds an IAM policy statement associated with this distribution to an IAM
* principal's policy.
*
* @param identity The principal
* @param actions The set of actions to allow (i.e. "cloudfront:ListInvalidations")
*/
grant(identity: iam.IGrantable, ...actions: string[]): iam.Grant;

/**
* 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 @@ -257,6 +275,13 @@ export class Distribution extends Resource implements IDistribution {
this.distributionDomainName = attrs.domainName;
this.distributionId = attrs.distributionId;
}

public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({ grantee, actions, resourceArns: [formatDistributionArn(this)] });
}
public grantCreateInvalidation(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee, 'cloudfront:CreateInvalidation');
}
}();
}

Expand Down Expand Up @@ -345,6 +370,26 @@ export class Distribution extends Resource implements IDistribution {
this.additionalBehaviors.push(new CacheBehavior(originId, { pathPattern, ...behaviorOptions }));
}

/**
* Adds an IAM policy statement associated with this distribution to an IAM
* principal's policy.
*
* @param identity The principal
* @param actions The set of actions to allow (i.e. "cloudfront:ListInvalidations")
*/
public grant(identity: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({ grantee: identity, actions, resourceArns: [formatDistributionArn(this)] });
}

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

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

Expand Down
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/private/utils.ts
@@ -0,0 +1,14 @@
import { Stack } from '@aws-cdk/core';
import { IDistribution } from '..';

/**
* Format distribution ARN from stack and distribution ID.
*/
export function formatDistributionArn(dist: IDistribution) {
return Stack.of(dist).formatArn({
service: 'cloudfront',
region: '',
resource: 'distribution',
resourceName: dist.distributionId,
});
}
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts
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 } from './private/utils';

/**
* HTTP status code to failover to second origin
Expand Down Expand Up @@ -758,6 +759,13 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu
this.distributionDomainName = attrs.domainName;
this.distributionId = attrs.distributionId;
}

public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({ grantee, actions, resourceArns: [formatDistributionArn(this)] });
}
public grantCreateInvalidation(identity: iam.IGrantable): iam.Grant {
return this.grant(identity, 'cloudfront:CreateInvalidation');
}
}();
}

Expand Down Expand Up @@ -983,6 +991,26 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu
this.distributionId = distribution.ref;
}

/**
* Adds an IAM policy statement associated with this distribution to an IAM
* principal's policy.
*
* @param identity The principal
* @param actions The set of actions to allow (i.e. "cloudfront:ListInvalidations")
*/
public grant(identity: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({ grantee: identity, actions, resourceArns: [formatDistributionArn(this)] });
}

/**
* 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 this.grant(identity, 'cloudfront:CreateInvalidation');
}

private toBehavior(input: BehaviorWithOrigin, protoPolicy?: ViewerProtocolPolicy) {
let toReturn = {
allowedMethods: this.METHOD_LOOKUP_MAP[input.allowedMethods || CloudFrontAllowedMethods.GET_HEAD],
Expand Down
60 changes: 60 additions & 0 deletions packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts
@@ -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,62 @@ describe('supported HTTP versions', () => {
});
});
});

test('grants custom actions', () => {
const distribution = new Distribution(stack, 'Distribution', {
defaultBehavior: { origin: defaultOrigin() },
});
const role = new iam.Role(stack, 'Role', {
assumedBy: new iam.AccountRootPrincipal(),
});
distribution.grant(role, 'cloudfront:ListInvalidations', 'cloudfront:GetInvalidation');

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: [
'cloudfront:ListInvalidations',
'cloudfront:GetInvalidation',
],
Resource: {
'Fn::Join': [
'', [
'arn:', { Ref: 'AWS::Partition' }, ':cloudfront::1234:distribution/',
{ Ref: 'Distribution830FAC52' },
],
],
},
},
],
},
});
});

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' },
],
],
},
},
],
},
});
});
@@ -1 +1 @@
{"version":"20.0.0"}
{"version":"21.0.0"}
@@ -1,15 +1,15 @@
{
"version": "20.0.0",
"version": "21.0.0",
"files": {
"e56a9eb81eb88d748b8062f87488a5f2d9ec1137d330b9d5e0eb33f3ea9de5c7": {
"36c6ded30c4c42464c2753c997c004bf740b9311a744c363fda5f951929a9504": {
"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": "36c6ded30c4c42464c2753c997c004bf740b9311a744c363fda5f951929a9504.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down