From a125e2ea9e045af0cbfe28a467c65620df794a7b Mon Sep 17 00:00:00 2001 From: ali-imran7 Date: Sun, 30 May 2021 18:41:44 +0500 Subject: [PATCH 1/4] feature/AKD-191: Added Azure 'Automated Backups Configured' Plugin and test cases --- collectors/azure/collector.js | 6 + exports.js | 1 + .../appservice/automatedBackupsConfigured.js | 65 +++++++++ .../automatedBackupsConfigured.spec.js | 130 ++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 plugins/azure/appservice/automatedBackupsConfigured.js create mode 100644 plugins/azure/appservice/automatedBackupsConfigured.spec.js diff --git a/collectors/azure/collector.js b/collectors/azure/collector.js index 1e6d38daab..c156deec86 100644 --- a/collectors/azure/collector.js +++ b/collectors/azure/collector.js @@ -307,6 +307,12 @@ var postcalls = { reliesOnPath: 'webApps.list', properties: ['id'], url: 'https://management.azure.com/{id}/config?api-version=2019-08-01' + }, + listBackupConfig: { + reliesOnPath: 'webApps.list', + properties: ['id'], + url: 'https://management.azure.com/{id}/config/backup/list?api-version=2019-08-01', + post: true } }, endpoints: { diff --git a/exports.js b/exports.js index 70d66bac85..23db1c0eae 100644 --- a/exports.js +++ b/exports.js @@ -494,6 +494,7 @@ module.exports = { 'http20Enabled' : require(__dirname + '/plugins/azure/appservice/http20Enabled.js'), 'httpsOnlyEnabled' : require(__dirname + '/plugins/azure/appservice/httpsOnlyEnabled.js'), 'tlsVersionCheck' : require(__dirname + '/plugins/azure/appservice/tlsVersionCheck.js'), + 'automatedBackupsEnabled' : require(__dirname + '/plugins/azure/appservice/automatedBackupsEnabled.js'), 'rbacEnabled' : require(__dirname + '/plugins/azure/kubernetesservice/rbacEnabled.js'), 'aksLatestVersion' : require(__dirname + '/plugins/azure/kubernetesservice/aksLatestVersion.js'), diff --git a/plugins/azure/appservice/automatedBackupsConfigured.js b/plugins/azure/appservice/automatedBackupsConfigured.js new file mode 100644 index 0000000000..6d502a6b58 --- /dev/null +++ b/plugins/azure/appservice/automatedBackupsConfigured.js @@ -0,0 +1,65 @@ +var async = require('async'); +var helpers = require('../../../helpers/azure'); + +module.exports = { + title: 'Web Apps Automated Backups Configured', + category: 'App Service', + description: 'Ensures that Azure Web Apps have automated backups configured.', + more_info: 'The Backup and Restore feature in Azure App Service lets you easily create app backups manually or on a schedule.You can restore the app to a snapshot of a previous state by overwriting the existing app or restoring to another app.', + recommended_action: 'Configure Automated Backups for Azure Web Apps', + link: 'https://docs.microsoft.com/en-us/azure/app-service/manage-backup', + apis: ['webApps:list', 'webApps:listBackupConfig'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var locations = helpers.locations(settings.govcloud); + + async.each(locations.webApps, function(location, rcb) { + const webApps = helpers.addSource( + cache, source, ['webApps', 'list', location] + ); + + if (!webApps) return rcb(); + + if (webApps.err || !webApps.data) { + helpers.addResult(results, 3, + 'Unable to query for Web Apps: ' + helpers.addError(webApps), location); + return rcb(); + } + + if (!webApps.data.length) { + helpers.addResult( + results, 0, 'No existing Web Apps found', location); + return rcb(); + } + + async.each(webApps.data, function(webApp, scb) { + if (webApp && webApp.kind === 'functionapp') { + helpers.addResult(results, 0, 'Backups can not be configured for the function App', location, webApp.id); + return scb(); + } else { + const backupConfig = helpers.addSource( + cache, source, ['webApps', 'listBackupConfig', location, webApp.id] + ); + + if (!backupConfig || backupConfig.err || !backupConfig.data) { + helpers.addResult(results, 2, 'Automated Backups are not configured for the webApp', location, webApp.id); + return scb(); + } + + if (backupConfig.data) { + helpers.addResult(results, 0, 'Automated Backups are configured for the webApp', location, webApp.id); + } + } + + scb(); + }, function() { + rcb(); + }); + }, function() { + // Global checking goes here + callback(null, results, source); + }); + } +}; \ No newline at end of file diff --git a/plugins/azure/appservice/automatedBackupsConfigured.spec.js b/plugins/azure/appservice/automatedBackupsConfigured.spec.js new file mode 100644 index 0000000000..54e10aa723 --- /dev/null +++ b/plugins/azure/appservice/automatedBackupsConfigured.spec.js @@ -0,0 +1,130 @@ +var expect = require('chai').expect; +var automatedBackupsConfigured = require('./automatedBackupsConfigured'); + +const webApps = [ + { + 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Web/sites/test-app', + 'name': 'test-app', + 'type': 'Microsoft.Web/sites', + 'kind': 'app,linux', + 'location': 'East US' + }, + { + 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Web/sites/test-app', + 'name': 'test-app', + 'type': 'Microsoft.Web/sites', + 'kind': 'functionapp', + 'location': 'East US' + } +]; + +const backupConfig = { + 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Web/sites/test-app', + 'name': 'test-app', + 'type': 'Microsoft.Web/sites', + 'location': 'East US', + 'backupSchedule': { + 'frequencyInterval': 1, + 'frequencyUnit': 'Day', + 'keepAtLeastOneBackup': true, + 'retentionPeriodInDays': 30, + 'startTime': '2021-05-29T19:58:29.702' + } +}; + +const createCache = (webApps, backupConfig) => { + let app = {}; + let config = {}; + + if (webApps) { + app['data'] = webApps; + if (webApps && webApps.length) { + config[webApps[0].id] = backupConfig; + } + } + + return { + webApps: { + list: { + 'eastus': app + }, + listBackupConfig: { + 'eastus': config + } + } + }; +}; + +describe('automatedBackupsConfigured', function() { + describe('run', function() { + it('should give passing result if no web apps', function(done) { + const cache = createCache([]); + automatedBackupsConfigured.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No existing Web Apps found'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give unknown result if unable to query for web apps', function(done) { + const cache = createCache(); + automatedBackupsConfigured.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(3); + expect(results[0].message).to.include('Unable to query for Web Apps:'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give passing result if app is a function app', function(done) { + const cache = createCache([webApps[1]]); + automatedBackupsConfigured.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('Backups can not be configured for the function App'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if there is an error in getting backup config', function(done) { + const cache = createCache([webApps[0]], { + err: 'Unknown error occurred while calling the Azure API' + }); + automatedBackupsConfigured.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Automated Backups are not configured for the webApp'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if no app config found', function(done) { + const cache = createCache([webApps[0]], {}); + automatedBackupsConfigured.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Automated Backups are not configured for the webApp'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give passing result if automated backups are configured', function(done) { + const cache = createCache([webApps[0]], { + 'data': backupConfig + }); + automatedBackupsConfigured.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('Automated Backups are configured for the webApp'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + }); +}); \ No newline at end of file From 62acb24e7901f462cd012973a453a04422a340db Mon Sep 17 00:00:00 2001 From: ali-imran7 Date: Sun, 30 May 2021 18:44:36 +0500 Subject: [PATCH 2/4] feature/AKD-191: Added Azure 'Automated Backups Configured' Plugin and test cases --- exports.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exports.js b/exports.js index 23db1c0eae..4fd9294650 100644 --- a/exports.js +++ b/exports.js @@ -494,7 +494,7 @@ module.exports = { 'http20Enabled' : require(__dirname + '/plugins/azure/appservice/http20Enabled.js'), 'httpsOnlyEnabled' : require(__dirname + '/plugins/azure/appservice/httpsOnlyEnabled.js'), 'tlsVersionCheck' : require(__dirname + '/plugins/azure/appservice/tlsVersionCheck.js'), - 'automatedBackupsEnabled' : require(__dirname + '/plugins/azure/appservice/automatedBackupsEnabled.js'), + 'automatedBackupsConfigured' : require(__dirname + '/plugins/azure/appservice/automatedBackupsConfigured.js'), 'rbacEnabled' : require(__dirname + '/plugins/azure/kubernetesservice/rbacEnabled.js'), 'aksLatestVersion' : require(__dirname + '/plugins/azure/kubernetesservice/aksLatestVersion.js'), From f3d621ce948da5bd10d938515e24b2ead348ad38 Mon Sep 17 00:00:00 2001 From: ali-imran7 Date: Wed, 2 Jun 2021 21:42:56 +0500 Subject: [PATCH 3/4] feature/AKD-191: Added Azure 'Automated Backups Configured' Plugin and test cases --- exports.js | 1 - 1 file changed, 1 deletion(-) diff --git a/exports.js b/exports.js index bacb851dfc..405fb64963 100644 --- a/exports.js +++ b/exports.js @@ -505,7 +505,6 @@ module.exports = { 'http20Enabled' : require(__dirname + '/plugins/azure/appservice/http20Enabled.js'), 'httpsOnlyEnabled' : require(__dirname + '/plugins/azure/appservice/httpsOnlyEnabled.js'), 'tlsVersionCheck' : require(__dirname + '/plugins/azure/appservice/tlsVersionCheck.js'), - 'automatedBackupsConfigured' : require(__dirname + '/plugins/azure/appservice/automatedBackupsConfigured.js'), 'remoteDebuggingDisabled' : require(__dirname + '/plugins/azure/appservice/remoteDebuggingDisabled.js'), 'appserviceAutomatedBackups' : require(__dirname + '/plugins/azure/appservice/appserviceAutomatedBackups.js'), 'alwaysOnEnabled' : require(__dirname + '/plugins/azure/appservice/alwaysOnEnabled.js'), From bb3fb6d529cbc1b543ecd1e2309a88d1bcf3f4a7 Mon Sep 17 00:00:00 2001 From: ali-imran7 Date: Wed, 2 Jun 2021 21:47:18 +0500 Subject: [PATCH 4/4] feature/AKD-191: Added Azure 'Automated Backups Configured' Plugin and test cases --- .../appservice/appserviceAutomatedBackups.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/plugins/azure/appservice/appserviceAutomatedBackups.js b/plugins/azure/appservice/appserviceAutomatedBackups.js index a04fdc9130..b88dc8e435 100644 --- a/plugins/azure/appservice/appserviceAutomatedBackups.js +++ b/plugins/azure/appservice/appserviceAutomatedBackups.js @@ -16,37 +16,34 @@ module.exports = { var locations = helpers.locations(settings.govcloud); async.each(locations.webApps, function(location, rcb) { - const webApps = helpers.addSource( - cache, source, ['webApps', 'list', location] + const webApps = helpers.addSource(cache, source, + ['webApps', 'list', location] ); if (!webApps) return rcb(); if (webApps.err || !webApps.data) { - helpers.addResult(results, 3, - 'Unable to query for Web Apps: ' + helpers.addError(webApps), location); + helpers.addResult(results, 3, 'Unable to query for Web Apps: ' + helpers.addError(webApps), location); return rcb(); } if (!webApps.data.length) { - helpers.addResult( - results, 0, 'No existing Web Apps found', location); + helpers.addResult(results, 0, 'No existing Web Apps found', location); return rcb(); } async.each(webApps.data, function(webApp, scb) { - if (webApp.kind && webApp.kind === 'functionapp') { + if (webApp.kind && webApp.kind && webApp.kind === 'functionapp') { helpers.addResult(results, 0, 'Backups can not be configured for the function App', location, webApp.id); return scb(); } - const backupConfig = helpers.addSource( - cache, source, ['webApps', 'listBackupConfig', location, webApp.id] + const backupConfig = helpers.addSource(cache, source, + ['webApps', 'listBackupConfig', location, webApp.id] ); if (!backupConfig || backupConfig.err || !backupConfig.data) { helpers.addResult(results, 2, 'Automated Backups are not configured for the webApp', location, webApp.id); - return scb(); } else { helpers.addResult(results, 0, 'Automated Backups are configured for the webApp', location, webApp.id); }