Azure Resource Graph
Azure Resource Graph integration is designed to allow for executing Azure Resource Graph commands, like querying resource data.
Cloud Services · Azure Resource Graph
Details
| ID | Azure Resource Graph |
|---|---|
| Provider | Microsoft |
| Category | Cloud Services |
| From Version | 6.10.0 |
| Docker Image | demisto/crypto:1.0.0.10120494 |
| Supported Modules | Agentix XSIAM Cloud Posture Security |
README
Azure Resource Graph is an Azure service designed to extend Azure Resource Management by providing efficient and performant resource exploration with the ability to query at scale across a given set of resources.
Authorize Cortex XSOAR for Azure Resource Graph (Self-Deployed Configuration)
To use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal. To add the registration, see the Microsoft article.
Authentication
To enable and configure authentication using self deployed app, follow the Self-Deployed Application Authentication
For more details about the authentication used in this integration, see Microsoft Integrations - Authentication.
- After authorizing the Self-Deployed Application, you will get an ID, Token, and Key, which should be inserted in the integration instance configuration’s corresponding fields. After giving consent, the application has to have a role assigned so it can access the relevant resources per subscription.
- In order to assign a role to the application after consent was given:
- Go to the Azure Portal UI.
- Go to Subscriptions, and then Access Control (IAM).
- Click “Add role assignment”.
- Create a new role or select a role that includes permissions for the queries you plan to run.
- Select the Azure Resource Graph application. By default, Azure Applications aren’t displayed in the available options. To find your application, search for the name and select it.
Client Credentials Flow
Follow these steps for client-credentials configuration:.
- In the instance configuration, select the Use a self-deployed Azure application - Client Credentials Authorization Flow checkbox.
- Enter your Client ID in the ID (Client ID) parameter.
- Enter your Client Secret in the Key (Client Secret) parameter.
- Enter your Tenant ID in the Token (Tenant ID) parameter.
- Click Test to validate the URLs, token, and connection.
To use The Azure Resource Graph, you must have appropriate rights in Azure role-based access control (Azure RBAC) with at least read access to the resources you want to query. No results are returned if you don’t have at least read permissions to the Azure object or object group.
Configure Azure Resource Graph in Cortex
| Parameter | Description | Required |
|---|---|---|
| Token / Tenant ID | Received from the authorization process or from the self-deployed configuration process (find the tenant ID in your app overview page in the Azure portal) | False |
| Token / Tenant ID | False | |
| ID / Client ID | Received from the authorization process or from the self-deployed configuration process. | False |
| Key / Client Secret | False | |
| Certificate Thumbprint | Used for certificate authentication. As appears in the “Certificates & secrets” page of the app. | False |
| Private Key | Used for certificate authentication. The private key of the registered certificate. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Gov Account | Select this checkbox if you are using a US Government cloud account (GCC-High). When enabled, the integration uses the US Government management endpoint and Azure US GCC-High cloud configuration. | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
azure-rg-list-operations
Base Command
azure-rg-list-operations
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of operations to return (Default is 50). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureResourceGraph.Operations | String | A list of available Azure Resource Graph operations permissions and descriptions. |
Command Example
!azure-rg-list-operations limit=50
Context Example
{
"value": [
{
"name": "Microsoft.ResourceGraph/operations/read",
"display": {
"provider": "Microsoft Resource Graph",
"resource": "Operation",
"operation": "Get Operations",
"description": "Gets the list of supported operations"
}
},
{
"name": "Microsoft.ResourceGraph/resources/read",
"display": {
"provider": "Microsoft Resource Graph",
"resource": "Resources",
"operation": "Query resources",
"description": "Submits a query on resources within specified subscriptions, management groups or tenant scope"
}
}
]
}
Human Readable Output
| Display | Name |
|---|---|
| provider: Microsoft Resource Graph resource: Operation operation: Get Operations description: Gets the list of supported operations |
Microsoft.ResourceGraph/operations/read |
| provider: Microsoft Resource Graph resource: Resources operation: Query resources description: Submits a query on resources within specified subscriptions, management groups or tenant scope |
Microsoft.ResourceGraph/resources/read |
azure-rg-query
Base Command
azure-rg-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | The query to execute. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| AzureResourceGraph.Query | String | Data returned from query. |
Command Example
!azure-rg-query query="resources | where type == 'microsoft.network/publicipaddresses'| where properties['ipAddress'] == '11.22.33.44' | project name, id, tenantId, subscriptionId, resourceGroup | join kind=inner (resourcecontainers | where type == 'microsoft.resources/subscriptions' | project subscriptionId, properties.managementGroupAncestorsChain) on subscriptionId"
Context Example
{
"count": 1,
"data": [
{
"id": "/subscriptions/1abc234d-12a3-12a3-12a3-1234abcde123/resourceGroups/test-vm-resource-group/providers/Microsoft.Network/publicIPAddresses/test-vm-1-ip",
"name": "test-vm-1-ip",
"properties_managementGroupAncestorsChain": [
{
"displayName": "grand-child-managment-group",
"name": "grand-child-managment-group"
},
{
"displayName": "child-management-group",
"name": "child-management-group"
},
{
"displayName": "test-new-managment-group",
"name": "test-new-managment-group"
},
{
"displayName": "Tenant Root Group",
"name": "a11111111-222-3333-12a3-1234abcde123"
}
],
"resourceGroup": "test-vm-resource-group",
"subscriptionId": "1abc234d-12a3-12a3-12a3-1234abcde123",
"tenantId": "a11111111-222-3333-12a3-1234abcde123"
}
],
"facets": [],
"resultTruncated": "false",
"totalRecords": 1
}
Human Readable Output
| id | name | properties_managementGroupAncestorsChain | resourceGroup | subscriptionId | tenantId | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| /subscriptions/1abc234d-12a3-12a3-12a3-1234abcde123/resourceGroups/test-vm-resource-group/providers/Microsoft.Network/publicIPAddresses/test-vm-1-ip | test-vm-1-ip | {‘displayName’: ‘grand-child-managment-group’, ‘name’: ‘grand-child-managment-group’}, {‘displayName’: ‘child-management-group’, ‘name’: ‘child-management-group’}, {‘displayName’: ‘test-new-managment-group’, ‘name’: ‘test-new-managment-group’}, {‘displayName’: ‘Tenant Root Group’, ‘name’: ‘a11111111-222-3333-12a3-1234abcde123’} |
test-vm-resource-group | 1abc234d-12a3-12a3-12a3-1234abcde123 | a11111111-222-3333-12a3-1234abcde123 |
azure-rg-auth-reset
Run this command if for some reason you need to rerun the authentication process.
Base Command
azure-rg-auth-reset
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Configuration parameters
host— Server URL (e.g., https://management.azure.com) (required)cred_auth_id—cred_token—cred_enc_key—cred_certificate_thumbprint—private_key— Private Keygov_account— Use Gov Account Settingsproxy— Use system proxy settingsunsecure— Trust any certificate (not secure)
Commands (3)
-
azure-rg-auth-resetRun this command if for some reason you need to rerun the authentication process.
-
azure-rg-list-operationsGets all Azure Resource Graph operations permissions and descriptions.
-
azure-rg-queryExecutes a given Azure Resource Graph Query. (Ex: query="Resources | project name, type | limit 5 | order by name asc").
import json import os import pytest from AzureResourceGraph import AzureResourceGraphClient, list_operations_command, pagination, query_resources_command client = AzureResourceGraphClient( base_url="url", tenant_id="tenant", auth_id="auth_id", enc_key="enc_key", app_name="APP_NAME", verify="verify", proxy="proxy", self_deployed="self_deployed", ok_codes=(1, 2), server="server", certificate_thumbprint="", private_key="", ) """ HELPER FUNCTIONS """ def util_load_json(path): full_path = os.path.join(os.path.dirname(__file__), path) with open(file=full_path, encoding="utf-8") as f: return json.loads(f.read()) def get_azure_access_token_mock() -> dict: """ Mock Azure access token object. Returns: dict: Azure access token mock. """ return { "access_token": "my-access-token", "expires_in": 3595, "refresh_token": "my-refresh-token", } """TESTS""" @pytest.mark.parametrize( "function_name, data_file_name, args, additional_args", [ ("list_operations", "test_list_operations_output", None, None), ( "query_resources", "test_resources_query_output", {"query": "dummy"}, {"paging_options": "dummy", "subscriptions": "dummy", "management_groups": "dummy"}, ), ], ) def test_client_api_calls(mocker, function_name, data_file_name, args, additional_args): data = util_load_json(f"test_data/{data_file_name}.json") mock_http_request = mocker.patch.object(client.ms_client, "http_request") mock_http_request.return_value = data if args and not additional_args: result = getattr(client, function_name)(args) elif args and additional_args: result = getattr(client, function_name)(args, **additional_args) else: result = getattr(client, function_name)() assert result == data @pytest.mark.parametrize( "limit, page_size, page_number, expected_number_of_operations, expected_first_name", [ (1, None, None, 1, "Microsoft.ResourceGraph/operations/read"), (3, 2, 2, 2, "Microsoft.ResourceGraph/resourcesHistory/read"), ], ) def test_successful_list_operations_command( mocker, limit, page_size, page_number, expected_number_of_operations, expected_first_name ): operations_data = util_load_json("test_data/test_list_operations_output.json") mocker.patch.object(client, "list_operations", return_value=operations_data) args: dict = {"limit": limit, "page": page_number, "page_size": page_size} command_results = list_operations_command(client, args) assert command_results.outputs[0]["Name"] == expected_first_name assert len(command_results.outputs) == expected_number_of_operations @pytest.mark.parametrize( "limit, page_size, page_number, expected_error_message", [ (None, None, 2, 'Please enter a value for "page_size" when using "page".'), (None, 2, None, 'Please enter a value for "page" when using "page_size".'), ], ) def test_failure_list_operations_command(mocker, limit, page_size, page_number, expected_error_message): try: operations_data = util_load_json("test_data/test_list_operations_output.json") mocker.patch.object(client, "list_operations", return_value=operations_data) args: dict = {"limit": limit, "page": page_number, "page_size": page_size} list_operations_command(client, args) except Exception as e: assert e.message == expected_error_message @pytest.mark.parametrize( "query, data_file_name, expected_output", [("Resources | project id, name, type, location, tags | limit 3", "test_resources_query_output", "test-ssh-nsg")], ) def test_query_resources_command_output(mocker, query, data_file_name, expected_output): query_data = util_load_json(f"test_data/{data_file_name}.json") args: dict = {"query": query, "limit": 1} mocker.patch.object(client, "query_resources", return_value=query_data) command_results = query_resources_command(client, args=args) assert command_results.outputs[0]["name"] == expected_output @pytest.mark.parametrize( "query, data_file_name, extra_args, expected_length, expected_name", [ ( "Resources | project id, name, type, location, tags", "test_resources_query_paging_output", {"page": 1, "page_size": 3}, 3, "test-ssh-nsg-2", ) ], ) def test_query_resources_command_paging(mocker, query, data_file_name, extra_args, expected_length, expected_name): query_data = util_load_json(f"test_data/{data_file_name}.json") args: dict = {"query": query} args.update(extra_args) mocker.patch.object(client, "query_resources", return_value=query_data) command_results = query_resources_command(client, args=args) assert len(command_results.outputs) == expected_length assert command_results.outputs[0]["name"] == expected_name def test_pagination_helper(): fake_response = list(range(1, 101)) results = pagination(fake_response, page_size=3, page_number=5) assert len(results) == 3 def test_test_module_command(mocker) -> None: """ Scenario: run test module when managed identities client id provided. Given: - User has provided managed identities client oid. When: - test-module called. Then: - Ensure the output are as expected """ import AzureResourceGraph import demistomock as demisto from AzureResourceGraph import MicrosoftClient, main params = { "auth_id": "test_client_id", "use_managed_identities": "True", "cred_token": {"password": "test"}, "client_credentials": True, "host": "https://management.azure.com", "tenant_id": "1abc234d-12a3-12a3-12a3-1234abcde123", "cred_auth_id": {"password": "test_api"}, "unsecure": False, "proxy": False, "private_key": "test-key", "self_deployed": True, "enc_key": "test", } # operations_data = util_load_json('./test_data/test_list_operations_output.json') mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(MicrosoftClient, "http_request", return_value=get_azure_access_token_mock()) mocker.patch.object(AzureResourceGraph, "return_results") main() assert "ok" in AzureResourceGraph.return_results.call_args[0][0] class TestGovAccountToggle: """Tests for the Gov Account checkbox feature.""" GOV_SCOPE = "https://management.usgovcloudapi.net/.default" GOV_SERVER = "https://management.usgovcloudapi.net" GOV_BASE_URL = "https://management.usgovcloudapi.net/providers/Microsoft.ResourceGraph" NORMAL_SCOPE = "https://management.azure.com/.default" NORMAL_SERVER = "https://management.azure.com" NORMAL_BASE_URL = "https://management.azure.com/providers/Microsoft.ResourceGraph" def test_client_with_gov_account_enabled(self, mocker): """ Given: - is_gov=True is passed to AzureResourceGraphClient. When: - The client is initialized. Then: - MicrosoftClient is created with the gov scope, AZURE_US_GCC_HIGH_CLOUD azure_cloud, and the gov base_url. - The client server is set to the gov management URL. """ from AzureResourceGraph import AZURE_US_GCC_HIGH_CLOUD, AzureResourceGraphClient, MicrosoftClient mock_ms_client = mocker.patch.object(MicrosoftClient, "__init__", return_value=None) gov_client = AzureResourceGraphClient( base_url=self.NORMAL_BASE_URL, tenant_id="tenant", auth_id="auth_id", enc_key="enc_key", app_name="APP_NAME", verify=False, proxy=False, self_deployed=True, ok_codes=(200,), server=self.NORMAL_SERVER, certificate_thumbprint="", private_key="", is_gov=True, ) call_kwargs = mock_ms_client.call_args[1] assert call_kwargs["scope"] == self.GOV_SCOPE assert call_kwargs["azure_cloud"] == AZURE_US_GCC_HIGH_CLOUD assert call_kwargs["base_url"] == self.GOV_BASE_URL assert gov_client.server == self.GOV_SERVER def test_client_with_gov_account_disabled(self, mocker): """ Given: - is_gov=False (default) is passed to AzureResourceGraphClient. When: - The client is initialized. Then: - MicrosoftClient is created with the standard scope, no azure_cloud parameter, and the standard base_url. - The client server is set to the standard management URL. """ from AzureResourceGraph import AzureResourceGraphClient, MicrosoftClient mock_ms_client = mocker.patch.object(MicrosoftClient, "__init__", return_value=None) normal_client = AzureResourceGraphClient( base_url=self.NORMAL_BASE_URL, tenant_id="tenant", auth_id="auth_id", enc_key="enc_key", app_name="APP_NAME", verify=False, proxy=False, self_deployed=True, ok_codes=(200,), server=self.NORMAL_SERVER, certificate_thumbprint="", private_key="", is_gov=False, ) call_kwargs = mock_ms_client.call_args[1] assert call_kwargs["scope"] == self.NORMAL_SCOPE assert "azure_cloud" not in call_kwargs assert call_kwargs["base_url"] == self.NORMAL_BASE_URL assert normal_client.server == self.NORMAL_SERVER def test_client_default_is_not_gov(self, mocker): """ Given: - is_gov is not passed to AzureResourceGraphClient (uses default). When: - The client is initialized. Then: - MicrosoftClient is created with the standard scope (same as is_gov=False). """ from AzureResourceGraph import AzureResourceGraphClient, MicrosoftClient mock_ms_client = mocker.patch.object(MicrosoftClient, "__init__", return_value=None) default_client = AzureResourceGraphClient( base_url=self.NORMAL_BASE_URL, tenant_id="tenant", auth_id="auth_id", enc_key="enc_key", app_name="APP_NAME", verify=False, proxy=False, self_deployed=True, ok_codes=(200,), server=self.NORMAL_SERVER, certificate_thumbprint="", private_key="", ) call_kwargs = mock_ms_client.call_args[1] assert call_kwargs["scope"] == self.NORMAL_SCOPE assert "azure_cloud" not in call_kwargs assert call_kwargs["base_url"] == self.NORMAL_BASE_URL assert default_client.server == self.NORMAL_SERVER def test_main_passes_gov_account_param(self, mocker): """ Given: - The gov_account parameter is set to True in demisto.params(). When: - main() is called with test-module command. Then: - AzureResourceGraphClient is initialized with is_gov=True and the gov URLs are used. """ import AzureResourceGraph import demistomock as demisto from AzureResourceGraph import AZURE_US_GCC_HIGH_CLOUD, MicrosoftClient, main params = { "auth_id": "test_client_id", "cred_token": {"password": "test"}, "host": "https://management.azure.com", "tenant_id": "1abc234d-12a3-12a3-12a3-1234abcde123", "cred_auth_id": {"password": "test_api"}, "unsecure": False, "proxy": False, "private_key": "test-key", "self_deployed": True, "enc_key": "test", "gov_account": True, } mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="test-module") mock_ms_init = mocker.patch.object(MicrosoftClient, "__init__", return_value=None) mocker.patch.object(MicrosoftClient, "http_request", return_value=get_azure_access_token_mock()) mocker.patch.object(AzureResourceGraph, "return_results") main() ms_call_kwargs = mock_ms_init.call_args[1] assert ms_call_kwargs["scope"] == self.GOV_SCOPE assert ms_call_kwargs["azure_cloud"] == AZURE_US_GCC_HIGH_CLOUD assert ms_call_kwargs["base_url"] == self.GOV_BASE_URL assert "ok" in AzureResourceGraph.return_results.call_args[0][0] def test_main_without_gov_account_param(self, mocker): """ Given: - The gov_account parameter is not set (defaults to False) in demisto.params(). When: - main() is called with test-module command. Then: - AzureResourceGraphClient is initialized with is_gov=False and the standard URLs are used. """ import AzureResourceGraph import demistomock as demisto from AzureResourceGraph import MicrosoftClient, main params = { "auth_id": "test_client_id", "cred_token": {"password": "test"}, "host": "https://management.azure.com", "tenant_id": "1abc234d-12a3-12a3-12a3-1234abcde123", "cred_auth_id": {"password": "test_api"}, "unsecure": False, "proxy": False, "private_key": "test-key", "self_deployed": True, "enc_key": "test", } mocker.patch.object(demisto, "params", return_value=params) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="test-module") mock_ms_init = mocker.patch.object(MicrosoftClient, "__init__", return_value=None) mocker.patch.object(MicrosoftClient, "http_request", return_value=get_azure_access_token_mock()) mocker.patch.object(AzureResourceGraph, "return_results") main() ms_call_kwargs = mock_ms_init.call_args[1] assert ms_call_kwargs["scope"] == self.NORMAL_SCOPE assert "azure_cloud" not in ms_call_kwargs assert ms_call_kwargs["base_url"] == self.NORMAL_BASE_URL assert "ok" in AzureResourceGraph.return_results.call_args[0][0]