Skip to content
6 changes: 6 additions & 0 deletions collectors/aws/collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,12 @@ var postcalls = [
reliesOnCall: 'listIdentities',
override: true,
rateLimit: 1000
},
getIdentityVerificationAttributes: {
reliesOnService: 'ses',
reliesOnCall: 'listIdentities',
override: true,
rateLimit: 1000
}
},
SNS: {
Expand Down
15 changes: 15 additions & 0 deletions collectors/aws/ses/getIdentityVerificationAttributes.js
Original file line number Diff line number Diff line change
@@ -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();
});
};
1 change: 1 addition & 0 deletions exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,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'),
Expand Down
74 changes: 74 additions & 0 deletions plugins/aws/ses/sesValidatedDomains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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, 2, 'Domain identity exists with unknown status', region, i);
} else if (identity.VerificationStatus == 'Success') {
helpers.addResult(results, 2,
'Domain identity exists and is verified', region, i);
} else if (identity.VerificationStatus == 'Pending') {
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, 2, 'Domain identity exists with unknown status', region, i);

}
}

rcb();
}, function(){
callback(null, results, source);
});
}
};
126 changes: 126 additions & 0 deletions plugins/aws/ses/sesValidatedDomains.spec.js
Original file line number Diff line number Diff line change
@@ -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 identity exists and is verified')
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(2)
expect(results[0].message).to.include('Domain identity exists and has verification that is pending')
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(2)
expect(results[0].message).to.include('Domain identity exists with unknown status')
done()
};

const cache = createCache(
[{
"Identities": [
"test@test.com"
]
}],
{
"VerificationAttributes": [
{}
]
}
);

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

})