Skip to content

Commit

Permalink
feat(aws-s3): add option to specify block public access settings (#1664)
Browse files Browse the repository at this point in the history
Adds option to enable all block public access settings on the bucket (`blockPublicAccess`). Also throws an error when trying to use grantPublicAccess if blockPublicAccess is set to true.

https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html
  • Loading branch information
jogold authored and Elad Ben-Israel committed Feb 4, 2019
1 parent 91194d4 commit 299fb6a
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 1 deletion.
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,33 @@ bucket.onEvent(s3.EventType.ObjectRemoved, myQueue, { prefix: 'foo/', suffix: '.
```

[S3 Bucket Notifications]: https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html


### Block Public Access

Use `blockPublicAccess` to specify [block public access settings] on the bucket.

Enable all block public access settings:
```ts
const bucket = new Bucket(this, 'MyBlockedBucket', {
blockPublicAccess: BlockPublicAccess.BlockAll
});
```

Block and ignore public ACLs:
```ts
const bucket = new Bucket(this, 'MyBlockedBucket', {
blockPublicAccess: BlockPublicAccess.BlockAcls
});
```

Alternatively, specify the settings manually:
```ts
const bucket = new Bucket(this, 'MyBlockedBucket', {
blockPublicAccess: new BlockPublicAccess({ blockPublicPolicy: true })
});
```

When `blockPublicPolicy` is set to `true`, `grantPublicRead()` throws an error.

[block public access settings]: https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html
80 changes: 79 additions & 1 deletion packages/@aws-cdk/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ export abstract class BucketBase extends cdk.Construct implements IBucket {
*/
protected abstract autoCreatePolicy = false;

/**
* Whether to disallow public access
*/
protected abstract disallowPublicAccess?: boolean;

/**
* Exports this bucket from the stack.
*/
Expand Down Expand Up @@ -514,6 +519,10 @@ export abstract class BucketBase extends cdk.Construct implements IBucket {
* @returns The `iam.PolicyStatement` object, which can be used to apply e.g. conditions.
*/
public grantPublicAccess(keyPrefix = '*', ...allowedActions: string[]): iam.PolicyStatement {
if (this.disallowPublicAccess) {
throw new Error("Cannot grant public access when 'blockPublicPolicy' is enabled");
}

allowedActions = allowedActions.length > 0 ? allowedActions : [ 's3:GetObject' ];

const statement = new iam.PolicyStatement()
Expand Down Expand Up @@ -555,6 +564,62 @@ export abstract class BucketBase extends cdk.Construct implements IBucket {
}
}

export interface BlockPublicAccessOptions {
/**
* Whether to block public ACLs
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options
*/
blockPublicAcls?: boolean;

/**
* Whether to block public policy
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options
*/
blockPublicPolicy?: boolean;

/**
* Whether to ignore public ACLs
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options
*/
ignorePublicAcls?: boolean;

/**
* Whether to restrict public access
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options
*/
restrictPublicBuckets?: boolean;
}

export class BlockPublicAccess {
public static readonly BlockAll = new BlockPublicAccess({
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true
});

public static readonly BlockAcls = new BlockPublicAccess({
blockPublicAcls: true,
ignorePublicAcls: true
});

public blockPublicAcls: boolean | undefined;
public blockPublicPolicy: boolean | undefined;
public ignorePublicAcls: boolean | undefined;
public restrictPublicBuckets: boolean | undefined;

constructor(options: BlockPublicAccessOptions) {
this.blockPublicAcls = options.blockPublicAcls;
this.blockPublicPolicy = options.blockPublicPolicy;
this.ignorePublicAcls = options.ignorePublicAcls;
this.restrictPublicBuckets = options.restrictPublicBuckets;
}
}

export interface BucketProps {
/**
* The kind of server-side encryption to apply to this bucket.
Expand Down Expand Up @@ -623,6 +688,13 @@ export interface BucketProps {
* Similar to calling `bucket.grantPublicAccess()`
*/
publicReadAccess?: boolean;

/**
* The block public access configuration of this bucket.
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html
*/
blockPublicAccess?: BlockPublicAccess;
}

/**
Expand Down Expand Up @@ -652,6 +724,7 @@ export class Bucket extends BucketBase {
public readonly encryptionKey?: kms.IEncryptionKey;
public policy?: BucketPolicy;
protected autoCreatePolicy = true;
protected disallowPublicAccess?: boolean;
private readonly lifecycleRules: LifecycleRule[] = [];
private readonly versioned?: boolean;
private readonly notifications: BucketNotifications;
Expand All @@ -666,7 +739,8 @@ export class Bucket extends BucketBase {
bucketEncryption,
versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined,
lifecycleConfiguration: new cdk.Token(() => this.parseLifecycleConfiguration()),
websiteConfiguration: this.renderWebsiteConfiguration(props)
websiteConfiguration: this.renderWebsiteConfiguration(props),
publicAccessBlockConfiguration: props.blockPublicAccess
});

cdk.applyRemovalPolicy(resource, props.removalPolicy !== undefined ? props.removalPolicy : cdk.RemovalPolicy.Orphan);
Expand All @@ -678,6 +752,7 @@ export class Bucket extends BucketBase {
this.domainName = resource.bucketDomainName;
this.bucketWebsiteUrl = resource.bucketWebsiteUrl;
this.dualstackDomainName = resource.bucketDualStackDomainName;
this.disallowPublicAccess = props.blockPublicAccess && props.blockPublicAccess.blockPublicPolicy;

// Add all lifecycle rules
(props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this));
Expand Down Expand Up @@ -1042,6 +1117,8 @@ class ImportedBucket extends BucketBase {
public policy?: BucketPolicy;
protected autoCreatePolicy: boolean;

protected disallowPublicAccess?: boolean;

constructor(scope: cdk.Construct, id: string, private readonly props: BucketImportProps) {
super(scope, id);

Expand All @@ -1059,6 +1136,7 @@ class ImportedBucket extends BucketBase {
? false
: props.bucketWebsiteNewUrlFormat;
this.policy = undefined;
this.disallowPublicAccess = false;
}

/**
Expand Down
83 changes: 83 additions & 0 deletions packages/@aws-cdk/aws-s3/test/test.bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,76 @@ export = {
test.done();
},

'bucket with block public access set to BlockAll'(test: Test) {
const stack = new cdk.Stack();
new s3.Bucket(stack, 'MyBucket', {
blockPublicAccess: s3.BlockPublicAccess.BlockAll,
});

expect(stack).toMatch({
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"Properties": {
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"BlockPublicPolicy": true,
"IgnorePublicAcls": true,
"RestrictPublicBuckets": true,
}
},
"DeletionPolicy": "Retain",
}
}
});
test.done();
},

'bucket with block public access set to BlockAcls'(test: Test) {
const stack = new cdk.Stack();
new s3.Bucket(stack, 'MyBucket', {
blockPublicAccess: s3.BlockPublicAccess.BlockAcls,
});

expect(stack).toMatch({
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"Properties": {
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
}
},
"DeletionPolicy": "Retain",
}
}
});
test.done();
},

'bucket with custom block public access setting'(test: Test) {
const stack = new cdk.Stack();
new s3.Bucket(stack, 'MyBucket', {
blockPublicAccess: new s3.BlockPublicAccess({ restrictPublicBuckets: true })
});

expect(stack).toMatch({
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"Properties": {
"PublicAccessBlockConfiguration": {
"RestrictPublicBuckets": true,
}
},
"DeletionPolicy": "Retain",
}
}
});
test.done();
},

'permissions': {

'addPermission creates a bucket policy'(test: Test) {
Expand Down Expand Up @@ -1175,6 +1245,19 @@ export = {
"Version": "2012-10-17"
}
}));
test.done();
},

'throws when blockPublicPolicy is set to true'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const bucket = new s3.Bucket(stack, 'MyBucket', {
blockPublicAccess: new s3.BlockPublicAccess({ blockPublicPolicy: true })
});

// THEN
test.throws(() => bucket.grantPublicAccess(), /blockPublicPolicy/);

test.done();
}
},
Expand Down

0 comments on commit 299fb6a

Please sign in to comment.