From cfd3237f7a0cf4b2b8d7cc97ad8a8e6c45b71b8d Mon Sep 17 00:00:00 2001 From: AkhtarAmir Date: Tue, 5 Nov 2024 15:06:01 +0500 Subject: [PATCH] KeyVaultSecretExpiryNonRbac Plugin Update --- .../keyvaults/keyVaultSecretExpiryNonRbac.js | 90 ++++++++ .../keyVaultSecretExpiryNonRbac.spec.js | 215 ++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js create mode 100644 plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js new file mode 100644 index 0000000000..932eacb258 --- /dev/null +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js @@ -0,0 +1,90 @@ +var async = require('async'); +var helpers = require('../../../helpers/azure'); + +module.exports = { + title: 'Key Vault Secret Expiry Non RBAC', + category: 'Key Vaults', + domain: 'Application Integration', + severity: 'High', + description: 'Proactively check for Key Vault secrets expiry date and rotate them before expiry date is reached.', + more_info: 'After the expiry date has reached for Key Vault secret, it cannot be used for storing sensitive and confidential data such as passwords and database connection strings anymore.', + recommended_action: 'Ensure that Key Vault secrets are rotated before they get expired.', + link: 'https://learn.microsoft.com/en-us/azure/secret-vault/about-secrets-secrets-and-certificates', + apis: ['vaults:list', 'vaults:getSecrets'], + settings: { + key_vault_secret_expiry_fail: { + name: 'Key Vault Secret Expiry Fail', + description: 'Return a failing result when secret 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_secret_expiry_fail: parseInt(settings.key_vault_secret_expiry_fail || this.settings.key_vault_secret_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) { + var secrets = helpers.addSource(cache, source, + ['vaults', 'getSecrets', location, vault.id]); + + if (!secrets || secrets.err || !secrets.data) { + helpers.addResult(results, 3, 'Unable to query for Key Vault secrets: ' + helpers.addError(secrets), location, vault.id); + } else if (!secrets.data.length) { + helpers.addResult(results, 0, 'No Key Vault secrets found', location, vault.id); + } else { + secrets.data.forEach(function(secret) { + var secretName = secret.id.substring(secret.id.lastIndexOf('/') + 1); + var secretId = `${vault.id}/secrets/${secretName}`; + + if (!secret.attributes || !secret.attributes.enabled) { + helpers.addResult(results, 0, 'Secret is not enabled', location, secretId); + } else if (secret.attributes && (secret.attributes.exp || secret.attributes.expiry)) { + let attributes = secret.attributes; + let secretExpiry = attributes.exp ? attributes.exp * 1000 : attributes.expiry; + let difference = Math.round((new Date(secretExpiry).getTime() - (new Date).getTime())/(24*60*60*1000)); + if (difference > config.key_vault_secret_expiry_fail) { + helpers.addResult(results, 0, + `Secret expires in ${difference} days`, location, secretId); + } else if (difference > 0){ + helpers.addResult(results, 2, + `Secret expires in ${difference} days`, location, secretId); + } else { + helpers.addResult(results, 2, + `Secret expired ${Math.abs(difference)} days ago`, location, secretId); + } + } else { + helpers.addResult(results, 0, + 'Secret expiration is not enabled', location, secretId); + } + }); + } + }); + + rcb(); + }, function() { + callback(null, results, source); + }); + } +}; diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js new file mode 100644 index 0000000000..d08c57a3c9 --- /dev/null +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js @@ -0,0 +1,215 @@ +var expect = require('chai').expect; +var auth = require('./keyVaultSecretExpiry'); + +var secretExpiryPass = new Date(); +secretExpiryPass.setMonth(secretExpiryPass.getMonth() + 2); + +var secretExpiryFail = new Date(); +secretExpiryFail.setMonth(secretExpiryFail.getMonth() + 1); + +var secretExpired = new Date(); +secretExpired.setMonth(secretExpired.getMonth() - 1); + +const listKeyVaults = [ + { + "id": "/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault", + "name": "testvault", + "type": "Microsoft.KeyVault/vaults", + "location": "eastus", + "tags": {}, + "sku": { + "family": "A", + "name": "Standard" + } + }, + { + "id": "/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault", + "name": "testvault", + "type": "Microsoft.KeyVault/vaults", + "location": "eastus", + "tags": {}, + "sku": { + "family": "A", + "name": "Standard" + } + } +]; + +const getSecrets = [ + { + '/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault': { + data: [ + { + "id": "https://testvault.vault.azure.net/secrets/mysecret", + "attributes": { + "enabled": true, + "expiry": null, + "created": 1572289869, + "updated": 1572290380, + "recoveryLevel": "Recoverable+Purgeable" + }, + "tags": {} + } + ] + } + },{ + '/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault': { + data: [ + { + "id": "https://testvault.vault.azure.net/secrets/mysecret", + "attributes": { + "enabled": true, + "expiry": secretExpiryPass, + "created": 1572289869, + "updated": 1572290380, + "recoveryLevel": "Recoverable+Purgeable" + }, + "tags": {} + } + ] + } + }, + { + '/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault': { + data: [ + { + "id": "https://testvault.vault.azure.net/secrets/mysecret", + "attributes": { + "enabled": true, + "expiry": secretExpiryFail, + "created": 1572289869, + "updated": 1572290380, + "recoveryLevel": "Recoverable+Purgeable" + }, + "tags": {} + } + ] + } + }, + { + '/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault': { + data: [ + { + "id": "https://testvault.vault.azure.net/secrets/mysecret", + "attributes": { + "enabled": true, + "expiry": secretExpired, + "created": 1572289869, + "updated": 1572290380, + "recoveryLevel": "Recoverable+Purgeable" + }, + "tags": {} + } + ] + } + }, + { + '/subscriptions/abcdef123-ebf6-437f-a3b0-28fc0d22117e/resourceGroups/Default-ActivityLogAlerts/providers/Microsoft.KeyVault/vaults/testvault': { + data: [ + { + "id": "https://testvault.vault.azure.net/secrets/mysecret", + "attributes": { + "enabled": false, + "expiry": secretExpired, + "created": 1572289869, + "updated": 1572290380, + "recoveryLevel": "Recoverable+Purgeable" + }, + "tags": {} + } + ] + } + } +]; + +const createCache = (err, list, get) => { + return { + vaults: { + list: { + 'eastus': { + err: err, + data: list + } + }, + getSecrets: { + 'eastus': get + } + } + } +}; + +describe('keyVaultSecretExpiry', function() { + describe('run', function() { + it('should give passing result if no secrets 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 secret expiration is not enabled', 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('Secret expiration is not enabled'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], getSecrets[0]), {}, callback); + }); + + it('should give passing result if secret expiry is not yet reached', 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('Secret expires'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], getSecrets[1]), {}, callback); + }); + + it('should give failing result if secret has expired', 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('Secret expired'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], getSecrets[3]), {}, callback); + }); + + it('should give failing result if secret expires within failure expiry date', 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('Secret expires'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], getSecrets[2]), { key_vault_secret_expiry_fail: '40' }, callback); + }); + + it('should give passing result if key is disabled', 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('Secret is not enabled'); + expect(results[0].region).to.equal('eastus'); + done() + }; + + auth.run(createCache(null, [listKeyVaults[0]], getSecrets[4]), {}, callback); + }); + }) +});