Skip to content

Commit

Permalink
feat(aws-s3): support granting public access to objects (#886)
Browse files Browse the repository at this point in the history
Adds `bucket.grantPublicAccess` with a bunch of useful
capabilities.

Fixes #877
  • Loading branch information
Elad Ben-Israel authored Oct 10, 2018
1 parent c21ebb5 commit d730ac6
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 3 deletions.
35 changes: 35 additions & 0 deletions packages/@aws-cdk/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,41 @@ export abstract class BucketRef extends cdk.Construct {
this.arnForObjects(objectsKeyPattern));
}

/**
* Allows unrestricted access to objects from this bucket.
*
* IMPORTANT: This permission allows anyone to perform actions on S3 objects
* in this bucket, which is useful for when you configure your bucket as a
* website and want everyone to be able to read objects in the bucket without
* needing to authenticate.
*
* Without arguments, this method will grant read ("s3:GetObject") access to
* all objects ("*") in the bucket.
*
* The method returns the `iam.PolicyStatement` object, which can then be modified
* as needed. For example, you can add a condition that will restrict access only
* to an IPv4 range like this:
*
* const statement = bucket.grantPublicAccess();
* statement.addCondition('IpAddress', { "aws:SourceIp": "54.240.143.0/24" });
*
*
* @param keyPrefix the prefix of S3 object keys (e.g. `home/*`). Default is "*".
* @param allowedActions the set of S3 actions to allow. Default is "s3:GetObject".
* @returns The `iam.PolicyStatement` object, which can be used to apply e.g. conditions.
*/
public grantPublicAccess(keyPrefix = '*', ...allowedActions: string[]): iam.PolicyStatement {
allowedActions = allowedActions.length > 0 ? allowedActions : [ 's3:GetObject' ];

const statement = new iam.PolicyStatement()
.addActions(...allowedActions)
.addResource(this.arnForObjects(keyPrefix))
.addPrincipal(new iam.Anyone());

this.addToResourcePolicy(statement);
return statement;
}

private grant(identity: iam.IPrincipal | undefined,
bucketActions: string[],
keyActions: string[],
Expand Down
111 changes: 108 additions & 3 deletions packages/@aws-cdk/aws-s3/test/test.bucket.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { expect } from '@aws-cdk/assert';
import { expect, haveResource } from '@aws-cdk/assert';
import iam = require('@aws-cdk/aws-iam');
import kms = require('@aws-cdk/aws-kms');
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
import s3 = require('../lib');
import { Bucket } from '../lib';

// to make it easy to copy & paste from output:
// tslint:disable:object-literal-key-quotes
Expand Down Expand Up @@ -963,7 +962,7 @@ export = {

'urlForObject returns a token with the S3 URL of the token'(test: Test) {
const stack = new cdk.Stack();
const bucket = new Bucket(stack, 'MyBucket');
const bucket = new s3.Bucket(stack, 'MyBucket');

new cdk.Output(stack, 'BucketURL', { value: bucket.bucketUrl });
new cdk.Output(stack, 'MyFileURL', { value: bucket.urlForObject('my/file.txt') });
Expand Down Expand Up @@ -1059,5 +1058,111 @@ export = {
});

test.done();
},

'grantPublicAccess': {
'by default, grants s3:GetObject to all objects'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = new s3.Bucket(stack, 'b');

// WHEN
bucket.grantPublicAccess();

// THEN
expect(stack).to(haveResource('AWS::S3::BucketPolicy', {
"PolicyDocument": {
"Statement": [
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Principal": "*",
"Resource": { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "bC3BBCC65", "Arn" ] }, "/", "*" ] ] }
}
],
"Version": "2012-10-17"
}
}));
test.done();
},

'"keyPrefix" can be used to only grant access to certain objects'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = new s3.Bucket(stack, 'b');

// WHEN
bucket.grantPublicAccess('only/access/these/*');

// THEN
expect(stack).to(haveResource('AWS::S3::BucketPolicy', {
"PolicyDocument": {
"Statement": [
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Principal": "*",
"Resource": { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "bC3BBCC65", "Arn" ] }, "/", "only/access/these/*" ] ] }
}
],
"Version": "2012-10-17"
}
}));
test.done();
},

'"allowedActions" can be used to specify actions explicitly'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = new s3.Bucket(stack, 'b');

// WHEN
bucket.grantPublicAccess('*', 's3:GetObject', 's3:PutObject');

// THEN
expect(stack).to(haveResource('AWS::S3::BucketPolicy', {
"PolicyDocument": {
"Statement": [
{
"Action": [ "s3:GetObject", "s3:PutObject" ],
"Effect": "Allow",
"Principal": "*",
"Resource": { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "bC3BBCC65", "Arn" ] }, "/", "*" ] ] }
}
],
"Version": "2012-10-17"
}
}));
test.done();
},

'returns the PolicyStatement which can be then customized'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = new s3.Bucket(stack, 'b');

// WHEN
const statement = bucket.grantPublicAccess();
statement.addCondition('IpAddress', { "aws:SourceIp": "54.240.143.0/24" });

// THEN
expect(stack).to(haveResource('AWS::S3::BucketPolicy', {
"PolicyDocument": {
"Statement": [
{
"Action": "s3:GetObject",
"Effect": "Allow",
"Principal": "*",
"Resource": { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "bC3BBCC65", "Arn" ] }, "/", "*" ] ] },
"Condition": {
"IpAddress": { "aws:SourceIp": "54.240.143.0/24" }
}
}
],
"Version": "2012-10-17"
}
}));
test.done();
}
}
};

0 comments on commit d730ac6

Please sign in to comment.