Skip to content

Commit

Permalink
KeyVaultSecretExpiryNonRbac Plugin Update
Browse files Browse the repository at this point in the history
  • Loading branch information
AkhtarAmir authored and AkhtarAmir committed Nov 5, 2024
1 parent 117bcd7 commit cfd3237
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 0 deletions.
90 changes: 90 additions & 0 deletions plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js
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 plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js
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);
});
})
});

0 comments on commit cfd3237

Please sign in to comment.