Skip to content

Commit

Permalink
feat(cloudfront): add convenience grant methods to IDistribution (#22709
Browse files Browse the repository at this point in the history
)

This PR adds convenience grant methods to `IDistribution`.

- `grant(identity, ...actions)` - generic grant method
- `grantCreateInvalidation(identity)` - shorthand to grant `cloudfront:CreateInvalidation` action.

Fixes #13159

----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Tietew committed Nov 30, 2022
1 parent 85354f6 commit 2eb7d73
Show file tree
Hide file tree
Showing 15 changed files with 777 additions and 21 deletions.
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 @@ -1079,3 +1080,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

0 comments on commit 2eb7d73

Please sign in to comment.