diff --git a/exports.js b/exports.js index ef3dcf6881..d8150d0198 100644 --- a/exports.js +++ b/exports.js @@ -1211,6 +1211,9 @@ module.exports = { 'synapseWorkspaceAdAuthEnabled' : require(__dirname + '/plugins/azure/synapse/synapseWorkspaceAdAuthEnabled.js'), 'synapseWorkspacPrivateEndpoint': require(__dirname + '/plugins/azure/synapse/synapseWorkspacPrivateEndpoint.js'), + 'apiInstanceManagedIdentity' : require(__dirname + '/plugins/azure/apiManagement/apiInstanceManagedIdentity.js'), + 'apiInstanceHasTags' : require(__dirname + '/plugins/azure/apiManagement/apiInstanceHasTags.js'), + }, github: { 'publicKeysRotated' : require(__dirname + '/plugins/github/users/publicKeysRotated.js'), diff --git a/helpers/azure/api.js b/helpers/azure/api.js index 1c62d813b0..000b92f186 100644 --- a/helpers/azure/api.js +++ b/helpers/azure/api.js @@ -530,6 +530,11 @@ var calls = { url: 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Databricks/workspaces?api-version=2023-02-01' } }, + apiManagementService: { + list: { + url: 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.ApiManagement/service?api-version=2022-08-01' + } + }, // For CIEM aad: { listRoleAssignments: { diff --git a/helpers/azure/locations.js b/helpers/azure/locations.js index 99a1fd13f7..c376e3ded7 100644 --- a/helpers/azure/locations.js +++ b/helpers/azure/locations.js @@ -135,5 +135,6 @@ module.exports = { containerApps: locations, batchAccounts: locations, machineLearning: locations, + apiManagementService: locations, synapse: locations }; diff --git a/helpers/azure/locations_gov.js b/helpers/azure/locations_gov.js index 34ea27dda3..2f71aa31d7 100644 --- a/helpers/azure/locations_gov.js +++ b/helpers/azure/locations_gov.js @@ -85,5 +85,6 @@ module.exports = { computeGalleries: locations, databricks: locations, containerApps: locations, + apiManagementService: locations, synapse: locations }; diff --git a/helpers/azure/resources.js b/helpers/azure/resources.js index 111bd05ee4..7c05970efa 100644 --- a/helpers/azure/resources.js +++ b/helpers/azure/resources.js @@ -306,5 +306,8 @@ module.exports = { }, machineLearning: { listWorkspaces: 'id' + }, + apiManagementService: { + list: 'id' } }; diff --git a/plugins/azure/apiManagement/apiInstanceHasTags.js b/plugins/azure/apiManagement/apiInstanceHasTags.js new file mode 100644 index 0000000000..f1903dcc30 --- /dev/null +++ b/plugins/azure/apiManagement/apiInstanceHasTags.js @@ -0,0 +1,52 @@ +var async = require('async'); +var helpers = require('../../../helpers/azure'); + +module.exports = { + title: 'API Management Instance Has Tags', + category: 'API Management', + domain: 'Developer Tools', + severity: 'Medium', + description: 'Ensures that Azure API Management instance has tags associated.', + more_info: 'Tags help you to group resources together that are related to or associated with each other. It is a best practice to tag cloud resources to better organize and gain visibility into their usage.', + link: 'https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources', + recommended_action: 'Modify API Management instance and add tags.', + apis: ['apiManagementService:list'], + realtime_triggers: ['microsoftapimanagement:service:write','microsoftapimanagement:service:delete','microsoftresources:tags:write'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var locations = helpers.locations(settings.govcloud); + + async.each(locations.apiManagementService, function(location, rcb){ + var apiManagementService = helpers.addSource(cache, source, + ['apiManagementService', 'list', location]); + + if (!apiManagementService) return rcb(); + + if (apiManagementService.err || !apiManagementService.data) { + helpers.addResult(results, 3, 'Unable to query API Management instances:' + helpers.addError(apiManagementService), location); + return rcb(); + } + + if (!apiManagementService.data.length) { + helpers.addResult(results, 0, 'No existing API Management instances found', location); + return rcb(); + } + + for (let apiInstance of apiManagementService.data) { + if (!apiInstance.id) continue; + + if (apiInstance.tags && Object.entries(apiInstance.tags).length > 0) { + helpers.addResult(results, 0, 'API Management instance has tags associated', location, apiInstance.id); + } else { + helpers.addResult(results, 2, 'API Management instance does not have tags associated', location, apiInstance.id); + } + } + + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/azure/apiManagement/apiInstanceHasTags.spec.js b/plugins/azure/apiManagement/apiInstanceHasTags.spec.js new file mode 100644 index 0000000000..b8575e34e9 --- /dev/null +++ b/plugins/azure/apiManagement/apiInstanceHasTags.spec.js @@ -0,0 +1,101 @@ +var expect = require('chai').expect; +var apiInstanceHasTags = require('./apiInstanceHasTags.js'); + +const apiManagementService = [ + { + "etag": "AAAAAAGIUI4=", + "publisherEmail": "dummy.@aquasec.com", + "publisherName": "dummy", + "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com", + "provisioningState": "Succeeded", + "targetProvisioningState": "", + "identity": null, + "zones": null, + "tags": {}, + "location": "East US", + "id": "/subscriptions/123456/resourceGroups/testfunction_group/providers/Microsoft.ApiManagement/service/test", + "name": "meerab", + "type": "Microsoft.ApiManagement/service" + }, + { + "etag": "AAAAAAGIUI4=", + "publisherEmail": "dummy.@aquasec.com", + "publisherName": "dummy", + "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com", + "provisioningState": "Succeeded", + "targetProvisioningState": "", + "identity": { + "type": "SystemAssigned", + "principalId": "fdd1f197-d0e0-4d04-a5ef-9dbb654afd14", + "tenantId": "d207c7bd-fcb1-4dd3-855a-cfd2f9b651e8" + }, + "zones": null, + "location": "East US", + "tags": {"key": "value"}, + "id": "/subscriptions/123456/resourceGroups/testfunction_group/providers/Microsoft.ApiManagement/service/test", + "name": "meerab", + "type": "Microsoft.ApiManagement/service" + } +]; + +const createCache = (apiManagementService, err) => { + return { + apiManagementService: { + list: { + 'eastus': { + data: apiManagementService, + err: err + } + } + } + } +}; + +describe('apiInstanceHasTags', function () { + describe('run', function () { + + it('should give pass result if No existing API Management service instances found', function (done) { + const cache = createCache([]); + apiInstanceHasTags.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 API Management instances found'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give unknown result if Unable to query API Management service instances', function (done) { + const cache = createCache(null, 'Error'); + apiInstanceHasTags.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 API Management instances'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give passing result if API Management service instances has tags associated', function (done) { + const cache = createCache([apiManagementService[1]]); + apiInstanceHasTags.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('API Management instance has tags associated'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if API Management service instances does not have tags associated', function (done) { + const cache = createCache([apiManagementService[0]]); + apiInstanceHasTags.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('API Management instance does not have tags associated'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/plugins/azure/apiManagement/apiInstanceManagedIdentity.js b/plugins/azure/apiManagement/apiInstanceManagedIdentity.js new file mode 100644 index 0000000000..57557b0183 --- /dev/null +++ b/plugins/azure/apiManagement/apiInstanceManagedIdentity.js @@ -0,0 +1,52 @@ +var async = require('async'); +var helpers = require('../../../helpers/azure'); + +module.exports = { + title: 'API Management Instance Managed Identity', + category: 'API Management', + domain: 'Developer Tools', + severity: 'Medium', + description: 'Ensures that Azure API Management instance has managed identity enabled.', + more_info: 'Enabling managed identities eliminate the need for developers having to manage credentials by providing an identity for the Azure resource in Azure AD and using it to obtain Azure Active Directory (Azure AD) tokens.', + link: 'https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-use-managed-service-identity', + recommended_action: 'Modify API Management instance and add managed identity.', + apis: ['apiManagementService:list'], + realtime_triggers: ['microsoftapimanagement:service:write','microsoftapimanagement:service:delete'], + + run: function(cache, settings, callback) { + var results = []; + var source = {}; + var locations = helpers.locations(settings.govcloud); + + async.each(locations.apiManagementService, function(location, rcb){ + var apiManagementService = helpers.addSource(cache, source, + ['apiManagementService', 'list', location]); + + if (!apiManagementService) return rcb(); + + if (apiManagementService.err || !apiManagementService.data) { + helpers.addResult(results, 3, 'Unable to query API Management instances:' + helpers.addError(apiManagementService), location); + return rcb(); + } + + if (!apiManagementService.data.length) { + helpers.addResult(results, 0, 'No existing API Management instances found', location); + return rcb(); + } + + for (let apiInstance of apiManagementService.data) { + if (!apiInstance.id) continue; + + if (apiInstance.identity) { + helpers.addResult(results, 0, 'API Management instance has managed identity enabled', location, apiInstance.id); + } else { + helpers.addResult(results, 2, 'API Management instance does not have managed identity enabled', location, apiInstance.id); + } + } + + rcb(); + }, function(){ + callback(null, results, source); + }); + } +}; diff --git a/plugins/azure/apiManagement/apiInstanceManagedIdentity.spec.js b/plugins/azure/apiManagement/apiInstanceManagedIdentity.spec.js new file mode 100644 index 0000000000..145736284f --- /dev/null +++ b/plugins/azure/apiManagement/apiInstanceManagedIdentity.spec.js @@ -0,0 +1,101 @@ +var expect = require('chai').expect; +var apiInstanceManagedIdentity = require('./apiInstanceManagedIdentity.js'); + +const apiManagementService = [ + { + "etag": "AAAAAAGIUI4=", + "publisherEmail": "dummy.@aquasec.com", + "publisherName": "dummy", + "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com", + "provisioningState": "Succeeded", + "targetProvisioningState": "", + "identity": null, + "zones": null, + "location": "East US", + "tags": {}, + "id": "/subscriptions/123456/resourceGroups/testfunction_group/providers/Microsoft.ApiManagement/service/test", + "name": "meerab", + "type": "Microsoft.ApiManagement/service" + }, + { + "etag": "AAAAAAGIUI4=", + "publisherEmail": "dummy.@aquasec.com", + "publisherName": "dummy", + "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com", + "provisioningState": "Succeeded", + "targetProvisioningState": "", + "identity": { + "type": "SystemAssigned", + "principalId": "fdd1f197-d0e0-4d04-a5ef-9dbb654afd14", + "tenantId": "d207c7bd-fcb1-4dd3-855a-cfd2f9b651e8" + }, + "zones": null, + "location": "East US", + "tags": {}, + "id": "/subscriptions/123456/resourceGroups/testfunction_group/providers/Microsoft.ApiManagement/service/test", + "name": "meerab", + "type": "Microsoft.ApiManagement/service" + } +]; + +const createCache = (apiManagementService, err) => { + return { + apiManagementService: { + list: { + 'eastus': { + data: apiManagementService, + err: err + } + } + } + } +}; + +describe('apiInstanceManagedIdentity', function () { + describe('run', function () { + + it('should give pass result if No existing API Management service instances found', function (done) { + const cache = createCache([]); + apiInstanceManagedIdentity.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 API Management instances found'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give unknown result if Unable to query API Management service instances:', function (done) { + const cache = createCache(null, 'Error'); + apiInstanceManagedIdentity.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 API Management instances:'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give passing result if API Management service instances has managed identity enabled', function (done) { + const cache = createCache([apiManagementService[1]]); + apiInstanceManagedIdentity.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(0); + expect(results[0].message).to.include('API Management instance has managed identity enabled'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + + it('should give failing result if API Management service instances does not have managed identity enabled', function (done) { + const cache = createCache([apiManagementService[0]]); + apiInstanceManagedIdentity.run(cache, {}, (err, results) => { + expect(results.length).to.equal(1); + expect(results[0].status).to.equal(2); + expect(results[0].message).to.include('API Management instance does not have managed identity enabled'); + expect(results[0].region).to.equal('eastus'); + done(); + }); + }); + }); +}); \ No newline at end of file