From 599462bbdc4ebd9a34776fb57e9975d327927f44 Mon Sep 17 00:00:00 2001 From: Hamza Aziz Date: Fri, 14 Aug 2026 13:53:28 +0000 Subject: [PATCH 1/6] SLK-130789/azure-plugin-changes --- helpers/azure/api.js | 2 +- helpers/azure/functions.js | 19 +- .../azure/applicationGateway/agWafEnabled.js | 8 +- .../applicationGateway/agWafEnabled.spec.js | 22 +++ .../databricks/workspaceDiagnosticLogs.js | 40 ++++- .../workspaceDiagnosticLogs.spec.js | 37 +++- .../databricks/workspaceManagedDiskCmk.js | 4 +- .../workspaceManagedDiskCmk.spec.js | 28 +++ .../databricks/workspaceManagedServicesCmk.js | 4 +- .../workspaceManagedServicesCmk.spec.js | 28 +++ .../defender/enableDefenderForContainers.js | 6 +- .../enableDefenderForContainers.spec.js | 41 ++++- plugins/azure/keyvaults/keyVaultKeyExpiry.js | 4 +- .../azure/keyvaults/keyVaultKeyExpiry.spec.js | 6 +- .../keyvaults/keyVaultKeyExpiryNonRbac.js | 4 +- .../keyVaultKeyExpiryNonRbac.spec.js | 6 +- .../azure/keyvaults/keyVaultSecretExpiry.js | 4 +- .../keyvaults/keyVaultSecretExpiry.spec.js | 6 +- .../keyvaults/keyVaultSecretExpiryNonRbac.js | 4 +- .../keyVaultSecretExpiryNonRbac.spec.js | 6 +- .../azure/keyvaults/kvLogAnalyticsEnabled.js | 30 +++- .../keyvaults/kvLogAnalyticsEnabled.spec.js | 166 ++++++++++++++++++ .../logalerts/sqlServerFirewallRuleEnabled.js | 2 +- .../sqlServerFirewallRuleEnabled.spec.js | 6 +- .../nsgFlowLogsRetentionPeriod.js | 11 +- .../nsgFlowLogsRetentionPeriod.spec.js | 23 ++- .../azure/networksecuritygroups/openUDP.js | 12 +- .../networksecuritygroups/openUDP.spec.js | 114 ++++++++++++ .../azure/resources/managementLockEnabled.js | 36 ++-- .../resources/managementLockEnabled.spec.js | 145 +++++++++++++++ .../virtualnetworks/virtualNetworkFlowLogs.js | 66 +++++-- .../virtualNetworkFlowLogs.spec.js | 163 ++++++++++++++--- 32 files changed, 940 insertions(+), 113 deletions(-) create mode 100644 plugins/azure/keyvaults/kvLogAnalyticsEnabled.spec.js create mode 100644 plugins/azure/resources/managementLockEnabled.spec.js 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/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..0708357f7f 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiry.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiry.js @@ -79,8 +79,8 @@ module.exports = { `Key in RBAC vault expired ${Math.abs(difference)} days ago`, location, keyId); } } else { - helpers.addResult(results, 0, - 'Key expiration is not enabled in RBAC vault', location, keyId); + helpers.addResult(results, 2, + 'Key does not have an expiration date set in RBAC vault', location, keyId); } }); } diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js b/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js index 7aa4a804b9..134f6d1a30 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js @@ -115,11 +115,11 @@ 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].message).to.include('Key expiration is not enabled in RBAC vault'); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Key does not have an expiration date set 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..04e6ec8ca1 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js @@ -81,8 +81,8 @@ module.exports = { `Key in non RBAC vault expired ${Math.abs(difference)} days ago`, location, keyId); } } else { - helpers.addResult(results, 0, - 'Key expiration is not enabled in non RBAC vault', location, keyId); + helpers.addResult(results, 2, + 'Key does not have an expiration date set in non RBAC vault', location, keyId); } }); } diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js index 43ed0ab134..3d07d4622e 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js @@ -124,11 +124,11 @@ 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].message).to.include('Key expiration is not enabled in non RBAC vault'); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Key does not have an expiration date set 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..710492e893 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiry.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiry.js @@ -80,8 +80,8 @@ module.exports = { `Secret in RBAC vault expired ${Math.abs(difference)} days ago`, location, secretId); } } else { - helpers.addResult(results, 0, - 'Secret expiration is not enabled in RBAC vault', location, secretId); + helpers.addResult(results, 2, + 'Secret does not have an expiration date set in RBAC vault', location, secretId); } }); } diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js b/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js index 4bf693b9e5..0909e7ed55 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js @@ -155,11 +155,11 @@ 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].message).to.include('Secret expiration is not enabled in RBAC vault'); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Secret does not have an expiration date set 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..9824f10bba 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js @@ -80,8 +80,8 @@ module.exports = { `Secret in non RBAC vault expired ${Math.abs(difference)} days ago`, location, secretId); } } else { - helpers.addResult(results, 0, - 'Secret expiration is not enabled in non RBAC vault', location, secretId); + helpers.addResult(results, 2, + 'Secret does not have an expiration date set in non RBAC vault', location, secretId); } }); } diff --git a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js index 2a64720d2e..a677cb0992 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js @@ -158,11 +158,11 @@ 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].message).to.include('Secret expiration is not enabled in non RBAC vault'); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('Secret does not have an expiration date set 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/keyvaults/kvLogAnalyticsEnabled.spec.js b/plugins/azure/keyvaults/kvLogAnalyticsEnabled.spec.js new file mode 100644 index 0000000000..d5fbe1bc67 --- /dev/null +++ b/plugins/azure/keyvaults/kvLogAnalyticsEnabled.spec.js @@ -0,0 +1,166 @@ +var expect = require('chai').expect; +var kvLogAnalyticsEnabled = require('./kvLogAnalyticsEnabled'); + +const vaults = [ + { + id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault', + name: 'test-vault', + type: 'Microsoft.KeyVault/vaults', + location: 'eastus' + } +]; + +const diagnosticSettings = [ + // PASS: destination + audit + allLogs both enabled + { + id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault/providers/microsoft.insights/diagnosticSettings/test', + workspaceId: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/test-law', + storageAccountId: null, + eventHubAuthorizationRuleId: null, + logs: [ + { category: null, categoryGroup: 'audit', enabled: true }, + { category: null, categoryGroup: 'allLogs', enabled: true } + ] + }, + // FAIL: destination set but missing allLogs + { + id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault/providers/microsoft.insights/diagnosticSettings/test2', + workspaceId: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/test-law', + storageAccountId: null, + eventHubAuthorizationRuleId: null, + logs: [ + { category: null, categoryGroup: 'audit', enabled: true } + ] + }, + // FAIL: no destination + { + id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault/providers/microsoft.insights/diagnosticSettings/test3', + workspaceId: null, + storageAccountId: null, + eventHubAuthorizationRuleId: null, + logs: [ + { category: null, categoryGroup: 'audit', enabled: true }, + { category: null, categoryGroup: 'allLogs', enabled: true } + ] + } +]; + +const createCache = (vault, diagnostics) => { + let diagnostic = {}; + if (vault && vault.length) { + diagnostic[vault[0].id] = { data: diagnostics }; + } + return { + vaults: { + list: { + 'eastus': { data: vault } + } + }, + diagnosticSettings: { + listByKeyVault: { + 'eastus': diagnostic + } + } + }; +}; + +const createErrorCache = (key) => { + if (key === 'vault') { + return { + vaults: { list: { 'eastus': {} } } + }; + } else if (key === 'noVault') { + return { + vaults: { list: { 'eastus': { data: [] } } } + }; + } else { + return { + vaults: { list: { 'eastus': { data: [vaults[0]] } } }, + diagnosticSettings: { + listByKeyVault: { 'eastus': {} } + } + }; + } +}; + +describe('kvLogAnalyticsEnabled', function() { + describe('run', function() { + + it('should give passing result if no Key Vaults found', function(done) { + const cache = createErrorCache('noVault'); + kvLogAnalyticsEnabled.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 Key Vaults found'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give unknown result if unable to query for Key Vaults', function(done) { + const cache = createErrorCache('vault'); + kvLogAnalyticsEnabled.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 Key Vaults'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give unknown result if unable to query diagnostic settings', function(done) { + const cache = createErrorCache('diagnostic'); + kvLogAnalyticsEnabled.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 diagnostics settings'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if no diagnostic settings exist', function(done) { + const cache = createCache([vaults[0]], []); + kvLogAnalyticsEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('No existing diagnostics settings'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if no destination is configured', function(done) { + const cache = createCache([vaults[0]], [diagnosticSettings[2]]); + kvLogAnalyticsEnabled.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 a diagnostic logs destination configured'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if required category groups are missing', function(done) { + const cache = createCache([vaults[0]], [diagnosticSettings[1]]); + kvLogAnalyticsEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('missing required category groups'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give passing result if Key Vault has diagnostic logs enabled with required category groups', function(done) { + const cache = createCache([vaults[0]], [diagnosticSettings[0]]); + kvLogAnalyticsEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('Key Vault has diagnostic logs enabled with required category groups'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + }); +}); 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..da1411fcf2 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js @@ -69,13 +69,18 @@ module.exports = { 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, - `NSG fLow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, + 'Flow log is retained indefinitely as no retention policy is set', + location, flowLog.id); + } else if (retentionDays >= config.retentionDays) { + helpers.addResult(results, 0, + `Flow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, location, flowLog.id); } else { helpers.addResult(results, 2, - `NSG fLow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, + `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..c4f77a82bb 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js @@ -24,6 +24,14 @@ const flowLogs = [ 'days': 45, 'enabled': true } + }, + { + 'id': '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', + 'name': 'test-flowlog', + 'retentionPolicy': { + 'days': 0, + 'enabled': false + } } ]; @@ -130,7 +138,7 @@ describe('nsgFlowLogsRetentionPeriod', function() { nsgFlowLogsRetentionPeriod.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('NSG fLow log has retention period set to 100 of 90 days desired limit'); + expect(results[0].message).to.include('Flow log has retention period set to 100 of 90 days desired limit'); expect(results[0].region).to.equal('eastus'); done(); }); @@ -141,7 +149,18 @@ describe('nsgFlowLogsRetentionPeriod', function() { nsgFlowLogsRetentionPeriod.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('NSG fLow log has retention period set to 45 of 90 days desired limit'); + expect(results[0].message).to.include('Flow log has retention period set to 45 of 90 days desired limit'); + expect(results[0].region).to.equal('eastus'); + 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(); }); 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..1a44f1dfe3 100644 --- a/plugins/azure/resources/managementLockEnabled.js +++ b/plugins/azure/resources/managementLockEnabled.js @@ -39,21 +39,20 @@ 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; + var resType = myLockedResource[resourceLength - 6]; + var resName = myLockedResource[resourceLength - 5]; + var lockLevel = managementLock.properties && managementLock.properties.level; - if (!myLockedResourceObj[myLockedResource[resourceLength - 6]]) { - myLockedResourceObj[myLockedResource[resourceLength - 6]] = []; + if (!myLockedResourceObj[resType]) { + myLockedResourceObj[resType] = {}; } - myLockedResourceObj[myLockedResource[resourceLength - 6]].push(myLockedResource[resourceLength - 5]); + myLockedResourceObj[resType][resName] = lockLevel; }); async.each(locations.resources, (loc, lcb) => { @@ -73,23 +72,34 @@ 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 resType = myResource[myResource.length - 2]; + var resName = myResource[myResource.length - 1]; + var lockLevel = myLockedResourceObj[resType] && myLockedResourceObj[resType][resName]; + var validLevels = ['cannotdelete', 'readonly']; + + if (lockLevel && validLevels.includes(lockLevel.toLowerCase())) { helpers.addResult(results, 0, - 'Resource has Management Lock Enabled', loc, resource.id); - return resCb(); + `Resource has Management Lock Enabled with level: ${lockLevel}`, loc, resource.id); + } else if (lockLevel) { + helpers.addResult(results, 2, + `Resource lock level "${lockLevel}" is not CanNotDelete or ReadOnly`, loc, resource.id); } else { helpers.addResult(results, 2, 'Resource does not have Management Lock Enabled', loc, resource.id); - return resCb(); } + return resCb(); }, function() { lcb(); }); }); + if (!taggedResourceCount) { + helpers.addResult(results, 0, 'No Management Locks', location); + } + rcb(); }, function() { // Global checking goes here diff --git a/plugins/azure/resources/managementLockEnabled.spec.js b/plugins/azure/resources/managementLockEnabled.spec.js new file mode 100644 index 0000000000..6d38b86578 --- /dev/null +++ b/plugins/azure/resources/managementLockEnabled.spec.js @@ -0,0 +1,145 @@ +var expect = require('chai').expect; +var managementLockEnabled = require('./managementLockEnabled'); + +const resourceId = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm'; +const lockIdCanNotDelete = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm/providers/Microsoft.Authorization/locks/testlock'; +const lockIdReadOnly = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm2/providers/Microsoft.Authorization/locks/testlock2'; +const lockIdBadLevel = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm3/providers/Microsoft.Authorization/locks/testlock3'; + +const resources = [ + { + id: resourceId, + name: 'testvm', + type: 'Microsoft.Compute/virtualMachines', + location: 'eastus', + tags: { cloudsploitLock: 'true' } + }, + { + id: '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm2', + name: 'testvm2', + type: 'Microsoft.Compute/virtualMachines', + location: 'eastus', + tags: { cloudsploitLock: 'true' } + }, + { + id: '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm3', + name: 'testvm3', + type: 'Microsoft.Compute/virtualMachines', + location: 'eastus', + tags: { cloudsploitLock: 'true' } + }, + { + id: '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm-no-tag', + name: 'testvm-no-tag', + type: 'Microsoft.Compute/virtualMachines', + location: 'eastus', + tags: {} + } +]; + +const locks = [ + { + id: lockIdCanNotDelete, + name: 'testlock', + properties: { level: 'CanNotDelete' } + }, + { + id: lockIdReadOnly, + name: 'testlock2', + properties: { level: 'ReadOnly' } + }, + { + id: lockIdBadLevel, + name: 'testlock3', + properties: { level: 'Unknown' } + } +]; + +const createCache = (resourceData, lockData) => { + return { + resources: { + list: { eastus: { data: resourceData || [] } } + }, + managementLocks: { + listAtSubscriptionLevel: { global: { data: lockData || [] } } + } + }; +}; + +const createErrorCache = () => ({ + resources: { list: { eastus: { err: 'error', data: null } } }, + managementLocks: { listAtSubscriptionLevel: { global: { err: 'error', data: null } } } +}); + +describe('managementLockEnabled', function() { + describe('run', function() { + + it('should give passing result if no management locks exist', function(done) { + const cache = createCache([], []); + managementLockEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('No Management Locks'); + done(); + }); + }); + + it('should give unknown result if unable to query management locks', function(done) { + const cache = createErrorCache(); + managementLockEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.be.at.least(1); + expect(results[0].status).to.equal(3); + done(); + }); + }); + + it('should pass if tagged resource has a CanNotDelete lock', function(done) { + const cache = createCache([resources[0]], [locks[0]]); + managementLockEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('CanNotDelete'); + done(); + }); + }); + + it('should pass if tagged resource has a ReadOnly lock', function(done) { + const cache = createCache([resources[1]], [locks[1]]); + managementLockEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('ReadOnly'); + done(); + }); + }); + + it('should fail if tagged resource has no lock', function(done) { + const cache = createCache([resources[0]], []); + managementLockEnabled.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 Management Lock'); + done(); + }); + }); + + it('should fail if tagged resource has a lock with invalid level', function(done) { + const cache = createCache([resources[2]], [locks[2]]); + managementLockEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('is not CanNotDelete or ReadOnly'); + done(); + }); + }); + + it('should give passing result if no resources are tagged for lock verification', function(done) { + const cache = createCache([resources[3]], [locks[0]]); + managementLockEnabled.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + done(); + }); + }); + }); +}); 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..b79b6115ab 100644 --- a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js +++ b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js @@ -1,48 +1,137 @@ 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-nolaw', + name: 'test-flowlog-nolaw', + targetResourceId: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet-nolaw', + enabled: true, + flowAnalyticsConfiguration: { + networkWatcherFlowAnalyticsConfiguration: { + enabled: false, + workspaceId: null + } + } + } +]; + const virtualNetworks = [ { - "name": 'test-vnet', - "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', - "type": 'Microsoft.Network/virtualNetworks', - "tags": { "key": "value" }, - "location": 'eastus', - "provisioningState": 'Succeeded', - "virtualNetworkPeerings": [], - "enableDdosProtection": true, - "flowLogs":[ + name: 'test-vnet', + id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', + type: 'Microsoft.Network/virtualNetworks', + tags: { key: 'value' }, + location: 'eastus', + provisioningState: 'Succeeded', + virtualNetworkPeerings: [], + enableDdosProtection: true, + flowLogs: [ { - "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog', - + id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog' + } + ] + }, + { + name: 'test-vnet-nolaw', + id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet-nolaw', + type: 'Microsoft.Network/virtualNetworks', + tags: {}, + location: 'eastus', + provisioningState: 'Succeeded', + virtualNetworkPeerings: [], + enableDdosProtection: false, + flowLogs: [ + { + id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog-nolaw' } - ], + ] }, { - "name": 'test-vnet', - "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', - "type": 'Microsoft.Network/virtualNetworks', - "tags": {}, - "location": 'eastus', - "provisioningState": 'Succeeded', - "virtualNetworkPeerings": [], - "enableDdosProtection": false + name: 'test-vnet-no-logs', + id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet-no-logs', + type: 'Microsoft.Network/virtualNetworks', + tags: {}, + location: 'eastus', + provisioningState: 'Succeeded', + virtualNetworkPeerings: [], + enableDdosProtection: false } ]; -const createCache = (virtualNetworks) => { +const createCache = (vnets, logs) => { + var logData = {}; + if (networkWatchers.length) { + logData[networkWatchers[0].id] = { data: logs || [] }; + } + return { virtualNetworks: { listAll: { 'eastus': { - data: virtualNetworks + data: vnets + } + } + }, + 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 +155,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[2]], []); virtualNetworkFlowLogs.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); @@ -87,8 +176,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[1]], [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 +209,4 @@ describe('virtualNetworkFlowLogs', function() { }); }); }); -}); \ No newline at end of file +}); From 719e80b7f694ebbf5a9d355a77c5073429fd7b7e Mon Sep 17 00:00:00 2001 From: Hamza Aziz Date: Mon, 17 Aug 2026 14:39:30 +0000 Subject: [PATCH 2/6] SLK-130789/azure-plugin-changes --- plugins/azure/keyvaults/keyVaultKeyExpiry.js | 2 +- .../azure/keyvaults/keyVaultKeyExpiry.spec.js | 2 +- .../keyvaults/keyVaultKeyExpiryNonRbac.js | 2 +- .../keyVaultKeyExpiryNonRbac.spec.js | 2 +- .../azure/keyvaults/keyVaultSecretExpiry.js | 2 +- .../keyvaults/keyVaultSecretExpiry.spec.js | 2 +- .../keyvaults/keyVaultSecretExpiryNonRbac.js | 2 +- .../keyVaultSecretExpiryNonRbac.spec.js | 2 +- .../keyvaults/kvLogAnalyticsEnabled.spec.js | 166 ------------------ .../azure/resources/managementLockEnabled.js | 2 +- .../resources/managementLockEnabled.spec.js | 145 --------------- 11 files changed, 9 insertions(+), 320 deletions(-) delete mode 100644 plugins/azure/keyvaults/kvLogAnalyticsEnabled.spec.js delete mode 100644 plugins/azure/resources/managementLockEnabled.spec.js diff --git a/plugins/azure/keyvaults/keyVaultKeyExpiry.js b/plugins/azure/keyvaults/keyVaultKeyExpiry.js index 0708357f7f..2b18dba5b9 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiry.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiry.js @@ -80,7 +80,7 @@ module.exports = { } } else { helpers.addResult(results, 2, - 'Key does not have an expiration date set in RBAC vault', location, keyId); + '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 134f6d1a30..7a162888aa 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiry.spec.js @@ -119,7 +119,7 @@ describe('keyVaultKeyExpiryRbac', function() { const callback = (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('Key does not have an expiration date set in RBAC vault'); + 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 04e6ec8ca1..a125d26f97 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.js @@ -82,7 +82,7 @@ module.exports = { } } else { helpers.addResult(results, 2, - 'Key does not have an expiration date set in non RBAC vault', location, keyId); + '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 3d07d4622e..6b09e6639a 100644 --- a/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js +++ b/plugins/azure/keyvaults/keyVaultKeyExpiryNonRbac.spec.js @@ -128,7 +128,7 @@ describe('keyVaultKeyExpiryNonRbac', function() { const callback = (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('Key does not have an expiration date set in non RBAC vault'); + 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 710492e893..45423e0d94 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiry.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiry.js @@ -81,7 +81,7 @@ module.exports = { } } else { helpers.addResult(results, 2, - 'Secret does not have an expiration date set in RBAC vault', location, secretId); + '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 0909e7ed55..7690ef7f61 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiry.spec.js @@ -159,7 +159,7 @@ describe('keyVaultSecretExpiry', function() { const callback = (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('Secret does not have an expiration date set in RBAC vault'); + 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 9824f10bba..aa435e68c7 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.js @@ -81,7 +81,7 @@ module.exports = { } } else { helpers.addResult(results, 2, - 'Secret does not have an expiration date set in non RBAC vault', location, secretId); + '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 a677cb0992..9d3640efff 100644 --- a/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js +++ b/plugins/azure/keyvaults/keyVaultSecretExpiryNonRbac.spec.js @@ -162,7 +162,7 @@ describe('keyVaultSecretExpiryNonRbac', function() { const callback = (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('Secret does not have an expiration date set in non RBAC vault'); + 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.spec.js b/plugins/azure/keyvaults/kvLogAnalyticsEnabled.spec.js deleted file mode 100644 index d5fbe1bc67..0000000000 --- a/plugins/azure/keyvaults/kvLogAnalyticsEnabled.spec.js +++ /dev/null @@ -1,166 +0,0 @@ -var expect = require('chai').expect; -var kvLogAnalyticsEnabled = require('./kvLogAnalyticsEnabled'); - -const vaults = [ - { - id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault', - name: 'test-vault', - type: 'Microsoft.KeyVault/vaults', - location: 'eastus' - } -]; - -const diagnosticSettings = [ - // PASS: destination + audit + allLogs both enabled - { - id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault/providers/microsoft.insights/diagnosticSettings/test', - workspaceId: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/test-law', - storageAccountId: null, - eventHubAuthorizationRuleId: null, - logs: [ - { category: null, categoryGroup: 'audit', enabled: true }, - { category: null, categoryGroup: 'allLogs', enabled: true } - ] - }, - // FAIL: destination set but missing allLogs - { - id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault/providers/microsoft.insights/diagnosticSettings/test2', - workspaceId: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspaces/test-law', - storageAccountId: null, - eventHubAuthorizationRuleId: null, - logs: [ - { category: null, categoryGroup: 'audit', enabled: true } - ] - }, - // FAIL: no destination - { - id: '/subscriptions/1234/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/test-vault/providers/microsoft.insights/diagnosticSettings/test3', - workspaceId: null, - storageAccountId: null, - eventHubAuthorizationRuleId: null, - logs: [ - { category: null, categoryGroup: 'audit', enabled: true }, - { category: null, categoryGroup: 'allLogs', enabled: true } - ] - } -]; - -const createCache = (vault, diagnostics) => { - let diagnostic = {}; - if (vault && vault.length) { - diagnostic[vault[0].id] = { data: diagnostics }; - } - return { - vaults: { - list: { - 'eastus': { data: vault } - } - }, - diagnosticSettings: { - listByKeyVault: { - 'eastus': diagnostic - } - } - }; -}; - -const createErrorCache = (key) => { - if (key === 'vault') { - return { - vaults: { list: { 'eastus': {} } } - }; - } else if (key === 'noVault') { - return { - vaults: { list: { 'eastus': { data: [] } } } - }; - } else { - return { - vaults: { list: { 'eastus': { data: [vaults[0]] } } }, - diagnosticSettings: { - listByKeyVault: { 'eastus': {} } - } - }; - } -}; - -describe('kvLogAnalyticsEnabled', function() { - describe('run', function() { - - it('should give passing result if no Key Vaults found', function(done) { - const cache = createErrorCache('noVault'); - kvLogAnalyticsEnabled.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 Key Vaults found'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - - it('should give unknown result if unable to query for Key Vaults', function(done) { - const cache = createErrorCache('vault'); - kvLogAnalyticsEnabled.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 Key Vaults'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - - it('should give unknown result if unable to query diagnostic settings', function(done) { - const cache = createErrorCache('diagnostic'); - kvLogAnalyticsEnabled.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 diagnostics settings'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - - it('should give failing result if no diagnostic settings exist', function(done) { - const cache = createCache([vaults[0]], []); - kvLogAnalyticsEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('No existing diagnostics settings'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - - it('should give failing result if no destination is configured', function(done) { - const cache = createCache([vaults[0]], [diagnosticSettings[2]]); - kvLogAnalyticsEnabled.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 a diagnostic logs destination configured'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - - it('should give failing result if required category groups are missing', function(done) { - const cache = createCache([vaults[0]], [diagnosticSettings[1]]); - kvLogAnalyticsEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('missing required category groups'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - - it('should give passing result if Key Vault has diagnostic logs enabled with required category groups', function(done) { - const cache = createCache([vaults[0]], [diagnosticSettings[0]]); - kvLogAnalyticsEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('Key Vault has diagnostic logs enabled with required category groups'); - expect(results[0].region).to.equal('eastus'); - done(); - }); - }); - }); -}); diff --git a/plugins/azure/resources/managementLockEnabled.js b/plugins/azure/resources/managementLockEnabled.js index 1a44f1dfe3..3cb4b76811 100644 --- a/plugins/azure/resources/managementLockEnabled.js +++ b/plugins/azure/resources/managementLockEnabled.js @@ -47,7 +47,7 @@ module.exports = { var resourceLength = myLockedResource.length; var resType = myLockedResource[resourceLength - 6]; var resName = myLockedResource[resourceLength - 5]; - var lockLevel = managementLock.properties && managementLock.properties.level; + var lockLevel = managementLock.level; if (!myLockedResourceObj[resType]) { myLockedResourceObj[resType] = {}; diff --git a/plugins/azure/resources/managementLockEnabled.spec.js b/plugins/azure/resources/managementLockEnabled.spec.js deleted file mode 100644 index 6d38b86578..0000000000 --- a/plugins/azure/resources/managementLockEnabled.spec.js +++ /dev/null @@ -1,145 +0,0 @@ -var expect = require('chai').expect; -var managementLockEnabled = require('./managementLockEnabled'); - -const resourceId = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm'; -const lockIdCanNotDelete = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm/providers/Microsoft.Authorization/locks/testlock'; -const lockIdReadOnly = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm2/providers/Microsoft.Authorization/locks/testlock2'; -const lockIdBadLevel = '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm3/providers/Microsoft.Authorization/locks/testlock3'; - -const resources = [ - { - id: resourceId, - name: 'testvm', - type: 'Microsoft.Compute/virtualMachines', - location: 'eastus', - tags: { cloudsploitLock: 'true' } - }, - { - id: '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm2', - name: 'testvm2', - type: 'Microsoft.Compute/virtualMachines', - location: 'eastus', - tags: { cloudsploitLock: 'true' } - }, - { - id: '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm3', - name: 'testvm3', - type: 'Microsoft.Compute/virtualMachines', - location: 'eastus', - tags: { cloudsploitLock: 'true' } - }, - { - id: '/subscriptions/123/resourceGroups/testrg/providers/Microsoft.Compute/virtualMachines/testvm-no-tag', - name: 'testvm-no-tag', - type: 'Microsoft.Compute/virtualMachines', - location: 'eastus', - tags: {} - } -]; - -const locks = [ - { - id: lockIdCanNotDelete, - name: 'testlock', - properties: { level: 'CanNotDelete' } - }, - { - id: lockIdReadOnly, - name: 'testlock2', - properties: { level: 'ReadOnly' } - }, - { - id: lockIdBadLevel, - name: 'testlock3', - properties: { level: 'Unknown' } - } -]; - -const createCache = (resourceData, lockData) => { - return { - resources: { - list: { eastus: { data: resourceData || [] } } - }, - managementLocks: { - listAtSubscriptionLevel: { global: { data: lockData || [] } } - } - }; -}; - -const createErrorCache = () => ({ - resources: { list: { eastus: { err: 'error', data: null } } }, - managementLocks: { listAtSubscriptionLevel: { global: { err: 'error', data: null } } } -}); - -describe('managementLockEnabled', function() { - describe('run', function() { - - it('should give passing result if no management locks exist', function(done) { - const cache = createCache([], []); - managementLockEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('No Management Locks'); - done(); - }); - }); - - it('should give unknown result if unable to query management locks', function(done) { - const cache = createErrorCache(); - managementLockEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.be.at.least(1); - expect(results[0].status).to.equal(3); - done(); - }); - }); - - it('should pass if tagged resource has a CanNotDelete lock', function(done) { - const cache = createCache([resources[0]], [locks[0]]); - managementLockEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('CanNotDelete'); - done(); - }); - }); - - it('should pass if tagged resource has a ReadOnly lock', function(done) { - const cache = createCache([resources[1]], [locks[1]]); - managementLockEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); - expect(results[0].message).to.include('ReadOnly'); - done(); - }); - }); - - it('should fail if tagged resource has no lock', function(done) { - const cache = createCache([resources[0]], []); - managementLockEnabled.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 Management Lock'); - done(); - }); - }); - - it('should fail if tagged resource has a lock with invalid level', function(done) { - const cache = createCache([resources[2]], [locks[2]]); - managementLockEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(2); - expect(results[0].message).to.include('is not CanNotDelete or ReadOnly'); - done(); - }); - }); - - it('should give passing result if no resources are tagged for lock verification', function(done) { - const cache = createCache([resources[3]], [locks[0]]); - managementLockEnabled.run(cache, {}, (err, results) => { - expect(results.length).to.equal(1); - expect(results[0].status).to.equal(0); - done(); - }); - }); - }); -}); From 809ca61eacae19eec5135dc3930aa4ba98ae105b Mon Sep 17 00:00:00 2001 From: Hamza Aziz Date: Tue, 18 Aug 2026 12:16:46 +0000 Subject: [PATCH 3/6] azure-plugin-changes --- .../nsgFlowLogsRetentionPeriod.js | 6 +++++ .../nsgFlowLogsRetentionPeriod.spec.js | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js index da1411fcf2..0a66c82de6 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js @@ -64,6 +64,12 @@ 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; diff --git a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js index c4f77a82bb..77cdfe0980 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,6 +21,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': 45, 'enabled': true @@ -28,10 +30,20 @@ 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': 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 + } } ]; @@ -165,5 +177,16 @@ describe('nsgFlowLogsRetentionPeriod', function() { 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 From 0d0ad4eaae055da2a6be3d0ee881a71b60759c13 Mon Sep 17 00:00:00 2001 From: Hamza Aziz Date: Tue, 18 Aug 2026 13:21:22 +0000 Subject: [PATCH 4/6] plugin-changes --- .../nsgFlowLogsRetentionPeriod.js | 4 +-- .../nsgFlowLogsRetentionPeriod.spec.js | 4 +-- .../azure/resources/managementLockEnabled.js | 28 +++++++------------ 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js index 0a66c82de6..d117e746e7 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.js @@ -82,11 +82,11 @@ module.exports = { location, flowLog.id); } else if (retentionDays >= config.retentionDays) { helpers.addResult(results, 0, - `Flow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, + `NSG fLow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, location, flowLog.id); } else { helpers.addResult(results, 2, - `Flow log has retention period set to ${retentionDays} of ${config.retentionDays} days desired limit`, + `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 77cdfe0980..903f346bc8 100644 --- a/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js +++ b/plugins/azure/networksecuritygroups/nsgFlowLogsRetentionPeriod.spec.js @@ -150,7 +150,7 @@ describe('nsgFlowLogsRetentionPeriod', function() { 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 has retention period set to 100 of 90 days desired limit'); + expect(results[0].message).to.include('NSG fLow log has retention period set to 100 of 90 days desired limit'); expect(results[0].region).to.equal('eastus'); done(); }); @@ -161,7 +161,7 @@ describe('nsgFlowLogsRetentionPeriod', function() { 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 has retention period set to 45 of 90 days desired limit'); + expect(results[0].message).to.include('NSG fLow log has retention period set to 45 of 90 days desired limit'); expect(results[0].region).to.equal('eastus'); done(); }); diff --git a/plugins/azure/resources/managementLockEnabled.js b/plugins/azure/resources/managementLockEnabled.js index 3cb4b76811..05376f2422 100644 --- a/plugins/azure/resources/managementLockEnabled.js +++ b/plugins/azure/resources/managementLockEnabled.js @@ -45,14 +45,11 @@ module.exports = { managementLocks.data.forEach(managementLock => { var myLockedResource = managementLock.id.split('/'); var resourceLength = myLockedResource.length; - var resType = myLockedResource[resourceLength - 6]; - var resName = myLockedResource[resourceLength - 5]; - var lockLevel = managementLock.level; - if (!myLockedResourceObj[resType]) { - myLockedResourceObj[resType] = {}; + if (!myLockedResourceObj[myLockedResource[resourceLength - 6]]) { + myLockedResourceObj[myLockedResource[resourceLength - 6]] = {}; } - myLockedResourceObj[resType][resName] = lockLevel; + myLockedResourceObj[myLockedResource[resourceLength - 6]][myLockedResource[resourceLength - 5]] = managementLock.level; }); async.each(locations.resources, (loc, lcb) => { @@ -74,22 +71,17 @@ module.exports = { if (!resource.tags[config.tag]) return resCb(); taggedResourceCount++; var myResource = resource.id.split('/'); - var resType = myResource[myResource.length - 2]; - var resName = myResource[myResource.length - 1]; - var lockLevel = myLockedResourceObj[resType] && myLockedResourceObj[resType][resName]; - var validLevels = ['cannotdelete', 'readonly']; - - if (lockLevel && validLevels.includes(lockLevel.toLowerCase())) { + 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 with level: ${lockLevel}`, loc, resource.id); - } else if (lockLevel) { - helpers.addResult(results, 2, - `Resource lock level "${lockLevel}" is not CanNotDelete or ReadOnly`, loc, resource.id); + 'Resource has Management Lock Enabled', loc, resource.id); + return resCb(); } else { helpers.addResult(results, 2, 'Resource does not have Management Lock Enabled', loc, resource.id); + return resCb(); } - return resCb(); }, function() { lcb(); @@ -97,7 +89,7 @@ module.exports = { }); if (!taggedResourceCount) { - helpers.addResult(results, 0, 'No Management Locks', location); + helpers.addResult(results, 0, 'No resources tagged for lock verification', location); } rcb(); From 26a7fc3f29f2d6f64ae2b6f9acca52705dc09d00 Mon Sep 17 00:00:00 2001 From: Hamza Aziz Date: Wed, 19 Aug 2026 09:29:36 +0000 Subject: [PATCH 5/6] plugin-changes --- .../virtualNetworkFlowLogs.spec.js | 100 +++++++----------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js index b79b6115ab..6de5ecae44 100644 --- a/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js +++ b/plugins/azure/virtualnetworks/virtualNetworkFlowLogs.spec.js @@ -3,92 +3,64 @@ var virtualNetworkFlowLogs = require('./virtualNetworkFlowLogs'); const networkWatchers = [ { - id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus', - name: 'NetworkWatcher_eastus' + "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, + "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-nolaw', - name: 'test-flowlog-nolaw', - targetResourceId: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet-nolaw', - enabled: true, - flowAnalyticsConfiguration: { - networkWatcherFlowAnalyticsConfiguration: { - enabled: false, - workspaceId: null - } - } + "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', - id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', - type: 'Microsoft.Network/virtualNetworks', - tags: { key: 'value' }, - location: 'eastus', - provisioningState: 'Succeeded', - virtualNetworkPeerings: [], - enableDdosProtection: true, - flowLogs: [ - { - id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog' - } - ] + "name": 'test-vnet', + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', + "type": 'Microsoft.Network/virtualNetworks', + "tags": { "key": "value" }, + "location": 'eastus', + "provisioningState": 'Succeeded', + "virtualNetworkPeerings": [], + "enableDdosProtection": true }, { - name: 'test-vnet-nolaw', - id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet-nolaw', - type: 'Microsoft.Network/virtualNetworks', - tags: {}, - location: 'eastus', - provisioningState: 'Succeeded', - virtualNetworkPeerings: [], - enableDdosProtection: false, - flowLogs: [ - { - id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/networkWatchers/NetworkWatcher_eastus/flowLogs/test-flowlog-nolaw' - } - ] - }, - { - name: 'test-vnet-no-logs', - id: '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet-no-logs', - type: 'Microsoft.Network/virtualNetworks', - tags: {}, - location: 'eastus', - provisioningState: 'Succeeded', - virtualNetworkPeerings: [], - enableDdosProtection: false + "name": 'test-vnet', + "id": '/subscriptions/123/resourceGroups/aqua-resource-group/providers/Microsoft.Network/virtualNetworks/test-vnet', + "type": 'Microsoft.Network/virtualNetworks', + "tags": {}, + "location": 'eastus', + "provisioningState": 'Succeeded', + "virtualNetworkPeerings": [], + "enableDdosProtection": false } ]; -const createCache = (vnets, logs) => { +const createCache = (virtualNetworks, flowLogs) => { var logData = {}; - if (networkWatchers.length) { - logData[networkWatchers[0].id] = { data: logs || [] }; - } + logData[networkWatchers[0].id] = { data: flowLogs || [] }; return { virtualNetworks: { listAll: { 'eastus': { - data: vnets + data: virtualNetworks } } }, @@ -155,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[2]], []); + const cache = createCache([virtualNetworks[1]], []); virtualNetworkFlowLogs.run(cache, {}, (err, results) => { expect(results.length).to.equal(1); expect(results[0].status).to.equal(2); @@ -188,7 +160,7 @@ describe('virtualNetworkFlowLogs', function() { }); 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[1]], [flowLogs[1]]); + 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); @@ -209,4 +181,4 @@ describe('virtualNetworkFlowLogs', function() { }); }); }); -}); +}); \ No newline at end of file From ba666710bd39ad427105d2801927d1c9615edb22 Mon Sep 17 00:00:00 2001 From: Hamza Aziz Date: Fri, 21 Aug 2026 13:50:47 +0000 Subject: [PATCH 6/6] plugin-changes --- plugins/aws/iam/accessKeysLastUsed.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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);