From 9c0fa4727d73e32d17fd5157d4b997c35f913b0b Mon Sep 17 00:00:00 2001 From: Robert Ramsey Date: Fri, 3 Apr 2020 16:20:14 -0400 Subject: [PATCH 1/3] Add collector --- collectors/aws/collector.js | 6 ++++++ .../aws/ses/getIdentityVerificationAttributes.js | 15 +++++++++++++++ plugins/aws/ses/sesValidatedDomains.js | 0 3 files changed, 21 insertions(+) create mode 100644 collectors/aws/ses/getIdentityVerificationAttributes.js create mode 100644 plugins/aws/ses/sesValidatedDomains.js diff --git a/collectors/aws/collector.js b/collectors/aws/collector.js index 99d405c13a..b8b348a673 100644 --- a/collectors/aws/collector.js +++ b/collectors/aws/collector.js @@ -754,6 +754,12 @@ var postcalls = [ reliesOnCall: 'listIdentities', override: true, rateLimit: 1000 + }, + getIdentityVerificationAttributes: { + reliesOnService: 'ses', + reliesOnCall: 'listIdentities', + override: true, + rateLimit: 1000 } }, SNS: { diff --git a/collectors/aws/ses/getIdentityVerificationAttributes.js b/collectors/aws/ses/getIdentityVerificationAttributes.js new file mode 100644 index 0000000000..8d38d5d6b4 --- /dev/null +++ b/collectors/aws/ses/getIdentityVerificationAttributes.js @@ -0,0 +1,15 @@ +var AWS = require('aws-sdk'); + +module.exports = function(AWSConfig, collection, callback) { + var ses = new AWS.SES(AWSConfig); + + ses.getIdentityVerificationAttributes({Identities: collection.ses.listIdentities[AWSConfig.region].data}, function(err, data){ + if (err) { + collection.ses.getIdentityVerificationAttributes[AWSConfig.region].err = err; + } + + collection.ses.getIdentityVerificationAttributes[AWSConfig.region].data = data; + + callback(); + }); +}; \ No newline at end of file diff --git a/plugins/aws/ses/sesValidatedDomains.js b/plugins/aws/ses/sesValidatedDomains.js new file mode 100644 index 0000000000..e69de29bb2 From 0f8ae2fd50ebac7b5c83c47ad73c7ed423fe6e72 Mon Sep 17 00:00:00 2001 From: Robert Ramsey Date: Mon, 6 Apr 2020 13:58:10 -0400 Subject: [PATCH 2/3] Add plugin and tests --- exports.js | 1 + plugins/aws/ses/sesValidatedDomains.js | 68 +++++++++++ plugins/aws/ses/sesValidatedDomains.spec.js | 126 ++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 plugins/aws/ses/sesValidatedDomains.spec.js diff --git a/exports.js b/exports.js index 4ca9f15daa..c2ed02f9d5 100644 --- a/exports.js +++ b/exports.js @@ -152,6 +152,7 @@ module.exports = { 'notebookDirectInternetAccess' : require(__dirname + '/plugins/aws/sagemaker/notebookDirectInternetAccess.js'), 'dkimEnabled' : require(__dirname + '/plugins/aws/ses/dkimEnabled.js'), + 'sesValidatedDomains' : require(__dirname + '/plugins/aws/ses/sesValidatedDomains.js'), 'topicPolicies' : require(__dirname + '/plugins/aws/sns/topicPolicies.js'), 'sqsCrossAccount' : require(__dirname + '/plugins/aws/sqs/sqsCrossAccount.js'), diff --git a/plugins/aws/ses/sesValidatedDomains.js b/plugins/aws/ses/sesValidatedDomains.js index e69de29bb2..ee3ab48065 100644 --- a/plugins/aws/ses/sesValidatedDomains.js +++ b/plugins/aws/ses/sesValidatedDomains.js @@ -0,0 +1,68 @@ +var async = require('async'); +var helpers = require('../../../helpers/aws'); + +module.exports = { + title: 'SES Validated Domains Flagged', + category: 'SES', + description: 'Ensures that only Email Identities are used for verification of identities.', + more_info: 'SES allows for either domains or email addresses to be valid identities. This checks to ensure that only emails have been verified.', + recommended_action: 'Remove any domains', + link: 'https://docs.aws.amazon.com/ses/latest/DeveloperGuide/remove-verified-domain.html', + apis: ['SES:listIdentities', 'SES:getIdentityVerificationAttributes'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + async.each(regions.ses, function(region, rcb){ + var listIdentities = helpers.addSource(cache, source, + ['ses', 'listIdentities', region]); + + if (!listIdentities) return rcb(); + + if (listIdentities.err || !listIdentities.data) { + helpers.addResult(results, 3, + 'Unable to query for SES identities: ' + helpers.addError(listIdentities), region); + return rcb(); + } + + if (!listIdentities.data.length) { + helpers.addResult(results, 0, 'No SES domain identities found', region); + return rcb(); + } + + var getIdentityVerificationAttributes = helpers.addSource(cache, source, + ['ses', 'getIdentityVerificationAttributes', region]); + + if (!getIdentityVerificationAttributes || + getIdentityVerificationAttributes.err || + !getIdentityVerificationAttributes.data) { + helpers.addResult(results, 3, + 'Unable to get SES Verification attributes: ' + helpers.addError(getIdentityVerificationAttributes), region); + return rcb(); + } + + for (i in getIdentityVerificationAttributes.data.VerificationAttributes) { + var identity = getIdentityVerificationAttributes.data.VerificationAttributes[i]; + + if (!identity.VerificationStatus) { + helpers.addResult(results, 1, 'Verification has not been requested', region, i); + } else if (identity.VerificationStatus == 'Success') { + helpers.addResult(results, 2, + 'Domain is being used as the verified identity', region, i); + } else if (identity.VerificationStatus == 'Pending') { + helpers.addResult(results, 1, + 'Domain has not been verified, but has requested verification', region, i); + } else { + helpers.addResult(results, 0, + 'Verification is configured properly', region, i); + } + } + + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; \ No newline at end of file diff --git a/plugins/aws/ses/sesValidatedDomains.spec.js b/plugins/aws/ses/sesValidatedDomains.spec.js new file mode 100644 index 0000000000..d4c2f52dbf --- /dev/null +++ b/plugins/aws/ses/sesValidatedDomains.spec.js @@ -0,0 +1,126 @@ +var assert = require('assert'); +var expect = require('chai').expect; +var ses = require('./sesValidatedDomains'); + +const createCache = (lData, gData) => { + return { + ses: { + listIdentities: { + 'us-east-1': { + err: null, + data: lData + } + }, + getIdentityVerificationAttributes: { + 'us-east-1': { + err: null, + data: gData + } + } + + } + } +}; + +describe('sesValidatedDomains', function () { + describe('run', function () { + it('should give passing result if no domain identities are found', function (done) { + const callback = (err, results) => { + expect(results.length).to.equal(1) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No SES domain identities found') + done() + }; + + const cache = createCache( + [], + [] + ); + + ses.run(cache, {}, callback); + }) + }); + + describe('run', function () { + it('should give error result if verified domain identities exist', function (done) { + const callback = (err, results) => { + expect(results.length).to.equal(1) + expect(results[0].status).to.equal(2) + expect(results[0].message).to.include('Domain is being used as the verified identity') + done() + }; + + const cache = createCache( + [{ + "Identities": [ + "test@test.com" + ] + }], + { + "VerificationAttributes": [ + { + "VerificationStatus": "Success" + } + ] + } + ); + + ses.run(cache, {}, callback); + }) + }) + + describe('run', function () { + it('should give warning result if verified domain identities are pending', function (done) { + const callback = (err, results) => { + expect(results.length).to.equal(1) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('Domain has not been verified, but has requested verification') + done() + }; + + const cache = createCache( + [{ + "Identities": [ + "test@test.com" + ] + }], + { + "VerificationAttributes": [ + { + "VerificationStatus": "Pending" + } + ] + } + ); + + ses.run(cache, {}, callback); + }) + }) + + describe('run', function () { + it('should give warning result if domain identity exists, but verification has not yet been requested', function (done) { + const callback = (err, results) => { + expect(results.length).to.equal(1) + expect(results[0].status).to.equal(1) + expect(results[0].message).to.include('Verification has not been requested') + done() + }; + + const cache = createCache( + [{ + "Identities": [ + "test@test.com" + ] + }], + { + "VerificationAttributes": [ + {} + ] + } + ); + + ses.run(cache, {}, callback); + }) + }) + +}) \ No newline at end of file From e0d495a9522fb4071b772ab7a502d12b74eefe71 Mon Sep 17 00:00:00 2001 From: Robert Ramsey Date: Thu, 9 Apr 2020 16:21:27 -0400 Subject: [PATCH 3/3] Ensure failure for any domains --- plugins/aws/ses/sesValidatedDomains.js | 18 ++++++++++++------ plugins/aws/ses/sesValidatedDomains.spec.js | 10 +++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/aws/ses/sesValidatedDomains.js b/plugins/aws/ses/sesValidatedDomains.js index ee3ab48065..1f59cdd876 100644 --- a/plugins/aws/ses/sesValidatedDomains.js +++ b/plugins/aws/ses/sesValidatedDomains.js @@ -47,16 +47,22 @@ module.exports = { var identity = getIdentityVerificationAttributes.data.VerificationAttributes[i]; if (!identity.VerificationStatus) { - helpers.addResult(results, 1, 'Verification has not been requested', region, i); + helpers.addResult(results, 2, 'Domain identity exists with unknown status', region, i); } else if (identity.VerificationStatus == 'Success') { helpers.addResult(results, 2, - 'Domain is being used as the verified identity', region, i); + 'Domain identity exists and is verified', region, i); } else if (identity.VerificationStatus == 'Pending') { - helpers.addResult(results, 1, - 'Domain has not been verified, but has requested verification', region, i); + helpers.addResult(results, 2, + 'Domain identity exists and has verification that is pending', region, i); + } else if (identity.VerificationStatus == 'Failed') { + helpers.addResult(results, 2, + 'Domain identity exists but verification failed', region, i); + } else if (identity.VerificationStatus == 'TemporaryFailure') { + helpers.addResult(results, 2, + 'Domain identity exists but verification temporarily failed', region, i); } else { - helpers.addResult(results, 0, - 'Verification is configured properly', region, i); + helpers.addResult(results, 2, 'Domain identity exists with unknown status', region, i); + } } diff --git a/plugins/aws/ses/sesValidatedDomains.spec.js b/plugins/aws/ses/sesValidatedDomains.spec.js index d4c2f52dbf..8fe1d8597d 100644 --- a/plugins/aws/ses/sesValidatedDomains.spec.js +++ b/plugins/aws/ses/sesValidatedDomains.spec.js @@ -46,7 +46,7 @@ describe('sesValidatedDomains', function () { const callback = (err, results) => { expect(results.length).to.equal(1) expect(results[0].status).to.equal(2) - expect(results[0].message).to.include('Domain is being used as the verified identity') + expect(results[0].message).to.include('Domain identity exists and is verified') done() }; @@ -73,8 +73,8 @@ describe('sesValidatedDomains', function () { it('should give warning result if verified domain identities are pending', function (done) { const callback = (err, results) => { expect(results.length).to.equal(1) - expect(results[0].status).to.equal(1) - expect(results[0].message).to.include('Domain has not been verified, but has requested verification') + expect(results[0].status).to.equal(2) + expect(results[0].message).to.include('Domain identity exists and has verification that is pending') done() }; @@ -101,8 +101,8 @@ describe('sesValidatedDomains', function () { it('should give warning result if domain identity exists, but verification has not yet been requested', function (done) { const callback = (err, results) => { expect(results.length).to.equal(1) - expect(results[0].status).to.equal(1) - expect(results[0].message).to.include('Verification has not been requested') + expect(results[0].status).to.equal(2) + expect(results[0].message).to.include('Domain identity exists with unknown status') done() };