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

Feature/plugin/cloudtrail bucket ignore #297

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
64 changes: 56 additions & 8 deletions plugins/aws/cloudtrail/cloudtrailBucketAccessLogging.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
var async = require('async');
var helpers = require('../../../helpers/aws');

function bucketIsInAccount(allBuckets, targetBucketName) {
for (let i in allBuckets) {
let bucket = allBuckets[i];
if (bucket.Name === targetBucketName) {
return true; // target bucket present in account
}
}
return false; // not present in account
}

function bucketExists(err) {
return !(err &&
err.code &&
err.code === 'NoSuchBucket');
}

module.exports = {
title: 'CloudTrail Bucket Access Logging',
category: 'CloudTrail',
description: 'Ensures CloudTrail logging bucket has access logging enabled to detect tampering of log files',
more_info: 'CloudTrail buckets should utilize access logging for an additional layer of auditing. If the log files are deleted or modified in any way, the additional access logs can help determine who made the changes.',
recommended_action: 'Enable access logging on the CloudTrail bucket from the S3 console',
link: 'http://docs.aws.amazon.com/AmazonS3/latest/UG/ManagingBucketLogging.html',
apis: ['CloudTrail:describeTrails', 'S3:getBucketLogging'],
apis: ['CloudTrail:describeTrails', 'S3:getBucketLogging', 'S3:listBuckets'],
compliance: {
hipaa: 'Access logging for CloudTrail helps ensure strict integrity controls, ' +
'verifying that the audit logs for the AWS environment are not modified.',
Expand All @@ -17,14 +33,24 @@ module.exports = {
'helps audit the bucket in which these logs are stored.',
cis1: '2.6 Ensure CloudTrail bucket access logging is enabled'
},
settings: {
ignore_bucket_not_in_account: {
name: 'Ignore CloudTrail Buckets Not in Account',
description: 'enable to ignore cloudtrail buckets that are not in the account',
regex: '^(true|false)$', // string true or boolean true to enable, string false or boolean false to disable
default: false
},
},

run: function(cache, settings, callback) {
var config = {
ignore_bucket_not_in_account: settings.ignore_bucket_not_in_account || this.settings.ignore_bucket_not_in_account.default
};
if (config.ignore_bucket_not_in_account === 'false') config.ignore_bucket_not_in_account = false;
var results = [];
var source = {};
var regions = helpers.regions(settings);

async.each(regions.cloudtrail, function(region, rcb){

var describeTrails = helpers.addSource(cache, source,
['cloudtrail', 'describeTrails', region]);

Expand All @@ -48,15 +74,37 @@ module.exports = {

var s3Region = helpers.defaultRegion(settings);

const listBuckets = helpers.addSource(cache, source, ['s3', 'listBuckets', s3Region]);
if (!listBuckets) return cb();
if (listBuckets.err || !listBuckets.data) {
helpers.addResult(results, 3, `Unable to query for S3 buckets: ${helpers.addError(listBuckets)}`);
return cb();
}

var getBucketLogging = helpers.addSource(cache, source,
['s3', 'getBucketLogging', s3Region, trail.S3BucketName]);

if (!getBucketLogging || getBucketLogging.err || !getBucketLogging.data) {
helpers.addResult(results, 3,
'Error querying for bucket policy for bucket: ' + trail.S3BucketName + ': ' + helpers.addError(getBucketLogging),
if (!getBucketLogging || getBucketLogging.err) { // data will be {} if logging is disabled.
if (getBucketLogging && !bucketExists(getBucketLogging.err)) {
helpers.addResult(results, 2,
'Bucket: ' + trail.S3BucketName + ' does not exist' ,
region, 'arn:aws:s3:::' + trail.S3BucketName);

return cb();
}
else if (config.ignore_bucket_not_in_account && !bucketIsInAccount(listBuckets.data, trail.S3BucketName)) {
helpers.addResult(results, 0,
'Bucket: ' + trail.S3BucketName + ' is not in account',
region, 'arn:aws:s3:::' + trail.S3BucketName);

return cb();
return cb();
} else {
helpers.addResult(results, 3,
'Error querying for bucket policy for bucket: ' + trail.S3BucketName + ': ' + helpers.addError(getBucketLogging),
region, 'arn:aws:s3:::' + trail.S3BucketName);

return cb();
}
}

if (getBucketLogging &&
Expand All @@ -79,4 +127,4 @@ module.exports = {
callback(null, results, source);
});
}
};
};
199 changes: 199 additions & 0 deletions plugins/aws/cloudtrail/cloudtrailBucketAccessLogging.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
const assert = require('assert');
const expect = require('chai').expect;
const eks = require('./cloudtrailBucketAccessLogging');
const createCache = (descTrailsData, getBuckLogData, listBuckData, buckName) => {
let to_return = {
cloudtrail: {
describeTrails: {
'us-east-1': {
data: descTrailsData
}
},
},
s3: {
getBucketLogging: {
'us-east-1': {}
},
listBuckets: {
'us-east-1': {
data: listBuckData
}
},
}
};
to_return.s3.getBucketLogging['us-east-1'][buckName] = getBuckLogData;
return to_return
};

describe('cloudtrailBucketAccessLogging', function () {
describe('run', function () {
it('should PASS if CloudTrail logging bucket has access logging enabled', function (done) {
const callback = (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
done();
};

const cache = createCache(
[
{
"Name": "delete-me-ms",
"S3BucketName": "delete-me-ueoeuaou-ms",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"HomeRegion": "us-east-1",
"TrailARN": "arn:aws:cloudtrail:us-east-1:000000000000:trail/delete-me-ms",
"LogFileValidationEnabled": true,
"HasCustomEventSelectors": true,
"HasInsightSelectors": false,
"IsOrganizationTrail": false
},
],
{
data: {
"LoggingEnabled": {
"TargetPrefix": "foo",
"TargetBucket": "delete-me-ueoeuaou-ms"
}
},
},
[
{
"Name": "delete-me-ueoeuaou-ms",
"CreationDate": "2020-06-30T14:29:53.000Z"
},
],
'delete-me-ueoeuaou-ms'
);

eks.run(cache, {}, callback);
});

it('should WARN if CloudTrail logging bucket has access logging disabled', function (done) {
const callback = (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(1);
done();
};

const cache = createCache(
[
{
"Name": "delete-me-ms",
"S3BucketName": "delete-me-ueoeuaou-ms",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"HomeRegion": "us-east-1",
"TrailARN": "arn:aws:cloudtrail:us-east-1:000000000000:trail/delete-me-ms",
"LogFileValidationEnabled": true,
"HasCustomEventSelectors": true,
"HasInsightSelectors": false,
"IsOrganizationTrail": false
},
],
{
data: {},
},
[
{
"Name": "delete-me-ueoeuaou-ms",
"CreationDate": "2020-06-30T14:29:53.000Z"
},
],
'delete-me-ueoeuaou-ms'
);

eks.run(cache, {}, callback);
});

it('should FAIL if CloudTrail logging bucket has access logging enabled but bucket does not exist', function (done) {
const callback = (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
done();
};

const cache = createCache(
[
{
"Name": "delete-me-ms",
"S3BucketName": "delete-me-ueoeuaou-ms",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"HomeRegion": "us-east-1",
"TrailARN": "arn:aws:cloudtrail:us-east-1:000000000000:trail/delete-me-ms",
"LogFileValidationEnabled": true,
"HasCustomEventSelectors": true,
"HasInsightSelectors": false,
"IsOrganizationTrail": false
},
],
{
data: {},
err: {
"message": "The specified bucket does not exist",
"code": "NoSuchBucket",
"region": null,
"time": "2020-08-13T17:17:02.086Z",
"requestId": "0000000000000000",
"extendedRequestId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"statusCode": 404,
"retryable": false,
"retryDelay": 52.88593440570173
},
},
[
{
"Name": "delete-me-ueoeuaou-ms",
"CreationDate": "2020-06-30T14:29:53.000Z"
},
],
'delete-me-ueoeuaou-ms'
);

eks.run(cache, {}, callback);
});

it('should PASS if CloudTrail logging bucket has access logging enabled and bucket is another account, with ignore_bucket_not_in_account enabled', function (done) {
const callback = (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
done();
};

const cache = createCache(
[
{
"Name": "delete-me-ms",
"S3BucketName": "delete-me-ueoeuaou-ms",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": true,
"HomeRegion": "us-east-1",
"TrailARN": "arn:aws:cloudtrail:us-east-1:000000000000:trail/delete-me-ms",
"LogFileValidationEnabled": true,
"HasCustomEventSelectors": true,
"HasInsightSelectors": false,
"IsOrganizationTrail": false
},
],
{
data: {
"LoggingEnabled": {
"TargetPrefix": "foo",
"TargetBucket": "delete-me-ueoeuaou-ms"
},
},
},
[
{
"Name": "not-delete-me-ueoeuaou-ms",
"CreationDate": "2020-06-30T14:29:53.000Z"
},
],
'delete-me-ueoeuaou-ms'
);

eks.run(cache, {ignore_bucket_not_in_account: true}, callback);
});
})
});
Loading