diff --git a/exports.js b/exports.js index dcbf2a3dbb..e5850da5ac 100644 --- a/exports.js +++ b/exports.js @@ -497,6 +497,7 @@ module.exports = { 'ssmSessionDuration' : require(__dirname + '/plugins/aws/ssm/ssmSessionDuration'), 'ledgerEncrypted' : require(__dirname + '/plugins/aws/qldb/ledgerEncrypted'), + 'ledgerDeletionProtection' : require(__dirname + '/plugins/aws/qldb/ledgerDeletionProtection'), 'lambdaAdminPrivileges' : require(__dirname + '/plugins/aws/lambda/lambdaAdminPrivileges.js'), 'envVarsClientSideEncryption' : require(__dirname + '/plugins/aws/lambda/envVarsClientSideEncryption.js'), diff --git a/plugins/aws/qldb/ledgerDeletionProtection.js b/plugins/aws/qldb/ledgerDeletionProtection.js new file mode 100644 index 0000000000..586866037c --- /dev/null +++ b/plugins/aws/qldb/ledgerDeletionProtection.js @@ -0,0 +1,73 @@ +var async = require('async'); +var helpers = require('../../../helpers/aws'); + +module.exports = { + title: 'Ledger Deletion Protection', + category: 'QLDB', + domain: 'Databases', + severity: 'Medium', + description: 'Ensure that AWS QLDB ledger has deletion protection feature enabled.', + more_info: 'Enabling deletion protection feature for Amazon QLDB ledger acts as a safety net, preventing accidental database deletions or deletion by an unauthorized user. It ensures that the data stays secure and accessible at all times.', + recommended_action: 'Modify QLDB ledger and enable deletion protection.', + link: 'https://docs.aws.amazon.com/qldb/latest/developerguide/ledger-management.basics.html', + apis: ['QLDB:listLedgers','QLDB:describeLedger','STS:getCallerIdentity'], + realtime_triggers: ['qldb:CreateLedger', 'qldb:UpdateLedger', 'qldb:DeleteLedger'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings); + + var defaultRegion = helpers.defaultRegion(settings); + var awsOrGov = helpers.defaultPartition(settings); + var accountId = helpers.addSource(cache, source, ['sts', 'getCallerIdentity', defaultRegion, 'data']); + + async.each(regions.qldb, function(region, rcb){ + var listLedgers = helpers.addSource(cache, source, + ['qldb', 'listLedgers', region]); + + if (!listLedgers) return rcb(); + + if (listLedgers.err || !listLedgers.data) { + helpers.addResult(results, 3, + 'Unable to query Ledgers: ' + helpers.addError(listLedgers), region); + return rcb(); + } + + if (!listLedgers.data.length) { + helpers.addResult(results, 0, 'No Ledgers found', region); + return rcb(); + } + + for (let ledger of listLedgers.data) { + if (!ledger.Name) continue; + + let resource = `arn:${awsOrGov}:qldb:${region}:${accountId}:ledger/${ledger.Name}`; + + var describeLedger = helpers.addSource(cache, source, + ['qldb', 'describeLedger', region, ledger.Name]); + + if (!describeLedger || describeLedger.err || !describeLedger.data ) { + helpers.addResult(results, 3, + `Unable to get Ledgers description: ${helpers.addError(describeLedger)}`, + region, resource); + continue; + } + + if (describeLedger.data.DeletionProtection) { + helpers.addResult(results, 0, + 'QLDB ledger has deletion protection enabled', + region, resource); + } else { + helpers.addResult(results, 2, + 'QLDB ledger does not have deletion protection enabled', + region, resource); + } + } + + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; \ No newline at end of file diff --git a/plugins/aws/qldb/ledgerDeletionProtection.spec.js b/plugins/aws/qldb/ledgerDeletionProtection.spec.js new file mode 100644 index 0000000000..a22d30c62c --- /dev/null +++ b/plugins/aws/qldb/ledgerDeletionProtection.spec.js @@ -0,0 +1,96 @@ +var expect = require('chai').expect; +var ledgerDeletionProtection = require('./ledgerDeletionProtection'); + +const listLedgers = [ + { + "Name": "test-ledger", + "State": "ACTIVE", + "CreationDateTime": "2021-11-19T16:29:08.899000+05:00" + } +]; + +const describeLedger = [ + { + "Name": "test-ledger", + "Arn": "arn:aws:qldb:us-east-1:000111222333:ledger/test-ledger", + "State": "ACTIVE", + "CreationDateTime": "2021-11-19T16:29:08.899000+05:00", + "PermissionsMode": "STANDARD", + "DeletionProtection": true, + }, + { + "Name": "test-ledger", + "Arn": "arn:aws:qldb:us-east-1:000111222333:ledger/test-ledger", + "State": "ACTIVE", + "CreationDateTime": "2021-11-19T16:29:08.899000+05:00", + "PermissionsMode": "STANDARD", + "DeletionProtection": false, + } +]; + +const createCache = (ledgers, describeLedger, ledgersErr, describeLedgerErr) => { + var name = (ledgers && ledgers.length) ? ledgers[0].Name: null; + return { + qldb: { + listLedgers: { + 'us-east-1': { + err: ledgersErr, + data: ledgers + }, + }, + describeLedger: { + 'us-east-1': { + [name]: { + data: describeLedger, + err: describeLedgerErr + } + } + } + }, + }; +}; + +describe('ledgerDeletionProtection', function () { + describe('run', function () { + it('should PASS if QLDB ledger has deletion protection enabled', function (done) { + const cache = createCache(listLedgers, describeLedger[0]); + ledgerDeletionProtection.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].region).to.equal('us-east-1'); + done(); + }); + }); + + it('should FAIL if QLDb ledger does not have deletion protection enabled', function (done) { + const cache = createCache(listLedgers, describeLedger[1]); + ledgerDeletionProtection.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].region).to.equal('us-east-1'); + done(); + }); + }); + + it('should PASS if no QLDB ledgers found', function (done) { + const cache = createCache([]); + ledgerDeletionProtection.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].region).to.equal('us-east-1'); + done(); + }); + }); + + it('should UNKNOWN if unable to list QLDB ledgers', function (done) { + const cache = createCache(null, null, null, { message: "Unable to list QLDB ledgers" }); + ledgerDeletionProtection.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].region).to.equal('us-east-1'); + done(); + }); + }); + + }); +})