forked from aquasecurity/cloudsploit
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KeyVaultSecretExpiryNonRbac Plugin Update
- Loading branch information
AkhtarAmir
authored and
AkhtarAmir
committed
Nov 5, 2024
1 parent
117bcd7
commit cfd3237
Showing
2 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
} | ||
}; |
215 changes: 215 additions & 0 deletions
215
plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}) | ||
}); |