From e939cd0a268a4ff19c971ba14563642d94505df8 Mon Sep 17 00:00:00 2001 From: AkhtarAmir Date: Wed, 6 Nov 2024 16:19:11 +0500 Subject: [PATCH] Revised keyVaultKeyExpiryNonRbac (2) --- exports.js | 3 +- .../keyvaults/keyVaultKeyExpiryNonRbac.js | 101 ++++++++++ .../keyVaultKeyExpiryNonRbac.spec.js | 175 ++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js create mode 100644 plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js diff --git a/exports.js b/exports.js index dcbf2a3dbb..c33c16f34b 100644 --- a/exports.js +++ b/exports.js @@ -1063,7 +1063,8 @@ module.exports = { 'manageKeyAccessAndPermissions' : require(__dirname + '/plugins/azure/keyvaults/manageKeyAccessAndPermissions.js'), 'rsaCertificateKeySize' : require(__dirname + '/plugins/azure/keyvaults/rsaCertificateKeySize.js'), 'keyVaultSecretExpiry' : require(__dirname + '/plugins/azure/keyvaults/keyVaultSecretExpiry.js'), - 'keyVaultKeyExpiry' : require(__dirname + '/plugins/azure/keyvaults/keyVaultKeyExpiry.js'), + 'keyVaultKeyExpiry': require(__dirname + '/plugins/azure/keyvaults/keyVaultKeyExpiry.js'), + 'keyVaultKeyExpiryNonRbac' : require(__dirname + '/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js'), 'allowedCertificateKeyTypes' : require(__dirname + '/plugins/azure/keyvaults/allowedCertificateKeyTypes.js'), 'appTierCmkInUse' : require(__dirname + '/plugins/azure/keyvaults/appTierCmkInUse.js'), 'keyVaultInUse' : require(__dirname + '/plugins/azure/keyvaults/keyVaultInUse.js'), diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js new file mode 100644 index 0000000000..d71cab70a2 --- /dev/null +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js @@ -0,0 +1,101 @@ +var async = require('async'); +var helpers = require('../../../helpers/azure'); + +module.exports = { + title: 'Key Vault Key Expiry Non-RBAC', + category: 'Key Vaults', + domain: 'Application Integration', + severity: 'High', + description: 'Ensures that expiration date is set for all keys in Non-RBAC Key Vaults.', + more_info: 'Setting an expiration date on keys helps in key lifecycle management and ensures that keys are rotated regularly.', + recommended_action: 'Modify keys in Non-RBAC Key Vaults to have an expiration date set.', + link: 'https://learn.microsoft.com/en-us/azure/key-vault/about-keys-secrets-and-certificates', + apis: ['vaults:list', 'vaults:getKeys'], + settings: { + key_vault_key_expiry_fail: { + name: 'Key Vault Key Expiry Fail', + description: 'Return a failing result when key expiration date is within this number of days in the future', + regex: '^[1-9]{1}[0-9]{0,3}$', + default: '30' + } + }, + realtime_triggers: ['microsoftkeyvault:vaults:write', 'microsoftkeyvault:vaults:delete'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var locations = helpers.locations(settings.govcloud); + var config = { + key_vault_key_expiry_fail: parseInt(settings.key_vault_key_expiry_fail || this.settings.key_vault_key_expiry_fail.default) + }; + + async.each(locations.vaults, function(location, rcb) { + var vaults = helpers.addSource(cache, source, + ['vaults', 'list', location]); + + if (!vaults) return rcb(); + + if (vaults.err || !vaults.data) { + helpers.addResult(results, 3, 'Unable to query for Key Vaults: ' + helpers.addError(vaults), location); + return rcb(); + } + + if (!vaults.data.length) { + helpers.addResult(results, 0, 'No Key Vaults found', location); + return rcb(); + } + + vaults.data.forEach(function(vault) { + if (!vault || !vault.properties) { + helpers.addResult(results, 3, 'Unable to read vault properties', location, vault.id); + return; + } + + if (vault.properties.enableRbacAuthorization) { + return; + } + + var keys = helpers.addSource(cache, source, + ['vaults', 'getKeys', location, vault.id]); + + if (!keys || keys.err || !keys.data) { + helpers.addResult(results, 3, + 'Unable to query for Key Vault keys: ' + helpers.addError(keys), location, vault.id); + } else if (!keys.data.length) { + helpers.addResult(results, 0, + 'No Key Vault keys found in non-RBAC vault', location, vault.id); + } else { + keys.data.forEach(function(key) { + var keyName = key.kid.substring(key.kid.lastIndexOf('/') + 1); + var keyId = `${vault.id}/keys/${keyName}`; + + if (!key.attributes || !key.attributes.enabled) { + helpers.addResult(results, 0, + 'Key in non-RBAC vault is not enabled', location, keyId); + } else if (key.attributes && (key.attributes.expires || key.attributes.exp)) { + let keyExpiry = key.attributes.exp ? key.attributes.exp * 1000 : key.attributes.expires; + let difference = Math.round((new Date(keyExpiry).getTime() - (new Date).getTime())/(24*60*60*1000)); + if (difference > config.key_vault_key_expiry_fail) { + helpers.addResult(results, 0, + `Key in non-RBAC vault expires in ${difference} days`, location, keyId); + } else if (difference > 0){ + helpers.addResult(results, 2, + `Key in non-RBAC vault expires in ${difference} days`, location, keyId); + } else { + helpers.addResult(results, 2, + `Key in non-RBAC vault expired ${Math.abs(difference)} days ago`, location, keyId); + } + } else { + helpers.addResult(results, 0, + 'Key expiration is not enabled in non-RBAC vault', location, keyId); + } + }); + } + }); + + rcb(); + }, function() { + callback(null, results, source); + }); + } +}; diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js new file mode 100644 index 0000000000..678642295e --- /dev/null +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js @@ -0,0 +1,175 @@ +var expect = require('chai').expect; +var auth = require('./keyVaultKeyExpiryNonRbac'); + +var keyExpiryPass = new Date(); +keyExpiryPass.setMonth(keyExpiryPass.getMonth() + 2); + +var keyExpiryFail = new Date(); +keyExpiryFail.setMonth(keyExpiryFail.getMonth() + 1); + +var keyExpired = new Date(); +keyExpired.setMonth(keyExpired.getMonth() - 1); + +const listKeyVaults = [ + { + id: '/subscriptions/123/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault', + name: 'test-vault', + type: 'Microsoft.KeyVault/vaults', + location: 'eastus', + properties: { + enableRbacAuthorization: false, + vaultUri: 'https://test-vault.vault.azure.net/' + } + }, + { + id: '/subscriptions/123/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault-2', + name: 'test-vault-2', + type: 'Microsoft.KeyVault/vaults', + location: 'eastus', + properties: { + enableRbacAuthorization: true, + vaultUri: 'https://test-vault-2.vault.azure.net/' + } + } +]; + +const getKeys = [ + { + "attributes": { + "created": "2022-04-10T17:57:43+00:00", + "enabled": true, + "expires": null, + "notBefore": null, + "updated": "2022-04-10T17:57:43+00:00" + }, + "kid": "https://test-vault.vault.azure.net/keys/test-key", + "name": "test-key" + }, + { + "attributes": { + "created": "2022-04-10T17:57:43+00:00", + "enabled": true, + "expires": keyExpiryPass, + "notBefore": null, + "updated": "2022-04-10T17:57:43+00:00" + }, + "kid": "https://test-vault.vault.azure.net/keys/test-key-2", + "name": "test-key-2" + }, + { + "attributes": { + "created": "2022-04-10T17:57:43+00:00", + "enabled": true, + "expires": keyExpiryFail, + "notBefore": null, + "updated": "2022-04-10T17:57:43+00:00" + }, + "kid": "https://test-vault.vault.azure.net/keys/test-key-3", + "name": "test-key-3" + }, + { + "attributes": { + "created": "2022-04-10T17:57:43+00:00", + "enabled": true, + "expires": keyExpired, + "notBefore": null, + "updated": "2022-04-10T17:57:43+00:00" + }, + "kid": "https://test-vault.vault.azure.net/keys/test-key-4", + "name": "test-key-4" + } +]; + +const createCache = (err, list, keys) => { + return { + vaults: { + list: { + 'eastus': { + err: err, + data: list + } + }, + getKeys: { + 'eastus': { + '/subscriptions/123/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault': { + err: err, + data: keys + } + } + } + } + } +}; + +describe('keyVaultKeyExpiryNonRbac', function() { + describe('run', function() { + it('should give passing result if no key vaults 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 Key Vaults found'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [], {}), {}, callback); + }); + + it('should give passing result if no non-RBAC key vaults found', function(done) { + const callback = (err, results) => { + expect(results.length).to.equal(0); // No results since we skip RBAC vaults + done() + }; + + auth.run(createCache(null, [listKeyVaults[1]], []), {}, callback); + }); + + it('should give passing result if expiration is not set on keys in non-RBAC vault', 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('Key expiration is not enabled in non-RBAC vault'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], [getKeys[0]]), {}, callback); + }); + + it('should give passing result if expiry date is not yet reached in non-RBAC vault', 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('Key in non-RBAC vault expires in'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], [getKeys[1]]), { key_vault_key_expiry_fail: '30' }, callback); + }); + + it('should give failing result if the key has expired in non-RBAC vault', 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('Key in non-RBAC vault expired'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], [getKeys[3]]), { key_vault_key_expiry_fail: '40' }, callback); + }); + + it('should give failing result if the key expires within failure expiry date in non-RBAC vault', 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('Key in non-RBAC vault expires'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], [getKeys[2]]), { key_vault_key_expiry_fail: '40' }, callback); + }); + }); +}); \ No newline at end of file