Skip to content

Commit

Permalink
Adds S3 public access block plugin (aquasecurity#222)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Barney Jr <[email protected]>
  • Loading branch information
2 people authored and matthewdfuller committed Jan 25, 2020
1 parent e215595 commit 392be26
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 0 deletions.
5 changes: 5 additions & 0 deletions collectors/aws/collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ var postcalls = [
signatureVersion: 'v4',
override: true
},
getPublicAccessBlock: {
deleteRegion: true,
signatureVersion: 'v4',
override: true
},
getBucketWebsite: {
deleteRegion: true,
signatureVersion: 'v4',
Expand Down
5 changes: 5 additions & 0 deletions collectors/aws/s3/getPublicAccessBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
var index = require(__dirname + '/index.js');

module.exports = function(AWSConfig, collection, callback) {
index('getPublicAccessBlock', false, AWSConfig, collection, callback);
};
1 change: 1 addition & 0 deletions exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ module.exports = {
'bucketAllUsersAcl' : require(__dirname + '/plugins/aws/s3/bucketAllUsersAcl.js'),
'bucketVersioning' : require(__dirname + '/plugins/aws/s3/bucketVersioning.js'),
'bucketLogging' : require(__dirname + '/plugins/aws/s3/bucketLogging.js'),
'bucketPublicAccessBlock' : require(__dirname + '/plugins/aws/s3/bucketPublicAccessBlock.js'),
'bucketEncryption' : require(__dirname + '/plugins/aws/s3/bucketEncryption.js'),
'bucketWebsiteEnabled' : require(__dirname + '/plugins/aws/s3/bucketWebsiteEnabled.js'),

Expand Down
52 changes: 52 additions & 0 deletions plugins/aws/s3/bucketPublicAccessBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
var async = require('async');
var helpers = require('../../../helpers/aws');

module.exports = {
title: 'S3 Bucket Public Access Block',
category: 'S3',
description: 'Ensures S3 public access block is enabled on all buckets',
link: 'https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html',
apis: ['S3:listBuckets', 'S3:getPublicAccessBlock'],

run: function(cache, settings, callback) {
var results = [];
var source = {};

var region = helpers.defaultRegion(settings);

var listBuckets = helpers.addSource(cache, source, ['s3', 'listBuckets', region]);

if (!listBuckets) return callback(null, results, source);
if (listBuckets.err || !listBuckets.data) {
helpers.addResult(results, 3, 'Unable to query for S3 buckets: ' + helpers.addError(listBuckets));
return callback(null, results, source);
}

if (!listBuckets.data.length) {
helpers.addResult(results, 0, 'No S3 buckets to check');
return callback(null, results, source);
}

for (let { Name: bucket } of listBuckets.data) {
var getPublicAccessBlock = helpers.addSource(cache, source, ['s3', 'getPublicAccessBlock', region, bucket]);
if (!getPublicAccessBlock) continue;
if (getPublicAccessBlock.err && getPublicAccessBlock.err.code === 'NoSuchPublicAccessBlockConfiguration') {
helpers.addResult(results, 2, 'Public Access Block not enabled', 'global', bucket);
continue;
}
if (getPublicAccessBlock.err || !getPublicAccessBlock.data) {
helpers.addResult(results, 3, `Error: ${helpers.addError(getPublicAccessBlock)}`, 'global', bucket);
continue;
}
var config = getPublicAccessBlock.data.PublicAccessBlockConfiguration;
var missingBlocks = Object.keys(config).filter(k => !config[k]);
if (missingBlocks.length) {
helpers.addResult(results, 2, `Missing public access blocks: ${missingBlocks.join(', ')}`, 'global', bucket);
continue;
}
helpers.addResult(results, 0, `Public access block fully enabled`, 'global', bucket);
}

callback(null, results, source);
}
};
214 changes: 214 additions & 0 deletions plugins/aws/s3/bucketPublicAccessBlock.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
var expect = require('chai').expect;
var bucketPublicAccessBlock = require('./bucketPublicAccessBlock');

const createCache = (BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets) => {
return {
s3: {
listBuckets: {
'us-east-1': {
data: [{
Name: 'mybucket',
}]
},
},
getPublicAccessBlock: {
'us-east-1': {
mybucket: {
data: {
PublicAccessBlockConfiguration: {
BlockPublicAcls,
IgnorePublicAcls,
BlockPublicPolicy,
RestrictPublicBuckets,
},
},
},
},
},
},
};
};

const createCacheNoPublicAccessBlock = () => {
return {
s3: {
listBuckets: {
'us-east-1': {
data: [{
Name: 'mybucket',
}]
},
},
getPublicAccessBlock: {
'us-east-1': {
mybucket: {
err: {
code: 'NoSuchPublicAccessBlockConfiguration',
},
},
},
},
},
};
};

const createCacheNullListBuckets = () => {
return {
s3: {
listBuckets: {
'us-east-1': null,
},
},
};
};

const createCacheNullGetPublicAccessBlock = () => {
return {
s3: {
listBuckets: {
'us-east-1': {
data: [{
Name: 'mybucket',
}],
},
},
getPublicAccessBlock: {
'us-east-1': {
mybucket: null,
},
},
},
};
};

const createCacheEmptyListBucket = () => {
return {
s3: {
listBuckets: {
'us-east-1': {
data: [],
},
},
},
};
};

const createCacheErrorListBucket = () => {
return {
s3: {
listBuckets: {
'us-east-1': {
error: {
message: 'bad error',
},
},
},
},
};
};

const createCacheErrorGetPublicAccessBlock = () => {
return {
s3: {
listBuckets: {
'us-east-1': {
data: [{
Name: 'mybucket',
}],
},
},
getPublicAccessBlock: {
'us-east-1': {
mybucket: {
error: {
message: 'bad error',
},
},
},
},
},
};
};

describe('bucketPublicAccessBlock', function () {
describe('run', function () {
it('should PASS if public access block is fully configured', function (done) {
const cache = createCache(true, true, true, true);
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
done();
});
});

it('should PASS if no buckets in account', function (done) {
const cache = createCacheEmptyListBucket();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
done();
});
});

it('should do nothing if null listBuckets', function (done) {
const cache = createCacheNullListBuckets();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(0);
done();
});
});

it('should UNKNOWN if error listing buckets', function (done) {
const cache = createCacheErrorListBucket();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(3);
done();
});
});

it('should FAIL if public access block is partially configured', function (done) {
const cache = createCache(true, true, false, false);
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
done();
});
});

it('should FAIL if public access block not found', function (done) {
const cache = createCacheNoPublicAccessBlock();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
done();
});
});

it('should FAIL if public access block not found', function (done) {
const cache = createCacheNoPublicAccessBlock();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
done();
});
});

it('should do nothing if null getPublicAccessBlock', function (done) {
const cache = createCacheNullGetPublicAccessBlock();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(0);
done();
});
});

it('should UNKNOWN if error getting public access block', function (done) {
const cache = createCacheErrorGetPublicAccessBlock();
bucketPublicAccessBlock.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(3);
done();
});
});
});
});

0 comments on commit 392be26

Please sign in to comment.