diff --git a/content/response_integrations/google/azure_security_center/actions/GetOAuthAuthorizationCode.py b/content/response_integrations/google/azure_security_center/actions/GetOAuthAuthorizationCode.py index b6b7e124f2..c558cb1d76 100644 --- a/content/response_integrations/google/azure_security_center/actions/GetOAuthAuthorizationCode.py +++ b/content/response_integrations/google/azure_security_center/actions/GetOAuthAuthorizationCode.py @@ -19,12 +19,17 @@ from TIPCommon.extraction import extract_action_param, extract_configuration_param -from ..core.consts import INTEGRATION_NAME, GET_AUTHORIZATION_SCRIPT_NAME +from ..core.consts import ( + INTEGRATION_NAME, + GET_AUTHORIZATION_SCRIPT_NAME, + DEFAULT_LOGIN_API_ROOT, + DEFAULT_API_ROOT, +) AUTHORIZATION_URL = ( - "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize?" + "{login_api_root}/{tenant_id}/oauth2/v2.0/authorize?" "response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&response_mode=query&" - "scope=https://management.azure.com/.default&state=12345" + "scope={api_root}/.default&state=12345" ) @@ -49,6 +54,22 @@ def main(): is_mandatory=True, print_value=True, ) + login_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) + api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="API Root", + default_value=DEFAULT_API_ROOT, + is_mandatory=False, + print_value=True, + ) # Action configuration redirect_url = extract_action_param( @@ -58,7 +79,11 @@ def main(): try: auth_link = AUTHORIZATION_URL.format( - tenant_id=tenant_id, client_id=client_id, redirect_uri=redirect_url + login_api_root=(login_api_root or DEFAULT_LOGIN_API_ROOT).rstrip("/"), + api_root=(api_root or DEFAULT_API_ROOT).rstrip("/"), + tenant_id=tenant_id, + client_id=client_id, + redirect_uri=redirect_url, ) siemplify.result.add_link("Authorization Code Link", auth_link) output_message = ( diff --git a/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.py b/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.py index 5024c12ec2..6e4a3af1d4 100644 --- a/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.py +++ b/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.py @@ -20,7 +20,11 @@ from TIPCommon.extraction import extract_action_param, extract_configuration_param from ..core.AzureSecurityCenterManager import AzureSecurityCenterManager -from ..core.consts import INTEGRATION_NAME, GENERATE_TOKEN_SCRIPT_NAME +from ..core.consts import ( + INTEGRATION_NAME, + GENERATE_TOKEN_SCRIPT_NAME, + DEFAULT_LOGIN_API_ROOT, +) @output_handler @@ -50,6 +54,14 @@ def main(): param_name="Client Secret", is_mandatory=True, ) + login_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) verify_ssl = extract_configuration_param( siemplify, provider_name=INTEGRATION_NAME, @@ -76,6 +88,7 @@ def main(): code=authorization_code, tenant_id=tenant_id, verify_ssl=verify_ssl, + login_api_root=login_api_root, ) siemplify.result.add_result_json(response_json) output_message = f"Successfully generated refresh token in {INTEGRATION_NAME}." diff --git a/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.yaml b/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.yaml index ce00b2f96d..e7221e03df 100644 --- a/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.yaml +++ b/content/response_integrations/google/azure_security_center/actions/GetOAuthRefreshToken.yaml @@ -29,5 +29,8 @@ parameters: description: Specify the authorization code from action "Get OAuth Authorization Code" is_mandatory: true -dynamic_results_metadata: [] +dynamic_results_metadata: +- result_example_path: resources/GetOAuthRefreshToken_JsonResult_example.json + result_name: JsonResult + show_result: true creator: admin diff --git a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.py b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.py index 642ee1858f..3724f58807 100644 --- a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.py +++ b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.py @@ -25,6 +25,9 @@ LIST_REGULATORY_STANDARD_CONTROLS_SCRIPT_NAME, REGULATORY_STANDARD_STATES, DEFAULT_NUM_STANDARDS_TO_RETURN, + DEFAULT_LOGIN_API_ROOT, + DEFAULT_API_ROOT, + DEFAULT_GRAPH_API_ROOT, ) from ..core.AzureSecurityCenterManager import AzureSecurityCenterManager from ..core.exceptions import AzureSecurityCenterValidationException @@ -89,6 +92,30 @@ def main(): param_name="Refresh Token", is_mandatory=False, ) + login_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) + api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="API Root", + default_value=DEFAULT_API_ROOT, + is_mandatory=False, + print_value=True, + ) + graph_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Graph API Root", + default_value=DEFAULT_GRAPH_API_ROOT, + is_mandatory=False, + print_value=True, + ) verify_ssl = extract_configuration_param( siemplify, provider_name=INTEGRATION_NAME, @@ -171,6 +198,9 @@ def main(): tenant_id=tenant_id, refresh_token=refresh_token, verify_ssl=verify_ssl, + login_api_root=login_api_root, + api_root=api_root, + graph_api_root=graph_api_root, ) manager.get_regulatory_compliance_standards(subscription_id=subscription_id) diff --git a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.yaml b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.yaml index 64ce712a29..a4ad9079fd 100644 --- a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.yaml +++ b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandardControls.yaml @@ -45,7 +45,7 @@ parameters: description: Specify how many controls to return per standard. is_mandatory: false dynamic_results_metadata: -- result_example_path: resources/list_regulatory_standard_controls_JsonResult_example.json +- result_example_path: resources/ListRegulatoryStandardControls_JsonResult_example.json result_name: JsonResult show_result: true creator: Admin diff --git a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.py b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.py index 343ea63afc..a1aabcea48 100644 --- a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.py +++ b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.py @@ -26,6 +26,9 @@ LIST_REGULATORY_STANDARDS_SCRIPT_NAME, REGULATORY_STANDARD_STATES, DEFAULT_NUM_STANDARDS_TO_RETURN, + DEFAULT_LOGIN_API_ROOT, + DEFAULT_API_ROOT, + DEFAULT_GRAPH_API_ROOT, ) from ..core.exceptions import AzureSecurityCenterValidationException from ..core.utils import load_csv_to_list @@ -89,6 +92,30 @@ def main(): param_name="Refresh Token", is_mandatory=False, ) + login_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) + api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="API Root", + default_value=DEFAULT_API_ROOT, + is_mandatory=False, + print_value=True, + ) + graph_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Graph API Root", + default_value=DEFAULT_GRAPH_API_ROOT, + is_mandatory=False, + print_value=True, + ) verify_ssl = extract_configuration_param( siemplify, provider_name=INTEGRATION_NAME, @@ -161,6 +188,9 @@ def main(): tenant_id=tenant_id, refresh_token=refresh_token, verify_ssl=verify_ssl, + login_api_root=login_api_root, + api_root=api_root, + graph_api_root=graph_api_root, ) regulatory_standards = manager.get_regulatory_standards( state_filters=state_filter, limit=max_standards_to_return diff --git a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.yaml b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.yaml index 4863a5dd8b..b38870a8fd 100644 --- a/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.yaml +++ b/content/response_integrations/google/azure_security_center/actions/ListRegulatoryStandards.yaml @@ -38,7 +38,7 @@ parameters: description: Specify how many standards to return. is_mandatory: false dynamic_results_metadata: -- result_example_path: resources/list_regulatory_standards_JsonResult_example.json +- result_example_path: resources/ListRegulatoryStandards_JsonResult_example.json result_name: JsonResult show_result: true creator: Admin diff --git a/content/response_integrations/google/azure_security_center/actions/Ping.py b/content/response_integrations/google/azure_security_center/actions/Ping.py index 04d6edabcc..ccc3a9c682 100644 --- a/content/response_integrations/google/azure_security_center/actions/Ping.py +++ b/content/response_integrations/google/azure_security_center/actions/Ping.py @@ -20,7 +20,13 @@ from TIPCommon.extraction import extract_configuration_param from ..core.AzureSecurityCenterManager import AzureSecurityCenterManager -from ..core.consts import PING_SCRIPT_NAME, INTEGRATION_NAME +from ..core.consts import ( + PING_SCRIPT_NAME, + INTEGRATION_NAME, + DEFAULT_LOGIN_API_ROOT, + DEFAULT_API_ROOT, + DEFAULT_GRAPH_API_ROOT, +) @output_handler @@ -73,6 +79,30 @@ def main(): param_name="Refresh Token", is_mandatory=False, ) + login_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) + api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="API Root", + default_value=DEFAULT_API_ROOT, + is_mandatory=False, + print_value=True, + ) + graph_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Graph API Root", + default_value=DEFAULT_GRAPH_API_ROOT, + is_mandatory=False, + print_value=True, + ) verify_ssl = extract_configuration_param( siemplify, provider_name=INTEGRATION_NAME, @@ -93,6 +123,9 @@ def main(): tenant_id=tenant_id, refresh_token=refresh_token, verify_ssl=verify_ssl, + login_api_root=login_api_root, + api_root=api_root, + graph_api_root=graph_api_root, ) manager.test_connectivity() status = EXECUTION_STATE_COMPLETED diff --git a/content/response_integrations/google/azure_security_center/actions/UpdateAlertStatus.py b/content/response_integrations/google/azure_security_center/actions/UpdateAlertStatus.py index ebbf5aba13..91ffe7fbb1 100644 --- a/content/response_integrations/google/azure_security_center/actions/UpdateAlertStatus.py +++ b/content/response_integrations/google/azure_security_center/actions/UpdateAlertStatus.py @@ -26,6 +26,9 @@ DEFAULT_ALERT_STATUS, MAPPED_ALERT_STATUS, PLURAL_ALERT_STATUS, + DEFAULT_LOGIN_API_ROOT, + DEFAULT_API_ROOT, + DEFAULT_GRAPH_API_ROOT, ) from ..core.utils import get_mapped_value @@ -86,6 +89,30 @@ def main(): param_name="Refresh Token", is_mandatory=False, ) + login_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) + api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="API Root", + default_value=DEFAULT_API_ROOT, + is_mandatory=False, + print_value=True, + ) + graph_api_root = extract_configuration_param( + siemplify, + provider_name=INTEGRATION_NAME, + param_name="Graph API Root", + default_value=DEFAULT_GRAPH_API_ROOT, + is_mandatory=False, + print_value=True, + ) verify_ssl = extract_configuration_param( siemplify, provider_name=INTEGRATION_NAME, @@ -135,6 +162,9 @@ def main(): tenant_id=tenant_id, refresh_token=refresh_token, verify_ssl=verify_ssl, + login_api_root=login_api_root, + api_root=api_root, + graph_api_root=graph_api_root, ) alert_status = get_mapped_value(MAPPED_ALERT_STATUS, alert_status) manager.update_alert_status( diff --git a/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.py b/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.py index ad962cb579..94a9d84647 100644 --- a/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.py +++ b/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.py @@ -37,6 +37,9 @@ TIME_FORMAT, MAX_EVENTS_PER_ALERT, HOURS_LIMIT_IN_IDS_FILE, + DEFAULT_LOGIN_API_ROOT, + DEFAULT_API_ROOT, + DEFAULT_GRAPH_API_ROOT, ) from ..core.exceptions import AzureSecurityCenterValidationException from ..core import utils @@ -88,6 +91,27 @@ def main(is_test_run): refresh_token = extract_connector_param( siemplify, param_name="Refresh Token", is_mandatory=False, print_value=False ) + login_api_root = extract_connector_param( + siemplify, + param_name="Login API Root", + default_value=DEFAULT_LOGIN_API_ROOT, + is_mandatory=False, + print_value=True, + ) + api_root = extract_connector_param( + siemplify, + param_name="API Root", + default_value=DEFAULT_API_ROOT, + is_mandatory=False, + print_value=True, + ) + graph_api_root = extract_connector_param( + siemplify, + param_name="Graph API Root", + default_value=DEFAULT_GRAPH_API_ROOT, + is_mandatory=False, + print_value=True, + ) max_alerts_to_fetch = extract_connector_param( siemplify, param_name="Max Alerts To Fetch", @@ -175,6 +199,9 @@ def main(is_test_run): tenant_id=tenant_id, siemplify=siemplify, refresh_token=refresh_token, + login_api_root=login_api_root, + api_root=api_root, + graph_api_root=graph_api_root, ) # Read already existing alerts ids diff --git a/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.yaml b/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.yaml index 5c381e8abd..aa524dc4f7 100644 --- a/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.yaml +++ b/content/response_integrations/google/azure_security_center/connectors/AzureSecurityAlertsConnector.yaml @@ -105,6 +105,27 @@ parameters: is_mandatory: false is_advanced: false mode: script +- name: Login API Root + default_value: https://login.microsoftonline.com + type: string + description: The API root of the Microsoft identity platform login service used for authentication. + is_mandatory: true + is_advanced: false + mode: script +- name: API Root + default_value: https://management.azure.com + type: string + description: The API root of the Azure Management service. + is_mandatory: true + is_advanced: false + mode: script +- name: Graph API Root + default_value: https://graph.microsoft.com + type: string + description: The API root of the Microsoft Graph service. + is_mandatory: true + is_advanced: false + mode: script - name: Max Hours Backwards default_value: 1 type: integer @@ -138,7 +159,7 @@ parameters: is_advanced: true mode: script - name: Verify SSL - default_value: false + default_value: true type: boolean description: If enabled, verify the SSL certificate for the connection to the Azure Security Center server is valid. diff --git a/content/response_integrations/google/azure_security_center/core/AzureSecurityCenterManager.py b/content/response_integrations/google/azure_security_center/core/AzureSecurityCenterManager.py index 10f59a1433..a86bb39398 100644 --- a/content/response_integrations/google/azure_security_center/core/AzureSecurityCenterManager.py +++ b/content/response_integrations/google/azure_security_center/core/AzureSecurityCenterManager.py @@ -45,6 +45,9 @@ def __init__( verify_ssl: Optional[bool] = False, siemplify=None, refresh_token=None, + login_api_root: Optional[str] = None, + api_root: Optional[str] = None, + graph_api_root: Optional[str] = None, ): """ The method is used to init an object of Manager class @@ -56,6 +59,9 @@ def __init__( :param subscription_id: {str} Subscription ID of the Microsoft Azure application :param siemplify: {ConnectorExecutor} connector executor instance :param refresh_token: {str} Refresh token for the OAuth authorization. + :param login_api_root: {str} The API root of the Microsoft identity platform login service. + :param api_root: {str} The API root of the Azure Management service. + :param graph_api_root: {str} The API root of the Microsoft Graph service. """ self.client_id = client_id self.client_secret = client_secret @@ -64,6 +70,9 @@ def __init__( self.tenant_id = tenant_id self.subscription_id = subscription_id self.siemplify = siemplify + self.login_api_root = (login_api_root or consts.DEFAULT_LOGIN_API_ROOT).rstrip("/") + self.api_root = (api_root or consts.DEFAULT_API_ROOT).rstrip("/") + self.graph_api_root = (graph_api_root or consts.DEFAULT_GRAPH_API_ROOT).rstrip("/") self.parser = AzureSecurtyCenterParser() self.session = requests.session() @@ -95,7 +104,7 @@ def get_access_token(self, refresh_token): "refresh_token": refresh_token, } response = self.session.post( - consts.OAUTH_URL.format(tenant_id=self.tenant_id), data=data + consts.OAUTH_URL.format(login_api_root=self.login_api_root, tenant_id=self.tenant_id), data=data ) self.validate_access_token_response(response, "Unable to obtain access token") self.new_refresh_token = response.json()["refresh_token"] @@ -103,7 +112,7 @@ def get_access_token(self, refresh_token): @staticmethod def obtain_refresh_token( - client_id, client_secret, redirect_uri, code, tenant_id, verify_ssl + client_id, client_secret, redirect_uri, code, tenant_id, verify_ssl, login_api_root=consts.DEFAULT_LOGIN_API_ROOT ): """ Obtain a refresh token @@ -113,8 +122,10 @@ def obtain_refresh_token( :param code: {str] The generated code from the authorizing step :param tenant_id: {str} Tenant ID of the Microsoft Azure application. :param verify_ssl: If enabled, verify the SSL certificate for the connection to the server is valid. + :param login_api_root: {str} The API root of the Microsoft identity platform login service. :return: {str} The new refresh token """ + login_api_root = (login_api_root or consts.DEFAULT_LOGIN_API_ROOT).rstrip("/") data = { "code": code, "client_id": client_id, @@ -123,7 +134,7 @@ def obtain_refresh_token( "grant_type": "authorization_code", } response = requests.post( - consts.OAUTH_URL.format(tenant_id=tenant_id), data=data, verify=verify_ssl + consts.OAUTH_URL.format(login_api_root=login_api_root, tenant_id=tenant_id), data=data, verify=verify_ssl ) AzureSecurityCenterManager.validate_access_token_response( response, error_msg="Unable to obtain refresh token" @@ -195,20 +206,28 @@ def _get_full_url(self, url_key, **kwargs) -> str: :param kwargs: {dict} Variables passed for string formatting :return: {str} The full url """ - return consts.ENDPOINTS[url_key].format(**kwargs) + return consts.ENDPOINTS[url_key].format( + login_api_root=self.login_api_root, + api_root=self.api_root, + graph_api_root=self.graph_api_root, + **kwargs, + ) - def _get_auth_token(self, scope=consts.MICROSOFT_SECURITY_CENTER_SCOPE): + def _get_auth_token(self, scope=None): """ Retrieves Bearer auth token for the manager. By default an auth token for Azure Security Center is returned :param scope: {str} Authentication scope. For example https://management.azure.com/.default or https://graph.microsoft.com/.default :return: {str} authentication token """ + if scope is None: + scope = f"{self.api_root}/.default" + request_url = self._get_full_url( url_key="get-auth-token", tenant_id=self.tenant_id ) grant_type = ( "password" - if scope == consts.MICROSOFT_SECURITY_CENTER_SCOPE + if scope == f"{self.api_root}/.default" else "client_credentials" ) payload = { @@ -218,7 +237,7 @@ def _get_auth_token(self, scope=consts.MICROSOFT_SECURITY_CENTER_SCOPE): "scope": scope, } - if scope == consts.MICROSOFT_SECURITY_CENTER_SCOPE: + if scope == f"{self.api_root}/.default": payload["userName"] = self.username payload["password"] = self.password @@ -391,7 +410,7 @@ def get_alert_ids( } # Authenticate with Graph - graph_token = self._get_auth_token(scope=consts.MICROSOFT_GRAPH_SCOPE) + graph_token = self._get_auth_token(scope=f"{self.graph_api_root}/.default") self.session.headers.update({"Authorization": f"Bearer {graph_token}"}) response = self.session.get(url=request_url, params=params) diff --git a/content/response_integrations/google/azure_security_center/core/consts.py b/content/response_integrations/google/azure_security_center/core/consts.py index f3b4f67acb..7522afba52 100644 --- a/content/response_integrations/google/azure_security_center/core/consts.py +++ b/content/response_integrations/google/azure_security_center/core/consts.py @@ -13,23 +13,39 @@ # limitations under the License. from __future__ import annotations + INTEGRATION_NAME = "Azure Security Center" INTEGRATION_IDENTIFIER = "AzureSecurityCenter" +DEFAULT_LOGIN_API_ROOT = "https://login.microsoftonline.com" +DEFAULT_API_ROOT = "https://management.azure.com" +DEFAULT_GRAPH_API_ROOT = "https://graph.microsoft.com" + ENDPOINTS = { - "get-auth-token": "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token", - "ping": "https://management.azure.com/providers/Microsoft.Security/operations", - "list-regulatory-standards": "https://management.azure.com/subscriptions/{subscription_id}/providers/Microsoft.Security/regulatoryComplianceStandards/", - "list-regulatory-standard-controls": "https://management.azure.com/subscriptions/{subscription_id}/providers/Microsoft.Security/regulatoryComplianceStandards/{standard_name}/regulatoryComplianceControls", - "update-alert-status": "https://management.azure.com/subscriptions/{subscription_id}/providers/Microsoft.Security/locations/{location}/alerts/{alert_id}/{status}", - "get-alert-ids": "https://graph.microsoft.com/v1.0/security/alerts", - "get-alert-details": "https://management.azure.com/subscriptions/{subscription_id}/providers/Microsoft.Security/locations/{location}/alerts/{alert_id}", - "get-regulatory-compliance-standards": "https://management.azure.com/subscriptions/{subscription_id}/providers/Microsoft.Security/regulatoryComplianceStandards/", + "get-auth-token": "{login_api_root}/{tenant_id}/oauth2/v2.0/token", + "ping": "{api_root}/providers/Microsoft.Security/operations", + "list-regulatory-standards": ( + "{api_root}/subscriptions/{subscription_id}/providers/Microsoft.Security/regulatoryComplianceStandards/" + ), + "list-regulatory-standard-controls": ( + "{api_root}/subscriptions/{subscription_id}/providers/Microsoft.Security/" + "regulatoryComplianceStandards/{standard_name}/regulatoryComplianceControls" + ), + "update-alert-status": ( + "{api_root}/subscriptions/{subscription_id}/providers/Microsoft.Security/" + "locations/{location}/alerts/{alert_id}/{status}" + ), + "get-alert-ids": "{graph_api_root}/v1.0/security/alerts", + "get-alert-details": ( + "{api_root}/subscriptions/{subscription_id}/providers/Microsoft.Security/" + "locations/{location}/alerts/{alert_id}" + ), + "get-regulatory-compliance-standards": ( + "{api_root}/subscriptions/{subscription_id}/providers/Microsoft.Security/regulatoryComplianceStandards/" + ), } -MICROSOFT_SECURITY_CENTER_SCOPE = "https://management.azure.com/.default" -MICROSOFT_GRAPH_SCOPE = "https://graph.microsoft.com/.default" -OAUTH_URL = "https://login.microsoftonline.com/{tenant_id}/oauth2/token" +OAUTH_URL = "{login_api_root}/{tenant_id}/oauth2/token" PING_SCRIPT_NAME = "Ping" LIST_REGULATORY_STANDARDS_SCRIPT_NAME = "List Regulatory Standards" diff --git a/content/response_integrations/google/azure_security_center/definition.yaml b/content/response_integrations/google/azure_security_center/definition.yaml index a2d5fa5026..3d74cf0271 100644 --- a/content/response_integrations/google/azure_security_center/definition.yaml +++ b/content/response_integrations/google/azure_security_center/definition.yaml @@ -54,11 +54,29 @@ parameters: - name: Refresh Token default_value: '' type: password - description: '' + description: Refresh token for the OAuth authorization. is_mandatory: false integration_identifier: AzureSecurityCenter +- name: Login API Root + default_value: https://login.microsoftonline.com + type: string + description: The API root of the Microsoft identity platform login service used for authentication. + is_mandatory: true + integration_identifier: AzureSecurityCenter +- name: API Root + default_value: https://management.azure.com + type: string + description: The API root of the Azure Management service. + is_mandatory: true + integration_identifier: AzureSecurityCenter +- name: Graph API Root + default_value: https://graph.microsoft.com + type: string + description: The API root of the Microsoft Graph service. + is_mandatory: true + integration_identifier: AzureSecurityCenter - name: Verify SSL - default_value: 'false' + default_value: 'true' type: boolean description: '' is_mandatory: true diff --git a/content/response_integrations/google/azure_security_center/jobs/RefreshTokenRenewalJob.py b/content/response_integrations/google/azure_security_center/jobs/RefreshTokenRenewalJob.py index 56a179e245..048131d84d 100644 --- a/content/response_integrations/google/azure_security_center/jobs/RefreshTokenRenewalJob.py +++ b/content/response_integrations/google/azure_security_center/jobs/RefreshTokenRenewalJob.py @@ -120,7 +120,10 @@ def _build_manager_for_instance( subscription_id=instance_settings.get("Subscription ID"), tenant_id=instance_settings.get("Tenant ID"), refresh_token=instance_settings.get("Refresh Token"), - verify_ssl=instance_settings.get("Verify SSL").lower() == "true", + verify_ssl=str(instance_settings.get("Verify SSL", "true")).lower() == "true", + login_api_root=instance_settings.get("Login API Root"), + api_root=instance_settings.get("API Root"), + graph_api_root=instance_settings.get("Graph API Root"), ) diff --git a/content/response_integrations/google/azure_security_center/pyproject.toml b/content/response_integrations/google/azure_security_center/pyproject.toml index 1b31c07ef3..4784461e2f 100644 --- a/content/response_integrations/google/azure_security_center/pyproject.toml +++ b/content/response_integrations/google/azure_security_center/pyproject.toml @@ -14,7 +14,7 @@ [project] name = "AzureSecurityCenter" -version = "17.0" +version = "18.0" description = "Azure Security Center is a unified infrastructure security management system that strengthens the security posture of your data centers, and provides advanced threat protection across your hybrid workloads in the cloud - whether they're in Azure or not - as well as on premises." requires-python = ">=3.11,<3.12" dependencies = [ "environmentcommon", "requests==2.32.5", "tipcommon",] diff --git a/content/response_integrations/google/azure_security_center/release_notes.yaml b/content/response_integrations/google/azure_security_center/release_notes.yaml index 80a1e60bde..8679eeb2fa 100644 --- a/content/response_integrations/google/azure_security_center/release_notes.yaml +++ b/content/response_integrations/google/azure_security_center/release_notes.yaml @@ -189,3 +189,13 @@ item_type: Integration publish_time: '2026-07-17' ticket_number: '' +- description: Added configurable Login API Root, API Root, and Graph API Root parameters to support sovereign clouds and custom gateways. + version: 18.0 + item_name: AzureSecurityCenter + item_type: Integration + publish_time: '2026-08-17' + ticket_number: '529765313' + new: false + regressive: false + deprecated: false + removed: false diff --git a/content/response_integrations/google/azure_security_center/resources/GetOAuthRefreshToken_JsonResult_example.json b/content/response_integrations/google/azure_security_center/resources/GetOAuthRefreshToken_JsonResult_example.json new file mode 100644 index 0000000000..cc87733c42 --- /dev/null +++ b/content/response_integrations/google/azure_security_center/resources/GetOAuthRefreshToken_JsonResult_example.json @@ -0,0 +1,8 @@ +{ + "token_type": "Bearer", + "scope": "https://management.azure.com/.default", + "expires_in": 3599, + "ext_expires_in": 3599, + "access_token": "eyJ0eXAiOiJKV1QiLC...", + "refresh_token": "0.ARwA..." +} diff --git a/content/response_integrations/google/azure_security_center/resources/list_regulatory_standard_controls_JsonResult_example.json b/content/response_integrations/google/azure_security_center/resources/ListRegulatoryStandardControls_JsonResult_example.json similarity index 100% rename from content/response_integrations/google/azure_security_center/resources/list_regulatory_standard_controls_JsonResult_example.json rename to content/response_integrations/google/azure_security_center/resources/ListRegulatoryStandardControls_JsonResult_example.json diff --git a/content/response_integrations/google/azure_security_center/resources/list_regulatory_standards_JsonResult_example.json b/content/response_integrations/google/azure_security_center/resources/ListRegulatoryStandards_JsonResult_example.json similarity index 100% rename from content/response_integrations/google/azure_security_center/resources/list_regulatory_standards_JsonResult_example.json rename to content/response_integrations/google/azure_security_center/resources/ListRegulatoryStandards_JsonResult_example.json diff --git a/content/response_integrations/google/azure_security_center/tests/config.json b/content/response_integrations/google/azure_security_center/tests/config.json new file mode 100644 index 0000000000..24600b872e --- /dev/null +++ b/content/response_integrations/google/azure_security_center/tests/config.json @@ -0,0 +1,13 @@ +{ + "Client ID": "", + "Client Secret": "", + "Username": "", + "Password": "", + "Subscription ID": "", + "Tenant ID": "", + "Refresh Token": "", + "Login API Root": "https://login.microsoftonline.com", + "API Root": "https://management.azure.com", + "Graph API Root": "https://graph.microsoft.com", + "Verify SSL": true +} diff --git a/content/response_integrations/google/azure_security_center/tests/test_manager.py b/content/response_integrations/google/azure_security_center/tests/test_manager.py new file mode 100644 index 0000000000..fd0d99f503 --- /dev/null +++ b/content/response_integrations/google/azure_security_center/tests/test_manager.py @@ -0,0 +1,144 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from typing import TYPE_CHECKING +from unittest.mock import MagicMock, patch + +import pytest + +from ..core import consts +from ..core.AzureSecurityCenterManager import AzureSecurityCenterManager + +if TYPE_CHECKING: + from collections.abc import Generator + + +@pytest.fixture +def mock_requests_session() -> Generator[MagicMock, None, None]: + """Fixture providing a mocked requests session.""" + with patch("azure_security_center.core.AzureSecurityCenterManager.requests.session") as mock_session_cls: + session_instance = MagicMock() + mock_session_cls.return_value = session_instance + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "mock_access_token", + "refresh_token": "mock_new_refresh_token", + } + mock_response.status_code = 200 + mock_response.ok = True + mock_session_instance_post = MagicMock(return_value=mock_response) + session_instance.post = mock_session_instance_post + yield session_instance + + +def test_manager_default_endpoints(mock_requests_session: MagicMock) -> None: + """Test AzureSecurityCenterManager initializes with default endpoints.""" + manager = AzureSecurityCenterManager( + client_id="test_client_id", + client_secret="test_client_secret", # ruff: ignore[hardcoded-password-func-arg] + username="test_user", + password="test_password", # ruff: ignore[hardcoded-password-func-arg] + tenant_id="test_tenant_id", + subscription_id="test_sub_id", + ) + assert manager.login_api_root == consts.DEFAULT_LOGIN_API_ROOT + assert manager.api_root == consts.DEFAULT_API_ROOT + assert manager.graph_api_root == consts.DEFAULT_GRAPH_API_ROOT + + +def test_manager_custom_endpoints(mock_requests_session: MagicMock) -> None: + """Test AzureSecurityCenterManager formats URLs with custom sovereign endpoints.""" + manager = AzureSecurityCenterManager( + client_id="test_client_id", + client_secret="test_client_secret", # ruff: ignore[hardcoded-password-func-arg] + username="test_user", + password="test_password", # ruff: ignore[hardcoded-password-func-arg] + tenant_id="test_tenant_id", + subscription_id="test_sub_id", + login_api_root="https://login.microsoftonline.us/", + api_root="https://management.usgovcloudapi.net/", + graph_api_root="https://graph.microsoft.us/", + ) + assert manager.login_api_root == "https://login.microsoftonline.us" + assert manager.api_root == "https://management.usgovcloudapi.net" + assert manager.graph_api_root == "https://graph.microsoft.us" + + url = manager._get_full_url("ping") + assert url == "https://management.usgovcloudapi.net/providers/Microsoft.Security/operations" + + auth_endpoint_url = manager._get_full_url("get-auth-token", tenant_id="test_tenant") + assert auth_endpoint_url == "https://login.microsoftonline.us/test_tenant/oauth2/v2.0/token" + + url_graph = manager._get_full_url("get-alert-ids") + assert url_graph == "https://graph.microsoft.us/v1.0/security/alerts" + + +def test_manager_obtain_refresh_token_custom_login_api_root() -> None: + """Test obtain_refresh_token posts to the custom login_api_root.""" + with patch("azure_security_center.core.AzureSecurityCenterManager.requests.post") as mock_post: + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "access_token": "token123", + "refresh_token": "refreshtoken123", + } + mock_post.return_value = mock_response + + res = AzureSecurityCenterManager.obtain_refresh_token( + client_id="cid", + client_secret="csec", # ruff: ignore[hardcoded-password-func-arg] + redirect_uri="https://redirect", + code="code123", + tenant_id="tid", + verify_ssl=False, + login_api_root="https://login.microsoftonline.us/", + ) + assert res["refresh_token"] == "refreshtoken123" # ruff: ignore[hardcoded-password-string] + mock_post.assert_called_once_with( + "https://login.microsoftonline.us/tid/oauth2/token", + data={ + "code": "code123", + "client_id": "cid", + "client_secret": "csec", + "redirect_uri": "https://redirect", + "grant_type": "authorization_code", + }, + verify=False, + ) + + +def test_manager_get_access_token_custom_login_api_root(mock_requests_session: MagicMock) -> None: + """Test get_access_token uses custom login_api_root.""" + manager = AzureSecurityCenterManager( + client_id="test_client_id", + client_secret="test_client_secret", # ruff: ignore[hardcoded-password-func-arg] + username="test_user", + password="test_password", # ruff: ignore[hardcoded-password-func-arg] + tenant_id="test_tenant_id", + subscription_id="test_sub_id", + refresh_token="initial_refresh_token", # ruff: ignore[hardcoded-password-func-arg] + login_api_root="https://login.microsoftonline.us/", + ) + mock_requests_session.post.assert_called_with( + "https://login.microsoftonline.us/test_tenant_id/oauth2/token", + data={ + "client_id": "test_client_id", + "client_secret": "test_client_secret", + "grant_type": "refresh_token", + "refresh_token": "initial_refresh_token", + }, + ) + assert manager.auth_token == "mock_access_token" # ruff: ignore[hardcoded-password-string] diff --git a/content/response_integrations/google/azure_security_center/uv.lock b/content/response_integrations/google/azure_security_center/uv.lock index b09154605d..a1944b48eb 100644 --- a/content/response_integrations/google/azure_security_center/uv.lock +++ b/content/response_integrations/google/azure_security_center/uv.lock @@ -95,7 +95,7 @@ wheels = [ [[package]] name = "azuresecuritycenter" -version = "17.0" +version = "18.0" source = { virtual = "." } dependencies = [ { name = "environmentcommon" },