diff --git a/exports.js b/exports.js index 30a874a699..e7594679df 100644 --- a/exports.js +++ b/exports.js @@ -1094,6 +1094,7 @@ module.exports = { 'vmssTrustedLaunchEnabled' : require(__dirname + '/plugins/azure/virtualmachinescaleset/vmssTrustedLaunchEnabled.js'), 'scaleSetAdAuthEnabled' : require(__dirname + '/plugins/azure/virtualmachinescaleset/scaleSetAdAuthEnabled.js'), 'vmssManagedIdentityEnabled' : require(__dirname + '/plugins/azure/virtualmachinescaleset/vmssManagedIdentityEnabled.js'), + 'vmScaleSetVnetIntegrated' : require(__dirname + '/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.js'), 'scalesetVTPMEnabled' : require(__dirname + '/plugins/azure/virtualmachinescaleset/scalesetVTPMEnabled.js'), 'scalesetSecureBootEnabled' : require(__dirname + '/plugins/azure/virtualmachinescaleset/scalesetSecureBootEnabled.js'), 'vmssApprovedExtensions' : require(__dirname + '/plugins/azure/virtualmachinescaleset/vmssApprovedExtensions'), diff --git a/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.js b/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.js new file mode 100644 index 0000000000..da2a74e998 --- /dev/null +++ b/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.js @@ -0,0 +1,51 @@ +var async = require('async'); + +var helpers = require('../../../helpers/azure'); + + +module.exports = { + title: 'VM Scale Set VNet Integrated', + category: 'Virtual Machine Scale Set', + domain: 'Compute', + severity: 'Medium', + description: 'Ensure that Azure Virtual Machine scale sets has VNet integrated.', + more_info: 'You can divide a virtual network into multiple subnets for organization and security. NICs connected to subnets (same or different) within a virtual network can communicate with each other without any extra configuration.', + link: 'https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-networking', + recommended_action: 'Modify VM scale set and configure a VNet.', + apis: ['vmScaleSet:listAll'], + realtime_triggers: ['microsoftcompute:virtualmachinescalesets:write', 'microsoftcompute:virtualmachinescalesets:delete','microsoftnetwork:virtualnetworks:subnets:write','microsoftnetwork:virtualnetworks:subnets:delete'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var locations = helpers.locations(settings.govcloud); + + async.each(locations.vmScaleSet, function(location, rcb) { + + var vmScaleSets = helpers.addSource(cache, source, ['vmScaleSet', 'listAll', location]); + + if (!vmScaleSets) return rcb(); + + if (vmScaleSets.err || !vmScaleSets.data) { + helpers.addResult(results, 3, 'Unable to query for VM scale sets: ' + helpers.addError(vmScaleSets), location); + return rcb(); + } + if (!vmScaleSets.data.length) { + helpers.addResult(results, 0, 'No existing VM scale sets found', location); + return rcb(); + } + for (let set of vmScaleSets.data) { + if (!set.id) continue; + let networkInterfaceConfigs = set.virtualMachineProfile.networkProfile.networkInterfaceConfigurations[0]; + if (networkInterfaceConfigs && networkInterfaceConfigs.properties && networkInterfaceConfigs.properties.ipConfigurations[0] && networkInterfaceConfigs.properties.ipConfigurations[0].properties.subnet && networkInterfaceConfigs.properties.ipConfigurations[0].properties.subnet.id) { + helpers.addResult(results, 0, 'VM scale set has VNet Integrated', location, set.id); + } else { + helpers.addResult(results, 2, 'VM scale set does not have VNet Integrated', location, set.id); + } + } + rcb(); + }, function() { + callback(null, results, source); + }); + } +}; \ No newline at end of file diff --git a/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.spec.js b/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.spec.js new file mode 100644 index 0000000000..930d2da6a5 --- /dev/null +++ b/plugins/azure/virtualmachinescaleset/vmScaleSetVnetIntegrated.spec.js @@ -0,0 +1,118 @@ +var expect = require('chai').expect; +var vmScaleSetVnetIntegrated = require('./vmScaleSetVnetIntegrated'); + +const vmScaleSet = [ + { "name": 'test', + "id": "/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/test", + "type": "Microsoft.Compute/virtualMachineScaleSets", + "location": "centralus", + "tags": { + "key" : "value" + }, + "virtualMachineProfile": { + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "pool12345", + "properties": { + "primary": true, + "enableIPForwarding": true, + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "primary": true, + "subnet": { + "id": "/subscriptions/123456789/resourceGroups/MC_new-eastus-group_new-eastus-cluster_westus2/providers/Microsoft.Network/virtualNetworks/aks-vnet1234567890/subnets/aks-subnet", + }, + "privateIPAddressVersion": "IPv4" + }, + }, + ], + }, + }, + ], + }, + }, + }, + { "name": 'test', + "id": "/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Compute/virtualMachineScaleSets/test", + "type": "Microsoft.Compute/virtualMachineScaleSets", + "location": "centralus", + "virtualMachineProfile": { + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "pool12345", + "properties": { + "primary": true, + "enableIPForwarding": true, + "ipConfigurations": [ + ], + }, + }, + ], + }, + }, +}, +]; + +const createCache = (vmScaleSet) => { + return { + vmScaleSet: { + listAll: { + 'eastus': { + data: vmScaleSet + } + } + } + }; +}; + +describe('vmScaleSetVnetIntegrated', function() { + describe('run', function() { + it('should give passing result if no scale set found', function(done) { + const cache = createCache([]); + vmScaleSetVnetIntegrated.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 VM scale sets found'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give unknown result if unable to query for scale set', function(done) { + const cache = createCache(); + vmScaleSetVnetIntegrated.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 VM scale sets'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give passing result if scale set has VNet integrated', function(done) { + const cache = createCache([vmScaleSet[0]]); + vmScaleSetVnetIntegrated.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('VM scale set has VNet Integrated'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if VM scale set does not have VNet integrated', function(done) { + const cache = createCache([vmScaleSet[1]]); + vmScaleSetVnetIntegrated.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('VM scale set does not have VNet Integrated'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + }); +}); \ No newline at end of file