diff --git a/exports.js b/exports.js index b93eb244d7..d5769fa842 100644 --- a/exports.js +++ b/exports.js @@ -811,6 +811,7 @@ module.exports = { 'bootVolumeRestorable' : require(__dirname + '/plugins/oracle/compute/bootVolumeRestorable.js'), 'bootVolumeBackupEnabled' : require(__dirname + '/plugins/oracle/compute/bootVolumeBackupEnabled.js'), 'instancePolicyProtection' : require(__dirname + '/plugins/oracle/compute/instancePolicyProtection.js'), + 'bootVolumeCMKEncryption' : require(__dirname + '/plugins/oracle/compute/bootVolumeCMKEncryption.js'), 'legacyEndpointDisabled' : require(__dirname + '/plugins/oracle/compute/legacyEndpointDisabled.js'), 'usersMfaEnabled' : require(__dirname + '/plugins/oracle/identity/usersMfaEnabled.js'), diff --git a/plugins/oracle/compute/bootVolumeCMKEncryption.js b/plugins/oracle/compute/bootVolumeCMKEncryption.js new file mode 100644 index 0000000000..86702c06aa --- /dev/null +++ b/plugins/oracle/compute/bootVolumeCMKEncryption.js @@ -0,0 +1,95 @@ +var async = require('async'); +var helpers = require('../../../helpers/oracle/'); + +module.exports = { + title: 'Boot Volume CMK Encryption', + category: 'Block Storage', + domain: 'Storage', + description: 'Ensures that boot volumes have encryption enabled using desired protection level.', + more_info: 'By default, boot volumes are encrypted using an Oracle-managed master encryption key. To have better control over the encryption process, you can use Customer-Managed Keys (CMKs).', + recommended_action: 'Ensure all boot volumes have desired encryption level.', + link: 'https://docs.oracle.com/en-us/iaas/Content/Security/Reference/blockstorage_security.htm#data-encryption', + apis: ['vault:list', 'keys:list', 'bootVolume:list'], + settings: { + volume_encryption_level: { + name: 'Boot Volume Encryption Level', + description: 'Desired protection level for boot volumes. default: oracle-managed, cloudcmek: customer managed encryption keys, ' + + 'cloudhsm: customer managed HSM encryption key', + regex: '^(default|cloudcmek|cloudhsm)$', + default: 'cloudcmek' + } + }, + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var regions = helpers.regions(settings.govcloud); + var keysObj = {}; + + let desiredEncryptionLevelStr = settings.volume_encryption_level || this.settings.volume_encryption_level.default; + var desiredEncryptionLevel = helpers.PROTECTION_LEVELS.indexOf(desiredEncryptionLevelStr); + + async.series([ + function(cb) { + async.each(regions.keys, function(region, rcb) { + let keys = helpers.addSource( + cache, source, ['keys', 'list', region]); + if (keys && keys.data && keys.data.length) helpers.listToObj(keysObj, keys.data, 'id'); + rcb(); + }, function() { + cb(); + }); + }, + function(cb) { + async.each(regions.bootVolume, function(region, rcb) { + + if (helpers.checkRegionSubscription(cache, source, results, region)) { + + var bootVolumes = helpers.addSource(cache, source, + ['bootVolume', 'list', region]); + + if (!bootVolumes) return rcb(); + + if (bootVolumes.err || !bootVolumes.data) { + helpers.addResult(results, 3, + 'Unable to query for boot volumes: ' + helpers.addError(bootVolumes), region); + return rcb(); + } + + if (!bootVolumes.data.length) { + helpers.addResult(results, 0, 'No boot volumes found', region); + return rcb(); + } + + bootVolumes.data.forEach(bootVolume => { + if (bootVolume.lifecycleState && bootVolume.lifecycleState === 'TERMINATED') return; + + let currentEncryptionLevel = 1; //default + + if (bootVolume.kmsKeyId) { + currentEncryptionLevel = helpers.getProtectionLevel(keysObj[bootVolume.kmsKeyId], helpers.PROTECTION_LEVELS); + } + + let currentEncryptionLevelStr = helpers.PROTECTION_LEVELS[currentEncryptionLevel]; + + if (currentEncryptionLevel >= desiredEncryptionLevel) { + helpers.addResult(results, 0, + `Boot volume (${bootVolume.displayName}) has encryption level ${currentEncryptionLevelStr} which is greater than or equal to ${desiredEncryptionLevelStr}`, region, bootVolume.id); + } else { + helpers.addResult(results, 2, + `Boot volume (${bootVolume.displayName}) has encryption level ${currentEncryptionLevelStr} which is less than ${desiredEncryptionLevelStr}`, region, bootVolume.id); + } + }); + } + + rcb(); + }, function() { + cb(); + }); + } + ], function() { + // Global checking goes here + callback(null, results, source); + }); + } +}; diff --git a/plugins/oracle/compute/bootVolumeCMKEncryption.spec.js b/plugins/oracle/compute/bootVolumeCMKEncryption.spec.js new file mode 100644 index 0000000000..bbb13e9521 --- /dev/null +++ b/plugins/oracle/compute/bootVolumeCMKEncryption.spec.js @@ -0,0 +1,189 @@ +var expect = require('chai').expect; +var plugin = require('./bootVolumeCMKEncryption'); + +const bootVolumes = [ + { + "compartmentId": "ocid1.tenancy.oc1.aaaaaa.111111", + "definedTags": {}, + "displayName": "vol-2", + "freeformTags": {}, + "systemTags": {}, + "id": "ocid1.volume.oc1.aaaaaa.1111111", + "isHydrated": true, + "kmsKeyId": null, + "lifecycleState": "AVAILABLE", + "performanceTier": null, + "vpusPerGB": null, + "sizeInGBs": 1024, + "sizeInMBs": 1048576, + "sourceDetails": null, + "timeCreated": "2019-08-29T21:46:01.836Z", + "volumeGroupId": null + }, + { + "compartmentId": "ocid1.tenancy.oc1.aaaaaa.111111", + "definedTags": {}, + "displayName": "vol-2", + "freeformTags": {}, + "systemTags": {}, + "id": "ocid1.volume.oc1.aaaaaa.1111111", + "isHydrated": true, + "lifecycleState": "AVAILABLE", + "performanceTier": null, + "vpusPerGB": null, + "sizeInGBs": 1024, + "sizeInMBs": 1048576, + "sourceDetails": null, + "timeCreated": "2019-08-29T21:46:01.836Z", + "volumeGroupId": null, + "kmsKeyId": 'key-1' + } +]; + +const createCache = (err, data) => { + return { + regionSubscription: { + "list": { + "us-ashburn-1": { + "data": [ + { + "regionKey": "IAD", + "regionName": "us-ashburn-1", + "status": "READY", + "isHomeRegion": true + }, + { + "regionKey": "LHR", + "regionName": "uk-london-1", + "status": "READY", + "isHomeRegion": false + }, + { + "regionKey": "PHX", + "regionName": "us-phoenix-1", + "status": "READY", + "isHomeRegion": false + } + ] + } + } + }, + bootVolume: { + list: { + 'us-ashburn-1': { + err: err, + data: data + } + } + }, + vault: { + list: { + 'us-ashburn-1': { + data: [ + { + "compartmentId": "compartment-1", + "displayName": "vault-1", + "freeformTags": {}, + "id": "vault-1", + "lifecycleState": "ACTIVE", + }, + ] + } + } + }, + keys: { + list: { + 'us-ashburn-1': { + data: [ + { + "compartmentId": "compartment-1", + "definedTags": {}, + "displayName": "key-1", + "freeformTags": {}, + "id": "key-1", + "lifecycleState": "ENABLED", + "timeCreated": "2022-04-30T19:49:12.841Z", + "vaultId": "vault-1", + "protectionMode": "SOFTWARE", + "algorithm": "AES" + } + ], + + } + } + } + } +}; + +describe('bootVolumeCMKEncryption', function () { + describe('run', function () { + it('should give unknown result if a boot volume error occurs', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(3) + expect(results[0].message).to.include('Unable to query for boot volumes') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + ['error'], + null + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if no boot volumes are found', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('No boot volumes found') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [] + ); + + plugin.run(cache, {}, callback); + }) + + + it('should give failing result if boot volume does not have desired encryption level', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(2) + expect(results[0].message).to.include('which is less') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [bootVolumes[0]] + ); + + plugin.run(cache, {}, callback); + }) + + it('should give passing result if boot volume has desired encryption level', function (done) { + const callback = (err, results) => { + expect(results.length).to.be.above(0) + expect(results[0].status).to.equal(0) + expect(results[0].message).to.include('which is greater than or equal to') + expect(results[0].region).to.equal('us-ashburn-1') + done() + }; + + const cache = createCache( + null, + [bootVolumes[1]] + ); + + plugin.run(cache, {}, callback); + }); + }); +}); \ No newline at end of file