Skip to content
Open
6 changes: 6 additions & 0 deletions collectors/azure/collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,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: {
Expand Down
1 change: 1 addition & 0 deletions exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ module.exports = {
'httpsOnlyEnabled' : require(__dirname + '/plugins/azure/appservice/httpsOnlyEnabled.js'),
'tlsVersionCheck' : require(__dirname + '/plugins/azure/appservice/tlsVersionCheck.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'),

'rbacEnabled' : require(__dirname + '/plugins/azure/kubernetesservice/rbacEnabled.js'),
Expand Down
60 changes: 60 additions & 0 deletions plugins/azure/appservice/appserviceAutomatedBackups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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.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]
);

if (!backupConfig || backupConfig.err || !backupConfig.data) {
helpers.addResult(results, 2, 'Automated Backups are not configured for the webApp', location, webApp.id);
} else {
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);
});
}
};
130 changes: 130 additions & 0 deletions plugins/azure/appservice/appserviceAutomatedBackups.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
var expect = require('chai').expect;
var appserviceAutomatedBackups = require('./appserviceAutomatedBackups');

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('appserviceAutomatedBackups', function() {
describe('run', function() {
it('should give passing result if no web apps', function(done) {
const cache = createCache([]);
appserviceAutomatedBackups.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();
appserviceAutomatedBackups.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]]);
appserviceAutomatedBackups.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'
});
appserviceAutomatedBackups.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]], {});
appserviceAutomatedBackups.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
});
appserviceAutomatedBackups.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();
});
});
});
});