diff --git a/helpers/azure/api.js b/helpers/azure/api.js index c55ad527fd..c1f64926fb 100644 --- a/helpers/azure/api.js +++ b/helpers/azure/api.js @@ -533,7 +533,7 @@ var calls = { }, pricings: { list: { - url: 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Security/pricings?api-version=2018-06-01' + url: 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Security/pricings?api-version=2024-01-01' }, sendIntegration: serviceMap['Defender'][0] }, diff --git a/helpers/azure/functions.js b/helpers/azure/functions.js index 988611788b..4001d3a87d 100644 --- a/helpers/azure/functions.js +++ b/helpers/azure/functions.js @@ -391,12 +391,27 @@ function checkFlexibleServerConfigs(servers, cache, source, location, results, s }); } -function checkMicrosoftDefender(pricings, serviceName, serviceDisplayName, results, location ) { +function checkMicrosoftDefender(pricings, serviceName, serviceDisplayName, results, location, requiredExtensions) { let pricingData = pricings.data.find((pricing) => pricing.name.toLowerCase() === serviceName); if (pricingData) { if (pricingData.pricingTier.toLowerCase() === 'standard') { - addResult(results, 0, `Azure Defender is enabled for ${serviceDisplayName}`, location, pricingData.id); + if (requiredExtensions && requiredExtensions.length) { + let extensions = pricingData.extensions || []; + + let missingExtensions = requiredExtensions.filter((extensionName) => { + let extension = extensions.find((ext) => ext.name && ext.name.toLowerCase() === extensionName.toLowerCase()); + return !extension || !extension.isEnabled || extension.isEnabled.toString().toLowerCase() !== 'true'; + }); + + if (missingExtensions.length) { + addResult(results, 2, `Azure Defender for ${serviceDisplayName} is enabled but the following extensions are not enabled: ${missingExtensions.join(', ')}`, location, pricingData.id); + } else { + addResult(results, 0, `Azure Defender is enabled for ${serviceDisplayName} with all required extensions enabled`, location, pricingData.id); + } + } else { + addResult(results, 0, `Azure Defender is enabled for ${serviceDisplayName}`, location, pricingData.id); + } } else { addResult(results, 2, `Azure Defender is not enabled for ${serviceDisplayName}`, location, pricingData.id); } diff --git a/plugins/aws/iam/accessKeysLastUsed.spec.js b/plugins/aws/iam/accessKeysLastUsed.spec.js index 4da05c61dc..581ebfc269 100644 --- a/plugins/aws/iam/accessKeysLastUsed.spec.js +++ b/plugins/aws/iam/accessKeysLastUsed.spec.js @@ -4,7 +4,7 @@ const accessKeysLastUsed = require('./accessKeysLastUsed'); var warnDate = new Date(); warnDate.setMonth(warnDate.getMonth() - 4); var passDate = new Date(); -passDate.setMonth(passDate.getMonth() - 1); +passDate.setDate(passDate.getDate() - 15); var failDate = new Date(); failDate.setMonth(failDate.getMonth() - 7); diff --git a/plugins/azure/applicationGateway/agWafEnabled.js b/plugins/azure/applicationGateway/agWafEnabled.js index e6436db329..3ca2d8a154 100644 --- a/plugins/azure/applicationGateway/agWafEnabled.js +++ b/plugins/azure/applicationGateway/agWafEnabled.js @@ -42,8 +42,12 @@ module.exports = { continue; } - if (appGateway.webApplicationFirewallConfiguration && appGateway.webApplicationFirewallConfiguration.enabled - && appGateway.webApplicationFirewallConfiguration.enabled === true) { + // WAF can either be attached as a separate firewall policy or configured inline on the gateway + let firewallPolicyAttached = appGateway.firewallPolicy && appGateway.firewallPolicy.id; + let inlineWafEnabled = appGateway.webApplicationFirewallConfiguration && + appGateway.webApplicationFirewallConfiguration.enabled === true; + + if (firewallPolicyAttached || inlineWafEnabled) { helpers.addResult(results, 0, 'Web Application Firewall is enabled for Application Gateway', location, appGateway.id); } else { helpers.addResult(results, 2, 'Web Application Firewall is not enabled for Application Gateway', location, appGateway.id); diff --git a/plugins/azure/applicationGateway/agWafEnabled.spec.js b/plugins/azure/applicationGateway/agWafEnabled.spec.js index 779caee1f0..3429a21107 100644 --- a/plugins/azure/applicationGateway/agWafEnabled.spec.js +++ b/plugins/azure/applicationGateway/agWafEnabled.spec.js @@ -39,6 +39,17 @@ const appGateway = [ "enabled": false, "firewallMode": "Detection", }, + }, + { "sku": { + "tier": "WAF_v2" + }, + "name": 'test-gateway-policy', + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/applicationGateways/test-gateway-policy', + "type": "Microsoft.Network/applicationGateways", + "location": "eastus", + "firewallPolicy": { + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/test-waf-policy' + }, } ]; @@ -120,6 +131,17 @@ describe('agWafEnabled', function() { done(); }); }); + + it('should give passing result if Application Gateway has a firewall policy attached', function(done) { + const cache = createCache([appGateway[3]]); + agWafEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('Web Application Firewall is enabled for Application Gateway'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); }); }); diff --git a/plugins/azure/databricks/workspaceDiagnosticLogs.js b/plugins/azure/databricks/workspaceDiagnosticLogs.js index f2cf7cd2b8..78fb31f381 100644 --- a/plugins/azure/databricks/workspaceDiagnosticLogs.js +++ b/plugins/azure/databricks/workspaceDiagnosticLogs.js @@ -17,13 +17,14 @@ module.exports = { const results = []; const source = {}; const locations = helpers.locations(settings.govcloud); + const requiredCategories = ['accounts', 'clusters', 'notebook', 'jobs', 'filesystem']; async.each(locations.databricks, function(location, rcb) { const databricks = helpers.addSource(cache, source, ['databricks', 'listWorkspaces', location]); if (!databricks) return rcb(); - + if (databricks.err || !databricks.data) { helpers.addResult(results, 3, 'Unable to query for Databricks Workspaces: ' + helpers.addError(databricks), location); return rcb(); @@ -45,19 +46,40 @@ module.exports = { location, workspace.id); continue; } - - var found = diagnosticSettings.data.find(ds => ds.logs && ds.logs.length); - - if (found) { - helpers.addResult(results, 0, 'Databricks workspace has diagnostic logs enabled', location, workspace.id); + + const hasDestination = diagnosticSettings.data.some(ds => + ds.workspaceId || ds.storageAccountId || ds.eventHubAuthorizationRuleId + ); + + if (!hasDestination) { + helpers.addResult(results, 2, + 'Databricks workspace does not have diagnostic logs configured with a valid destination', + location, workspace.id); + continue; + } + + let missingLogs = requiredCategories.slice(); + diagnosticSettings.data.forEach(ds => { + if (!ds.logs || !ds.logs.length) return; + missingLogs = missingLogs.filter(requiredCategory => + !ds.logs.some(log => + (log.category && log.category.toLowerCase() === requiredCategory && log.enabled) || + (log.categoryGroup === 'allLogs' && log.enabled) + ) + ); + }); + + if (missingLogs.length) { + helpers.addResult(results, 2, + `Databricks workspace does not have diagnostic logs enabled for following: ${missingLogs.join(', ')}`, + location, workspace.id); } else { - helpers.addResult(results, 2, 'Databricks workspace does not have diagnostic logs enabled', location, workspace.id); - } + helpers.addResult(results, 0, 'Databricks workspace has diagnostic logs enabled', location, workspace.id); + } } rcb(); }, function() { - // Global checking goes here callback(null, results, source); }); } diff --git a/plugins/azure/databricks/workspaceDiagnosticLogs.spec.js b/plugins/azure/databricks/workspaceDiagnosticLogs.spec.js index 7ddf5ff013..b8a69cf216 100644 --- a/plugins/azure/databricks/workspaceDiagnosticLogs.spec.js +++ b/plugins/azure/databricks/workspaceDiagnosticLogs.spec.js @@ -34,6 +34,7 @@ const diagnosticSettings = [ serviceBusRuleId: null, eventHubAuthorizationRuleId: null, eventHubName: null, + workspaceId: '/subscriptions/1234/resourceGroups/test/providers/Microsoft.OperationalInsights/workspaces/test-law', metrics: [ [Object] ], logs: [ { @@ -57,12 +58,31 @@ const diagnosticSettings = [ serviceBusRuleId: null, eventHubAuthorizationRuleId: null, eventHubName: null, + workspaceId: null, metrics: [ [Object] ], logs: [ + { + category: null, + categoryGroup: 'allLogs', + enabled: true, + retentionPolicy: { enabled: false, days: 0 } + }, ], logAnalyticsDestinationType: null + }, + { + id: '/subscriptions/1234/resourcegroups/cloudsploit-dev/providers/Microsoft.Databricks/workspace/test/providers/microsoft.insights/diagnosticSettings/test-missing', + type: 'Microsoft.Insights/diagnosticSettings', + name: 'test-missing', + storageAccountId: null, + eventHubAuthorizationRuleId: null, + workspaceId: '/subscriptions/1234/resourceGroups/test/providers/Microsoft.OperationalInsights/workspaces/test-law', + logs: [ + { category: 'accounts', categoryGroup: null, enabled: true }, + { category: 'clusters', categoryGroup: null, enabled: true } + ] } -] +]; const createCache = (workspace, diagnostics) => { let diagnostic = {}; if (workspace.length) { @@ -193,10 +213,21 @@ describe('workspaceDiagnosticLogs', function () { workspaceDiagnosticLogs.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('Databricks workspace does not have diagnostic logs enabled'); + expect(results[0].message).to.include('does not have diagnostic logs configured with a valid destination'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if required log categories are missing', function (done) { + const cache = createCache([workspaces[0]], [diagnosticSettings[2]]); + workspaceDiagnosticLogs.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('does not have diagnostic logs enabled for following'); expect(results[0].region).to.equal('eastus'); done(); }); }); }); -}); \ No newline at end of file +}); diff --git a/plugins/azure/databricks/workspaceManagedDiskCmk.js b/plugins/azure/databricks/workspaceManagedDiskCmk.js index ef2c396651..76c733ad90 100644 --- a/plugins/azure/databricks/workspaceManagedDiskCmk.js +++ b/plugins/azure/databricks/workspaceManagedDiskCmk.js @@ -38,7 +38,9 @@ module.exports = { if (workspace.sku && workspace.sku.name && workspace.sku.name.toLowerCase()!='premium') { helpers.addResult(results, 0, 'Databricks workspace is not a premium workspace', location, workspace.id); - } else if (workspace.encryption && workspace.encryption.entities && workspace.encryption.entities.managedDisk) { + } else if (workspace.encryption && workspace.encryption.entities && workspace.encryption.entities.managedDisk && + workspace.encryption.entities.managedDisk.keySource && + workspace.encryption.entities.managedDisk.keySource.toLowerCase() === 'microsoft.keyvault') { helpers.addResult(results, 0, 'Databricks workspace managed disk has CMK encryption enabled', location, workspace.id); } else { helpers.addResult(results, 2, 'Databricks workspace managed disk does not have CMK encryption enabled', location, workspace.id); diff --git a/plugins/azure/databricks/workspaceManagedDiskCmk.spec.js b/plugins/azure/databricks/workspaceManagedDiskCmk.spec.js index 2ab01dfcce..a777904f2b 100644 --- a/plugins/azure/databricks/workspaceManagedDiskCmk.spec.js +++ b/plugins/azure/databricks/workspaceManagedDiskCmk.spec.js @@ -72,6 +72,24 @@ const workspaces = [ }, "location": "eastus", "tags": {} + }, + { + "managedResourceGroupId": "/subscriptions/1234/resourceGroups/test", + "encryption": { + "entities": { + "managedDisk": { + "keySource": "Microsoft.Managed" + } + } + }, + "id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace", + "name": "test-workspace", + "type": "Microsoft.Databricks/workspaces", + "sku": { + "name": "premium" + }, + "location": "eastus", + "tags": {} } ]; @@ -148,5 +166,15 @@ describe('workspaceManagedDiskCmk', function () { done(); }); }); + + it('should give failing result if managed disk encryption is not using Key Vault CMK', function (done) { + const cache = createCache([workspaces[3]], null); + workspaceManagedDiskCmk.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Databricks workspace managed disk does not have CMK encryption enabled'); + done(); + }); + }); }); }); \ No newline at end of file diff --git a/plugins/azure/databricks/workspaceManagedServicesCmk.js b/plugins/azure/databricks/workspaceManagedServicesCmk.js index 041ad423a5..7f3d160feb 100644 --- a/plugins/azure/databricks/workspaceManagedServicesCmk.js +++ b/plugins/azure/databricks/workspaceManagedServicesCmk.js @@ -38,7 +38,9 @@ module.exports = { if (workspace.sku && workspace.sku.name && workspace.sku.name.toLowerCase()!='premium') { helpers.addResult(results, 0, 'Databricks workspace is not a premium workspace', location, workspace.id); - } else if (workspace.encryption && workspace.encryption.entities && workspace.encryption.entities.managedServices) { + } else if (workspace.encryption && workspace.encryption.entities && workspace.encryption.entities.managedServices && + workspace.encryption.entities.managedServices.keySource && + workspace.encryption.entities.managedServices.keySource.toLowerCase() === 'microsoft.keyvault') { helpers.addResult(results, 0, 'Databricks workspace managed services has CMK encryption enabled', location, workspace.id); } else { helpers.addResult(results, 2, 'Databricks workspace managed services does not have CMK encryption enabled', location, workspace.id); diff --git a/plugins/azure/databricks/workspaceManagedServicesCmk.spec.js b/plugins/azure/databricks/workspaceManagedServicesCmk.spec.js index 8e61a3110e..792ec84670 100644 --- a/plugins/azure/databricks/workspaceManagedServicesCmk.spec.js +++ b/plugins/azure/databricks/workspaceManagedServicesCmk.spec.js @@ -64,6 +64,24 @@ const workspaces = [ }, "location": "eastus", "tags": {} + }, + { + "managedResourceGroupId": "/subscriptions/1234/resourceGroups/test", + "encryption": { + "entities": { + "managedServices": { + "keySource": "Microsoft.Managed" + } + } + }, + "id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace", + "name": "test-workspace", + "type": "Microsoft.Databricks/workspaces", + "sku": { + "name": "premium" + }, + "location": "eastus", + "tags": {} } ]; @@ -140,5 +158,15 @@ describe('workspaceManagedServicesCmk', function () { done(); }); }); + + it('should give failing result if managed services encryption is not using Key Vault CMK', function (done) { + const cache = createCache([workspaces[3]], null); + workspaceManagedServicesCmk.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Databricks workspace managed services does not have CMK encryption enabled'); + done(); + }); + }); }); }); \ No newline at end of file diff --git a/plugins/azure/defender/enableDefenderForContainers.js b/plugins/azure/defender/enableDefenderForContainers.js index c44709f546..7f5ac63241 100644 --- a/plugins/azure/defender/enableDefenderForContainers.js +++ b/plugins/azure/defender/enableDefenderForContainers.js @@ -13,10 +13,14 @@ module.exports = { apis: ['pricings:list'], realtime_triggers: ['microsoftsecurity:pricings:write','microsoftsecurity:pricings:delete'], + // Per CIS: az security pricing show --name "Containers" --query [pricingTier,extensions[*].[name,isEnabled]] + requiredExtensions: ['ContainerRegistriesVulnerabilityAssessments', 'AgentlessDiscoveryForKubernetes', 'AgentlessVmScanning', 'ContainerSensor'], + run: function(cache, settings, callback) { var results = []; var source = {}; var locations = helpers.locations(settings.govcloud); + var requiredExtensions = this.requiredExtensions; async.each(locations.pricings, function(location, rcb) { var pricings = helpers.addSource(cache, source, @@ -35,7 +39,7 @@ module.exports = { return rcb(); } - helpers.checkMicrosoftDefender(pricings, 'containers', 'Containers', results, location); + helpers.checkMicrosoftDefender(pricings, 'containers', 'Containers', results, location, requiredExtensions); rcb(); }, function(){ diff --git a/plugins/azure/defender/enableDefenderForContainers.spec.js b/plugins/azure/defender/enableDefenderForContainers.spec.js index e90e3e672a..76dce7d4eb 100644 --- a/plugins/azure/defender/enableDefenderForContainers.spec.js +++ b/plugins/azure/defender/enableDefenderForContainers.spec.js @@ -59,7 +59,7 @@ describe('enableDefenderForKubernetes', function() { auth.run(cache, {}, callback); }); - it('should give passing result if Azure Defender for Containers is enabled', function(done) { + it('should give passing result if Azure Defender for Containers is enabled with all required extensions', function(done) { const callback = (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); @@ -75,8 +75,45 @@ describe('enableDefenderForKubernetes', function() { "id": "/subscriptions/e79d9a03-3ab3-4481-bdcd-c5db1d55420a/providers/Microsoft.Security/pricings/default", "name": "Containers", "type": "Microsoft.Security/pricings", + "location": "global", "pricingTier": "Standard", - "location": "global" + "extensions": [ + { "name": "ContainerRegistriesVulnerabilityAssessments", "isEnabled": "True" }, + { "name": "AgentlessDiscoveryForKubernetes", "isEnabled": "True" }, + { "name": "AgentlessVmScanning", "isEnabled": "True" }, + { "name": "ContainerSensor", "isEnabled": "True" } + ] + } + ] + ); + + auth.run(cache, {}, callback); + }) + + it('should give failing result if Azure Defender for Containers is enabled but a required extension is disabled', 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('AgentlessDiscoveryForKubernetes'); + expect(results[0].region).to.equal('global'); + done() + }; + + const cache = createCache( + null, + [ + { + "id": "/subscriptions/e79d9a03-3ab3-4481-bdcd-c5db1d55420a/providers/Microsoft.Security/pricings/default", + "name": "Containers", + "type": "Microsoft.Security/pricings", + "location": "global", + "pricingTier": "Standard", + "extensions": [ + { "name": "ContainerRegistriesVulnerabilityAssessments", "isEnabled": "True" }, + { "name": "AgentlessDiscoveryForKubernetes", "isEnabled": "False" }, + { "name": "AgentlessVmScanning", "isEnabled": "True" }, + { "name": "ContainerSensor", "isEnabled": "True" } + ] } ] ); diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiry.js b/plugins/azure/keyvaults/keyVaultKeyExpiry.js index 6ba32db018..2b18dba5b9 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiry.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiry.js @@ -79,7 +79,7 @@ module.exports = { `Key in RBAC vault expired ${Math.abs(difference)} days ago`, location, keyId); } } else { - helpers.addResult(results, 0, + helpers.addResult(results, 2, 'Key expiration is not enabled in RBAC vault', location, keyId); } }); diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js b/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js index 7aa4a804b9..7a162888aa 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js @@ -115,10 +115,10 @@ describe('keyVaultKeyExpiryRbac', function() { auth.run(createCache(null, [], {}), {}, callback); }); - it('should give passing result if expiration is not set on keys', function(done) { + it('should give failing result if expiration is not set on keys', function(done) { const callback = (err, results) => { expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); + expect(results[0].status).to.equal(2); expect(results[0].message).to.include('Key expiration is not enabled in RBAC vault'); expect(results[0].region).to.equal('eastus'); done() diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js index df9665a291..a125d26f97 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js @@ -81,7 +81,7 @@ module.exports = { `Key in non RBAC vault expired ${Math.abs(difference)} days ago`, location, keyId); } } else { - helpers.addResult(results, 0, + helpers.addResult(results, 2, 'Key expiration is not enabled in non RBAC vault', location, keyId); } }); diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js index 43ed0ab134..6b09e6639a 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js @@ -124,10 +124,10 @@ describe('keyVaultKeyExpiryNonRbac', function() { auth.run(createCache(null, [listKeyVaults[1]], []), {}, callback); }); - it('should give passing result if expiration is not set on keys in non-RBAC vault', function(done) { + it('should give failing result if expiration is not set on keys in non-RBAC vault', function(done) { const callback = (err, results) => { expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); + expect(results[0].status).to.equal(2); expect(results[0].message).to.include('Key expiration is not enabled in non RBAC vault'); expect(results[0].region).to.equal('eastus'); done() diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiry.js b/plugins/azure/keyvaults/keyVaultSecretExpiry.js index 2dd31788d5..45423e0d94 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiry.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiry.js @@ -80,7 +80,7 @@ module.exports = { `Secret in RBAC vault expired ${Math.abs(difference)} days ago`, location, secretId); } } else { - helpers.addResult(results, 0, + helpers.addResult(results, 2, 'Secret expiration is not enabled in RBAC vault', location, secretId); } }); diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js b/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js index 4bf693b9e5..7690ef7f61 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js @@ -155,10 +155,10 @@ describe('keyVaultSecretExpiry', function() { auth.run(createCache(null, [], {}), {}, callback); }); - it('should give passing result if secret expiration is not enabled in RBAC vault', function(done) { + it('should give failing result if secret expiration is not set in RBAC vault', function(done) { const callback = (err, results) => { expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); + expect(results[0].status).to.equal(2); expect(results[0].message).to.include('Secret expiration is not enabled in RBAC vault'); expect(results[0].region).to.equal('eastus'); done() diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js index e2f8a15fd1..aa435e68c7 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js @@ -80,7 +80,7 @@ module.exports = { `Secret in non RBAC vault expired ${Math.abs(difference)} days ago`, location, secretId); } } else { - helpers.addResult(results, 0, + helpers.addResult(results, 2, 'Secret expiration is not enabled in non RBAC vault', location, secretId); } }); diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js index 2a64720d2e..9d3640efff 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js @@ -158,10 +158,10 @@ describe('keyVaultSecretExpiryNonRbac', function() { auth.run(createCache(null, [], {}), {}, callback); }); - it('should give passing result if secret expiration is not enabled in non-RBAC vault', function(done) { + it('should give failing result if secret expiration is not set in non-RBAC vault', function(done) { const callback = (err, results) => { expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); + expect(results[0].status).to.equal(2); expect(results[0].message).to.include('Secret expiration is not enabled in non RBAC vault'); expect(results[0].region).to.equal('eastus'); done() diff --git a/plugins/azure/keyvaults/kvLogAnalyticsEnabled.js b/plugins/azure/keyvaults/kvLogAnalyticsEnabled.js index 7a48831076..07c3bab652 100644 --- a/plugins/azure/keyvaults/kvLogAnalyticsEnabled.js +++ b/plugins/azure/keyvaults/kvLogAnalyticsEnabled.js @@ -50,15 +50,31 @@ module.exports = { } else if (!diagnosticSettings.data.length) { helpers.addResult(results, 2, 'No existing diagnostics settings', location, vault.id); } else { - var found = false; - diagnosticSettings.data.forEach(function(ds) { - if (ds.logs && ds.logs.length) found = true; - }); + const hasDestination = diagnosticSettings.data.some(ds => + ds.workspaceId || ds.storageAccountId || ds.eventHubAuthorizationRuleId || ds.marketplacePartnerId + ); - if (found) { - helpers.addResult(results, 0, 'Key vault analytics is enabled for vault', location, vault.id); + if (!hasDestination) { + helpers.addResult(results, 2, + 'Key Vault does not have a diagnostic logs destination configured', location, vault.id); + return; + } + + const requiredCategoryGroups = ['audit', 'allLogs']; + const missingGroups = requiredCategoryGroups.filter(required => + !diagnosticSettings.data.some(ds => + ds.logs && ds.logs.some(log => + log.categoryGroup && log.categoryGroup.toLowerCase() === required.toLowerCase() && log.enabled + ) + ) + ); + + if (missingGroups.length) { + helpers.addResult(results, 2, + `Key Vault diagnostic logs missing required category groups: ${missingGroups.join(', ')}`, location, vault.id); } else { - helpers.addResult(results, 2, 'Key vault analytics is not enabled for vault', location, vault.id); + helpers.addResult(results, 0, + 'Key Vault has diagnostic logs enabled with required category groups', location, vault.id); } } }); diff --git a/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.js b/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.js index 6af3fc1efa..55a3614228 100644 --- a/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.js +++ b/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.js @@ -26,7 +26,7 @@ module.exports = { const locations = helpers.locations(settings.govcloud); async.each(locations.activityLogAlerts, function(location, rcb) { - var conditionResource = 'microsoft.sql/servers'; + var conditionResource = 'microsoft.sql/servers/firewallrules'; var text = 'SQL Server Firewall Rule'; diff --git a/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.spec.js b/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.spec.js index 2d8708f842..d069e13f1a 100644 --- a/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.spec.js +++ b/plugins/azure/logalerts/sqlServerFirewallRuleEnabled.spec.js @@ -20,7 +20,7 @@ const activityLogAlerts = [ }, { "field": "operationName", - "equals": "Microsoft.Sql/servers/delete" + "equals": "Microsoft.Sql/servers/firewallRules/delete" } ] }, @@ -51,7 +51,7 @@ const activityLogAlerts = [ }, { "field": "operationName", - "equals": "Microsoft.Sql/servers/write" + "equals": "Microsoft.Sql/servers/firewallRules/write" } ] }, @@ -82,7 +82,7 @@ const activityLogAlerts = [ }, { "field": "operationName", - "equals": "Microsoft.Sql/servers/update" + "equals": "Microsoft.Sql/servers/write" } ] }, diff --git a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js index 41f815869b..d117e746e7 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js @@ -64,12 +64,23 @@ module.exports = { for (const flowLog of flowLogs.data) { if (!flowLog.id) continue; + + if (!flowLog.enabled) { + helpers.addResult(results, 2, 'Flow log is not enabled', location, flowLog.id); + continue; + } + let retentionDays = 0; if (flowLog.retentionPolicy && flowLog.retentionPolicy.days) { retentionDays = flowLog.retentionPolicy.days; } - if (retentionDays >= config.retentionDays) { + // A retention period of 0 means the logs are retained indefinitely with no retention policy + if (!retentionDays) { + helpers.addResult(results, 0, + 'Flow log is retained indefinitely as no retention policy is set', + location, flowLog.id); + } else if (retentionDays >= config.retentionDays) { helpers.addResult(results, 0, `NSG fLow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, location, flowLog.id); diff --git a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js index b389914e9e..903f346bc8 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js @@ -12,6 +12,7 @@ const flowLogs = [ { 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', 'name': 'test-flowlog', + 'enabled': true, 'retentionPolicy': { 'days': 100, 'enabled': true @@ -20,10 +21,29 @@ const flowLogs = [ { 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', 'name': 'test-flowlog', + 'enabled': true, 'retentionPolicy': { 'days': 45, 'enabled': true } + }, + { + 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', + 'name': 'test-flowlog', + 'enabled': true, + 'retentionPolicy': { + 'days': 0, + 'enabled': false + } + }, + { + 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', + 'name': 'test-flowlog', + 'enabled': false, + 'retentionPolicy': { + 'days': 100, + 'enabled': true + } } ]; @@ -146,5 +166,27 @@ describe('nsgFlowLogsRetentionPeriod', function() { done(); }); }); + + it('should give passing result if flow logs are retained indefinitely', function(done) { + const cache = createCache([networkWatchers[0]], [flowLogs[2]]); + nsgFlowLogsRetentionPeriod.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('Flow log is retained indefinitely as no retention policy is set'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if flow log is not enabled', function(done) { + const cache = createCache([networkWatchers[0]], [flowLogs[3]]); + nsgFlowLogsRetentionPeriod.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Flow log is not enabled'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); }); }); \ No newline at end of file diff --git a/plugins/azure/networksecuritygroups/openUDP.js b/plugins/azure/networksecuritygroups/openUDP.js index 4008087344..549a62b156 100644 --- a/plugins/azure/networksecuritygroups/openUDP.js +++ b/plugins/azure/networksecuritygroups/openUDP.js @@ -42,14 +42,14 @@ module.exports = { let openUdpPorts = false; if (sg.securityRules && sg.securityRules.length) { - let InvalidSourceAddressPrefixes = ['*', '0.0.0.0', '/0', '/0', 'internet', 'any']; + let InvalidSourceAddressPrefixes = ['*', '0.0.0.0', '0.0.0.0/0', '/0', '', '/0', '::/0', 'internet', 'any']; var accessRules = sg.securityRules.filter((rule) => { return (rule.properties && rule.properties.access && rule.properties.access.toLowerCase() == 'allow' && rule.properties.direction.toLowerCase() === 'inbound' && - rule.properties.protocol.toLowerCase() === 'udp'); + ['udp', '*'].indexOf(rule.properties.protocol.toLowerCase()) > -1); }); for (var rule in accessRules) { @@ -57,8 +57,12 @@ module.exports = { let dRule = accessRules[rule].properties; - if (dRule.sourceAddressPrefix && - InvalidSourceAddressPrefixes.indexOf(dRule.sourceAddressPrefix) > -1) { + let sourceAddressPrefixes = (dRule.sourceAddressPrefixes && dRule.sourceAddressPrefixes.length) ? + dRule.sourceAddressPrefixes.slice() : []; + if (dRule.sourceAddressPrefix) sourceAddressPrefixes.push(dRule.sourceAddressPrefix); + + if (sourceAddressPrefixes.some(prefix => prefix && + InvalidSourceAddressPrefixes.indexOf(prefix) > -1)) { openUdpPorts = true; } } diff --git a/plugins/azure/networksecuritygroups/openUDP.spec.js b/plugins/azure/networksecuritygroups/openUDP.spec.js index 5d9377feeb..83f3f03fea 100644 --- a/plugins/azure/networksecuritygroups/openUDP.spec.js +++ b/plugins/azure/networksecuritygroups/openUDP.spec.js @@ -69,6 +69,87 @@ const networkSecurityGroups = [ "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkInterfaces/test-vm-1969" } ] + }, + { + "name": "test-vm-2-nsg", + "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-vm-2-nsg", + "type": "Microsoft.Network/networkSecurityGroups", + "location": "eastus", + "provisioningState": "Succeeded", + "securityRules": [ + { + "name": "AllowAnyProtocolDns", + "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-vm-2-nsg/securityRules/AllowAnyProtocolDns", + "type": "Microsoft.Network/networkSecurityGroups/securityRules", + "properties": { + "provisioningState": "Succeeded", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "53", + "sourceAddressPrefix": "0.0.0.0/0", + "destinationAddressPrefix": "*", + "access": "Allow", + "priority": 301, + "direction": "Inbound", + } + } + ], + "defaultSecurityRules": [] + }, + { + "name": "test-vm-3-nsg", + "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-vm-3-nsg", + "type": "Microsoft.Network/networkSecurityGroups", + "location": "eastus", + "provisioningState": "Succeeded", + "securityRules": [ + { + "name": "AllowNtpFromAnyIpv6", + "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-vm-3-nsg/securityRules/AllowNtpFromAnyIpv6", + "type": "Microsoft.Network/networkSecurityGroups/securityRules", + "properties": { + "provisioningState": "Succeeded", + "protocol": "Udp", + "sourcePortRange": "*", + "destinationPortRange": "123", + "sourceAddressPrefix": "::/0", + "destinationAddressPrefix": "*", + "access": "Allow", + "priority": 302, + "direction": "Inbound", + } + } + ], + "defaultSecurityRules": [] + }, + { + "name": "test-vm-4-nsg", + "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-vm-4-nsg", + "type": "Microsoft.Network/networkSecurityGroups", + "location": "eastus", + "provisioningState": "Succeeded", + "securityRules": [ + { + "name": "AllowSsdpFromPrefixes", + "id": "/subscriptions/ab12c345-def7-890g-a1b2-28fc0d22117e/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-vm-4-nsg/securityRules/AllowSsdpFromPrefixes", + "type": "Microsoft.Network/networkSecurityGroups/securityRules", + "properties": { + "provisioningState": "Succeeded", + "protocol": "Udp", + "sourcePortRange": "*", + "destinationPortRange": "1900", + "destinationAddressPrefix": "*", + "access": "Allow", + "priority": 303, + "direction": "Inbound", + "sourceAddressPrefixes": [ + "10.0.0.0/24", + "0.0.0.0/0" + ] + } + } + ], + "defaultSecurityRules": [] } ]; @@ -139,5 +220,38 @@ describe('openUDP', function() { done(); }); }); + + it('should give failing result if the security group allows any protocol from 0.0.0.0/0', function(done) { + const cache = createCache([networkSecurityGroups[2]]); + openUDP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has open UDP ports for internet access'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if the security group allows UDP from any IPv6 address', function(done) { + const cache = createCache([networkSecurityGroups[3]]); + openUDP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has open UDP ports for internet access'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if the security group allows UDP from an open source address prefix in sourceAddressPrefixes', function(done) { + const cache = createCache([networkSecurityGroups[4]]); + openUDP.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('has open UDP ports for internet access'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); }); }); diff --git a/plugins/azure/resources/managementLockEnabled.js b/plugins/azure/resources/managementLockEnabled.js index 90e2d72def..05376f2422 100644 --- a/plugins/azure/resources/managementLockEnabled.js +++ b/plugins/azure/resources/managementLockEnabled.js @@ -39,21 +39,17 @@ module.exports = { return rcb(); } - if (!managementLocks.data.length) { - helpers.addResult(results, 0, 'No Management Locks', location); - return rcb(); - } - var myLockedResourceObj = {}; + var taggedResourceCount = 0; managementLocks.data.forEach(managementLock => { var myLockedResource = managementLock.id.split('/'); var resourceLength = myLockedResource.length; if (!myLockedResourceObj[myLockedResource[resourceLength - 6]]) { - myLockedResourceObj[myLockedResource[resourceLength - 6]] = []; + myLockedResourceObj[myLockedResource[resourceLength - 6]] = {}; } - myLockedResourceObj[myLockedResource[resourceLength - 6]].push(myLockedResource[resourceLength - 5]); + myLockedResourceObj[myLockedResource[resourceLength - 6]][myLockedResource[resourceLength - 5]] = managementLock.level; }); async.each(locations.resources, (loc, lcb) => { @@ -73,9 +69,11 @@ module.exports = { async.each(resources.data, (resource, resCb) => { if (!resource.tags) return resCb(); if (!resource.tags[config.tag]) return resCb(); + taggedResourceCount++; var myResource = resource.id.split('/'); - if (myLockedResourceObj[myResource[myResource.length-2]] && - myLockedResourceObj[myResource[myResource.length-2]] == myResource[myResource.length-1]) { + var lockLevel = myLockedResourceObj[myResource[myResource.length-2]] && + myLockedResourceObj[myResource[myResource.length-2]][myResource[myResource.length-1]]; + if (lockLevel && ['cannotdelete', 'readonly'].includes(lockLevel.toLowerCase())) { helpers.addResult(results, 0, 'Resource has Management Lock Enabled', loc, resource.id); return resCb(); @@ -90,6 +88,10 @@ module.exports = { }); }); + if (!taggedResourceCount) { + helpers.addResult(results, 0, 'No resources tagged for lock verification', location); + } + rcb(); }, function() { // Global checking goes here diff --git a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.js b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.js index e037a2d918..878ae23287 100644 --- a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.js +++ b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.js @@ -6,12 +6,12 @@ module.exports = { category: 'Virtual Networks', domain: 'Network Access Control', severity: 'Medium', - description: 'Ensures that Microsoft Azure Virtual Network has flow logs enabled.', - more_info: 'Enabling flow logs for Microsoft Azure Virtual Networks is essential for comprehensive network visibility, security enhancement, and optimizing resources by providing detailed insights into traffic patterns and potential threats.', - recommended_action: 'Modify virtual networks and enable flow logs.', + description: 'Ensures that Microsoft Azure Virtual Network has flow logs enabled and configured to send logs to a Log Analytics workspace.', + more_info: 'Enabling flow logs for Microsoft Azure Virtual Networks is essential for comprehensive network visibility, security enhancement, and optimizing resources by providing detailed insights into traffic patterns and potential threats. Sending logs to a Log Analytics workspace enables centralized analysis, correlation, and alerting for faster threat detection and response.', + recommended_action: 'Enable flow logs for each Virtual Network and configure Traffic Analytics with a Log Analytics workspace.', link: 'https://learn.microsoft.com/en-us/azure/network-watcher/vnet-flow-logs-overview', - apis: ['virtualNetworks:listAll'], - realtime_triggers: ['microsoftnetwork:virtualnetworks:write','microsoftnetwork:virtualnetworks:delete'], + apis: ['virtualNetworks:listAll', 'networkWatchers:listAll', 'flowLogs:list'], + realtime_triggers: ['microsoftnetwork:virtualnetworks:write', 'microsoftnetwork:virtualnetworks:delete', 'microsoftnetwork:networkwatchers:flowlogs:write', 'microsoftnetwork:networkwatchers:flowlogs:delete'], run: function(cache, settings, callback) { const results = []; @@ -32,16 +32,58 @@ module.exports = { if (!virtualNetworks.data.length) { helpers.addResult(results, 0, 'No existing Virtual Networks found', location); return rcb(); - } - - for (let virtualNetwork of virtualNetworks.data) { + } + + const networkWatchers = helpers.addSource(cache, source, + ['networkWatchers', 'listAll', location]); + + const vnetFlowLogs = []; + if (networkWatchers && networkWatchers.data) { + for (const networkWatcher of networkWatchers.data) { + const flowLogs = helpers.addSource(cache, source, + ['flowLogs', 'list', location, networkWatcher.id]); + + if (!flowLogs || flowLogs.err || !flowLogs.data) { + helpers.addResult(results, 3, + 'Unable to query for flow logs data: ' + helpers.addError(flowLogs), location); + return rcb(); + } + + flowLogs.data.forEach(fl => { + if (fl.targetResourceId && + fl.targetResourceId.toLowerCase().includes('/virtualnetworks/')) { + vnetFlowLogs.push(fl); + } + }); + } + } + + for (const virtualNetwork of virtualNetworks.data) { if (!virtualNetwork.id) continue; - if (virtualNetwork.flowLogs && virtualNetwork.flowLogs.length){ - helpers.addResult(results, 0, 'Virtual Network has flow logs enabled', location, virtualNetwork.id); + const flowLog = vnetFlowLogs.find(fl => + fl.targetResourceId && + fl.targetResourceId.toLowerCase() === virtualNetwork.id.toLowerCase() + ); + + if (!flowLog || !flowLog.enabled) { + helpers.addResult(results, 2, + 'Virtual Network does not have flow logs enabled', location, virtualNetwork.id); + continue; + } + + const analyticsConfig = flowLog.flowAnalyticsConfiguration && + flowLog.flowAnalyticsConfiguration.networkWatcherFlowAnalyticsConfiguration; + + if (!analyticsConfig || !analyticsConfig.enabled || !analyticsConfig.workspaceId) { + helpers.addResult(results, 2, + 'Virtual Network flow logs are not configured to send logs to Log Analytics', + location, virtualNetwork.id); } else { - helpers.addResult(results, 2, 'Virtual Network does not have flow logs enabled', location, virtualNetwork.id); - } + helpers.addResult(results, 0, + 'Virtual Network has flow logs enabled and configured to send to Log Analytics', + location, virtualNetwork.id); + } } rcb(); diff --git a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js index b476ff3669..6de5ecae44 100644 --- a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js +++ b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js @@ -1,6 +1,34 @@ var expect = require('chai').expect; var virtualNetworkFlowLogs = require('./virtualNetworkFlowLogs'); +const networkWatchers = [ + { + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus', + "name": 'NetworkWatcher_eastus' + } +]; + +const flowLogs = [ + { + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', + "name": 'test-flowlog', + "targetResourceId": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', + "enabled": true, + "flowAnalyticsConfiguration": { + "networkWatcherFlowAnalyticsConfiguration": { + "enabled": true, + "workspaceId": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.OperationalInsights/workspaces/test-law' + } + } + }, + { + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', + "name": 'test-flowlog', + "targetResourceId": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', + "enabled": true + } +]; + const virtualNetworks = [ { "name": 'test-vnet', @@ -10,13 +38,7 @@ const virtualNetworks = [ "location": 'eastus', "provisioningState": 'Succeeded', "virtualNetworkPeerings": [], - "enableDdosProtection": true, - "flowLogs":[ - { - "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', - - } - ], + "enableDdosProtection": true }, { "name": 'test-vnet', @@ -30,7 +52,10 @@ const virtualNetworks = [ } ]; -const createCache = (virtualNetworks) => { +const createCache = (virtualNetworks, flowLogs) => { + var logData = {}; + logData[networkWatchers[0].id] = { data: flowLogs || [] }; + return { virtualNetworks: { listAll: { @@ -38,11 +63,47 @@ const createCache = (virtualNetworks) => { data: virtualNetworks } } + }, + networkWatchers: { + listAll: { + 'eastus': { + data: networkWatchers + } + } + }, + flowLogs: { + list: { + 'eastus': logData + } } }; }; -const createErrorCache = () => { +const createErrorCache = (key) => { + if (key === 'flowLog') { + return { + virtualNetworks: { + listAll: { + 'eastus': { + data: [virtualNetworks[0]] + } + } + }, + networkWatchers: { + listAll: { + 'eastus': { + data: networkWatchers + } + } + }, + flowLogs: { + list: { + 'eastus': {} + } + } + }; + } + return { virtualNetworks: { listAll: { @@ -66,7 +127,7 @@ describe('virtualNetworkFlowLogs', function() { }); it('should give failing result if virtual Network does not have flow logs enabled', function(done) { - const cache = createCache([virtualNetworks[1]]); + const cache = createCache([virtualNetworks[1]], []); virtualNetworkFlowLogs.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); @@ -87,8 +148,30 @@ describe('virtualNetworkFlowLogs', function() { }); }); + it('should give unknown result if unable to query for flow logs', function(done) { + const cache = createErrorCache('flowLog'); + virtualNetworkFlowLogs.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 flow logs data'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if virtual Network flow logs are not configured to send logs to Log Analytics', function(done) { + const cache = createCache([virtualNetworks[0]], [flowLogs[1]]); + virtualNetworkFlowLogs.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('not configured to send logs to Log Analytics'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + it('should give passing result if virtual Network has flow logs enabled', function(done) { - const cache = createCache([virtualNetworks[0]]); + const cache = createCache([virtualNetworks[0]], [flowLogs[0]]); virtualNetworkFlowLogs.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); @@ -98,4 +181,4 @@ describe('virtualNetworkFlowLogs', function() { }); }); }); -}); \ No newline at end of file +}); \ No newline at end of file