Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key vault secret expiry rbac hotfix #2099

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/azure/keyvaults/keyVaultSecretExpiry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var async = require('async');
var helpers = require('../../../helpers/azure');

module.exports = {
title: 'Key Vault Secret Expiry',
title: 'Key Vault Secret Expiry RBAC',
category: 'Key Vaults',
domain: 'Application Integration',
severity: 'High',
Expand Down
90 changes: 90 additions & 0 deletions plugins/azure/keyvaults/keyVaultSecretExpiryRbac.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 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/keyVaultSecretExpiryRbac.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);
});
})
});
Loading
Loading