diff --git a/Packs/ApiModules/Scripts/MicrosoftApiModule/MicrosoftApiModule.py b/Packs/ApiModules/Scripts/MicrosoftApiModule/MicrosoftApiModule.py
index 2ee554bd4834..7eddf51d02e5 100644
--- a/Packs/ApiModules/Scripts/MicrosoftApiModule/MicrosoftApiModule.py
+++ b/Packs/ApiModules/Scripts/MicrosoftApiModule/MicrosoftApiModule.py
@@ -145,6 +145,62 @@ class Resources:
'gcc-high': 'https://login.microsoftonline.us',
}
+MICROSOFT_365_DEFENDER_TYPE = {
+ "Worldwide": "com",
+ "US Geo Proximity": "geo-us",
+ "EU Geo Proximity": "geo-eu",
+ "UK Geo Proximity": "geo-uk",
+ "AU Geo Proximity": "geo-au",
+ "SWA Geo Proximity": "geo-swa",
+ "INA Geo Proximity": "geo-ina",
+ "US GCC": "gcc",
+ "US GCC-High": "gcc-high",
+ "DoD": "dod",
+}
+
+# https://learn.microsoft.com/en-us/defender-endpoint/api/exposed-apis-list
+# https://learn.microsoft.com/en-us/defender-xdr/usgov?view=o365-worldwide
+MICROSOFT_365_DEFENDER_API_ENDPOINTS = {
+ "com": "https://api.security.microsoft.com",
+ "geo-us": "https://us.api.security.microsoft.com",
+ "geo-eu": "https://eu.api.security.microsoft.com",
+ "geo-uk": "https://uk.api.security.microsoft.com",
+ "geo-au": "https://au.api.security.microsoft.com",
+ "geo-swa": "https://swa.api.security.microsoft.com",
+ "geo-ina": "https://ina.api.security.microsoft.com",
+ "gcc": "https://api-gcc.security.microsoft.us",
+ "gcc-high": "https://api-gov.security.microsoft.us",
+ "dod": "https://api-gov.security.microsoft.us",
+}
+
+# https://learn.microsoft.com/en-us/defender-xdr/usgov?view=o365-worldwide
+MICROSOFT_365_DEFENDER_TOKEN_RETRIEVAL_ENDPOINTS = {
+ 'com': 'https://login.windows.net',
+ 'geo-us': 'https://login.windows.net',
+ 'geo-eu': 'https://login.windows.net',
+ 'geo-uk': 'https://login.windows.net',
+ "geo-au": 'https://login.windows.net',
+ "geo-swa": 'https://login.windows.net',
+ "geo-ina": 'https://login.windows.net',
+ "gcc": "https://login.microsoftonline.com",
+ "gcc-high": "https://login.microsoftonline.us",
+ "dod": "https://login.microsoftonline.us",
+}
+
+MICROSOFT_365_DEFENDER_SCOPES = {
+ 'com': "https://security.microsoft.com/mtp",
+ 'geo-us': 'https://security.microsoft.com',
+ 'geo-eu': 'https://security.microsoft.com',
+ 'geo-uk': 'https://security.microsoft.com',
+ "geo-au": 'https://security.microsoft.com',
+ "geo-swa": 'https://security.microsoft.com',
+ "geo-ina": 'https://security.microsoft.com',
+ 'gcc': 'https://security.microsoft.com',
+ 'gcc-high': 'https://security.microsoft.us',
+ 'dod': 'https://security.apps.mil',
+}
+
+
# Azure Managed Identities
MANAGED_IDENTITIES_TOKEN_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01'
MANAGED_IDENTITIES_SYSTEM_ASSIGNED = 'SYSTEM_ASSIGNED'
@@ -636,6 +692,17 @@ def get_azure_cloud(params, integration_name):
return AZURE_CLOUDS.get(AZURE_CLOUD_NAME_MAPPING.get(azure_cloud_arg), AZURE_WORLDWIDE_CLOUD) # type: ignore[arg-type]
+def microsoft_defender_get_base_url(base_url: str, endpoint_type: str) -> str:
+ if endpoint_type == 'Custom':
+ if not base_url:
+ raise DemistoException("Endpoint type is set to 'Custom' but no URL was provided.")
+ url = base_url
+ else:
+ endpoint = MICROSOFT_365_DEFENDER_TYPE.get(endpoint_type, 'com')
+ url = MICROSOFT_365_DEFENDER_API_ENDPOINTS.get(endpoint, 'https://api.security.microsoft.com')
+ return url
+
+
class MicrosoftClient(BaseClient):
def __init__(self, tenant_id: str = '',
auth_id: str = '',
@@ -1387,8 +1454,12 @@ def _add_info_headers() -> dict[str, str]:
def device_auth_request(self) -> dict:
response_json = {}
try:
+ if self.tenant_id:
+ url = f'{self.azure_ad_endpoint}/{self.tenant_id}/oauth2/v2.0/devicecode'
+ else:
+ url = f'{self.azure_ad_endpoint}/organizations/oauth2/v2.0/devicecode'
response = requests.post(
- url=f'{self.azure_ad_endpoint}/organizations/oauth2/v2.0/devicecode',
+ url=url,
data={
'client_id': self.client_id,
'scope': self.scope
@@ -1396,6 +1467,9 @@ def device_auth_request(self) -> dict:
verify=self.verify
)
if not response.ok:
+ if "National Cloud" in self.error_parser(response):
+ return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
+ f' The tenant is not supported by GCC-High. body: {self.error_parser(response)}')
return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
f' body: {self.error_parser(response)}')
response_json = response.json()
diff --git a/Packs/AzureActiveDirectory/ReleaseNotes/1_3_28.md b/Packs/AzureActiveDirectory/ReleaseNotes/1_3_28.md
new file mode 100644
index 000000000000..c37ae8b42336
--- /dev/null
+++ b/Packs/AzureActiveDirectory/ReleaseNotes/1_3_28.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Active Directory Identity Protection (Deprecated)
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureActiveDirectory/pack_metadata.json b/Packs/AzureActiveDirectory/pack_metadata.json
index 90878d2f2965..c3459b1d4c97 100644
--- a/Packs/AzureActiveDirectory/pack_metadata.json
+++ b/Packs/AzureActiveDirectory/pack_metadata.json
@@ -3,7 +3,7 @@
"description": "Deprecated. Use Microsoft Graph Identity and Access instead.",
"support": "xsoar",
"hidden": true,
- "currentVersion": "1.3.27",
+ "currentVersion": "1.3.28",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureCompute/ReleaseNotes/1_2_33.md b/Packs/AzureCompute/ReleaseNotes/1_2_33.md
new file mode 100644
index 000000000000..e38a03d19f0e
--- /dev/null
+++ b/Packs/AzureCompute/ReleaseNotes/1_2_33.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Compute v2
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureCompute/pack_metadata.json b/Packs/AzureCompute/pack_metadata.json
index 7654d3ee38ae..a702ed778baf 100644
--- a/Packs/AzureCompute/pack_metadata.json
+++ b/Packs/AzureCompute/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Compute",
"description": "Create and Manage Azure Virtual Machines",
"support": "xsoar",
- "currentVersion": "1.2.32",
+ "currentVersion": "1.2.33",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureDataExplorer/ReleaseNotes/1_3_7.md b/Packs/AzureDataExplorer/ReleaseNotes/1_3_7.md
new file mode 100644
index 000000000000..96c758c9177e
--- /dev/null
+++ b/Packs/AzureDataExplorer/ReleaseNotes/1_3_7.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Data Explorer
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureDataExplorer/pack_metadata.json b/Packs/AzureDataExplorer/pack_metadata.json
index f1e0ed77a32f..d2ff00165aeb 100644
--- a/Packs/AzureDataExplorer/pack_metadata.json
+++ b/Packs/AzureDataExplorer/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Data Explorer",
"description": "Use Azure Data Explorer integration to collect and analyze data inside clusters of Azure Data Explorer and manage search queries.",
"support": "xsoar",
- "currentVersion": "1.3.6",
+ "currentVersion": "1.3.7",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureDevOps/ReleaseNotes/1_4_7.md b/Packs/AzureDevOps/ReleaseNotes/1_4_7.md
new file mode 100644
index 000000000000..89ead3fefefb
--- /dev/null
+++ b/Packs/AzureDevOps/ReleaseNotes/1_4_7.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### AzureDevOps
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureDevOps/pack_metadata.json b/Packs/AzureDevOps/pack_metadata.json
index f6fab062dc99..b99ea8c7e7c6 100644
--- a/Packs/AzureDevOps/pack_metadata.json
+++ b/Packs/AzureDevOps/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "AzureDevOps",
"description": "Create and manage Git repositories in Azure DevOps Services.",
"support": "xsoar",
- "currentVersion": "1.4.6",
+ "currentVersion": "1.4.7",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureFirewall/ReleaseNotes/1_2_0.md b/Packs/AzureFirewall/ReleaseNotes/1_2_0.md
new file mode 100644
index 000000000000..668baa6c5db7
--- /dev/null
+++ b/Packs/AzureFirewall/ReleaseNotes/1_2_0.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Firewall
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureFirewall/pack_metadata.json b/Packs/AzureFirewall/pack_metadata.json
index cf9392e53766..c774601d646a 100644
--- a/Packs/AzureFirewall/pack_metadata.json
+++ b/Packs/AzureFirewall/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Firewall",
"description": "Azure Firewall is a cloud-native and intelligent network firewall security service that provides breed threat protection for cloud workloads running in Azure. It's a fully stateful firewall as a service, with built-in high availability and unrestricted cloud scalability. This pack contains an integration with a main goal to manage Azure Firewall security service, and normalization rules for ingesting and modeling Azure Firewall Resource logs.",
"support": "xsoar",
- "currentVersion": "1.1.46",
+ "currentVersion": "1.2.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureKeyVault/ReleaseNotes/1_1_50.md b/Packs/AzureKeyVault/ReleaseNotes/1_1_50.md
new file mode 100644
index 000000000000..f5433b59780b
--- /dev/null
+++ b/Packs/AzureKeyVault/ReleaseNotes/1_1_50.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Key Vault
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureKeyVault/pack_metadata.json b/Packs/AzureKeyVault/pack_metadata.json
index f7e890024f5c..635537e3e341 100644
--- a/Packs/AzureKeyVault/pack_metadata.json
+++ b/Packs/AzureKeyVault/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Key Vault",
"description": "Use Key Vault to safeguard and manage cryptographic keys and secrets used by cloud applications and services.",
"support": "xsoar",
- "currentVersion": "1.1.49",
+ "currentVersion": "1.1.50",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureKubernetesServices/ReleaseNotes/1_2_5.md b/Packs/AzureKubernetesServices/ReleaseNotes/1_2_5.md
new file mode 100644
index 000000000000..9a05a4cc1683
--- /dev/null
+++ b/Packs/AzureKubernetesServices/ReleaseNotes/1_2_5.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Kubernetes Services
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureKubernetesServices/pack_metadata.json b/Packs/AzureKubernetesServices/pack_metadata.json
index 05e13be2f011..826d05b6a37a 100644
--- a/Packs/AzureKubernetesServices/pack_metadata.json
+++ b/Packs/AzureKubernetesServices/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Kubernetes Services",
"description": "Deploy and manage containerized applications with a fully managed Kubernetes service.",
"support": "xsoar",
- "currentVersion": "1.2.4",
+ "currentVersion": "1.2.5",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureLogAnalytics/ReleaseNotes/1_2_0.md b/Packs/AzureLogAnalytics/ReleaseNotes/1_2_0.md
new file mode 100644
index 000000000000..79aa51dcb399
--- /dev/null
+++ b/Packs/AzureLogAnalytics/ReleaseNotes/1_2_0.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Log Analytics
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureLogAnalytics/pack_metadata.json b/Packs/AzureLogAnalytics/pack_metadata.json
index 26bcd7e1793f..c9fb3630fd33 100644
--- a/Packs/AzureLogAnalytics/pack_metadata.json
+++ b/Packs/AzureLogAnalytics/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Log Analytics",
"description": "Log Analytics is a service that helps you collect and analyze data generated by resources in your cloud and on-premises environments.",
"support": "xsoar",
- "currentVersion": "1.1.43",
+ "currentVersion": "1.2.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureNetworkSecurityGroups/ReleaseNotes/1_2_37.md b/Packs/AzureNetworkSecurityGroups/ReleaseNotes/1_2_37.md
new file mode 100644
index 000000000000..3007302ed954
--- /dev/null
+++ b/Packs/AzureNetworkSecurityGroups/ReleaseNotes/1_2_37.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Network Security Groups
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureNetworkSecurityGroups/pack_metadata.json b/Packs/AzureNetworkSecurityGroups/pack_metadata.json
index 0f0e741c4a47..ebafab70afce 100644
--- a/Packs/AzureNetworkSecurityGroups/pack_metadata.json
+++ b/Packs/AzureNetworkSecurityGroups/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Network Security Groups",
"description": "Azure Network Security Groups are used to filter network traffic to and from Azure resources in an Azure virtual network",
"support": "xsoar",
- "currentVersion": "1.2.36",
+ "currentVersion": "1.2.37",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureResourceGraph/ReleaseNotes/1_0_6.md b/Packs/AzureResourceGraph/ReleaseNotes/1_0_6.md
new file mode 100644
index 000000000000..dec0808fbad5
--- /dev/null
+++ b/Packs/AzureResourceGraph/ReleaseNotes/1_0_6.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Resource Graph
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureResourceGraph/pack_metadata.json b/Packs/AzureResourceGraph/pack_metadata.json
index 534e88e18d8f..e4796f02cfdb 100644
--- a/Packs/AzureResourceGraph/pack_metadata.json
+++ b/Packs/AzureResourceGraph/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Resource Graph",
"description": "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. This pack is primarily used to allow for executing Azure Resource Graph queries.",
"support": "xsoar",
- "currentVersion": "1.0.5",
+ "currentVersion": "1.0.6",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureRiskyUsers/ReleaseNotes/1_2_0.md b/Packs/AzureRiskyUsers/ReleaseNotes/1_2_0.md
new file mode 100644
index 000000000000..f19c22d4995a
--- /dev/null
+++ b/Packs/AzureRiskyUsers/ReleaseNotes/1_2_0.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Risky Users
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureRiskyUsers/pack_metadata.json b/Packs/AzureRiskyUsers/pack_metadata.json
index 11f4ff565cd2..97593089de0e 100644
--- a/Packs/AzureRiskyUsers/pack_metadata.json
+++ b/Packs/AzureRiskyUsers/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Risky Users",
"description": "Azure Risky Users provides access to all at-risk users and risk detections in Azure AD environment.",
"support": "xsoar",
- "currentVersion": "1.1.42",
+ "currentVersion": "1.2.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureSQLManagement/ReleaseNotes/1_2_5.md b/Packs/AzureSQLManagement/ReleaseNotes/1_2_5.md
new file mode 100644
index 000000000000..8810fe30c52d
--- /dev/null
+++ b/Packs/AzureSQLManagement/ReleaseNotes/1_2_5.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure SQL Management
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureSQLManagement/pack_metadata.json b/Packs/AzureSQLManagement/pack_metadata.json
index 8d17ed9d6e6a..5d1a123db2ab 100644
--- a/Packs/AzureSQLManagement/pack_metadata.json
+++ b/Packs/AzureSQLManagement/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure SQL Management",
"description": "Microsoft Azure SQL Database is a managed cloud database provided as part of Microsoft Azure",
"support": "xsoar",
- "currentVersion": "1.2.4",
+ "currentVersion": "1.2.5",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureSecurityCenter/ReleaseNotes/2_1_0.md b/Packs/AzureSecurityCenter/ReleaseNotes/2_1_0.md
new file mode 100644
index 000000000000..2b5aebbb4a10
--- /dev/null
+++ b/Packs/AzureSecurityCenter/ReleaseNotes/2_1_0.md
@@ -0,0 +1,9 @@
+#### Integrations
+
+##### Microsoft Defender for Cloud Event Collector
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
+
+##### Microsoft Defender for Cloud
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureSecurityCenter/pack_metadata.json b/Packs/AzureSecurityCenter/pack_metadata.json
index dbe9509cdc0a..d4a7f94524f4 100644
--- a/Packs/AzureSecurityCenter/pack_metadata.json
+++ b/Packs/AzureSecurityCenter/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Defender for Cloud",
"description": "Unified security management and advanced threat protection across hybrid cloud workloads.",
"support": "xsoar",
- "currentVersion": "2.0.35",
+ "currentVersion": "2.1.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureSentinel/ReleaseNotes/1_6_0.md b/Packs/AzureSentinel/ReleaseNotes/1_6_0.md
new file mode 100644
index 000000000000..ee1eec46af3a
--- /dev/null
+++ b/Packs/AzureSentinel/ReleaseNotes/1_6_0.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Sentinel
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureSentinel/pack_metadata.json b/Packs/AzureSentinel/pack_metadata.json
index 5bc1dfe62d3e..3f4e0b0d2276 100644
--- a/Packs/AzureSentinel/pack_metadata.json
+++ b/Packs/AzureSentinel/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Sentinel",
"description": "Microsoft Sentinel is a cloud-native security information and event manager (SIEM) platform that uses built-in AI to help analyze large volumes of data across an enterprise.",
"support": "xsoar",
- "currentVersion": "1.5.57",
+ "currentVersion": "1.6.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/AzureStorage/ReleaseNotes/1_2_32.md b/Packs/AzureStorage/ReleaseNotes/1_2_32.md
new file mode 100644
index 000000000000..03fb13c5e8b9
--- /dev/null
+++ b/Packs/AzureStorage/ReleaseNotes/1_2_32.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Storage Management
+
+- Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/AzureStorage/pack_metadata.json b/Packs/AzureStorage/pack_metadata.json
index 422abe802b5b..73575279aaaa 100644
--- a/Packs/AzureStorage/pack_metadata.json
+++ b/Packs/AzureStorage/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Azure Storage Management",
"description": "Deploy and manage storage accounts and blob service properties.",
"support": "xsoar",
- "currentVersion": "1.2.31",
+ "currentVersion": "1.2.32",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.py b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.py
index 9178ff29d676..0b317d4aebd2 100644
--- a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.py
+++ b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.py
@@ -22,7 +22,9 @@ class Client:
def __init__(self, app_id: str, verify: bool, proxy: bool, base_url: str = BASE_URL, tenant_id: str = None,
enc_key: str = None, client_credentials: bool = False, certificate_thumbprint: Optional[str] = None,
private_key: Optional[str] = None,
- managed_identities_client_id: Optional[str] = None):
+ managed_identities_client_id: Optional[str] = None,
+ endpoint: str = 'com',
+ azure_cloud: AzureCloud = AZURE_WORLDWIDE_CLOUD):
if app_id and '@' in app_id:
app_id, refresh_token = app_id.split('@')
integration_context = get_integration_context()
@@ -35,7 +37,7 @@ def __init__(self, app_id: str, verify: bool, proxy: bool, base_url: str = BASE_
verify=verify,
proxy=proxy,
ok_codes=(200, 201, 202, 204),
- scope='offline_access https://security.microsoft.com/mtp/.default',
+ scope=f'offline_access {MICROSOFT_365_DEFENDER_SCOPES.get(endpoint)}/.default',
self_deployed=True, # We always set the self_deployed key as True because when not using a self
# deployed machine, the DEVICE_CODE flow should behave somewhat like a self deployed
# flow and most of the same arguments should be set, as we're !not! using OProxy.
@@ -44,8 +46,9 @@ def __init__(self, app_id: str, verify: bool, proxy: bool, base_url: str = BASE_
grant_type=CLIENT_CREDENTIALS if client_credentials else DEVICE_CODE,
# used for device code flow
- resource='https://api.security.microsoft.com' if not client_credentials else None,
- token_retrieval_url='https://login.windows.net/organizations/oauth2/v2.0/token' if not client_credentials else None,
+ resource=MICROSOFT_365_DEFENDER_API_ENDPOINTS.get(endpoint) if not client_credentials else None,
+ token_retrieval_url=f'{MICROSOFT_365_DEFENDER_TOKEN_RETRIEVAL_ENDPOINTS.get(endpoint)}'
+ f'/organizations/oauth2/v2.0/token' if not client_credentials else None,
# used for client credentials flow
tenant_id=tenant_id,
enc_key=enc_key,
@@ -53,6 +56,8 @@ def __init__(self, app_id: str, verify: bool, proxy: bool, base_url: str = BASE_
private_key=private_key,
managed_identities_client_id=managed_identities_client_id,
managed_identities_resource_uri=Resources.security,
+ endpoint=endpoint,
+ azure_cloud=azure_cloud,
command_prefix="microsoft-365-defender",
)
self.ms_client = MicrosoftClient(**client_args) # type: ignore
@@ -600,6 +605,9 @@ def main() -> None:
proxy = params.get('proxy', False)
app_id = params.get('creds_client_id', {}).get('password', '') or params.get('app_id') or params.get('_app_id')
base_url = params.get('base_url')
+ endpoint_type = params.get('endpoint_type', 'Worldwide')
+ endpoint = MICROSOFT_365_DEFENDER_TYPE.get(endpoint_type, 'com')
+ base_url = microsoft_defender_get_base_url(base_url, endpoint_type)
tenant_id = params.get('creds_tenant_id', {}).get('password', '') or params.get('tenant_id') or params.get('_tenant_id')
client_credentials = params.get('client_credentials', False)
@@ -623,6 +631,7 @@ def main() -> None:
if not managed_identities_client_id and not app_id:
raise Exception('Application ID must be provided.')
+ azure_cloud = AZURE_CLOUDS.get(endpoint)
client = Client(
app_id=app_id,
verify=verify_certificate,
@@ -633,7 +642,9 @@ def main() -> None:
client_credentials=client_credentials,
certificate_thumbprint=certificate_thumbprint,
private_key=private_key,
- managed_identities_client_id=managed_identities_client_id
+ managed_identities_client_id=managed_identities_client_id,
+ endpoint=endpoint,
+ azure_cloud=azure_cloud,
)
if demisto.command() == 'test-module':
# This is the call made when pressing the integration Test button.
diff --git a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.yml b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.yml
index 52aaddfefe49..bcc42136f935 100644
--- a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.yml
+++ b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender.yml
@@ -7,13 +7,31 @@ commonfields:
version: -1
configuration:
- additionalinfo: |-
- The United States: api-us.security.microsoft.com
- Europe: api-eu.security.microsoft.com
- The United Kingdom: api-uk.security.microsoft.co
- defaultvalue: https://api.security.microsoft.com
- display: Endpoint URI
+ The endpoint type. When selecting the Custom option, the Server URL parameter must be filled.
+ defaultvalue: Worldwide
+ display: Endpoint Type
+ name: endpoint_type
+ required: false
+ type: 15
+ section: Connect
+ options:
+ - Worldwide
+ - US Geo Proximity
+ - EU Geo Proximity
+ - UK Geo Proximity
+ - AU Geo Proximity
+ - SWA Geo Proximity
+ - INA Geo Proximity
+ - US GCC
+ - US GCC-High
+ - DoD
+ - Custom
+ advanced: true
+- additionalinfo: |-
+ Custom Server URL. Required when Endpoint Type is Custom.
+ display: Server URL
name: base_url
- required: true
+ required: false
type: 0
section: Connect
- name: creds_client_id
diff --git a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_description.md b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_description.md
index cb3765cf4dee..7c4603998ab7 100644
--- a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_description.md
+++ b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_description.md
@@ -79,3 +79,11 @@ Follow one of these steps for authentication based on Azure Managed Identities:
3. Select the **Use Azure Managed Identities** checkbox.
For more information, see [Managed identities for Azure resources](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview).
+
+
+### Using National Cloud
+Using a national cloud endpoint is supported by setting the *Endpoint Type* parameter to one of the following options:
+* US Government GCC Endpoint: `https://api-gcc.security.microsoft.us`
+* US Government GCC-High Endpoint: `https://api-gov.security.microsoft.us`
+* US Government Department of Defence (DoD) Endpoint: `https://api-gov.security.microsoft.us`
+When using **US Government GCC-High Endpoint** with **Device Code** Flow, tenant ID is a required parameter in the instance configuration.
diff --git a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_test.py b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_test.py
index f66ac3083f2f..4c48b2063e38 100644
--- a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_test.py
+++ b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/Microsoft365Defender_test.py
@@ -185,3 +185,87 @@ def test_test_module_command_with_managed_identities(mocker, requests_mock, clie
qs = get_mock.last_request.qs
assert qs['resource'] == [Resources.security]
assert client_id and qs['client_id'] == [client_id] or 'client_id' not in qs
+
+
+@pytest.mark.parametrize(
+ 'server_url, expected_endpoint', [
+ ('com', 'https://api.security.microsoft.com'),
+ ('geo-us', 'https://us.api.security.microsoft.com'),
+ ('geo-eu', 'https://eu.api.security.microsoft.com'),
+ ('geo-uk', 'https://uk.api.security.microsoft.com'),
+ ('geo-au', 'https://au.api.security.microsoft.com'),
+ ('geo-swa', 'https://swa.api.security.microsoft.com'),
+ ('geo-ina', 'https://ina.api.security.microsoft.com'),
+ ('gcc', 'https://api-gcc.security.microsoft.us'),
+ ('gcc-high', 'https://api-gov.security.microsoft.us'),
+ ('dod', 'https://api-gov.security.microsoft.us'),
+ ]
+)
+def test_endpoint_to_api(server_url, expected_endpoint):
+ """
+ Given:
+ - A dictionary mapping server URLs to Microsoft 365 Defender API endpoints.
+ When:
+ - Testing the endpoint lookup for various server URLs.
+ Then:
+ - Ensure the returned endpoint matches the expected value for each server URL.
+ """
+ from MicrosoftApiModule import MICROSOFT_365_DEFENDER_API_ENDPOINTS
+
+ assert MICROSOFT_365_DEFENDER_API_ENDPOINTS[server_url] == expected_endpoint
+
+
+@pytest.mark.parametrize(
+ 'server_url, expected_endpoint', [
+ ('com', 'https://login.windows.net'),
+ ('geo-us', 'https://login.windows.net'),
+ ('geo-eu', 'https://login.windows.net'),
+ ('geo-uk', 'https://login.windows.net'),
+ ('geo-au', 'https://login.windows.net'),
+ ('geo-swa', 'https://login.windows.net'),
+ ('geo-ina', 'https://login.windows.net'),
+ ('gcc', 'https://login.microsoftonline.com'),
+ ('gcc-high', 'https://login.microsoftonline.us'),
+ ('dod', 'https://login.microsoftonline.us'),
+ ]
+)
+def test_token_retrieval_endpoint(server_url, expected_endpoint):
+ """
+ Given:
+ - A dictionary mapping server URLs to Microsoft 365 Defender token retrieval endpoints.
+ When:
+ - Testing the token retrieval endpoint lookup for various server URLs.
+ Then:
+ - Ensure the returned endpoint matches the expected value for each server URL.
+ """
+ from MicrosoftApiModule import MICROSOFT_365_DEFENDER_TOKEN_RETRIEVAL_ENDPOINTS
+
+ assert MICROSOFT_365_DEFENDER_TOKEN_RETRIEVAL_ENDPOINTS[server_url] == expected_endpoint
+
+
+@pytest.mark.parametrize(
+ 'server_url, expected_scope', [
+ ('com', 'https://security.microsoft.com'),
+ ('geo-us', 'https://security.microsoft.com'),
+ ('geo-eu', 'https://security.microsoft.com'),
+ ('geo-uk', 'https://security.microsoft.com'),
+ ('geo-au', 'https://security.microsoft.com'),
+ ('geo-swa', 'https://security.microsoft.com'),
+ ('geo-ina', 'https://security.microsoft.com'),
+ ('gcc', 'https://security.microsoft.com'),
+ ('gcc-high', 'https://security.microsoft.us'),
+ ('dod', 'https://security.apps.mil'),
+ ]
+)
+def test_scope_to_api(server_url, expected_scope):
+ """
+ Given:
+ - A dictionary mapping server URLs to Microsoft 365 Defender scopes.
+ When:
+ - Testing the scope lookup for various server URLs.
+ Then:
+ - Ensure the returned scope matches the expected value for each server URL.
+ """
+ from MicrosoftApiModule import MICROSOFT_365_DEFENDER_SCOPES
+
+ assert MICROSOFT_365_DEFENDER_SCOPES[server_url] == expected_scope
diff --git a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/README.md b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/README.md
index 88adb6777995..c9180198bc56 100644
--- a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/README.md
+++ b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/README.md
@@ -24,6 +24,14 @@ In order to use the Cortex XSOAR application, use the default application ID.
To use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal. For more details, follow [Self Deployed Application - Device Code Flow](https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication#device-code-flow).
+### Using National Cloud
+Using a national cloud endpoint is supported by setting the *Endpoint Type* parameter to one of the following options:
+* US Government GCC Endpoint: `https://api-gcc.security.microsoft.us`
+* US Government GCC-High Endpoint: `https://api-gov.security.microsoft.us`
+* US Government Department of Defence (DoD) Endpoint: `https://api-gov.security.microsoft.us`
+When using **US Government GCC-High Endpoint** with **Device Code** Flow, tenant ID is a required parameter in the instance configuration.
+
+
#### Required Permissions
The required API permissions are for the ***Microsoft Threat Protection*** app.
* offline_access - Delegate
@@ -51,24 +59,45 @@ Follow these steps for a self-deployed configuration:
2. Search for Microsoft Defender XDR.
3. Click **Add instance** to create and configure a new integration instance.
- | **Parameter** | **Description** | **Required** |
- | --- | --- | --- |
- | Application ID or Client ID | The API key to use to connect. | False |
- | Endpoint URI | The United States: api-us.security.microsoft.com Europe: api-eu.security.microsoft.com The United Kingdom: api-uk.security.microsoft.co | True |
- | Use Client Credentials Authorization Flow | Use a self-deployed Azure application and authenticate using the Client Credentials flow. | False |
- | Token or Tenant ID (for Client Credentials mode) | | False |
- | Password | | 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 |
- | Use Azure Managed Identities | Relevant only if the integration is running on Azure VM. If selected, authenticates based on the value provided for the Azure Managed Identities Client ID field. If no value is provided for the Azure Managed Identities Client ID field, authenticates based on the System Assigned Managed Identity. For additional information, see the Help tab. | False |
- | Azure Managed Identities Client ID | The Managed Identities client ID for authentication - relevant only if the integration is running on Azure VM. | False |
- | First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) | | False |
- | Fetch incidents timeout | The time limit in seconds for fetch incidents to run. Leave this empty to cancel the timeout limit. | False |
- | Number of incidents for each fetch. | Due to API limitations, the maximum is 100. | False |
- | Incident type | | False |
- | Fetch incidents | | False |
- | Trust any certificate (not secure) | | False |
- | Use system proxy settings | | False |
+
+| **Parameter** | **Description** | **Required** |
+| --- | --- | --- |
+| Application ID or Client ID | The API key to use to connect. | False |
+| Endpoint Type | The endpoint type. When selecting the Custom option, the Server URL parameter must be filled. | False |
+| Server URL | Custom Server URL. Required when Endpoint Type is Custom. | False |
+| Use Client Credentials Authorization Flow | Use a self-deployed Azure application and authenticate using the Client Credentials flow. | False |
+| Token or Tenant ID (for Client Credentials mode) | | False |
+| Password | | 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 |
+| Use Azure Managed Identities | Relevant only if the integration is running on Azure VM. If selected, authenticates based on the value provided for the Azure Managed Identities Client ID field. If no value is provided for the Azure Managed Identities Client ID field, authenticates based on the System Assigned Managed Identity. For additional information, see the Help tab. | False |
+| Azure Managed Identities Client ID | The Managed Identities client ID for authentication - relevant only if the integration is running on Azure VM. | False |
+| First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days) | | False |
+| Fetch incidents timeout | The time limit in seconds for fetch incidents to run. Leave this empty to cancel the timeout limit. | False |
+| Number of incidents for each fetch. | Due to API limitations, the maximum is 100. | False |
+| Incident type | | False |
+| Fetch incidents | | False |
+| Trust any certificate (not secure) | | False |
+| Use system proxy settings | | False |
+
+
+Endpoint Type Options:
+
+| Endpoint Type | Description |
+|--------|-----------------------------------------------------------------------------------------|
+| Worldwide | The publicly accessible Microsoft 365 Defender. |
+| US Geo Proximity | Microsoft 365 Defender Geo proximity endpoint for the US customers. |
+| EU Geo Proximity | Microsoft 365 Defender Geo proximity endpoint for the EU customers. |
+| UK Geo Proximity | Microsoft 365 Defender Geo proximity endpoint for the UK customers. |
+| AU Geo Proximity | Microsoft 365 Defender Geo proximity endpoint for the AU customers. |
+| SWA Geo Proximity | Microsoft 365 Defender Geo proximity endpoint for the SWA customers. |
+| INA Geo Proximity | Microsoft 365 Defender Geo proximity endpoint for the INA customers. |
+| US GCC | Microsoft 365 Defender endpoint for the USA Government Cloud Community (GCC). |
+| US GCC-High | Microsoft 365 Defender endpoint for the USA Government Cloud Community High (GCC-High). |
+| DoD | Microsoft 365 Defender endpoint for the USA Department of Defense (DoD). |
+| Custom | Custom endpoint configuration for Microsoft 365 Defender. |
+
+
4. Run the !microsoft-365-defender-auth-test command to validate the authentication process.
@@ -350,4 +379,4 @@ Details on how to write queries you can find [here](https://docs.microsoft.com/e
>### Result of query: AlertInfo:
>|Timestamp|AlertId|Title|Category|Severity|ServiceSource|DetectionSource|AttackTechniques|
>|---|---|---|---|---|---|---|---|
->| 2021-04-25T10:11:00Z | alertId | eDiscovery search started or exported | InitialAccess | Medium | Microsoft Defender for Office 365 | Microsoft Defender for Office 365 | |
+>| 2021-04-25T10:11:00Z | alertId | eDiscovery search started or exported | InitialAccess | Medium | Microsoft Defender for Office 365 | Microsoft Defender for Office 365 | |
\ No newline at end of file
diff --git a/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/integration-Microsoft365Defender-SecondFix.yml b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/integration-Microsoft365Defender-SecondFix.yml
new file mode 100644
index 000000000000..bd6985cd33a0
--- /dev/null
+++ b/Packs/Microsoft365Defender/Integrations/Microsoft365Defender/integration-Microsoft365Defender-SecondFix.yml
@@ -0,0 +1,2851 @@
+category: Network Security
+sectionOrder:
+- Connect
+- Collect
+commonfields:
+ id: Microsoft 365 Defender - Second Fix
+ version: -1
+configuration:
+- additionalinfo: The endpoint type. When selecting the Custom option, the Server URL parameter must be filled.
+ defaultvalue: Worldwide
+ display: Endpoint Type
+ name: endpoint_type
+ required: false
+ type: 15
+ section: Connect
+ options:
+ - Worldwide
+ - US Geo Proximity
+ - EU Geo Proximity
+ - UK Geo Proximity
+ - AU Geo Proximity
+ - SWA Geo Proximity
+ - INA Geo Proximity
+ - US GCC
+ - US GCC-High
+ - DoD
+ - Custom
+ advanced: true
+- additionalinfo: Custom Server URL. Required when Endpoint Type is Custom.
+ display: Server URL
+ name: base_url
+ required: false
+ type: 0
+ section: Connect
+- name: creds_client_id
+ type: 9
+ displaypassword: ID or Client ID
+ hiddenusername: true
+ section: Connect
+ required: false
+- name: creds_tenant_id
+ type: 9
+ displaypassword: Token or Tenant ID
+ hiddenusername: true
+ section: Connect
+ required: false
+- additionalinfo: The API key to use to connect.
+ defaultvalue: 9093c354-630a-47f1-b087-6768eb9427e6
+ display: Application ID
+ name: _app_id
+ type: 0
+ section: Connect
+ hidden: true
+ required: false
+- additionalinfo: Use a self-deployed Azure application and authenticate using the Client Credentials flow.
+ display: Use Client Credentials Authorization Flow
+ name: client_credentials
+ type: 8
+ section: Connect
+ required: false
+- display: Tenant ID (for Client Credentials mode)
+ name: _tenant_id
+ type: 0
+ section: Connect
+ hidden: true
+ required: false
+- display: Client Secret (for Client Credentials mode)
+ hiddenusername: true
+ name: credentials
+ type: 9
+ section: Connect
+ displaypassword: Client Secret
+ required: false
+- additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.
+ display: Certificate Thumbprint
+ name: creds_certificate
+ type: 9
+ displaypassword: Private Key
+ section: Connect
+ advanced: true
+ required: false
+- additionalinfo: Used for certificate authentication. As appears in the "Certificates & secrets" page of the app.
+ display: Certificate Thumbprint
+ name: certificate_thumbprint
+ type: 4
+ section: Connect
+ advanced: true
+ hidden: true
+ required: false
+- additionalinfo: Used for certificate authentication. The private key of the registered certificate.
+ display: Private Key
+ name: private_key
+ type: 14
+ section: Connect
+ advanced: true
+ hidden: true
+ required: false
+- additionalinfo: Relevant only if the integration is running on Azure VM. If selected, authenticates based on the value provided for the Azure Managed Identities Client ID field. If no value is provided for the Azure Managed Identities Client ID field, authenticates based on the System Assigned Managed Identity. For additional information, see the Help tab.
+ display: Use Azure Managed Identities
+ name: use_managed_identities
+ type: 8
+ section: Connect
+ advanced: true
+ required: false
+- additionalinfo: The Managed Identities client ID for authentication - relevant only if the integration is running on Azure VM.
+ displaypassword: Azure Managed Identities Client ID
+ name: managed_identities_client_id
+ hiddenusername: true
+ type: 9
+ section: Connect
+ advanced: true
+ required: false
+- defaultvalue: 12 hours
+ display: First fetch timestamp ( , e.g., 12 hours, 7 days)
+ name: first_fetch
+ type: 0
+ section: Collect
+ required: false
+- additionalinfo: The time limit in seconds for fetch incidents to run. Leave this empty to cancel the timeout limit.
+ defaultvalue: '30'
+ display: Fetch incidents timeout
+ name: fetch_timeout
+ type: 0
+ section: Collect
+ advanced: true
+ required: false
+- additionalinfo: Due to API limitations, the maximum is 100.
+ defaultvalue: '10'
+ display: Number of incidents for each fetch.
+ name: max_fetch
+ type: 0
+ section: Collect
+ required: false
+- defaultvalue: Microsoft 365 Defender Incident
+ display: Incident type
+ name: incidentType
+ type: 13
+ section: Connect
+ required: false
+- display: Fetch incidents
+ name: isFetch
+ type: 8
+ section: Collect
+ required: false
+- display: Trust any certificate (not secure)
+ name: insecure
+ type: 8
+ section: Connect
+ advanced: true
+ required: false
+- display: Use system proxy settings
+ name: proxy
+ type: 8
+ section: Connect
+ advanced: true
+ required: false
+- display: Application ID (Deprecated)
+ hidden: true
+ name: app_id
+ type: 4
+ section: Connect
+ advanced: true
+ required: false
+- display: Tenant ID (for Client Credentials mode) (Deprecated)
+ hidden: true
+ name: tenant_id
+ type: 4
+ section: Connect
+ advanced: true
+ required: false
+- display: Client Secret (for Client Credentials mode) (Deprecated)
+ hidden: true
+ name: enc_key
+ type: 4
+ section: Connect
+ advanced: true
+ required: false
+- defaultvalue: '1'
+ display: Incidents Fetch Interval
+ name: incidentFetchInterval
+ type: 19
+ section: Connect
+ advanced: true
+ required: false
+description: Microsoft 365 Defender is a unified pre- and post-breach enterprise defense suite that natively coordinates detection, prevention, investigation, and response across endpoints, identities, email, and applications to provide integrated protection against sophisticated attacks.
+display: Microsoft 365 Defender - Second Fix
+name: Microsoft 365 Defender - Second Fix
+script:
+ commands:
+ - arguments: []
+ description: Run this command to start the authorization process and follow the instructions in the command results. (for device-code mode).
+ name: microsoft-365-defender-auth-start
+ - arguments: []
+ description: Run this command to complete the authorization process. Should be used after running the microsoft-365-defender-auth-start command. (for device-code mode).
+ name: microsoft-365-defender-auth-complete
+ - arguments: []
+ description: Run this command if for some reason you need to rerun the authentication process.
+ name: microsoft-365-defender-auth-reset
+ - arguments: []
+ description: Tests the connectivity to the Microsoft 365 Defender.
+ name: microsoft-365-defender-auth-test
+ - arguments:
+ - auto: PREDEFINED
+ description: Categorize incidents (as Active, Resolved, or Redirected).
+ name: status
+ predefined:
+ - Active
+ - Resolved
+ - Redirected
+ - description: Owner of the incident.
+ name: assigned_to
+ - defaultValue: '100'
+ description: Number of incidents in the list. Maximum is 100.
+ name: limit
+ - description: Number of entries to skip.
+ name: offset
+ - defaultValue: '30'
+ description: The time limit in seconds for the http request to run.
+ name: timeout
+ - description: 'Filter incidents using odata query: https://docs.microsoft.com/en-us/microsoft-365/security/defender/api-list-incidents?view=o365-worldwide. Example: `{"$filter":"lastUpdateTime gt 2022-08-29T06:00:00.29Z"}`.'
+ name: odata
+ description: Get the most recent incidents.
+ name: microsoft-365-defender-incidents-list
+ outputs:
+ - contextPath: Microsoft365Defender.Incident.incidentId
+ description: Incident's ID.
+ type: Number
+ - contextPath: Microsoft365Defender.Incident.redirectIncidentId
+ description: Only populated in case an incident is grouped together with another incident, as part of the incident processing logic.
+ type: Unknown
+ - contextPath: Microsoft365Defender.Incident.incidentName
+ description: The name of the incident.
+ type: String
+ - contextPath: Microsoft365Defender.Incident.createdTime
+ description: The date and time (in UTC) the incident was created.
+ type: Date
+ - contextPath: Microsoft365Defender.Incident.lastUpdateTime
+ description: The date and time (in UTC) the incident was last updated.
+ type: Date
+ - contextPath: Microsoft365Defender.Incident.assignedTo
+ description: Owner of the incident.
+ type: String
+ - contextPath: Microsoft365Defender.Incident.classification
+ description: 'Specification of the incident. Possible values are: Unknown, FalsePositive, and TruePositive.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.determination
+ description: 'The determination of the incident. Possible values are: NotAvailable, Apt, Malware, SecurityPersonnel, SecurityTesting, UnwantedSoftware, and Other.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.status
+ description: 'The current status of the incident. Possible values are: Active, Resolved, and Redirected.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.severity
+ description: 'Severity of the incident. Possible values are: UnSpecified, Informational, Low, Medium, and High.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.alerts
+ description: List of alerts relevant for the incidents.
+ type: Unknown
+ - arguments:
+ - auto: PREDEFINED
+ description: Categorize incidents (as Active, Resolved, or Redirected).
+ name: status
+ predefined:
+ - Active
+ - Resolved
+ - Redirected
+ - InProgress
+ - description: Owner of the incident.
+ name: assigned_to
+ - description: Incident's ID.
+ name: id
+ required: true
+ - auto: PREDEFINED
+ description: The specification for the incident.
+ name: classification
+ predefined:
+ - Unknown
+ - FalsePositive
+ - TruePositive
+ - InformationalExpectedActivity
+ - description: Comment to be added to the incident.
+ name: comment
+ - auto: PREDEFINED
+ description: Determination of the incident.
+ name: determination
+ predefined:
+ - NotAvailable
+ - Malware
+ - SecurityTesting
+ - UnwantedSoftware
+ - MultiStagedAttack
+ - MaliciousUserActivity
+ - CompromisedAccount
+ - Phishing
+ - LineOfBusinessApplication
+ - ConfirmedActivity
+ - NotMalicious
+ - Other
+ - description: 'A comma-separated list of custom tags associated with an incident. For example: tag1,tag2,tag3.'
+ isArray: true
+ name: tags
+ - defaultValue: '30'
+ description: The time limit in seconds for the http request to run.
+ name: timeout
+ description: Update the incident with the given ID.
+ name: microsoft-365-defender-incident-update
+ outputs:
+ - contextPath: Microsoft365Defender.Incident.incidentId
+ description: Incident's ID.
+ type: Number
+ - contextPath: Microsoft365Defender.Incident.redirectIncidentId
+ description: Only populated in case an incident is grouped together with another incident, as part of the incident processing logic.
+ type: Unknown
+ - contextPath: Microsoft365Defender.Incident.incidentName
+ description: The name of the incident.
+ type: String
+ - contextPath: Microsoft365Defender.Incident.createdTime
+ description: The date and time (in UTC) the incident was created.
+ type: Date
+ - contextPath: Microsoft365Defender.Incident.lastUpdateTime
+ description: The date and time (in UTC) the incident was last updated.
+ type: Date
+ - contextPath: Microsoft365Defender.Incident.assignedTo
+ description: Owner of the incident.
+ type: String
+ - contextPath: Microsoft365Defender.Incident.classification
+ description: 'Specification of the incident. Possible values are: Unknown, FalsePositive, and TruePositive.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.determination
+ description: 'The determination of the incident. Possible values are: NotAvailable, Apt, Malware, SecurityPersonnel, SecurityTesting, UnwantedSoftware, and Other.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.severity
+ description: 'Severity of the incident. Possible values are: UnSpecified, Informational, Low, Medium, and High.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.status
+ description: 'The current status of the incident. Possible values are: Active, Resolved, and Redirected.'
+ type: String
+ - contextPath: Microsoft365Defender.Incident.alerts
+ description: List of alerts relevant for the incidents.
+ type: Unknown
+ - arguments:
+ - description: Advanced hunting query.
+ name: query
+ required: true
+ - defaultValue: '30'
+ description: The time limit in seconds for the http request to run.
+ name: timeout
+ - defaultValue: '50'
+ description: Number of entries. Enter -1 for unlimited query.
+ name: limit
+ required: true
+ description: 'Advanced hunting is a threat-hunting tool that uses specially constructed queries to examine the past 30 days of event data in Microsoft 365 Defender. Details on how to write queries: https://docs.microsoft.com/en-us/microsoft-365/security/defender/advanced-hunting-query-language?view=o365-worldwide.'
+ name: microsoft-365-defender-advanced-hunting
+ outputs:
+ - contextPath: Microsoft365Defender.Hunt.query
+ description: The query used, also acted as a key.
+ type: String
+ - contextPath: Microsoft365Defender.Hunt.results.
+ description: The results of the query.
+ type: Unknown
+ - arguments:
+ - description: Incident's ID.
+ name: id
+ required: true
+ - defaultValue: '30'
+ description: The time limit in seconds for the http request to run.
+ name: timeout
+ description: Gets the incident with the given ID.
+ name: microsoft-365-defender-incident-get
+ outputs:
+ - contextPath: Microsoft365Defender.Incident.incidentId
+ description: Incident's ID.
+ type: number
+ - contextPath: Microsoft365Defender.Incident.redirectIncidentId
+ description: Only populated in case an incident is grouped together with another incident, as part of the incident processing logic.
+ - contextPath: Microsoft365Defender.Incident.incidentName
+ description: The name of the incident.
+ type: string
+ - contextPath: Microsoft365Defender.Incident.createdTime
+ description: The date and time (in UTC) the incident was created.
+ type: date
+ - contextPath: Microsoft365Defender.Incident.lastUpdateTime
+ description: The date and time (in UTC) the incident was last updated.
+ type: date
+ - contextPath: Microsoft365Defender.Incident.assignedTo
+ description: Owner of the incident.
+ type: string
+ - contextPath: Microsoft365Defender.Incident.classification
+ description: 'Specification of the incident. Possible values are: Unknown, FalsePositive, and TruePositive.'
+ type: string
+ - contextPath: Microsoft365Defender.Incident.determination
+ description: 'The determination of the incident. Possible values are: NotAvailable, Apt, Malware, SecurityPersonnel, SecurityTesting, UnwantedSoftware, and Other.'
+ type: string
+ - contextPath: Microsoft365Defender.Incident.severity
+ description: 'Severity of the incident. Possible values are: UnSpecified, Informational, Low, Medium, and High.'
+ type: string
+ - contextPath: Microsoft365Defender.Incident.status
+ description: 'The current status of the incident. Possible values are: Active, Resolved, and Redirected.'
+ type: string
+ - contextPath: Microsoft365Defender.Incident.alerts
+ description: List of alerts relevant for the incidents.
+ dockerimage: demisto/crypto:1.0.0.114611
+ isfetch: true
+ script: >
+ register_module_line('Microsoft 365 Defender', 'start', __line__())
+
+ demisto.debug('pack name = Microsoft 365 Defender, pack version = 4.5.42')
+
+
+ import urllib3
+
+
+
+ ### GENERATED CODE ###: from MicrosoftApiModule import * # noqa: E402
+
+ # This code was inserted in place of an API module.
+
+ register_module_line('MicrosoftApiModule', 'start', __line__(), wrapper=-3)
+
+
+
+ # pylint: disable=E9010, E9011
+
+ import traceback
+
+
+
+ import requests
+
+ import re
+
+ import base64
+
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+
+
+
+ class Scopes:
+ graph = 'https://graph.microsoft.com/.default'
+ security_center = 'https://api.securitycenter.windows.com/.default'
+ security_center_apt_service = 'https://securitycenter.onmicrosoft.com/windowsatpservice/.default'
+ management_azure = 'https://management.azure.com/.default' # resource_manager
+
+
+ class Resources:
+ graph = 'https://graph.microsoft.com/'
+ security_center = 'https://api.securitycenter.microsoft.com/'
+ security = 'https://api.security.microsoft.com/'
+ management_azure = 'https://management.azure.com/' # resource_manager
+ manage_office = 'https://manage.office.com/'
+
+
+ # authorization types
+
+ OPROXY_AUTH_TYPE = 'oproxy'
+
+ SELF_DEPLOYED_AUTH_TYPE = 'self_deployed'
+
+
+ # grant types in self-deployed authorization
+
+ CLIENT_CREDENTIALS = 'client_credentials'
+
+ AUTHORIZATION_CODE = 'authorization_code'
+
+ REFRESH_TOKEN = 'refresh_token' # guardrails-disable-line
+
+ DEVICE_CODE = 'urn:ietf:params:oauth:grant-type:device_code'
+
+ REGEX_SEARCH_URL = r'(?Phttps?://[^\s]+)'
+
+ REGEX_SEARCH_ERROR_DESC = r"^.*?:\s(?P.*?\.)"
+
+ SESSION_STATE = 'session_state'
+
+
+ # Deprecated, prefer using AZURE_CLOUDS
+
+ TOKEN_RETRIEVAL_ENDPOINTS = {
+ 'com': 'https://login.microsoftonline.com',
+ 'gcc': 'https://login.microsoftonline.com',
+ 'gcc-high': 'https://login.microsoftonline.us',
+ 'dod': 'https://login.microsoftonline.us',
+ 'de': 'https://login.microsoftonline.de',
+ 'cn': 'https://login.chinacloudapi.cn',
+ }
+
+
+ # Deprecated, prefer using AZURE_CLOUDS
+
+ GRAPH_ENDPOINTS = {
+ 'com': 'https://graph.microsoft.com',
+ 'gcc': 'https://graph.microsoft.us',
+ 'gcc-high': 'https://graph.microsoft.us',
+ 'dod': 'https://dod-graph.microsoft.us',
+ 'de': 'https://graph.microsoft.de',
+ 'cn': 'https://microsoftgraph.chinacloudapi.cn'
+ }
+
+
+ # Deprecated, prefer using AZURE_CLOUDS
+
+ GRAPH_BASE_ENDPOINTS = {
+ 'https://graph.microsoft.com': 'com',
+ # can't create an entry here for 'gcc' as the url is the same for both 'gcc' and 'gcc-high'
+ 'https://graph.microsoft.us': 'gcc-high',
+ 'https://dod-graph.microsoft.us': 'dod',
+ 'https://graph.microsoft.de': 'de',
+ 'https://microsoftgraph.chinacloudapi.cn': 'cn'
+ }
+
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_TYPE = {
+ "Worldwide": "com",
+ "US Geo Proximity": "geo-us",
+ "EU Geo Proximity": "geo-eu",
+ "UK Geo Proximity": "geo-uk",
+ "US GCC": "gcc",
+ "US GCC-High": "gcc-high",
+ "DoD": "dod",
+ }
+
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_TYPE_CUSTOM = "Custom"
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_DEFAULT_ENDPOINT_TYPE = "com"
+
+
+
+ # https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-supported?view=o365-worldwide#endpoint-uris
+
+ # https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/gov?view=o365-worldwide#api
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_API = {
+ "com": "https://api.securitycenter.microsoft.com",
+ "geo-us": "https://api.securitycenter.microsoft.com",
+ "geo-eu": "https://api-eu.securitycenter.microsoft.com",
+ "geo-uk": "https://api-uk.securitycenter.microsoft.com",
+ "gcc": "https://api-gcc.securitycenter.microsoft.us",
+ "gcc-high": "https://api-gov.securitycenter.microsoft.us",
+ "dod": "https://api-gov.securitycenter.microsoft.us",
+ }
+
+
+ # https://learn.microsoft.com/en-us/graph/deployments#app-registration-and-token-service-root-endpoints
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_TOKEN_RETRIVAL_ENDPOINTS = {
+ 'com': 'https://login.microsoftonline.com',
+ 'geo-us': 'https://login.microsoftonline.com',
+ 'geo-eu': 'https://login.microsoftonline.com',
+ 'geo-uk': 'https://login.microsoftonline.com',
+ 'gcc': 'https://login.microsoftonline.com',
+ 'gcc-high': 'https://login.microsoftonline.us',
+ 'dod': 'https://login.microsoftonline.us',
+ }
+
+
+ # https://learn.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_GRAPH_ENDPOINTS = {
+ 'com': 'https://graph.microsoft.com',
+ 'geo-us': 'https://graph.microsoft.com',
+ 'geo-eu': 'https://graph.microsoft.com',
+ 'geo-uk': 'https://graph.microsoft.com',
+ 'gcc': 'https://graph.microsoft.com',
+ 'gcc-high': 'https://graph.microsoft.us',
+ 'dod': 'https://dod-graph.microsoft.us',
+ }
+
+
+ MICROSOFT_DEFENDER_FOR_ENDPOINT_APT_SERVICE_ENDPOINTS = {
+ 'com': 'https://securitycenter.onmicrosoft.com',
+ 'geo-us': 'https://securitycenter.onmicrosoft.com',
+ 'geo-eu': 'https://securitycenter.onmicrosoft.com',
+ 'geo-uk': 'https://securitycenter.onmicrosoft.com',
+ 'gcc': 'https://securitycenter.onmicrosoft.com',
+ 'gcc-high': 'https://securitycenter.onmicrosoft.us',
+ 'dod': 'https://securitycenter.onmicrosoft.us',
+ }
+
+
+ MICROSOFT_DEFENDER_FOR_APPLICATION_API = {
+ "com": "https://api.securitycenter.microsoft.com",
+ "gcc": "https://api-gcc.securitycenter.microsoft.us",
+ "gcc-high": "https://api-gcc.securitycenter.microsoft.us",
+ }
+
+
+
+ MICROSOFT_DEFENDER_FOR_APPLICATION_TYPE = {
+ "Worldwide": "com",
+ "US GCC": "gcc",
+ "US GCC-High": "gcc-high",
+ }
+
+
+ MICROSOFT_DEFENDER_FOR_APPLICATION_TOKEN_RETRIEVAL_ENDPOINTS = {
+ 'com': 'https://login.microsoftonline.com',
+ 'gcc': 'https://login.microsoftonline.com',
+ 'gcc-high': 'https://login.microsoftonline.us',
+ }
+
+
+ MICROSOFT_365_DEFENDER_TYPE = {
+ "Worldwide": "com",
+ "US Geo Proximity": "geo-us",
+ "EU Geo Proximity": "geo-eu",
+ "UK Geo Proximity": "geo-uk",
+ "AU Geo Proximity": "geo-au",
+ "SWA Geo Proximity": "geo-swa",
+ "INA Geo Proximity": "geo-ina",
+ "US GCC": "gcc",
+ "US GCC-High": "gcc-high",
+ "DoD": "dod",
+ }
+
+
+ # https://learn.microsoft.com/en-us/defender-endpoint/api/exposed-apis-list
+
+ # https://learn.microsoft.com/en-us/defender-xdr/usgov?view=o365-worldwide
+
+ MICROSOFT_365_DEFENDER_API_ENDPOINTS = {
+ "com": "https://api.security.microsoft.com",
+ "geo-us": "https://us.api.security.microsoft.com",
+ "geo-eu": "https://eu.api.security.microsoft.com",
+ "geo-uk": "https://uk.api.security.microsoft.com",
+ "geo-au": "https://au.api.security.microsoft.com",
+ "geo-swa": "https://swa.api.security.microsoft.com",
+ "geo-ina": "https://ina.api.security.microsoft.com",
+ "gcc": "https://api-gcc.security.microsoft.us",
+ "gcc-high": "https://api-gov.security.microsoft.us",
+ "dod": "https://api-gov.security.microsoft.us",
+ }
+
+
+ # https://learn.microsoft.com/en-us/defender-xdr/usgov?view=o365-worldwide
+
+ MICROSOFT_365_DEFENDER_TOKEN_RETRIEVAL_ENDPOINTS = {
+ 'com': 'https://login.windows.net',
+ 'geo-us': 'https://login.windows.net',
+ 'geo-eu': 'https://login.windows.net',
+ 'geo-uk': 'https://login.windows.net',
+ "geo-au": 'https://login.windows.net',
+ "geo-swa": 'https://login.windows.net',
+ "geo-ina": 'https://login.windows.net',
+ "gcc": "https://login.microsoftonline.com",
+ "gcc-high": "https://login.microsoftonline.us",
+ "dod": "https://login.microsoftonline.us",
+ }
+
+
+ MICROSOFT_365_DEFENDER_SCOPES = {
+ 'com': 'https://security.microsoft.com',
+ 'geo-us': 'https://security.microsoft.com',
+ 'geo-eu': 'https://security.microsoft.com',
+ 'geo-uk': 'https://security.microsoft.com',
+ "geo-au": 'https://security.microsoft.com',
+ "geo-swa": 'https://security.microsoft.com',
+ "geo-ina": 'https://security.microsoft.com',
+ 'gcc': 'https://security.microsoft.com',
+ 'gcc-high': 'https://security.microsoft.us',
+ 'dod': 'https://security.apps.mil',
+ }
+
+
+
+ # Azure Managed Identities
+
+ MANAGED_IDENTITIES_TOKEN_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01'
+
+ MANAGED_IDENTITIES_SYSTEM_ASSIGNED = 'SYSTEM_ASSIGNED'
+
+ TOKEN_EXPIRED_ERROR_CODES = {50173, 700082, 70008, 54005, 7000222,
+ } # See: https://login.microsoftonline.com/error?code=
+
+ # Moderate Retry Mechanism
+
+ MAX_DELAY_REQUEST_COUNTER = 6
+
+
+
+ class CloudEndpointNotSetException(Exception):
+ pass
+
+
+ class CloudSuffixNotSetException(Exception):
+ pass
+
+
+ class AzureCloudEndpoints: # pylint: disable=too-few-public-methods,too-many-instance-attributes
+
+ def __init__(self, # pylint: disable=unused-argument
+ management=None,
+ resource_manager=None,
+ sql_management=None,
+ batch_resource_id=None,
+ gallery=None,
+ active_directory=None,
+ active_directory_resource_id=None,
+ active_directory_graph_resource_id=None,
+ microsoft_graph_resource_id=None,
+ active_directory_data_lake_resource_id=None,
+ vm_image_alias_doc=None,
+ media_resource_id=None,
+ ossrdbms_resource_id=None,
+ log_analytics_resource_id=None,
+ app_insights_resource_id=None,
+ app_insights_telemetry_channel_resource_id=None,
+ synapse_analytics_resource_id=None,
+ attestation_resource_id=None,
+ portal=None,
+ keyvault=None,
+ exchange_online=None):
+ # Attribute names are significant. They are used when storing/retrieving clouds from config
+ self.management = management
+ self.resource_manager = resource_manager
+ self.sql_management = sql_management
+ self.batch_resource_id = batch_resource_id
+ self.gallery = gallery
+ self.active_directory = active_directory
+ self.active_directory_resource_id = active_directory_resource_id
+ self.active_directory_graph_resource_id = active_directory_graph_resource_id
+ self.microsoft_graph_resource_id = microsoft_graph_resource_id
+ self.active_directory_data_lake_resource_id = active_directory_data_lake_resource_id
+ self.vm_image_alias_doc = vm_image_alias_doc
+ self.media_resource_id = media_resource_id
+ self.ossrdbms_resource_id = ossrdbms_resource_id
+ self.log_analytics_resource_id = log_analytics_resource_id
+ self.app_insights_resource_id = app_insights_resource_id
+ self.app_insights_telemetry_channel_resource_id = app_insights_telemetry_channel_resource_id
+ self.synapse_analytics_resource_id = synapse_analytics_resource_id
+ self.attestation_resource_id = attestation_resource_id
+ self.portal = portal
+ self.keyvault = keyvault
+ self.exchange_online = exchange_online
+
+ def has_endpoint_set(self, endpoint_name):
+ try:
+ # Can't simply use hasattr here as we override __getattribute__ below.
+ # Python 3 hasattr() only returns False if an AttributeError is raised, but we raise
+ # CloudEndpointNotSetException. This exception is not a subclass of AttributeError.
+ getattr(self, endpoint_name)
+ return True
+ except Exception: # pylint: disable=broad-except
+ return False
+
+ def __getattribute__(self, name):
+ val = object.__getattribute__(self, name)
+ if val is None:
+ raise CloudEndpointNotSetException("The endpoint '{}' for this cloud is not set but is used.")
+ return val
+
+
+ class AzureCloudSuffixes: # pylint: disable=too-few-public-methods,too-many-instance-attributes
+
+ def __init__(self, # pylint: disable=unused-argument
+ storage_endpoint=None,
+ storage_sync_endpoint=None,
+ keyvault_dns=None,
+ mhsm_dns=None,
+ sql_server_hostname=None,
+ azure_datalake_store_file_system_endpoint=None,
+ azure_datalake_analytics_catalog_and_job_endpoint=None,
+ acr_login_server_endpoint=None,
+ mysql_server_endpoint=None,
+ postgresql_server_endpoint=None,
+ mariadb_server_endpoint=None,
+ synapse_analytics_endpoint=None,
+ attestation_endpoint=None):
+ # Attribute names are significant. They are used when storing/retrieving clouds from config
+ self.storage_endpoint = storage_endpoint
+ self.storage_sync_endpoint = storage_sync_endpoint
+ self.keyvault_dns = keyvault_dns
+ self.mhsm_dns = mhsm_dns
+ self.sql_server_hostname = sql_server_hostname
+ self.mysql_server_endpoint = mysql_server_endpoint
+ self.postgresql_server_endpoint = postgresql_server_endpoint
+ self.mariadb_server_endpoint = mariadb_server_endpoint
+ self.azure_datalake_store_file_system_endpoint = azure_datalake_store_file_system_endpoint
+ self.azure_datalake_analytics_catalog_and_job_endpoint = azure_datalake_analytics_catalog_and_job_endpoint
+ self.acr_login_server_endpoint = acr_login_server_endpoint
+ self.synapse_analytics_endpoint = synapse_analytics_endpoint
+ self.attestation_endpoint = attestation_endpoint
+
+ def __getattribute__(self, name):
+ val = object.__getattribute__(self, name)
+ if val is None:
+ raise CloudSuffixNotSetException("The suffix '{}' for this cloud is not set but is used.")
+ return val
+
+
+ class AzureCloud: # pylint: disable=too-few-public-methods
+ """ Represents an Azure Cloud instance """
+
+ def __init__(self,
+ origin,
+ name,
+ abbreviation,
+ endpoints=None,
+ suffixes=None):
+ self.name = name
+ self.abbreviation = abbreviation
+ self.origin = origin
+ self.endpoints = endpoints or AzureCloudEndpoints()
+ self.suffixes = suffixes or AzureCloudSuffixes()
+
+
+ AZURE_WORLDWIDE_CLOUD = AzureCloud(
+ 'Embedded',
+ 'AzureCloud',
+ 'com',
+ endpoints=AzureCloudEndpoints(
+ management='https://management.core.windows.net/',
+ resource_manager='https://management.azure.com/',
+ sql_management='https://management.core.windows.net:8443/',
+ batch_resource_id='https://batch.core.windows.net/',
+ gallery='https://gallery.azure.com/',
+ active_directory='https://login.microsoftonline.com',
+ active_directory_resource_id='https://management.core.windows.net/',
+ active_directory_graph_resource_id='https://graph.windows.net/',
+ microsoft_graph_resource_id='https://graph.microsoft.com/',
+ active_directory_data_lake_resource_id='https://datalake.azure.net/',
+ vm_image_alias_doc='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json', # noqa: E501
+ media_resource_id='https://rest.media.azure.net',
+ ossrdbms_resource_id='https://ossrdbms-aad.database.windows.net',
+ app_insights_resource_id='https://api.applicationinsights.io',
+ log_analytics_resource_id='https://api.loganalytics.io',
+ app_insights_telemetry_channel_resource_id='https://dc.applicationinsights.azure.com/v2/track',
+ synapse_analytics_resource_id='https://dev.azuresynapse.net',
+ attestation_resource_id='https://attest.azure.net',
+ portal='https://portal.azure.com',
+ keyvault='https://vault.azure.net',
+ exchange_online='https://outlook.office365.com'
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint='core.windows.net',
+ storage_sync_endpoint='afs.azure.net',
+ keyvault_dns='.vault.azure.net',
+ mhsm_dns='.managedhsm.azure.net',
+ sql_server_hostname='.database.windows.net',
+ mysql_server_endpoint='.mysql.database.azure.com',
+ postgresql_server_endpoint='.postgres.database.azure.com',
+ mariadb_server_endpoint='.mariadb.database.azure.com',
+ azure_datalake_store_file_system_endpoint='azuredatalakestore.net',
+ azure_datalake_analytics_catalog_and_job_endpoint='azuredatalakeanalytics.net',
+ acr_login_server_endpoint='.azurecr.io',
+ synapse_analytics_endpoint='.dev.azuresynapse.net',
+ attestation_endpoint='.attest.azure.net'))
+
+ AZURE_US_GCC_CLOUD = AzureCloud(
+ 'Embedded',
+ 'AzureUSGovernment',
+ 'gcc',
+ endpoints=AzureCloudEndpoints(
+ management='https://management.core.usgovcloudapi.net/',
+ resource_manager='https://management.usgovcloudapi.net/',
+ sql_management='https://management.core.usgovcloudapi.net:8443/',
+ batch_resource_id='https://batch.core.usgovcloudapi.net/',
+ gallery='https://gallery.usgovcloudapi.net/',
+ active_directory='https://login.microsoftonline.com',
+ active_directory_resource_id='https://management.core.usgovcloudapi.net/',
+ active_directory_graph_resource_id='https://graph.windows.net/',
+ microsoft_graph_resource_id='https://graph.microsoft.us/',
+ vm_image_alias_doc='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json', # noqa: E501
+ media_resource_id='https://rest.media.usgovcloudapi.net',
+ ossrdbms_resource_id='https://ossrdbms-aad.database.usgovcloudapi.net',
+ app_insights_resource_id='https://api.applicationinsights.us',
+ log_analytics_resource_id='https://api.loganalytics.us',
+ app_insights_telemetry_channel_resource_id='https://dc.applicationinsights.us/v2/track',
+ synapse_analytics_resource_id='https://dev.azuresynapse.usgovcloudapi.net',
+ portal='https://portal.azure.us',
+ keyvault='https://vault.usgovcloudapi.net',
+ exchange_online='https://outlook.office365.com'
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint='core.usgovcloudapi.net',
+ storage_sync_endpoint='afs.azure.us',
+ keyvault_dns='.vault.usgovcloudapi.net',
+ mhsm_dns='.managedhsm.usgovcloudapi.net',
+ sql_server_hostname='.database.usgovcloudapi.net',
+ mysql_server_endpoint='.mysql.database.usgovcloudapi.net',
+ postgresql_server_endpoint='.postgres.database.usgovcloudapi.net',
+ mariadb_server_endpoint='.mariadb.database.usgovcloudapi.net',
+ acr_login_server_endpoint='.azurecr.us',
+ synapse_analytics_endpoint='.dev.azuresynapse.usgovcloudapi.net'))
+
+ AZURE_US_GCC_HIGH_CLOUD = AzureCloud(
+ 'Embedded',
+ 'AzureUSGovernment',
+ 'gcc-high',
+ endpoints=AzureCloudEndpoints(
+ management='https://management.core.usgovcloudapi.net/',
+ resource_manager='https://management.usgovcloudapi.net/',
+ sql_management='https://management.core.usgovcloudapi.net:8443/',
+ batch_resource_id='https://batch.core.usgovcloudapi.net/',
+ gallery='https://gallery.usgovcloudapi.net/',
+ active_directory='https://login.microsoftonline.us',
+ active_directory_resource_id='https://management.core.usgovcloudapi.net/',
+ active_directory_graph_resource_id='https://graph.windows.net/',
+ microsoft_graph_resource_id='https://graph.microsoft.us/',
+ vm_image_alias_doc='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json', # noqa: E501
+ media_resource_id='https://rest.media.usgovcloudapi.net',
+ ossrdbms_resource_id='https://ossrdbms-aad.database.usgovcloudapi.net',
+ app_insights_resource_id='https://api.applicationinsights.us',
+ log_analytics_resource_id='https://api.loganalytics.us',
+ app_insights_telemetry_channel_resource_id='https://dc.applicationinsights.us/v2/track',
+ synapse_analytics_resource_id='https://dev.azuresynapse.usgovcloudapi.net',
+ portal='https://portal.azure.us',
+ keyvault='https://vault.usgovcloudapi.net',
+ exchange_online='https://outlook.office365.us'
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint='core.usgovcloudapi.net',
+ storage_sync_endpoint='afs.azure.us',
+ keyvault_dns='.vault.usgovcloudapi.net',
+ mhsm_dns='.managedhsm.usgovcloudapi.net',
+ sql_server_hostname='.database.usgovcloudapi.net',
+ mysql_server_endpoint='.mysql.database.usgovcloudapi.net',
+ postgresql_server_endpoint='.postgres.database.usgovcloudapi.net',
+ mariadb_server_endpoint='.mariadb.database.usgovcloudapi.net',
+ acr_login_server_endpoint='.azurecr.us',
+ synapse_analytics_endpoint='.dev.azuresynapse.usgovcloudapi.net'))
+
+ AZURE_DOD_CLOUD = AzureCloud(
+ 'Embedded',
+ 'AzureUSGovernment',
+ 'dod',
+ endpoints=AzureCloudEndpoints(
+ management='https://management.core.usgovcloudapi.net/',
+ resource_manager='https://management.usgovcloudapi.net/',
+ sql_management='https://management.core.usgovcloudapi.net:8443/',
+ batch_resource_id='https://batch.core.usgovcloudapi.net/',
+ gallery='https://gallery.usgovcloudapi.net/',
+ active_directory='https://login.microsoftonline.us',
+ active_directory_resource_id='https://management.core.usgovcloudapi.net/',
+ active_directory_graph_resource_id='https://graph.windows.net/',
+ microsoft_graph_resource_id='https://dod-graph.microsoft.us/',
+ vm_image_alias_doc='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json', # noqa: E501
+ media_resource_id='https://rest.media.usgovcloudapi.net',
+ ossrdbms_resource_id='https://ossrdbms-aad.database.usgovcloudapi.net',
+ app_insights_resource_id='https://api.applicationinsights.us',
+ log_analytics_resource_id='https://api.loganalytics.us',
+ app_insights_telemetry_channel_resource_id='https://dc.applicationinsights.us/v2/track',
+ synapse_analytics_resource_id='https://dev.azuresynapse.usgovcloudapi.net',
+ portal='https://portal.azure.us',
+ keyvault='https://vault.usgovcloudapi.net',
+ exchange_online='https://outlook-dod.office365.us'
+
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint='core.usgovcloudapi.net',
+ storage_sync_endpoint='afs.azure.us',
+ keyvault_dns='.vault.usgovcloudapi.net',
+ mhsm_dns='.managedhsm.usgovcloudapi.net',
+ sql_server_hostname='.database.usgovcloudapi.net',
+ mysql_server_endpoint='.mysql.database.usgovcloudapi.net',
+ postgresql_server_endpoint='.postgres.database.usgovcloudapi.net',
+ mariadb_server_endpoint='.mariadb.database.usgovcloudapi.net',
+ acr_login_server_endpoint='.azurecr.us',
+ synapse_analytics_endpoint='.dev.azuresynapse.usgovcloudapi.net'))
+
+
+ AZURE_GERMAN_CLOUD = AzureCloud(
+ 'Embedded',
+ 'AzureGermanCloud',
+ 'de',
+ endpoints=AzureCloudEndpoints(
+ management='https://management.core.cloudapi.de/',
+ resource_manager='https://management.microsoftazure.de',
+ sql_management='https://management.core.cloudapi.de:8443/',
+ batch_resource_id='https://batch.cloudapi.de/',
+ gallery='https://gallery.cloudapi.de/',
+ active_directory='https://login.microsoftonline.de',
+ active_directory_resource_id='https://management.core.cloudapi.de/',
+ active_directory_graph_resource_id='https://graph.cloudapi.de/',
+ microsoft_graph_resource_id='https://graph.microsoft.de',
+ vm_image_alias_doc='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json', # noqa: E501
+ media_resource_id='https://rest.media.cloudapi.de',
+ ossrdbms_resource_id='https://ossrdbms-aad.database.cloudapi.de',
+ portal='https://portal.microsoftazure.de',
+ keyvault='https://vault.microsoftazure.de',
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint='core.cloudapi.de',
+ keyvault_dns='.vault.microsoftazure.de',
+ mhsm_dns='.managedhsm.microsoftazure.de',
+ sql_server_hostname='.database.cloudapi.de',
+ mysql_server_endpoint='.mysql.database.cloudapi.de',
+ postgresql_server_endpoint='.postgres.database.cloudapi.de',
+ mariadb_server_endpoint='.mariadb.database.cloudapi.de'))
+
+ AZURE_CHINA_CLOUD = AzureCloud(
+ 'Embedded',
+ 'AzureChinaCloud',
+ 'cn',
+ endpoints=AzureCloudEndpoints(
+ management='https://management.core.chinacloudapi.cn/',
+ resource_manager='https://management.chinacloudapi.cn',
+ sql_management='https://management.core.chinacloudapi.cn:8443/',
+ batch_resource_id='https://batch.chinacloudapi.cn/',
+ gallery='https://gallery.chinacloudapi.cn/',
+ active_directory='https://login.chinacloudapi.cn',
+ active_directory_resource_id='https://management.core.chinacloudapi.cn/',
+ active_directory_graph_resource_id='https://graph.chinacloudapi.cn/',
+ microsoft_graph_resource_id='https://microsoftgraph.chinacloudapi.cn',
+ vm_image_alias_doc='https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/arm-compute/quickstart-templates/aliases.json', # noqa: E501
+ media_resource_id='https://rest.media.chinacloudapi.cn',
+ ossrdbms_resource_id='https://ossrdbms-aad.database.chinacloudapi.cn',
+ app_insights_resource_id='https://api.applicationinsights.azure.cn',
+ log_analytics_resource_id='https://api.loganalytics.azure.cn',
+ app_insights_telemetry_channel_resource_id='https://dc.applicationinsights.azure.cn/v2/track',
+ synapse_analytics_resource_id='https://dev.azuresynapse.azure.cn',
+ portal='https://portal.azure.cn',
+ keyvault='https://vault.azure.cn',
+ exchange_online='https://partner.outlook.cn'
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint='core.chinacloudapi.cn',
+ keyvault_dns='.vault.azure.cn',
+ mhsm_dns='.managedhsm.azure.cn',
+ sql_server_hostname='.database.chinacloudapi.cn',
+ mysql_server_endpoint='.mysql.database.chinacloudapi.cn',
+ postgresql_server_endpoint='.postgres.database.chinacloudapi.cn',
+ mariadb_server_endpoint='.mariadb.database.chinacloudapi.cn',
+ acr_login_server_endpoint='.azurecr.cn',
+ synapse_analytics_endpoint='.dev.azuresynapse.azure.cn'))
+
+
+ AZURE_CLOUD_NAME_MAPPING = {
+ "Worldwide": "com",
+ "Germany": "de",
+ "China": "cn",
+ "US GCC": "gcc",
+ "US GCC-High": "gcc-high",
+ "DoD": "dod",
+ }
+
+
+ AZURE_CLOUD_NAME_CUSTOM = "Custom"
+
+
+ AZURE_CLOUDS = {
+ "com": AZURE_WORLDWIDE_CLOUD,
+ "gcc": AZURE_US_GCC_CLOUD,
+ "gcc-high": AZURE_US_GCC_HIGH_CLOUD,
+ "dod": AZURE_DOD_CLOUD,
+ "de": AZURE_GERMAN_CLOUD,
+ "cn": AZURE_CHINA_CLOUD,
+ }
+
+
+
+ class AzureCloudNames:
+ WORLDWIDE = "com"
+ GERMANY = "de"
+ CHINA = "cn"
+ US_GCC = "gcc"
+ US_GCC_HIGH = "gcc-high"
+ DOD = "dod"
+ CUSTOM = "custom"
+
+
+ def create_custom_azure_cloud(origin: str,
+ name: str | None = None,
+ abbreviation: str | None = None,
+ defaults: AzureCloud | None = None,
+ endpoints: dict | None = None,
+ suffixes: dict | None = None):
+ defaults = defaults or AzureCloud(origin, name, abbreviation)
+ endpoints = endpoints or {}
+ suffixes = suffixes or {}
+ return AzureCloud(
+ origin,
+ name or defaults.name,
+ abbreviation or defaults.abbreviation,
+ endpoints=AzureCloudEndpoints(
+ management=endpoints.get('management', defaults.endpoints.management),
+ resource_manager=endpoints.get('resource_manager', defaults.endpoints.resource_manager),
+ sql_management=endpoints.get('sql_management', defaults.endpoints.sql_management),
+ batch_resource_id=endpoints.get('batch_resource_id', defaults.endpoints.batch_resource_id),
+ gallery=endpoints.get('gallery', defaults.endpoints.gallery),
+ active_directory=endpoints.get('active_directory', defaults.endpoints.active_directory),
+ active_directory_resource_id=endpoints.get('active_directory_resource_id',
+ defaults.endpoints.active_directory_resource_id),
+ active_directory_graph_resource_id=endpoints.get(
+ 'active_directory_graph_resource_id', defaults.endpoints.active_directory_graph_resource_id),
+ microsoft_graph_resource_id=endpoints.get('microsoft_graph_resource_id',
+ defaults.endpoints.microsoft_graph_resource_id),
+ active_directory_data_lake_resource_id=endpoints.get(
+ 'active_directory_data_lake_resource_id', defaults.endpoints.active_directory_data_lake_resource_id),
+ vm_image_alias_doc=endpoints.get('vm_image_alias_doc', defaults.endpoints.vm_image_alias_doc),
+ media_resource_id=endpoints.get('media_resource_id', defaults.endpoints.media_resource_id),
+ ossrdbms_resource_id=endpoints.get('ossrdbms_resource_id', defaults.endpoints.ossrdbms_resource_id),
+ app_insights_resource_id=endpoints.get('app_insights_resource_id', defaults.endpoints.app_insights_resource_id),
+ log_analytics_resource_id=endpoints.get('log_analytics_resource_id', defaults.endpoints.log_analytics_resource_id),
+ app_insights_telemetry_channel_resource_id=endpoints.get(
+ 'app_insights_telemetry_channel_resource_id', defaults.endpoints.app_insights_telemetry_channel_resource_id),
+ synapse_analytics_resource_id=endpoints.get(
+ 'synapse_analytics_resource_id', defaults.endpoints.synapse_analytics_resource_id),
+ attestation_resource_id=endpoints.get('attestation_resource_id', defaults.endpoints.attestation_resource_id),
+ portal=endpoints.get('portal', defaults.endpoints.portal),
+ keyvault=endpoints.get('keyvault', defaults.endpoints.keyvault),
+ ),
+ suffixes=AzureCloudSuffixes(
+ storage_endpoint=suffixes.get('storage_endpoint', defaults.suffixes.storage_endpoint),
+ storage_sync_endpoint=suffixes.get('storage_sync_endpoint', defaults.suffixes.storage_sync_endpoint),
+ keyvault_dns=suffixes.get('keyvault_dns', defaults.suffixes.keyvault_dns),
+ mhsm_dns=suffixes.get('mhsm_dns', defaults.suffixes.mhsm_dns),
+ sql_server_hostname=suffixes.get('sql_server_hostname', defaults.suffixes.sql_server_hostname),
+ mysql_server_endpoint=suffixes.get('mysql_server_endpoint', defaults.suffixes.mysql_server_endpoint),
+ postgresql_server_endpoint=suffixes.get('postgresql_server_endpoint', defaults.suffixes.postgresql_server_endpoint),
+ mariadb_server_endpoint=suffixes.get('mariadb_server_endpoint', defaults.suffixes.mariadb_server_endpoint),
+ azure_datalake_store_file_system_endpoint=suffixes.get(
+ 'azure_datalake_store_file_system_endpoint', defaults.suffixes.azure_datalake_store_file_system_endpoint),
+ azure_datalake_analytics_catalog_and_job_endpoint=suffixes.get(
+ 'azure_datalake_analytics_catalog_and_job_endpoint',
+ defaults.suffixes.azure_datalake_analytics_catalog_and_job_endpoint),
+ acr_login_server_endpoint=suffixes.get('acr_login_server_endpoint', defaults.suffixes.acr_login_server_endpoint),
+ synapse_analytics_endpoint=suffixes.get('synapse_analytics_endpoint', defaults.suffixes.synapse_analytics_endpoint),
+ attestation_endpoint=suffixes.get('attestation_endpoint', defaults.suffixes.attestation_endpoint),
+ ))
+
+
+ def microsoft_defender_for_endpoint_get_base_url(endpoint_type, url, is_gcc=None):
+ # Backward compatible argument parsing, preserve the url and is_gcc functionality if provided, otherwise use endpoint_type.
+ log_message_append = ""
+ if is_gcc: # Backward compatible.
+ endpoint_type = "US GCC"
+ log_message_append = f" ,Overriding endpoint to {endpoint_type}, backward compatible."
+ elif (endpoint_type == MICROSOFT_DEFENDER_FOR_ENDPOINT_TYPE_CUSTOM or not endpoint_type) and not url:
+ # When the integration was configured before our Azure Cloud support, the value will be None.
+ if endpoint_type == MICROSOFT_DEFENDER_FOR_ENDPOINT_TYPE_CUSTOM:
+ raise DemistoException("Endpoint type is set to 'Custom' but no URL was provided.")
+ raise DemistoException("'Endpoint Type' is not set and no URL was provided.")
+ endpoint_type = MICROSOFT_DEFENDER_FOR_ENDPOINT_TYPE.get(endpoint_type, 'com')
+ url = url or MICROSOFT_DEFENDER_FOR_ENDPOINT_API[endpoint_type]
+ demisto.info(f"Using url:{url}, endpoint type:{endpoint_type}{log_message_append}")
+ return endpoint_type, url
+
+
+ def get_azure_cloud(params, integration_name):
+ azure_cloud_arg = params.get('azure_cloud')
+ if not azure_cloud_arg or azure_cloud_arg == AZURE_CLOUD_NAME_CUSTOM:
+ # Backward compatibility before the azure cloud settings.
+ if 'server_url' in params:
+ return create_custom_azure_cloud(integration_name, defaults=AZURE_WORLDWIDE_CLOUD,
+ endpoints={'resource_manager': params.get('server_url')
+ or 'https://management.azure.com'})
+ if 'azure_ad_endpoint' in params:
+ return create_custom_azure_cloud(integration_name, defaults=AZURE_WORLDWIDE_CLOUD,
+ endpoints={
+ 'active_directory': params.get('azure_ad_endpoint')
+ or 'https://login.microsoftonline.com'
+ })
+ # in multiple Graph integrations, the url is called 'url' or 'host' instead of 'server_url' and the default url is
+ # different.
+ if 'url' in params or 'host' in params:
+ return create_custom_azure_cloud(integration_name, defaults=AZURE_WORLDWIDE_CLOUD,
+ endpoints={'microsoft_graph_resource_id': params.get('url') or params.get('host')
+ or 'https://graph.microsoft.com'})
+
+ # There is no need for backward compatibility support, as the integration didn't support it to begin with.
+ return AZURE_CLOUDS.get(AZURE_CLOUD_NAME_MAPPING.get(azure_cloud_arg), AZURE_WORLDWIDE_CLOUD) # type: ignore[arg-type]
+
+
+ def microsoft_defender_get_base_url(base_url: str, endpoint_type: str) -> str:
+ if endpoint_type == 'Custom':
+ if not base_url:
+ raise DemistoException("Endpoint type is set to 'Custom' but no URL was provided.")
+ url = base_url
+ else:
+ endpoint = MICROSOFT_365_DEFENDER_TYPE.get(endpoint_type, 'com')
+ url = MICROSOFT_365_DEFENDER_API_ENDPOINTS.get(endpoint, 'https://login.windows.net')
+ return url
+
+
+ class MicrosoftClient(BaseClient):
+ def __init__(self, tenant_id: str = '',
+ auth_id: str = '',
+ enc_key: str | None = '',
+ token_retrieval_url: str = '{endpoint}/{tenant_id}/oauth2/v2.0/token',
+ app_name: str = '',
+ refresh_token: str = '',
+ auth_code: str = '',
+ scope: str = '{graph_endpoint}/.default',
+ grant_type: str = CLIENT_CREDENTIALS,
+ redirect_uri: str = 'https://localhost/myapp',
+ resource: str | None = '',
+ multi_resource: bool = False,
+ resources: list[str] = None,
+ verify: bool = True,
+ self_deployed: bool = False,
+ timeout: int | None = None,
+ azure_ad_endpoint: str = '{endpoint}',
+ azure_cloud: AzureCloud = AZURE_WORLDWIDE_CLOUD,
+ endpoint: str = "__NA__", # Deprecated
+ certificate_thumbprint: str | None = None,
+ retry_on_rate_limit: bool = False,
+ private_key: str | None = None,
+ managed_identities_client_id: str | None = None,
+ managed_identities_resource_uri: str | None = None,
+ base_url: str | None = None,
+ command_prefix: str | None = "command_prefix",
+ *args, **kwargs):
+ """
+ Microsoft Client class that implements logic to authenticate with oproxy or self deployed applications.
+ It also provides common logic to handle responses from Microsoft.
+ Args:
+ tenant_id: If self deployed it's the tenant for the app url, otherwise (oproxy) it's the token
+ auth_id: If self deployed it's the client id, otherwise (oproxy) it's the auth id and may also
+ contain the token url
+ enc_key: If self deployed it's the client secret, otherwise (oproxy) it's the encryption key
+ refresh_token: The current used refresh token.
+ scope: The scope of the application (only if self deployed)
+ resource: The resource of the application (only if self deployed)
+ multi_resource: Where or not module uses a multiple resources (self-deployed, auth_code grant type only)
+ resources: Resources of the application (for multi-resource mode)
+ verify: Demisto insecure parameter
+ self_deployed: Indicates whether the integration mode is self deployed or oproxy
+ timeout: Connection timeout
+ azure_ad_endpoint: Custom endpoint to Azure Active Directory URL
+ azure_cloud: Azure Cloud.
+ certificate_thumbprint: Certificate's thumbprint that's associated to the app
+ private_key: Private key of the certificate
+ managed_identities_client_id: The Azure Managed Identities client id
+ managed_identities_resource_uri: The resource uri to get token for by Azure Managed Identities
+ retry_on_rate_limit: If the http request returns with a 429 - Rate limit reached response,
+ retry the request using a scheduled command.
+ base_url: Optionally override the calculated Azure endpoint, used for self-deployed and backward-compatibility with
+ integration that supported national cloud before the *azure_cloud* parameter.
+ command_prefix: The prefix for all integration commands.
+ """
+ self.command_prefix = command_prefix
+ demisto.debug(f'Initializing MicrosoftClient with: {endpoint=} | {azure_cloud.abbreviation}')
+ if endpoint != "__NA__":
+ # Backward compatible.
+ self.azure_cloud = AZURE_CLOUDS.get(endpoint, AZURE_WORLDWIDE_CLOUD)
+ else:
+ self.azure_cloud = azure_cloud
+
+ super().__init__(*args, verify=verify, base_url=base_url, **kwargs) # type: ignore[misc]
+
+ self.retry_on_rate_limit = retry_on_rate_limit
+ if retry_on_rate_limit and (429 not in self._ok_codes):
+ self._ok_codes = self._ok_codes + (429,)
+ if not self_deployed:
+ auth_id_and_token_retrieval_url = auth_id.split('@')
+ auth_id = auth_id_and_token_retrieval_url[0]
+ if len(auth_id_and_token_retrieval_url) != 2:
+ self.token_retrieval_url = 'https://oproxy.demisto.ninja/obtain-token' # guardrails-disable-line
+ else:
+ self.token_retrieval_url = auth_id_and_token_retrieval_url[1]
+
+ self.app_name = app_name
+ self.auth_id = auth_id
+ self.enc_key = enc_key
+ self.refresh_token = refresh_token
+
+ else:
+ self.token_retrieval_url = token_retrieval_url.format(tenant_id=tenant_id,
+ endpoint=self.azure_cloud.endpoints.active_directory
+ .rstrip("/"))
+ self.client_id = auth_id
+ self.client_secret = enc_key
+ self.auth_code = auth_code
+ self.grant_type = grant_type
+ self.resource = resource
+ self.scope = scope.format(graph_endpoint=self.azure_cloud.endpoints.microsoft_graph_resource_id.rstrip("/"))
+ self.redirect_uri = redirect_uri
+ if certificate_thumbprint and private_key:
+ try:
+ import msal # pylint: disable=E0401
+ self.jwt = msal.oauth2cli.assertion.JwtAssertionCreator(
+ private_key,
+ 'RS256',
+ certificate_thumbprint
+ ).create_normal_assertion(audience=self.token_retrieval_url, issuer=self.client_id)
+ except ModuleNotFoundError:
+ raise DemistoException('Unable to use certificate authentication because `msal` is missing.')
+ else:
+ self.jwt = None
+
+ self.tenant_id = tenant_id
+ self.auth_type = SELF_DEPLOYED_AUTH_TYPE if self_deployed else OPROXY_AUTH_TYPE
+ self.verify = verify
+ self.azure_ad_endpoint = azure_ad_endpoint.format(
+ endpoint=self.azure_cloud.endpoints.active_directory.rstrip("/"))
+ self.timeout = timeout # type: ignore
+
+ self.multi_resource = multi_resource
+ if self.multi_resource:
+ self.resources = resources if resources else []
+ self.resource_to_access_token: dict[str, str] = {}
+
+ # for Azure Managed Identities purpose
+ self.managed_identities_client_id = managed_identities_client_id
+ self.managed_identities_resource_uri = managed_identities_resource_uri
+
+ @staticmethod
+ def is_command_executed_from_integration():
+ ctx = demisto.callingContext.get('context', {})
+ executed_commands = ctx.get('ExecutedCommands', [{'moduleBrand': 'Scripts'}])
+
+ if executed_commands:
+ return executed_commands[0].get('moduleBrand', "") != 'Scripts'
+
+ return True
+
+ def http_request(
+ self, *args, resp_type='json', headers=None,
+ return_empty_response=False, scope: str | None = None,
+ resource: str = '', overwrite_rate_limit_retry=False, **kwargs):
+ """
+ Overrides Base client request function, retrieves and adds to headers access token before sending the request.
+
+ Args:
+ resp_type: Type of response to return. will be ignored if `return_empty_response` is True.
+ headers: Headers to add to the request.
+ return_empty_response: Return the response itself if the return_code is 206.
+ scope: A scope to request. Currently, will work only with self-deployed app.
+ resource (str): The resource identifier for which the generated token will have access to.
+ overwrite_rate_limit_retry : Skip rate limit retry
+ Returns:
+ Response from api according to resp_type. The default is `json` (dict or list).
+ """
+ if 'ok_codes' not in kwargs and not self._ok_codes:
+ kwargs['ok_codes'] = (200, 201, 202, 204, 206, 404)
+ token = self.get_access_token(resource=resource, scope=scope)
+ default_headers = {
+ 'Authorization': f'Bearer {token}',
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ }
+
+ if headers:
+ default_headers |= headers
+
+ if self.timeout:
+ kwargs['timeout'] = self.timeout
+
+ should_http_retry_on_rate_limit = self.retry_on_rate_limit and not overwrite_rate_limit_retry
+ if should_http_retry_on_rate_limit and not kwargs.get('error_handler'):
+ kwargs['error_handler'] = self.handle_error_with_metrics
+
+ response = super()._http_request( # type: ignore[misc]
+ *args, resp_type="response", headers=default_headers, **kwargs)
+
+ if should_http_retry_on_rate_limit and MicrosoftClient.is_command_executed_from_integration():
+ MicrosoftClient.create_api_metrics(response.status_code)
+ # 206 indicates Partial Content, reason will be in the warning header.
+ # In that case, logs with the warning header will be written.
+ if response.status_code == 206:
+ demisto.debug(str(response.headers))
+ is_response_empty_and_successful = (response.status_code == 204)
+ if is_response_empty_and_successful and return_empty_response:
+ return response
+
+ # Handle 404 errors instead of raising them as exceptions:
+ if response.status_code == 404:
+ try:
+ error_message = response.json()
+ except Exception:
+ error_message = 'Not Found - 404 Response'
+ raise NotFoundError(error_message)
+
+ if should_http_retry_on_rate_limit and response.status_code == 429 and is_demisto_version_ge('6.2.0'):
+ command_args = demisto.args()
+ ran_once_flag = command_args.get('ran_once_flag')
+ demisto.info(f'429 MS rate limit for command {demisto.command()}, where ran_once_flag is {ran_once_flag}')
+ # We want to retry on rate limit only once
+ if ran_once_flag:
+ try:
+ error_message = response.json()
+ except Exception:
+ error_message = 'Rate limit reached on retry - 429 Response'
+ demisto.info(f'Error in retry for MS rate limit - {error_message}')
+ raise DemistoException(error_message)
+
+ else:
+ demisto.info(f'Scheduling command {demisto.command()}')
+ command_args['ran_once_flag'] = True
+ return_results(MicrosoftClient.run_retry_on_rate_limit(command_args))
+ sys.exit(0)
+
+ try:
+ if resp_type == 'json':
+ return response.json()
+ if resp_type == 'text':
+ return response.text
+ if resp_type == 'content':
+ return response.content
+ if resp_type == 'xml':
+ try:
+ import defusedxml.ElementTree as defused_ET
+ defused_ET.fromstring(response.text)
+ except ImportError:
+ demisto.debug('defused_ET is not supported, using ET instead.')
+ ET.fromstring(response.text)
+ return response
+ except ValueError as exception:
+ raise DemistoException(f'Failed to parse json object from response: {response.content}', exception)
+
+ def get_access_token(self, resource: str = '', scope: str | None = None) -> str:
+ """
+ Obtains access and refresh token from oproxy server or just a token from a self deployed app.
+ Access token is used and stored in the integration context
+ until expiration time. After expiration, new refresh token and access token are obtained and stored in the
+ integration context.
+
+ Args:
+ resource: The resource identifier for which the generated token will have access to.
+ scope: A scope to get instead of the default on the API.
+
+ Returns:
+ str: Access token that will be added to authorization header.
+ """
+ integration_context = get_integration_context()
+ refresh_token = integration_context.get('current_refresh_token', '')
+ # Set keywords. Default without the scope prefix.
+ access_token_keyword = f'{scope}_access_token' if scope else 'access_token'
+ valid_until_keyword = f'{scope}_valid_until' if scope else 'valid_until'
+
+ access_token = integration_context.get(resource) if self.multi_resource else integration_context.get(access_token_keyword)
+
+ valid_until = integration_context.get(valid_until_keyword)
+
+ if access_token and valid_until and self.epoch_seconds() < valid_until:
+ return access_token
+
+ if self.auth_type == OPROXY_AUTH_TYPE:
+ if self.multi_resource:
+ expires_in = None
+ for resource_str in self.resources:
+ access_token, current_expires_in, refresh_token = self._oproxy_authorize(resource_str)
+ self.resource_to_access_token[resource_str] = access_token
+ self.refresh_token = refresh_token
+ expires_in = current_expires_in if expires_in is None else \
+ min(expires_in, current_expires_in) # type: ignore[call-overload]
+ if expires_in is None:
+ raise DemistoException("No resource was provided to get access token from")
+ else:
+ access_token, expires_in, refresh_token = self._oproxy_authorize(scope=scope)
+
+ else:
+ access_token, expires_in, refresh_token = self._get_self_deployed_token(
+ refresh_token, scope, integration_context)
+ time_now = self.epoch_seconds()
+ time_buffer = 5 # seconds by which to shorten the validity period
+ if expires_in - time_buffer > 0:
+ # err on the side of caution with a slightly shorter access token validity period
+ expires_in = expires_in - time_buffer
+ valid_until = time_now + expires_in
+ integration_context.update({
+ access_token_keyword: access_token,
+ valid_until_keyword: valid_until,
+ 'current_refresh_token': refresh_token
+ })
+
+ # Add resource access token mapping
+ if self.multi_resource:
+ integration_context.update(self.resource_to_access_token)
+
+ set_integration_context(integration_context)
+ demisto.debug('Set integration context successfully.')
+
+ if self.multi_resource:
+ return self.resource_to_access_token[resource]
+
+ return access_token
+
+ def _raise_authentication_error(self, oproxy_response: requests.Response):
+ """
+ Raises an exception for authentication error with the Oproxy server.
+ Args:
+ oproxy_response: Raw response from the Oproxy server to parse.
+ """
+ msg = 'Error in Microsoft authorization.'
+ try:
+ demisto.info(
+ f'Authentication failure from server: {oproxy_response.status_code} {oproxy_response.reason} '
+ f'{oproxy_response.text}'
+ )
+ msg += f" Status: {oproxy_response.status_code},"
+ search_microsoft_response = re.search(r'{.*}', oproxy_response.text)
+ microsoft_response = self.extract_microsoft_error(json.loads(search_microsoft_response.group())) \
+ if search_microsoft_response else ""
+ err_str = microsoft_response or oproxy_response.text
+ if err_str:
+ msg += f' body: {err_str}'
+ err_response = oproxy_response.json()
+ server_msg = err_response.get('message', '') or f'{err_response.get("title", "")}. {err_response.get("detail", "")}'
+ if server_msg:
+ msg += f' Server message: {server_msg}'
+ except Exception as ex:
+ demisto.error(f'Failed parsing error response - Exception: {ex}')
+ raise Exception(msg)
+
+ def _oproxy_authorize_build_request(self, headers: dict[str, str], content: str,
+ scope: str | None = None, resource: str = ''
+ ) -> requests.Response:
+ """
+ Build the Post request sent to the Oproxy server.
+ Args:
+ headers: The headers of the request.
+ content: The content for the request (usually contains the refresh token).
+ scope: A scope to add to the request. Do not use it.
+ resource: Resource to get.
+
+ Returns: The response from the Oproxy server.
+
+ """
+ return requests.post(
+ self.token_retrieval_url,
+ headers=headers,
+ json={
+ 'app_name': self.app_name,
+ 'registration_id': self.auth_id,
+ 'encrypted_token': self.get_encrypted(content, self.enc_key),
+ 'scope': scope,
+ 'resource': resource
+ },
+ verify=self.verify
+ )
+
+ def _oproxy_authorize(self, resource: str = '', scope: str | None = None) -> tuple[str, int, str]:
+ """
+ Gets a token by authorizing with oproxy.
+ Args:
+ scope: A scope to add to the request. Do not use it.
+ resource: Resource to get.
+ Returns:
+ tuple: An access token, its expiry and refresh token.
+ """
+ content = self.refresh_token or self.tenant_id
+ headers = self._add_info_headers()
+ context = get_integration_context()
+ next_request_time = context.get("next_request_time", 0.0)
+ delay_request_counter = min(int(context.get('delay_request_counter', 1)), MAX_DELAY_REQUEST_COUNTER)
+
+ should_delay_request(next_request_time)
+ oproxy_response = self._oproxy_authorize_build_request(headers, content, scope, resource)
+
+ if not oproxy_response.ok:
+ next_request_time = calculate_next_request_time(delay_request_counter=delay_request_counter)
+ set_retry_mechanism_arguments(next_request_time=next_request_time, delay_request_counter=delay_request_counter,
+ context=context)
+ self._raise_authentication_error(oproxy_response)
+
+ # In case of success, reset the retry mechanism arguments.
+ set_retry_mechanism_arguments(context=context)
+ # Oproxy authentication succeeded
+ try:
+ gcloud_function_exec_id = oproxy_response.headers.get('Function-Execution-Id')
+ demisto.info(f'Google Cloud Function Execution ID: {gcloud_function_exec_id}')
+ parsed_response = oproxy_response.json()
+ except ValueError:
+ raise Exception(
+ 'There was a problem in retrieving an updated access token.\n'
+ 'The response from the Oproxy server did not contain the expected content.'
+ )
+
+ return (parsed_response.get('access_token', ''), parsed_response.get('expires_in', 3595),
+ parsed_response.get('refresh_token', ''))
+
+ def _get_self_deployed_token(self,
+ refresh_token: str = '',
+ scope: str | None = None,
+ integration_context: dict | None = None
+ ) -> tuple[str, int, str]:
+ if self.managed_identities_client_id:
+
+ if not self.multi_resource:
+ return self._get_managed_identities_token()
+
+ expires_in = -1 # init variable as an int
+ for resource in self.resources:
+ access_token, expires_in, refresh_token = self._get_managed_identities_token(resource=resource)
+ self.resource_to_access_token[resource] = access_token
+ return '', expires_in, refresh_token
+
+ if self.grant_type == AUTHORIZATION_CODE:
+ if not self.multi_resource:
+ return self._get_self_deployed_token_auth_code(refresh_token, scope=scope)
+ expires_in = -1 # init variable as an int
+ for resource in self.resources:
+ access_token, expires_in, refresh_token = self._get_self_deployed_token_auth_code(refresh_token,
+ resource)
+ self.resource_to_access_token[resource] = access_token
+
+ return '', expires_in, refresh_token
+ elif self.grant_type == DEVICE_CODE:
+ return self._get_token_device_code(refresh_token, scope, integration_context)
+ else:
+ # by default, grant_type is CLIENT_CREDENTIALS
+ if self.multi_resource:
+ expires_in = -1 # init variable as an int
+ for resource in self.resources:
+ access_token, expires_in, refresh_token = self._get_self_deployed_token_client_credentials(
+ resource=resource)
+ self.resource_to_access_token[resource] = access_token
+ return '', expires_in, refresh_token
+ return self._get_self_deployed_token_client_credentials(scope=scope)
+
+ def _get_self_deployed_token_client_credentials(self, scope: str | None = None,
+ resource: str | None = None) -> tuple[str, int, str]:
+ """
+ Gets a token by authorizing a self deployed Azure application in client credentials grant type.
+
+ Args:
+ scope: A scope to add to the headers. Else will get self.scope.
+ resource: A resource to add to the headers. Else will get self.resource.
+ Returns:
+ tuple: An access token and its expiry.
+ """
+ data = {
+ 'client_id': self.client_id,
+ 'client_secret': self.client_secret,
+ 'grant_type': CLIENT_CREDENTIALS
+ }
+
+ if self.jwt:
+ data.pop('client_secret', None)
+ data['client_assertion_type'] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
+ data['client_assertion'] = self.jwt
+
+ # Set scope.
+ if self.scope or scope:
+ data['scope'] = scope or self.scope
+
+ if self.resource or resource:
+ data['resource'] = resource or self.resource # type: ignore
+
+ response_json: dict = {}
+ try:
+ response = requests.post(self.token_retrieval_url, data, verify=self.verify)
+ if response.status_code not in {200, 201}:
+ return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
+ f' body: {self.error_parser(response)}')
+ response_json = response.json()
+ except Exception as e:
+ return_error(f'Error in Microsoft authorization: {str(e)}')
+
+ access_token = response_json.get('access_token', '')
+ expires_in = int(response_json.get('expires_in', 3595))
+
+ return access_token, expires_in, ''
+
+ def _get_self_deployed_token_auth_code(
+ self, refresh_token: str = '', resource: str = '', scope: str | None = None) -> tuple[str, int, str]:
+ """
+ Gets a token by authorizing a self deployed Azure application.
+ Returns:
+ tuple: An access token, its expiry and refresh token.
+ """
+ data = assign_params(
+ client_id=self.client_id,
+ client_secret=self.client_secret,
+ resource=resource if resource else self.resource,
+ redirect_uri=self.redirect_uri
+ )
+
+ if self.jwt:
+ data.pop('client_secret', None)
+ data['client_assertion_type'] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
+ data['client_assertion'] = self.jwt
+
+ if scope:
+ data['scope'] = scope
+
+ refresh_token = refresh_token or self._get_refresh_token_from_auth_code_param()
+ if refresh_token:
+ data['grant_type'] = REFRESH_TOKEN
+ data['refresh_token'] = refresh_token
+ else:
+ if SESSION_STATE in self.auth_code:
+ raise ValueError('Malformed auth_code parameter: Please copy the auth code from the redirected uri '
+ 'without any additional info and without the "session_state" query parameter.')
+ data['grant_type'] = AUTHORIZATION_CODE
+ data['code'] = self.auth_code
+
+ response_json: dict = {}
+ try:
+ response = requests.post(self.token_retrieval_url, data, verify=self.verify)
+ if response.status_code not in {200, 201}:
+ return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
+ f' body: {self.error_parser(response)}')
+ response_json = response.json()
+ except Exception as e:
+ return_error(f'Error in Microsoft authorization: {str(e)}')
+
+ access_token = response_json.get('access_token', '')
+ expires_in = int(response_json.get('expires_in', 3595))
+ refresh_token = response_json.get('refresh_token', '')
+
+ return access_token, expires_in, refresh_token
+
+ def _get_managed_identities_token(self, resource=None):
+ """
+ Gets a token based on the Azure Managed Identities mechanism
+ in case user was configured the Azure VM and the other Azure resource correctly
+ """
+ try:
+ # system assigned are restricted to one per resource and is tied to the lifecycle of the Azure resource
+ # see https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
+ use_system_assigned = (self.managed_identities_client_id == MANAGED_IDENTITIES_SYSTEM_ASSIGNED)
+ resource = resource or self.managed_identities_resource_uri
+
+ demisto.debug('try to get Managed Identities token')
+
+ params = {'resource': resource}
+ if not use_system_assigned:
+ params['client_id'] = self.managed_identities_client_id
+
+ response_json = requests.get(MANAGED_IDENTITIES_TOKEN_URL, params=params, headers={'Metadata': 'True'}).json()
+ access_token = response_json.get('access_token')
+ expires_in = int(response_json.get('expires_in', 3595))
+ if access_token:
+ return access_token, expires_in, ''
+
+ err = response_json.get('error_description')
+ except Exception as e:
+ err = f'{str(e)}'
+
+ return_error(f'Error in Microsoft authorization with Azure Managed Identities: {err}')
+ return None
+
+ def _get_token_device_code(
+ self, refresh_token: str = '', scope: str | None = None, integration_context: dict | None = None
+ ) -> tuple[str, int, str]:
+ """
+ Gets a token by authorizing a self deployed Azure application.
+
+ Returns:
+ tuple: An access token, its expiry and refresh token.
+ """
+ data = {
+ 'client_id': self.client_id,
+ 'scope': scope
+ }
+
+ if refresh_token:
+ data['grant_type'] = REFRESH_TOKEN
+ data['refresh_token'] = refresh_token
+ else:
+ data['grant_type'] = DEVICE_CODE
+ if integration_context:
+ data['code'] = integration_context.get('device_code')
+
+ response_json: dict = {}
+ try:
+ response = requests.post(self.token_retrieval_url, data, verify=self.verify)
+ if response.status_code not in {200, 201}:
+ return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
+ f' body: {self.error_parser(response)}')
+ response_json = response.json()
+ except Exception as e:
+ return_error(f'Error in Microsoft authorization: {str(e)}')
+
+ access_token = response_json.get('access_token', '')
+ expires_in = int(response_json.get('expires_in', 3595))
+ refresh_token = response_json.get('refresh_token', '')
+
+ return access_token, expires_in, refresh_token
+
+ def _get_refresh_token_from_auth_code_param(self) -> str:
+ refresh_prefix = "refresh_token:"
+ if self.auth_code.startswith(refresh_prefix): # for testing we allow setting the refresh token directly
+ demisto.debug("Using refresh token set as auth_code")
+ return self.auth_code[len(refresh_prefix):]
+ return ''
+
+ @staticmethod
+ def run_retry_on_rate_limit(args_for_next_run: dict):
+ return CommandResults(readable_output="Rate limit reached, rerunning the command in 1 min",
+ scheduled_command=ScheduledCommand(command=demisto.command(), next_run_in_seconds=60,
+ args=args_for_next_run, timeout_in_seconds=900))
+
+ def handle_error_with_metrics(self, res):
+ MicrosoftClient.create_api_metrics(res.status_code)
+ self.client_error_handler(res)
+
+ @staticmethod
+ def create_api_metrics(status_code):
+ execution_metrics = ExecutionMetrics()
+ ok_codes = (200, 201, 202, 204, 206)
+
+ if not execution_metrics.is_supported() or demisto.command() in ['test-module', 'fetch-incidents']:
+ return
+ if status_code == 429:
+ execution_metrics.quota_error += 1
+ elif status_code in ok_codes:
+ execution_metrics.success += 1
+ else:
+ execution_metrics.general_error += 1
+ return_results(execution_metrics.metrics)
+
+ def error_parser(self, error: requests.Response) -> str:
+ """
+
+ Args:
+ error (requests.Response): response with error
+
+ Returns:
+ str: string of error
+
+ """
+ try:
+ response = error.json()
+ demisto.error(str(response))
+ err_str = self.extract_microsoft_error(response)
+ if err_str:
+ return err_str
+ # If no error message
+ raise ValueError
+ except ValueError:
+ return error.text
+
+ def extract_microsoft_error(self, response: dict) -> str | None:
+ """
+ Extracts the Microsoft error message from the JSON response.
+
+ Args:
+ response (dict): JSON response received from the microsoft server.
+
+ Returns:
+ str or None: Extracted Microsoft error message if found, otherwise returns None.
+ """
+ inner_error = response.get('error', {})
+ error_codes = response.get("error_codes", [""])
+ err_desc = response.get('error_description', '')
+
+ if isinstance(inner_error, dict):
+ err_str = f"{inner_error.get('code')}: {inner_error.get('message')}"
+ else:
+ err_str = inner_error
+ re_search = re.search(REGEX_SEARCH_ERROR_DESC, err_desc)
+ err_str += f". \n{re_search['desc']}" if re_search else ""
+
+ if err_str:
+ if set(error_codes).issubset(TOKEN_EXPIRED_ERROR_CODES):
+ err_str += f"\nYou can run the ***{self.command_prefix}-auth-reset*** command " \
+ f"to reset the authentication process."
+ return err_str
+ # If no error message
+ return None
+
+ @staticmethod
+ def epoch_seconds(d: datetime = None) -> int:
+ """
+ Return the number of seconds for given date. If no date, return current.
+
+ Args:
+ d (datetime): timestamp
+ Returns:
+ int: timestamp in epoch
+ """
+ if not d:
+ d = MicrosoftClient._get_utcnow()
+ return int((d - MicrosoftClient._get_utc_from_timestamp(0)).total_seconds())
+
+ @staticmethod
+ def _get_utcnow() -> datetime:
+ return datetime.utcnow()
+
+ @staticmethod
+ def _get_utc_from_timestamp(_time) -> datetime:
+ return datetime.utcfromtimestamp(_time)
+
+ @staticmethod
+ def get_encrypted(content: str, key: str | None) -> str:
+ """
+ Encrypts content with encryption key.
+ Args:
+ content: Content to encrypt
+ key: encryption key from oproxy
+
+ Returns:
+ timestamp: Encrypted content
+ """
+
+ def create_nonce():
+ return os.urandom(12)
+
+ def encrypt(string, enc_key):
+ """
+ Encrypts string input with encryption key.
+ Args:
+ string: String to encrypt
+ enc_key: Encryption key
+
+ Returns:
+ bytes: Encrypted value
+ """
+ # String to bytes
+ try:
+ enc_key = base64.b64decode(enc_key)
+ except Exception as err:
+ return_error(f"Error in Microsoft authorization: {str(err)}"
+ f" Please check authentication related parameters.", error=traceback.format_exc())
+
+ # Create key
+ aes_gcm = AESGCM(enc_key)
+ # Create nonce
+ nonce = create_nonce()
+ # Create ciphered data
+ data = string.encode()
+ ct = aes_gcm.encrypt(nonce, data, None)
+ return base64.b64encode(nonce + ct)
+
+ now = MicrosoftClient.epoch_seconds()
+ encrypted = encrypt(f'{now}:{content}', key).decode('utf-8')
+ return encrypted
+
+ @staticmethod
+ def _add_info_headers() -> dict[str, str]:
+ # pylint: disable=no-member
+ headers = {}
+ try:
+ headers = get_x_content_info_headers()
+ except Exception as e:
+ demisto.error(f'Failed getting integration info: {str(e)}')
+
+ return headers
+
+ def device_auth_request(self) -> dict:
+ response_json = {}
+ try:
+ if self.tenant_id:
+ url = f'{self.azure_ad_endpoint}/{self.tenant_id}/oauth2/v2.0/devicecode'
+ else:
+ url = f'{self.azure_ad_endpoint}/organizations/oauth2/v2.0/devicecode',
+ response = requests.post(
+ url=url,
+ data={
+ 'client_id': self.client_id,
+ 'scope': self.scope
+ },
+ verify=self.verify
+ )
+ if not response.ok:
+ if "National Cloud" in self.error_parser(response):
+ return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
+ f' The tenant is not supported by GCC-High. body: {self.error_parser(response)}')
+ return_error(f'Error in Microsoft authorization. Status: {response.status_code},'
+ f' body: {self.error_parser(response)}')
+ response_json = response.json()
+ except Exception as e:
+ return_error(f'Error in Microsoft authorization: {str(e)}')
+ set_integration_context({'device_code': response_json.get('device_code')})
+ return response_json
+
+ def start_auth(self, complete_command: str) -> str:
+ response = self.device_auth_request()
+ message = response.get('message', '')
+ re_search = re.search(REGEX_SEARCH_URL, message)
+ url = re_search['url'] if re_search else None
+ user_code = response.get('user_code')
+
+ return f"""### Authorization instructions
+ 1. To sign in, use a web browser to open the page [{url}]({url})
+
+ and enter the code **{user_code}** to authenticate.
+
+ 2. Run the **{complete_command}** command in the War Room."""
+
+
+
+ class NotFoundError(Exception):
+ """Exception raised for 404 - Not Found errors.
+
+ Attributes:
+ message -- explanation of the error
+ """
+
+ def __init__(self, message):
+ self.message = message
+
+
+ def calculate_next_request_time(delay_request_counter: int) -> float:
+ """
+ Calculates the next request time based on the delay_request_counter.
+ This is an implication of the Moderate Retry Mechanism for the Oproxy requests.
+ """
+ # The max delay time should be limited to ~60 sec.
+ next_request_time = get_current_time() + timedelta(seconds=(2 ** delay_request_counter))
+ return next_request_time.timestamp()
+
+
+ def set_retry_mechanism_arguments(context: dict, next_request_time: float = 0.0, delay_request_counter: int = 1):
+ """
+ Sets the next_request_time in the integration context.
+ This is an implication of the Moderate Retry Mechanism for the Oproxy requests.
+ """
+ context = context or {}
+ next_counter = delay_request_counter + 1
+
+ context['next_request_time'] = next_request_time
+ context['delay_request_counter'] = next_counter
+ # Should reset the context retry arguments.
+ if next_request_time == 0.0:
+ context['delay_request_counter'] = 1
+ set_integration_context(context)
+
+
+ def should_delay_request(next_request_time: float):
+ """
+ Checks if the request should be delayed based on context variables.
+ This is an implication of the Moderate Retry Mechanism for the Oproxy requests.
+ """
+ now = get_current_time().timestamp()
+
+ # If the next_request_time is 0 or negative, it means that the request should not be delayed because no error has occurred.
+ if next_request_time <= 0.0:
+ return
+ # Checking if the next_request_time has passed.
+ if now >= next_request_time:
+ return
+ raise Exception(f"The request will be delayed until {datetime.fromtimestamp(next_request_time)}")
+
+
+ def get_azure_managed_identities_client_id(params: dict) -> str | None:
+ """
+ Extract the Azure Managed Identities from the demisto params
+
+ Args:
+ params (dict): the demisto params
+
+ Returns:
+ Optional[str]: if the use_managed_identities are True
+ the managed_identities_client_id or MANAGED_IDENTITIES_SYSTEM_ASSIGNED
+ will return, otherwise - None
+
+ """
+ auth_type = params.get('auth_type') or params.get('authentication_type')
+ if params and (argToBoolean(params.get('use_managed_identities') or auth_type == 'Azure Managed Identities')):
+ client_id = params.get('managed_identities_client_id', {}).get('password')
+ return client_id or MANAGED_IDENTITIES_SYSTEM_ASSIGNED
+ return None
+
+
+ def generate_login_url(client: MicrosoftClient,
+ login_url: str = "https://login.microsoftonline.com/") -> CommandResults:
+ missing = []
+ if not client.client_id:
+ missing.append("client_id")
+ if not client.tenant_id:
+ missing.append("tenant_id")
+ if not client.scope:
+ missing.append("scope")
+ if not client.redirect_uri:
+ missing.append("redirect_uri")
+ if missing:
+ raise DemistoException("Please make sure you entered the Authorization configuration correctly. "
+ f"Missing:{','.join(missing)}")
+
+ login_url = urljoin(login_url, f'{client.tenant_id}/oauth2/v2.0/authorize?'
+ f'response_type=code&scope=offline_access%20{client.scope.replace(" ", "%20")}'
+ f'&client_id={client.client_id}&redirect_uri={client.redirect_uri}')
+
+ result_msg = f"""### Authorization instructions
+ 1. Click on the [login URL]({login_url}) to sign in and grant Cortex XSOAR permissions for your Azure Service Management.
+
+ You will be automatically redirected to a link with the following structure:
+
+ ```REDIRECT_URI?code=AUTH_CODE&session_state=SESSION_STATE```
+
+ 2. Copy the `AUTH_CODE` (without the `code=` prefix, and the `session_state` parameter)
+
+ and paste it in your instance configuration under the **Authorization code** parameter.
+ """
+ return CommandResults(readable_output=result_msg)
+
+
+ def get_from_args_or_params(args: dict[str, Any], params: dict[str, Any], key: str) -> Any:
+ """
+ Get a value from args or params, if the value is provided in both args and params, the value from args will be used.
+ if the value is not provided in args or params, an exception will be raised.
+ this function is used in commands that have a value that can be provided in the instance parameters or in the command,
+ e.g in azure-key-vault-delete 'subscription_id' can be provided in the instance parameters or in the command.
+ Args:
+ args (Dict[str, Any]): Demisto args.
+ params (Dict[str, Any]): Demisto params
+ key (str): Key to get.
+ """
+ if value := args.get(key, params.get(key)):
+ return value
+ else:
+ raise Exception(f'No {key} was provided. Please provide a {key} either in the \
+ instance configuration or as a command argument.')
+
+
+
+ def azure_tag_formatter(arg):
+ """
+ Formats a tag argument to the Azure format
+ Args:
+ arg (str): Tag argument as string
+ Returns:
+ str: Tag argument in Azure format
+ """
+ try:
+ tag = json.loads(arg)
+ tag_name = next(iter(tag))
+ tag_value = tag[tag_name]
+ return f"tagName eq '{tag_name}' and tagValue eq '{tag_value}'"
+ except Exception as e:
+ raise Exception(
+ """Invalid tag format, please use the following format: '{"key_name":"value_name"}'""",
+ e,
+ ) from e
+
+
+ def reset_auth() -> CommandResults:
+ """
+ This command resets the integration context.
+ After running the command, a new token/auth-code will need to be given by the user to regenerate the access token.
+ :return: Message about resetting the authorization process.
+ """
+ demisto.debug(f"Reset integration-context, before resetting {get_integration_context()=}")
+ set_integration_context({})
+ return CommandResults(readable_output='Authorization was reset successfully. Please regenerate the credentials, '
+ 'and then click **Test** to validate the credentials and connection.')
+
+ register_module_line('MicrosoftApiModule', 'end', __line__(), wrapper=1)
+
+ ### END GENERATED CODE ###
+
+
+ # Disable insecure warnings
+
+ urllib3.disable_warnings()
+
+
+ ''' CONSTANTS '''
+
+
+ DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' # ISO8601 format with UTC, default in XSOAR
+
+
+ MAX_ENTRIES = 100
+
+ TIMEOUT = '30'
+
+ BASE_URL = "https://api.security.microsoft.com"
+
+
+ ''' CLIENT CLASS '''
+
+
+
+ class Client:
+ @logger
+ def __init__(self, app_id: str, verify: bool, proxy: bool, base_url: str = BASE_URL, tenant_id: str = None,
+ enc_key: str = None, client_credentials: bool = False, certificate_thumbprint: Optional[str] = None,
+ private_key: Optional[str] = None,
+ managed_identities_client_id: Optional[str] = None,
+ endpoint: str = 'com',
+ azure_cloud: AzureCloud = AZURE_WORLDWIDE_CLOUD):
+ if app_id and '@' in app_id:
+ app_id, refresh_token = app_id.split('@')
+ integration_context = get_integration_context()
+ integration_context.update(current_refresh_token=refresh_token)
+ set_integration_context(integration_context)
+
+ self.client_credentials = client_credentials
+ client_args = assign_params(
+ base_url=base_url,
+ verify=verify,
+ proxy=proxy,
+ ok_codes=(200, 201, 202, 204),
+ scope=f'offline_access {MICROSOFT_365_DEFENDER_SCOPES.get(endpoint)}/mtp/.default',
+ self_deployed=True, # We always set the self_deployed key as True because when not using a self
+ # deployed machine, the DEVICE_CODE flow should behave somewhat like a self deployed
+ # flow and most of the same arguments should be set, as we're !not! using OProxy.
+
+ auth_id=app_id,
+ grant_type=CLIENT_CREDENTIALS if client_credentials else DEVICE_CODE,
+
+ # used for device code flow
+ resource=MICROSOFT_365_DEFENDER_API_ENDPOINTS.get(endpoint) if not client_credentials else None,
+ token_retrieval_url=f'{MICROSOFT_365_DEFENDER_TOKEN_RETRIEVAL_ENDPOINTS.get(endpoint)}'
+ f'/organizations/oauth2/v2.0/token',
+ # used for client credentials flow
+ tenant_id=tenant_id,
+ enc_key=enc_key,
+ certificate_thumbprint=certificate_thumbprint,
+ private_key=private_key,
+ managed_identities_client_id=managed_identities_client_id,
+ managed_identities_resource_uri=Resources.security,
+ endpoint=endpoint,
+ azure_cloud=azure_cloud,
+ command_prefix="microsoft-365-defender",
+ )
+ self.ms_client = MicrosoftClient(**client_args) # type: ignore
+
+ @logger
+ def incidents_list(self, timeout: int, limit: int = MAX_ENTRIES, status: Optional[str] = None,
+ assigned_to: Optional[str] = None, from_date: Optional[datetime] = None,
+ skip: Optional[int] = None, odata: Optional[dict] = None) -> dict:
+ """
+ GET request from the client using OData operators:
+ - $top: how many incidents to receive, maximum value is 100
+ - $filter: OData query to filter the the list on the properties:
+ lastUpdateTime, createdTime, status, and assignedTo
+ Args:
+ limit (int): how many incidents to receive, the maximum value is 100
+ status (str): filter list to contain only incidents with the given status (Active, Resolved, or Redirected)
+ assigned_to (str): Owner of the incident, or None if no owner is assigned
+ timeout (int): The amount of time (in seconds) that a request will wait for a client to
+ establish a connection to a remote machine before a timeout occurs.
+ from_date (datetime): get incident with creation date more recent than from_date
+ skip (int): how many entries to skip
+ odata (dict): alternative method for listing incidents, accepts dictionary of URI parameters
+
+ Returns (Dict): request results as dict:
+ { '@odata.context',
+ 'value': list of incidents,
+ '@odata.nextLink'
+ }
+
+ """
+ params = {}
+
+ if odata:
+ params = odata
+ else:
+ filter_query = ''
+ params = {'$top': limit}
+ if status:
+ filter_query += 'status eq ' + "'" + status + "'"
+
+ if assigned_to:
+ filter_query += ' and ' if filter_query else ''
+ filter_query += f"assignedTo eq '{assigned_to}'"
+
+ # fetch incidents
+ if from_date:
+ filter_query += ' and ' if filter_query else ''
+ filter_query += f"createdTime gt {from_date}"
+
+ if filter_query:
+ params['$filter'] = filter_query # type: ignore
+
+ if skip:
+ params['$skip'] = skip
+
+ return self.ms_client.http_request(method='GET', url_suffix='api/incidents', timeout=timeout,
+ params=params)
+
+ @logger
+ def update_incident(self, incident_id: int, status: Optional[str], assigned_to: Optional[str],
+ classification: Optional[str],
+ determination: Optional[str], tags: Optional[List[str]], timeout: int, comment: str) -> dict:
+ """
+ PATCH request to update single incident.
+ Args:
+ incident_id (int): incident's id
+ status (str): Specifies the current status of the alert. Possible values are: (Active, Resolved or Redirected)
+ assigned_to (str): Owner of the incident.
+ classification (str): Specification of the alert. Possible values are: Unknown, FalsePositive, TruePositive.
+ determination (str): Specifies the determination of the alert. Possible values are: NotAvailable, Apt,
+ Malware, SecurityPersonnel, SecurityTesting, UnwantedSoftware, Other.
+ tags (list): Custom tags associated with an incident. Separated by commas without spaces (CSV)
+ for example: tag1,tag2,tag3.
+ timeout (int): The amount of time (in seconds) that a request will wait for a client to
+ establish a connection to a remote machine before a timeout occurs.
+ comment (str): Comment to be added to the incident
+ Returns( Dict): request results as dict:
+ { '@odata.context',
+ 'value': updated incident,
+ }
+
+ """
+ body = assign_params(status=status, assignedTo=assigned_to, classification=classification,
+ determination=determination, tags=tags, comment=comment)
+ if assigned_to == "":
+ body['assignedTo'] = ""
+ updated_incident = self.ms_client.http_request(method='PATCH', url_suffix=f'api/incidents/{incident_id}',
+ json_data=body, timeout=timeout)
+ return updated_incident
+
+ @logger
+ def get_incident(self, incident_id: int, timeout: int) -> dict:
+ """
+ GET request to get single incident.
+ Args:
+ incident_id (int): incident's id
+ timeout (int): waiting time for command execution
+
+
+ Returns( Dict): request results as dict:
+ { '@odata.context',
+ 'value': updated incident,
+ }
+
+ """
+ incident = self.ms_client.http_request(
+ method='GET', url_suffix=f'api/incidents/{incident_id}', timeout=timeout)
+ return incident
+
+ @logger
+ def advanced_hunting(self, query: str, timeout: int):
+ """
+ POST request to the advanced hunting API:
+ Args:
+ query (str): query advanced hunting query language
+ timeout (int): The amount of time (in seconds) that a request will wait for a client to
+ establish a connection to a remote machine before a timeout occurs.
+
+ Returns:
+ The response object contains three top-level properties:
+
+ Stats - A dictionary of query performance statistics.
+ Schema - The schema of the response, a list of Name-Type pairs for each column.
+ Results - A list of advanced hunting events.
+ """
+ return self.ms_client.http_request(method='POST', url_suffix='api/advancedhunting/run',
+ json_data={"Query": query}, timeout=timeout)
+
+
+ @logger
+
+ def start_auth(client: Client) -> CommandResults:
+ result = client.ms_client.start_auth('!microsoft-365-defender-auth-complete')
+ return CommandResults(readable_output=result)
+
+
+ @logger
+
+ def complete_auth(client: Client) -> CommandResults:
+ client.ms_client.get_access_token()
+ return CommandResults(readable_output='✅ Authorization completed successfully.')
+
+
+ @logger
+
+ def test_connection(client: Client) -> CommandResults:
+ test_context_for_token(client)
+ client.ms_client.get_access_token() # If fails, MicrosoftApiModule returns an error
+ return CommandResults(readable_output='✅ Success!')
+
+
+ ''' HELPER FUNCTIONS '''
+
+
+
+ def test_context_for_token(client: Client) -> None:
+ """test_context_for_token
+ Checks if the user acquired token via the authentication process.
+ Args:
+ Returns:
+
+ """
+ if client.client_credentials or client.ms_client.managed_identities_client_id:
+ return
+ if not (get_integration_context().get('access_token') or get_integration_context().get('current_refresh_token')):
+ raise DemistoException(
+ "This integration does not have a test module. Please run !microsoft-365-defender-auth-start and "
+ "!microsoft-365-defender-auth-complete and check the connection using !microsoft-365-defender-auth-test")
+
+
+ def test_module(client: Client) -> str:
+ """Tests API connectivity and authentication'
+
+ Returning 'ok' indicates that the integration works like it is supposed to.
+ Connection to the service is successful.
+ Raises exceptions if something goes wrong.
+
+ :type client: ``Client``
+ :param Client: client to use
+
+ :return: 'ok' if test passed.
+ :rtype: ``str``
+ """
+ # This should validate all the inputs given in the integration configuration panel,
+ # either manually or by using an API that uses them.
+ if client.client_credentials:
+ raise DemistoException("When using a self-deployed configuration, run the !microsoft-365-defender-auth-test "
+ "command in order to test the connection")
+
+ test_connection(client)
+
+ return "ok"
+
+
+ def _get_meta_data_for_incident(raw_incident: dict) -> dict:
+ """
+ Calculated metadata for the gicen incident
+ Args:
+ raw_incident (Dict): The incident as received from microsoft 365 defender
+
+ Returns: Dictionary with the calculated data
+
+ """
+ if not raw_incident:
+ raw_incident = {}
+
+ alerts_list = raw_incident.get('alerts', [])
+
+ alerts_status = [alert.get('status') for alert in alerts_list if alert.get('status')]
+ first_activity_list = [alert.get('firstActivity') for alert in alerts_list]
+ last_activity_list = [alert.get('lastActivity') for alert in alerts_list]
+
+ return {
+ 'Categories': [alert.get('category', '') for alert in alerts_list],
+ 'Impacted entities': list({(entity.get('domainName', ''))
+ for alert in alerts_list
+ for entity in alert.get('entities') if entity.get('entityType') == 'User'}),
+ 'Active alerts': f'{alerts_status.count("Active") + alerts_status.count("New")} / {len(alerts_status)}',
+ 'Service sources': list({alert.get('serviceSource', '') for alert in alerts_list}),
+ 'Detection sources': list({alert.get('detectionSource', '') for alert in alerts_list}),
+ 'First activity': str(min(first_activity_list,
+ key=lambda x: dateparser.parse(x))) if alerts_list else '', # type: ignore
+ 'Last activity': str(max(last_activity_list,
+ key=lambda x: dateparser.parse(x))) if alerts_list else '', # type: ignore
+ 'Devices': [{'device name': device.get('deviceDnsName', ''),
+ 'risk level': device.get('riskScore', ''),
+ 'tags': ','.join(device.get('tags', []))
+ } for alert in alerts_list
+ for device in alert.get('devices', [])]
+ }
+
+
+ def convert_incident_to_readable(raw_incident: dict) -> dict:
+ """
+ Converts incident received from microsoft 365 defender to readable format
+ Args:
+ raw_incident (Dict): The incident as received from microsoft 365 defender
+
+ Returns: new dictionary with keys mapping.
+
+ """
+ if not raw_incident:
+ raw_incident = {}
+
+ incident_meta_data = _get_meta_data_for_incident(raw_incident)
+ device_groups = {device.get('device name') for device in incident_meta_data.get('Devices', [])
+ if device.get('device name')}
+ return {
+ 'Incident name': raw_incident.get('incidentName'),
+ 'Tags': ', '.join(raw_incident.get('tags', [])),
+ 'Severity': raw_incident.get('severity'),
+ 'Incident ID': raw_incident.get('incidentId'),
+ # investigation state - relevant only for alerts.
+ 'Categories': ', '.join(set(incident_meta_data.get('Categories', []))),
+ 'Impacted entities': ', '.join(incident_meta_data.get('Impacted entities', [])),
+ 'Active alerts': incident_meta_data.get('Active alerts', []),
+ 'Service sources': ', '.join(incident_meta_data.get('Service sources', [])),
+ 'Detection sources': ', '.join(incident_meta_data.get('Detection sources', [])),
+ # Data sensitivity - is not relevant
+ 'First activity': incident_meta_data.get('First activity', ''),
+ 'Last activity': incident_meta_data.get('Last activity', ''),
+ 'Status': raw_incident.get('status'),
+ 'Assigned to': raw_incident.get('assignedTo', 'Unassigned'),
+ 'Classification': raw_incident.get('classification', 'Not set'),
+ 'Device groups': ', '.join(device_groups),
+ }
+
+
+ ''' COMMAND FUNCTIONS '''
+
+
+
+ @logger
+
+ def microsoft_365_defender_incidents_list_command(client: Client, args: dict) -> CommandResults:
+ """
+ Returns list of the latest incidents in microsoft 365 defender in readable table.
+ The list can be filtered using the following arguments:
+ - limit (int) - number of incidents in the list, integer between 0 to 100.
+ - status (str) - fetch only incidents with the given status.
+ Args:
+ client(Client): Microsoft 365 Defender's client to preform the API calls.
+ args(Dict): Demisto arguments:
+ - limit (int) - integer between 0 to 100
+ - status (str) - get incidents with the given status (Active, Resolved or Redirected)
+ - assigned_to (str) - get incidents assigned to the given user
+ - offset (int) - skip the first N entries of the list
+ - odata (str) - json dictionary containing odata uri parameters
+ Returns: CommandResults
+
+ """
+ limit = arg_to_number(args.get('limit', MAX_ENTRIES), arg_name='limit', required=True)
+ status = args.get('status')
+ assigned_to = args.get('assigned_to')
+ offset = arg_to_number(args.get('offset'))
+ timeout = arg_to_number(args.get('timeout', TIMEOUT))
+ odata = args.get('odata')
+
+ if odata:
+ try:
+ odata = json.loads(odata)
+ except json.JSONDecodeError:
+ return_error(f"Can't parse odata argument as JSON array.\nvalue: {odata}")
+
+ response = client.incidents_list(limit=limit, status=status, assigned_to=assigned_to,
+ skip=offset, timeout=timeout, odata=odata)
+
+ raw_incidents = response.get('value')
+ readable_incidents = [convert_incident_to_readable(incident) for incident in raw_incidents]
+ if readable_incidents:
+ headers = list(readable_incidents[0].keys()) # the table headers are the incident keys.
+ human_readable = tableToMarkdown(name="Incidents:", t=readable_incidents, headers=headers)
+
+ else:
+ human_readable = "No incidents found"
+
+ return CommandResults(outputs_prefix='Microsoft365Defender.Incident', outputs_key_field='incidentId',
+ outputs=raw_incidents, readable_output=human_readable)
+
+
+ @logger
+
+ def microsoft_365_defender_incident_update_command(client: Client, args: dict) -> CommandResults:
+ """
+ Update an incident.
+ Args:
+ client(Client): Microsoft 365 Defender's client to preform the API calls.
+ args(Dict): Demisto arguments:
+ - id (int) - incident's id (required)
+ - status (str) - Specifies the current status of the alert. Possible values are: (Active, Resolved or Redirected)
+ - assigned_to (str) - Owner of the incident.
+ - classification (str) - Specification of the alert. Possible values are: Unknown, FalsePositive, TruePositive.
+ - determination (str) - Specifies the determination of the alert. Possible values are: NotAvailable, Apt,
+ Malware, SecurityPersonnel, SecurityTesting, UnwantedSoftware, Other.
+ - tags - Custom tags associated with an incident. Separated by commas without spaces (CSV)
+ for example: tag1,tag2,tag3.
+
+ Returns: CommandResults
+ """
+ raw_tags = args.get('tags')
+ tags = raw_tags.split(',') if raw_tags else None
+ status = args.get('status')
+ assigned_to = args.get('assigned_to')
+ classification = args.get('classification')
+ determination = args.get('determination')
+ incident_id = arg_to_number(args.get('id'))
+ timeout = arg_to_number(args.get('timeout', TIMEOUT))
+ comment = args.get('comment')
+
+ updated_incident = client.update_incident(incident_id=incident_id, status=status, assigned_to=assigned_to,
+ classification=classification, determination=determination, tags=tags,
+ timeout=timeout, comment=comment)
+ if updated_incident.get('@odata.context'):
+ del updated_incident['@odata.context']
+
+ readable_incident = convert_incident_to_readable(updated_incident)
+ human_readable_table = tableToMarkdown(name=f"Updated incident No. {incident_id}:", t=readable_incident,
+ headers=list(readable_incident.keys()))
+
+ return CommandResults(outputs_prefix='Microsoft365Defender.Incident', outputs_key_field='incidentId',
+ outputs=updated_incident, readable_output=human_readable_table)
+
+
+ @logger
+
+ def microsoft_365_defender_incident_get_command(client: Client, args: dict) -> CommandResults:
+ """
+ Get an incident.
+ Args:
+ client(Client): Microsoft 365 Defender's client to preform the API calls.
+ args(Dict): Demisto arguments:
+ - id (int) - incident's id (required)
+ - timeout (int) - waiting time for command execution
+
+ Returns: CommandResults
+ """
+ incident_id = arg_to_number(args.get('id'))
+ timeout = arg_to_number(args.get('timeout', TIMEOUT))
+
+ incident = client.get_incident(incident_id=incident_id, timeout=timeout)
+ if incident.get('@odata.context'):
+ del incident['@odata.context']
+
+ readable_incident = convert_incident_to_readable(incident)
+ human_readable_table = tableToMarkdown(name=f"Incident No. {incident_id}:", t=readable_incident,
+ headers=list(readable_incident.keys()))
+
+ return CommandResults(outputs_prefix='Microsoft365Defender.Incident', outputs_key_field='incidentId',
+ outputs=incident, readable_output=human_readable_table)
+
+
+ @logger
+
+ def fetch_incidents(client: Client, first_fetch_time: str, fetch_limit: int, timeout: int = None) -> List[dict]:
+ """
+ Uses to fetch incidents into Demisto
+ Documentation: https://xsoar.pan.dev/docs/integrations/fetching-incidents#the-fetch-incidents-command
+
+ Due to API limitations (The incidents are not ordered and there is a limit of 100 incident for each request),
+ We get all the incidents newer than last_run/first_fetch_time and saves them in a queue. The queue is sorted by
+ creation date of each incident.
+
+ As long as the queue has more incidents than fetch_limit the function will return the oldest incidents in the queue.
+ If the queue is smaller than fetch_limit we fetch more incidents with the same logic described above.
+
+
+ Args:
+ client(Client): Microsoft 365 Defender's client to preform the API calls.
+ first_fetch_time(str): From when to fetch if first time, e.g. `3 days`.
+ fetch_limit(int): The number of incidents in each fetch.
+ timeout(int): The time limit in seconds for this function to run.
+ Note: exceeding the time doesnt kill the function just prevents the next api call.
+ Returns:
+ incidents, new last_run
+ """
+ start_time = time.time()
+ test_context_for_token(client)
+
+ last_run_dict = demisto.getLastRun()
+
+ last_run = last_run_dict.get('last_run')
+ if not last_run: # this is the first run
+ first_fetch_date_time = dateparser.parse(first_fetch_time)
+ assert first_fetch_date_time is not None, f'could not parse {first_fetch_time}'
+ last_run = first_fetch_date_time.strftime(DATE_FORMAT)
+
+ # creates incidents queue
+ incidents_queue = last_run_dict.get('incidents_queue', [])
+
+ if len(incidents_queue) < fetch_limit:
+
+ incidents = []
+
+ # The API is limited to MAX_ENTRIES incidents for each requests, if we are trying to get more than MAX_ENTRIES
+ # incident we skip (offset) the number of incidents we already fetched.
+ offset = 0
+
+ # This loop fetches all the incidents that had been created after last_run, due to API limitations the fetching
+ # occurs in batches. If timeout was given, exceeding the time will result an error.
+ # Note: Because the list is cannot be ordered by creation date, reaching timeout will force to re-run this
+ # function from the start.
+
+ while True:
+ time_delta = time.time() - start_time
+ if timeout and time_delta > timeout:
+ raise DemistoException(
+ "Fetch incidents - Time out. Please change first_fetch parameter to be more recent one")
+
+ # HTTP request
+ response = client.incidents_list(from_date=last_run, skip=offset, timeout=timeout)
+ raw_incidents = response.get('value')
+ for incident in raw_incidents:
+ incident.update(_get_meta_data_for_incident(incident))
+
+ incidents += [{
+ "name": f"Microsoft 365 Defender {incident.get('incidentId')}",
+ "occurred": incident.get('createdTime'),
+ "rawJSON": json.dumps(incident)
+ } for incident in raw_incidents]
+
+ # raw_incidents length is less than MAX_ENTRIES than we fetch all the relevant incidents
+ if len(raw_incidents) < int(MAX_ENTRIES):
+ break
+ offset += int(MAX_ENTRIES)
+
+ # sort the incidents by the creation time
+ incidents.sort(key=lambda x: dateparser.parse(x['occurred'])) # type: ignore
+ incidents_queue += incidents
+
+ oldest_incidents = incidents_queue[:fetch_limit]
+ new_last_run = incidents_queue[-1]["occurred"] if oldest_incidents else last_run # newest incident creation time
+ demisto.setLastRun({'last_run': new_last_run,
+ 'incidents_queue': incidents_queue[fetch_limit:]})
+ return oldest_incidents
+
+
+ def _query_set_limit(query: str, limit: int) -> str:
+ """
+ Add limit to given query. If the query has limit, changes it.
+ Args:
+ query: the original query
+ limit: new limit value, if the value is negative return the original query.
+ Returns: query with limit parameters
+ """
+ if limit < 0:
+ return query
+
+ # the query has the structure of "section | section | section ..."
+ query_list = re.split(r'(? CommandResults:
+ """
+ Sends a query for the advanced hunting tool.
+ Args:
+ client(Client): Microsoft 365 Defender's client to preform the API calls.
+ args(Dict): Demisto arguments:
+ - query (str) - The query to run (required)
+ - limit (int) - number of entries in the result, -1 for no limit.
+ Returns:
+
+ """
+ query = args.get('query', '')
+ limit = arg_to_number(args.get('limit', '-1'))
+ timeout = arg_to_number(args.get('timeout', TIMEOUT))
+
+ query = _query_set_limit(query, limit) # type: ignore
+
+ response = client.advanced_hunting(query=query, timeout=timeout)
+ results = response.get('Results')
+ schema = response.get('Schema', {})
+ headers = [item.get('Name') for item in schema]
+ context_result = {'query': query, 'results': results}
+ human_readable_table = tableToMarkdown(name=f" Result of query: {query}:", t=results,
+ headers=headers)
+ return CommandResults(outputs_prefix='Microsoft365Defender.Hunt', outputs_key_field='query', outputs=context_result,
+ readable_output=human_readable_table)
+
+
+ ''' MAIN FUNCTION '''
+
+
+
+ def main() -> None:
+ """main function, parses params and runs command functions
+
+ :return:
+ :rtype:
+ """
+ params = demisto.params()
+ # if your Client class inherits from BaseClient, SSL verification is
+ # handled out of the box by it, just pass ``verify_certificate`` to
+ # the Client constructor
+ verify_certificate = not params.get('insecure', False)
+
+ # if your Client class inherits from BaseClient, system proxy is handled
+ # out of the box by it, just pass ``proxy`` to the Client constructor
+ proxy = params.get('proxy', False)
+ app_id = params.get('creds_client_id', {}).get('password', '') or params.get('app_id') or params.get('_app_id')
+ base_url = params.get('base_url')
+ endpoint_type = params.get('endpoint_type', 'Worldwide')
+ endpoint = MICROSOFT_365_DEFENDER_TYPE.get(endpoint_type, 'com')
+ base_url = microsoft_defender_get_base_url(base_url, endpoint_type)
+
+ tenant_id = params.get('creds_tenant_id', {}).get('password', '') or params.get('tenant_id') or params.get('_tenant_id')
+ client_credentials = params.get('client_credentials', False)
+ enc_key = params.get('enc_key') or (params.get('credentials') or {}).get('password')
+ certificate_thumbprint = params.get('creds_certificate', {}).get('identifier', '') or \
+ params.get('certificate_thumbprint', '')
+
+ private_key = (replace_spaces_in_credential(params.get('creds_certificate', {}).get('password', ''))
+ or params.get('private_key', ''))
+ managed_identities_client_id = get_azure_managed_identities_client_id(params)
+
+ first_fetch_time = params.get('first_fetch', '3 days').strip()
+ fetch_limit = arg_to_number(params.get('max_fetch', 10))
+ fetch_timeout = arg_to_number(params.get('fetch_timeout', TIMEOUT))
+ demisto.debug(f'Command being called is {demisto.command()}')
+
+ command = demisto.command()
+ args = demisto.args()
+
+ try:
+ if not managed_identities_client_id and not app_id:
+ raise Exception('Application ID must be provided.')
+
+ azure_cloud = AZURE_CLOUDS.get(endpoint)
+ client = Client(
+ app_id=app_id,
+ verify=verify_certificate,
+ base_url=base_url,
+ proxy=proxy,
+ tenant_id=tenant_id,
+ enc_key=enc_key,
+ client_credentials=client_credentials,
+ certificate_thumbprint=certificate_thumbprint,
+ private_key=private_key,
+ managed_identities_client_id=managed_identities_client_id,
+ endpoint=endpoint,
+ azure_cloud=azure_cloud,
+ )
+ if demisto.command() == 'test-module':
+ # This is the call made when pressing the integration Test button.
+ return_results(test_module(client))
+
+ elif command == 'microsoft-365-defender-auth-start':
+ return_results(start_auth(client))
+
+ elif command == 'microsoft-365-defender-auth-complete':
+ return_results(complete_auth(client))
+
+ elif command == 'microsoft-365-defender-auth-reset':
+ return_results(reset_auth())
+
+ elif command == 'microsoft-365-defender-auth-test':
+ return_results(test_connection(client))
+
+ elif command == 'microsoft-365-defender-incidents-list':
+ test_context_for_token(client)
+ return_results(microsoft_365_defender_incidents_list_command(client, args))
+
+ elif command == 'microsoft-365-defender-incident-update':
+ test_context_for_token(client)
+ return_results(microsoft_365_defender_incident_update_command(client, args))
+
+ elif command == 'microsoft-365-defender-advanced-hunting':
+ test_context_for_token(client)
+ return_results(microsoft_365_defender_advanced_hunting_command(client, args))
+
+ elif command == 'microsoft-365-defender-incident-get':
+ test_context_for_token(client)
+ return_results(microsoft_365_defender_incident_get_command(client, args))
+
+ elif command == 'fetch-incidents':
+ fetch_limit = arg_to_number(fetch_limit)
+ fetch_timeout = arg_to_number(fetch_timeout) if fetch_timeout else None
+ incidents = fetch_incidents(client, first_fetch_time, fetch_limit, fetch_timeout)
+ demisto.incidents(incidents)
+ else:
+ raise NotImplementedError
+ # Log exceptions and return errors
+ except Exception as e:
+ return_error(f'Failed to execute {demisto.command()} command.\nError:\n{str(e)}')
+
+
+ ''' ENTRY POINT '''
+
+
+ if __name__ in ('__main__', '__builtin__', 'builtins'):
+ main()
+
+ register_module_line('Microsoft 365 Defender', 'end', __line__())
+ subtype: python3
+ type: python
+ nativeimage:
+ - '8.8'
+ - '8.6'
+fromversion: 5.5.0
+tests:
+- No tests
+defaultmapperin: Microsoft 365 Defender - Incoming Mapper
+image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAyCAYAAACXpx/YAAAACXBIWXMAAAsSAAALEgHS3X78AAAAAXNSR0IArs4c6QAAEcdJREFUeF7tXAdUVGcW/sAuTUAFFWuwoNLFKCqIGgt2BIwm1mDf1WhcW6JHjTW2gCV6LInGxBY1VjSxiwWxiwULKiAqVmyIUvZ813044AzzBswucbnncGDe++v9bv//wSg9PT0d+fTBcsAoH+APFlvZWD7AHza++QB/4PjmA5wP8IfOgQ98f/k+OB9g3Rx4nQbsuvca2++8woXEFESfuA9nm6Lo7myO9g5mKGD8gXPvv7S91NRU3L9/H/xtbW2NIkWKqJ45xxocl5SGKZeTEPEoBUyk01LTcfFIgkxsbAR0qG6KEF8bWBcvoHoxWRs+ffoUSpperFgxFCpUSOdYz58/FwaQlLYpKSm4efMmypUrh6JFi+Z4Hf/LjomJifj5559x69Yt4UWPHj3g4OCgekkGA0wwTz1OwfhLSYh/mZYxkSbAykOPMkWxsK0NapZWL3GaK580aRJev34tj7y9vdGkSROtG0tOTsbs2bPx8uVLeR8YGIhatWphy5Yt2L9/P5ydndGtWzfVTMkrDQno8uXLERkZCSMjIxgbGwvANWvWVL1EgwB+mZqODfGvsOhGMp6nZi6AaQOYq7AxKYApTUohoLYZChUwUr0wNhwzZgxevXolfSpVqoRBgwbJRrPSlStXsGjRoozH3bt3h5OTExYvXoyoqChYWlri66+/NmjuvNA4Pj4ewcHBornt2rXDRx99hBIlSuDRo0c4e/YsKlSooBfsbAEW05sOJL5OR9iD11gRk4wbSW+1VpMJugBW2jSuWBzdnczRsqoJzIoao4CREbRglYmvmgBTeseOHQszM7N3eL9q1SqcOHHiHYAJ7tatW9GoUSPUrVs3L2Bm0BpOnTqFX375Baampvjmm29QsGBB6T9jxgzcvXtXLJW+fWkF+NCDFKyPfwVq7N3kNNx6mYYUPRVrfQBr7qy6ZWGUtSgIi6LGGFLXEvUqFNO6cQVgBhU0wx07dkSDBg0ytaWfnT59Ouiv09LS5EfRYIO4mQcbHzx4EJs2bZLAavTo0RkrzDXAw849x4EHKQZt2RCANQfu62qB4NY22QLs6uoKSnONGjUQFBSUqe2lS5ewZMkSVK1aVQKRFy9eZADMz5R0CwsLMW8KMRiLjo6WH7qAUqVKSeDCdnz2+PFjlC5dWhh7/PhxCdDq1Kkj7oHjX7hwQeaiRpUpUwaOjo7vBIA0r8eOHcOTJ09gZWUlmsYxFeI4tDCxsbEyrp2dnZhbCjPXd+7cOZnn5MmTYrXatm2b0Xf79u2yxo8//jjDbFepUkUrD7Vq8IjIF9hz/01wo5ZyCvCgOiUws+XbjWvOp2gwA6SVK1eicOHCIsk0WQpt2LABhw8fhp+fH3bu3AlG04oGh4aGYvfu3cK43r17SxcGYuvXr8eZM2dE2xXy9PSUMVasWCH+jYBQOBiF29jY4KuvvgJB4zqYsmhSxYoV0atXr4x1sf/q1asz4ge2bdiwITp06CDdOA7nyTqOra0t+vTpI/tkgEmrpYZq166Nnj17/n0BHjFiBJYuXYoHDx7gs88+AzWaRO2bOnWqMGLYsGGYO3euXoAVgWB/c3Nz0a579+6JFvr7+2cATG1misKUi4FN//79ERISImtgukbNZYR/584dCYIoRASZ2sc1sS/blC1bFjdu3BALQYD5ftq0aRIoFShQAASVGkzQKXDVqlWTPc6fP1/cTlJSkrTTjD34nOMUL15chKF69eoICAj4+wLMCPjQoUPYt28f3N3d0aVLF9mMEj3b29sLc6dMmZItwASH/ot+28XFRRhOa0CG3b59W5iraDCZzrSsXr168p6mdOPGjWKWCQC1hoDQTzKQY/shQ4YIw7/77jv5PGrUKDHztBoJCQkS9UZERGDNmjWS8lCgPDw8ZC98vm7dOhGWgQMHSu5Oy7Rt2zYZgwKsECNrjkeLQ35wLF01gr+FiSbABIepEH0UI0r6xd9++w1Hjx5FmzZtUL9+fb0A79mzB/Rf7Dtu3DgBIyspAJOptBzUHtKCBQvEP1PTmYsqRCtCjaSvbd26NShsBIAADxgwAFl9ozI+n/O9kvZRWCh8tCZNmzZFq1atRHj+kiArr/lgAkyTOWHCBNHQfv36SV7M6JmfCQTNlT4NVpir6ZN1Aezm5oauXbtmvB4/fjyePXuG9u3bS9qlST/99JMUI+i3GelzHdR6rokWgIBRMAkitZu+t3nz5vKjSdRsarKyvv8rgFmsYHB05MgRYTBN5MKFC8WUDR48WPyhPoBpAWjWmWoRCG2kCIFmG5rYb7/9Vnw9tZdarElr166ViFkJdk6fPi1rpf8k0RrQ7BJwCiUj4E6dOonV0SSl8kafymDr/w7gixcvSrBFbaaUE2xWeLy8vIT5+gD+4YcfcO3atWxLlwrALI0qqQnN8MSJE8WX0m9SKzWJkTVB5XO+JzECp29mKkTNZRrHSF4BmObcx8cn0zj0weHh4RmC8pcBPPr8C/x577+TJg32sMT0FqW0apOSJtFEU4PJaDKI/o6+kYwbOnSoRKtqAFYYSI0aOXKkBCe6TLQmwGxD08rAJutzroF1cEbTWUFjpLt582YJEOlrJ0+eDAoZAzYGV507d840/bx58yTiZnDn6+urV4O1FX6y7kdrkDXpUhJ+v/OmBqyWcpoHj/Wyxhgva1UAsxFLdyx6kJifDh8+XJinBmAWDpYtWyZ9GcSQkezLqDouLk78ujYNZntFOBigsSZOoSIxn+ZpD8ehq6DQ8G++52+lEMO2tDDM1XkAwnGYerHAQaLrYO2cAsNiDos6ujSYQRyFRBE2Rt7aavQcVyvAC6JfYlmMuiRbQSanAM/3tUFvNwvVAGsyjCaOWkNSAzCZN2fOHEmJCAQBZXWJOShzXRZIdAFMAaCGURiYWjGv5ZxcDy0LU6y+fftKsMUiBz9TAM+fPy/jM9+lMD58+BAzZ86UPsyxOQ7XRVNOn81UigJEC6ULYEXImQXQ75csWfKdgE1hqFaAQ2OS8I8Dmas1+jQ5PS0NCadv6mv2zvvQgS5oZG+uGmAygYUElvqoMWSIWoDZjsymFjPQ0SSePmUHMNvS1DJt0ayA8TlLnYzsKSQEmFG1JjF31jzHZfmTKR6FRZNYdOE4dCEkXQBfvXpVAkyFDK5kxT5MRrXJZ5DCoySVZJSWCpOYN6ZTLVmaFsGJBZ/D0kz7YTzNGf2YkmYo4/LkiJUimiglT2UUzUIIf7NuTM2kdjF35d98phA1j2aSZUimL9QupjgEiLVf+lPWrhnNZiUKSFhYmOTl1KDKlStLGVLJqRmIEUD6UgohwWfljdZCk2JiYiTyZsrEPbDcyeyAWq0Q18490GIwkNQkuhsKE/fCqJ6FG22k87jQaepZXEx4E+aroZwA7O1oh98ntlcz/F/SJjvfpW9CNX3VtuFcunyovnXoe68T4HHbYjF1V7y+/hnvcwLwzL5e+KJV5pxS9YT5DVVxQCfAl+8moc6sSCTxZp0KMhRgs2KFcHpRd1jpMM8qpsxvooIDOgHmV9I+W34F6848VDEMYCjA/Vo7YVpQ5pKfqonyGxnEgWyv7ETGv4DHrEhVwZYhAJsVK4wjwV1QrtTbc12DVp3fWDUH9F66+8ea61h09M112OzIEIBHdfbAyE//fnek9PEgL77XC/DtxFfwDrmA6w+zL3yoBbhWRWvsmOoH02LvHtXllkFPnz5DxLEI2NjaoFYt9VdLs87LOnJMTBw8PNxzu6T/eX+9AHOF+y8/QdslUdkGXGoALmFaBJsndoBj5ZJ6Nx4WdggXzl+UHLFS5UqoX78eihfXfjlPGWz1qjWIjr4Bnybe0j6ndOxYBPbt3Y8RI4cbPASLICtX/irzV61qj1OnTuN2/G20bNUCZ86cRcSx4zAyfnMHy8urIUxMTGQOZb/828qad7g8UKFCeYPnz9pBFcDsNHnHLYzfGadzQn0A84rs9/0bo3vzWqoWvW7tety+cwfVqtrj5MlTUt35IqhXxtVRbYNMmTIdvq1awsXVWdUcuhrlBmCO+du6DXIg0rNXD8ybOx8NGzaAm7sr/vxjF06eOg03VxcBu2DBQug/oI/UpZX9urg44XLUFSQk3MPwfw3VeinBkM2pBvhVSjq6rbiKDee0R9XZAUxwuzeriTkDfPTehVYWzw1TGzp/GoBbt+IREjwPQX16i1bwkH/r1u14cP8BGnk1hKNjbdGUNWvWoYytLfwDOqFs2TI4cOAgTp86A/uq9mjVqoXUnzdv2iKfj4VHIC09DYGB/lIpYtlw9649iL5+HaYmplKvVjT44sVL2Ltnn1S62rZrAzMzU5w9e07qybExsVJz9mzw9myX610wfyECAjvh942b8c3Y0SKYBDj6+g306xeExMeJ+G7GLHQODICTs6MArOyXFwsmT5qKL78cLO4mN6QaYE7y7GUqPpl/Ecfjnr8zZ3YAN3OtgJWjfVGkkPrvKWlumBWhObOD4e7uBu/GXlizep1oCBnz5x+78eXQf+L+vftYvHgZnJ2d0LxFM8TF3cLGDZvQvHkz7Nt/AM0/aQZXNxcEfz9PyowE5Fj4MTT28YaXVyMcOnQY27ftQLNPmuLC+QtvboqMHC6lxLkhC9C0qQ9i4+Lk0L5jx/bY9eduHD0aLqXK+p71UKVK5Uw8+fHH5bh65RoaNvREK9+W8k4TYH5etHAx7OzKoXUbXwE4NS1VBC46+rq8Gz1mJKysLHODr+FfAE948hq+Cy/hzO0XmSbWBbCnQxmsGdsWpsV0f3FM2w40Aeb7BQsWyn0nHx9vTJwwWTSbdd7Vq9eiRYtPUKNGdUwYPwl+fh3g6FQby5b+BFtbG7jXcUf40XA5rA/sHCAA16rtgGbNmoqvNDUxQYeO7YWhrEm379AWmib6wP6Doq0Bgf5IuJuA0NAdAjwBvnQpCgMH9dd6rrxn915s2xaKvv2CUL16Na0A//rLarksR03nfmm26ZNZZ6cP7ujXPtclTIM0WAHi3tPX8F96GYdvPsvARhvALdwrYvGw5jArbnjErAkwDxxmzpgjWufkVBvTps5AyZLW4sPS09PQtl1rORzQBHje3AVITHwi5pcWwMGhOlq0bC4Au7m5iGlnUFawUCH4+/th9qxg1PFwE23WBHhH6E7R7pIleSkhXa6v9v6ipwAcf/s2unf//B355IHH7NnBsLK0hImpCbp2/VQrwDTj1HwGYNzvw0cPxd1s2xqKf40YJi4ht5QjgDnp4xcp6LMqGpsiH8nXRzUBps/t6lMDM/p5o1jhN9+nMZQ0Ab569RqWLF6KwUP+KcHW1CnT8fnnXVD5P2ZROWTXBHj58p9hYW4uPpOkfDtPF8BLl/4IWxsbMZeHDx2RqJaaeuRIuJjyQYMGSPRLYmSfHcBHj4Rj//4D6NGzG0KC52PosMFibTRN9N27CZgzJxhBQb1hb/9Rhg8O7OwPCqcSNxjKt6ztcwwwB0pOScO4rbEIOXgXqSkpclxIPzsiwAND/FxRIBffACfAUVGXUap0ScTF3hKtoyklUL9v3ISoy1fk2YP7D+Ef4CdBjCbATLF+/XUV6tX7WMwzf9uVt9OpwRERx7Flyza4ujgjKuoKjI2NBGD6+jmzQ1CtWlVYWJjDytoa9erV1QkwrcX3c0Lg6OQofnvJkmVyIE+/TYDDwg6jnF05+epLTQcHMc8UUE2BPh5xQsz7yFHDc/295lwBTJB5ZLz6+H0M3xgNy7sXMKufN1rWzRxw5EQK6Y9ibsagcJHCcreYUq4cqfEM9OCBMEmjeM7q6VlfhOmPP3aJiStTxlbM8rlzkYg8dx7W1lZikhkgHTwYJjcxaRoZeVMbnZwckZaWjrCDYRI98zM1jAEYKSYmVvw4x6zvWR/ly9vh8uUrePLkKerUccu0PV5IoHZ7N/aGubmZnAtHX7suYzHo4s0N+t3yFcrDoUYNGP9HCbhfju/i4ozk5FfYu2evCEm5cmVzwr63rvN9/SO0uIfJKGyUitKWxXO1oPzO75cDudbg97uc/NHeNwfyAX7fHM1j4+UDnMcAed/LyQf4fXM0j42XD3AeA+R9Lycf4PfN0Tw2Xj7AeQyQ972cfwOCgNUBjwnIswAAAABJRU5ErkJggg==
+detaileddescription: "## Methods to Authenticate Microsoft Defender XDR\nYou can use the following methods to authenticate Microsoft Defender XDR.\n- Device Code Flow\n- Client Credentials Flow\n- Azure Managed Identities\n\n### Device Code Flow\n___\n\nUse the [device code flow](https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication#device-code-flow)\nto link Microsoft Defender XDR with Cortex XSOAR.\n\nTo connect to the Microsoft Defender XDR:\n1. Fill in the required parameters.\n2. Run the ***!microsoft-365-defender-auth-start*** command. \n3. Follow the instructions that appear.\n4. Run the ***!microsoft-365-defender-auth-complete*** command.\n\nAt the end of the process you'll see a message that you've logged in successfully.\n\n#### Cortex XSOAR App\n\nIn order to use the Cortex XSOAR application, use the default application ID.\n```9093c354-630a-47f1-b087-6768eb9427e6```\n\n#### Self-Deployed Application - Device Code Flow\n\nTo use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal. For more details, follow [Self Deployed Application - Device Code Flow](https://xsoar.pan.dev/docs/reference/articles/microsoft-integrations---authentication#device-code-flow).\n\n#### Required Permissions\n* microsoft-365-defender-incidents-list:\n * offline_access - Delegated \n * AdvancedQuery.Read.All - Application - can be found under WindowsDefenderATP on the \"APIs my organization uses\" section.\n \n And one of the following:\n * Incident.Read.All\t- Application - See section 4 in [this article](https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-user-context?view=o365-worldwide#create-an-app)\n * AdvancedHunting.Read.All - Application - See section 4 in [this article](https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-user-context?view=o365-worldwide#create-an-app)\n \n\n* microsoft-365-defender-incident-update:\n * offline_access - Delegated\n * AdvancedQuery.Read.All - Application - can be found under WindowsDefenderATP on the \"APIs my organization uses\" section.\n * Incident.ReadWrite.All - Application - See section 4 in [this article](https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-user-context?view=o365-worldwide#create-an-app)\n\n\n\n* microsoft-365-defender-advanced-hunting:\n * offline_access - Delegated \n * AdvancedHunting.Read.All - Application - See section 4 in [this article](https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-user-context?view=o365-worldwide#create-an-app)\n\n\n\n### Client Credentials Flow\n___\nFollow these steps for a self-deployed configuration:\n\n1. To use a self-configured Azure application, you need to add a new Azure App Registration in the Azure Portal. To add the registration, refer to the following [Microsoft article](https://docs.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-web?view=o365-worldwide#create-an-app) steps 1-8.\n2. In the instance configuration, select the ***Use Client Credentials Authorization Flow*** checkbox.\n3. Enter your Client/Application ID in the ***Application ID*** parameter. \n4. Enter your Client Secret in the ***Client Secret*** parameter.\n5. Enter your Tenant ID in the ***Tenant ID*** parameter.\n6. Run the ***microsoft-365-defender-auth-test*** command to test the connection and the authorization process.\n\n#### Required Permissions\n * AdvancedHunting.Read.All - Application\n * Incident.ReadWrite.All - Application\n\n### Azure Managed Identities Authentication\n____\n##### Note: This option is relevant only if the integration is running on Azure VM.\nFollow one of these steps for authentication based on Azure Managed Identities:\n\n- ##### To use System Assigned Managed Identity\n - Select the **Use Azure Managed Identities** checkbox and leave the **Azure Managed Identities Client ID** field empty.\n\n- ##### To use User Assigned Managed Identity\n 1. Go to [Azure Portal](https://portal.azure.com/) -> **Managed Identities**.\n 2. Select your User Assigned Managed Identity -> copy the Client ID -> paste it in the **Azure Managed Identities Client ID** field in the instance settings.\n 3. Select the **Use Azure Managed Identities** checkbox.\n\nFor more information, see [Managed identities for Azure resources](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview).\n\n\n### Using National Cloud\nUsing a national cloud endpoint is supported by setting the *Endpoint Type* parameter to one of the following options:\n* US Government GCC Endpoint: `https://api-gcc.security.microsoft.us`\n* US Government GCC-High Endpoint: `https://api-gov.security.microsoft.us`\n* US Government Department of Defence (DoD) Endpoint: `https://api-gov.security.microsoft.us`\n\n\n---\n[View Integration Documentation](https://xsoar.pan.dev/docs/reference/integrations/microsoft-365-defender)"
diff --git a/Packs/Microsoft365Defender/ReleaseNotes/4_5_42.md b/Packs/Microsoft365Defender/ReleaseNotes/4_5_42.md
new file mode 100644
index 000000000000..1f06b43a71ef
--- /dev/null
+++ b/Packs/Microsoft365Defender/ReleaseNotes/4_5_42.md
@@ -0,0 +1,7 @@
+
+#### Integrations
+
+##### Microsoft 365 Defender
+
+Added support for National Cloud.
+
diff --git a/Packs/Microsoft365Defender/pack_metadata.json b/Packs/Microsoft365Defender/pack_metadata.json
index c5a7636c79ac..20306c58af69 100644
--- a/Packs/Microsoft365Defender/pack_metadata.json
+++ b/Packs/Microsoft365Defender/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft 365 Defender",
"description": "Microsoft Defender XDR (formerly Microsoft 365 Defender) is a unified pre- and post-breach enterprise defense suite that natively coordinates detection, prevention, investigation, and response across endpoints, identities, email, and applications to provide integrated protection against sophisticated attacks.",
"support": "xsoar",
- "currentVersion": "4.5.41",
+ "currentVersion": "4.5.42",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftCloudAppSecurity/ReleaseNotes/2_2_11.md b/Packs/MicrosoftCloudAppSecurity/ReleaseNotes/2_2_11.md
new file mode 100644
index 000000000000..761d8c1e8d01
--- /dev/null
+++ b/Packs/MicrosoftCloudAppSecurity/ReleaseNotes/2_2_11.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Defender for Cloud Apps
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
\ No newline at end of file
diff --git a/Packs/MicrosoftCloudAppSecurity/pack_metadata.json b/Packs/MicrosoftCloudAppSecurity/pack_metadata.json
index 0f11a4ba6bbc..123fb71580cb 100644
--- a/Packs/MicrosoftCloudAppSecurity/pack_metadata.json
+++ b/Packs/MicrosoftCloudAppSecurity/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Defender for Cloud Apps",
"description": "Microsoft Cloud App Security Integration, a Cloud Access Security Broker that supports various deployment modes",
"support": "xsoar",
- "currentVersion": "2.2.10",
+ "currentVersion": "2.2.11",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftDefenderAdvancedThreatProtection/ReleaseNotes/1_18_2.md b/Packs/MicrosoftDefenderAdvancedThreatProtection/ReleaseNotes/1_18_2.md
new file mode 100644
index 000000000000..c498975dc51a
--- /dev/null
+++ b/Packs/MicrosoftDefenderAdvancedThreatProtection/ReleaseNotes/1_18_2.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Defender for Endpoint
+
+- Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftDefenderAdvancedThreatProtection/pack_metadata.json b/Packs/MicrosoftDefenderAdvancedThreatProtection/pack_metadata.json
index 17c8e2aba3dd..db77d281574f 100644
--- a/Packs/MicrosoftDefenderAdvancedThreatProtection/pack_metadata.json
+++ b/Packs/MicrosoftDefenderAdvancedThreatProtection/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Defender for Endpoint",
"description": "Microsoft Defender for Endpoint (previously Microsoft Defender Advanced Threat Protection (ATP)) is a unified platform for preventative protection, post-breach detection, automated investigation, and response.",
"support": "xsoar",
- "currentVersion": "1.18.1",
+ "currentVersion": "1.18.2",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftExchangeOnline/ReleaseNotes/1_6_3.md b/Packs/MicrosoftExchangeOnline/ReleaseNotes/1_6_3.md
new file mode 100644
index 000000000000..eef07ce0a984
--- /dev/null
+++ b/Packs/MicrosoftExchangeOnline/ReleaseNotes/1_6_3.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### EWS O365
+
+- Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftExchangeOnline/pack_metadata.json b/Packs/MicrosoftExchangeOnline/pack_metadata.json
index 5840fe01694f..8f04de09c33e 100644
--- a/Packs/MicrosoftExchangeOnline/pack_metadata.json
+++ b/Packs/MicrosoftExchangeOnline/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Exchange Online",
"description": "Exchange Online and Office 365 (mail)",
"support": "xsoar",
- "currentVersion": "1.6.2",
+ "currentVersion": "1.6.3",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphAPI/ReleaseNotes/1_1_53.md b/Packs/MicrosoftGraphAPI/ReleaseNotes/1_1_53.md
new file mode 100644
index 000000000000..6f04f0cba4d3
--- /dev/null
+++ b/Packs/MicrosoftGraphAPI/ReleaseNotes/1_1_53.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Graph API
+
+- Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphAPI/pack_metadata.json b/Packs/MicrosoftGraphAPI/pack_metadata.json
index 58457a842174..28048ba77566 100644
--- a/Packs/MicrosoftGraphAPI/pack_metadata.json
+++ b/Packs/MicrosoftGraphAPI/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph API",
"description": "Use the Microsoft Graph API integration to interact with Microsoft APIs that do not have dedicated integrations in Cortex XSOAR, for example, Mail Single-User, etc.",
"support": "xsoar",
- "currentVersion": "1.1.52",
+ "currentVersion": "1.1.53",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphApplications/ReleaseNotes/1_3_0.md b/Packs/MicrosoftGraphApplications/ReleaseNotes/1_3_0.md
new file mode 100644
index 000000000000..8d94d637f1d0
--- /dev/null
+++ b/Packs/MicrosoftGraphApplications/ReleaseNotes/1_3_0.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Active Directory Applications
+
+- Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphApplications/pack_metadata.json b/Packs/MicrosoftGraphApplications/pack_metadata.json
index b4eb156f16e1..96baff468054 100644
--- a/Packs/MicrosoftGraphApplications/pack_metadata.json
+++ b/Packs/MicrosoftGraphApplications/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Applications",
"description": "Use this pack to manage connected applications and services",
"support": "xsoar",
- "currentVersion": "1.2.48",
+ "currentVersion": "1.3.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphCalendar/ReleaseNotes/1_1_29.md b/Packs/MicrosoftGraphCalendar/ReleaseNotes/1_1_29.md
new file mode 100644
index 000000000000..d5d7456f5c51
--- /dev/null
+++ b/Packs/MicrosoftGraphCalendar/ReleaseNotes/1_1_29.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### O365 Outlook Calendar
+
+- Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphCalendar/pack_metadata.json b/Packs/MicrosoftGraphCalendar/pack_metadata.json
index f42950fe8a31..a58bb1c938ff 100644
--- a/Packs/MicrosoftGraphCalendar/pack_metadata.json
+++ b/Packs/MicrosoftGraphCalendar/pack_metadata.json
@@ -1,7 +1,7 @@
{
"name": "Microsoft Graph Calendar",
"description": "Microsoft Graph Calendar enables you to create and manage different calendars and events\n according to your requirements.",
- "currentVersion": "1.1.28",
+ "currentVersion": "1.1.29",
"support": "xsoar",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
diff --git a/Packs/MicrosoftGraphDeviceManagement/ReleaseNotes/1_1_34.md b/Packs/MicrosoftGraphDeviceManagement/ReleaseNotes/1_1_34.md
new file mode 100644
index 000000000000..72fd5574509a
--- /dev/null
+++ b/Packs/MicrosoftGraphDeviceManagement/ReleaseNotes/1_1_34.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Endpoint Manager (Intune)
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphDeviceManagement/pack_metadata.json b/Packs/MicrosoftGraphDeviceManagement/pack_metadata.json
index c116a9040fe8..1b8f0a5a83d4 100644
--- a/Packs/MicrosoftGraphDeviceManagement/pack_metadata.json
+++ b/Packs/MicrosoftGraphDeviceManagement/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Device Management",
"description": "Microsoft Graph Device Management",
"support": "xsoar",
- "currentVersion": "1.1.33",
+ "currentVersion": "1.1.34",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphFiles/ReleaseNotes/1_1_32.md b/Packs/MicrosoftGraphFiles/ReleaseNotes/1_1_32.md
new file mode 100644
index 000000000000..090ed802f8b8
--- /dev/null
+++ b/Packs/MicrosoftGraphFiles/ReleaseNotes/1_1_32.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### O365 File Management (Onedrive/Sharepoint/Teams)
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphFiles/pack_metadata.json b/Packs/MicrosoftGraphFiles/pack_metadata.json
index d5473c509599..bb8de1871c1d 100644
--- a/Packs/MicrosoftGraphFiles/pack_metadata.json
+++ b/Packs/MicrosoftGraphFiles/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Files",
"description": "Use the O365 File Management (Onedrive/Sharepoint/Teams) integration to enable your app get authorized access to files in OneDrive, SharePoint, and MS Teams across your entire organization. This integration requires admin consent.",
"support": "xsoar",
- "currentVersion": "1.1.31",
+ "currentVersion": "1.1.32",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphGroups/ReleaseNotes/1_1_53.md b/Packs/MicrosoftGraphGroups/ReleaseNotes/1_1_53.md
new file mode 100644
index 000000000000..b0532658e8ac
--- /dev/null
+++ b/Packs/MicrosoftGraphGroups/ReleaseNotes/1_1_53.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Active Directory Groups
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphGroups/pack_metadata.json b/Packs/MicrosoftGraphGroups/pack_metadata.json
index 3b75b5b1d34a..a665c9547d1f 100644
--- a/Packs/MicrosoftGraphGroups/pack_metadata.json
+++ b/Packs/MicrosoftGraphGroups/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Groups",
"description": "Microsoft Graph Groups enables you to create and manage different types of groups and group functionality according to your requirements.",
"support": "xsoar",
- "currentVersion": "1.1.52",
+ "currentVersion": "1.1.53",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphIdentityandAccess/ReleaseNotes/1_2_58.md b/Packs/MicrosoftGraphIdentityandAccess/ReleaseNotes/1_2_58.md
new file mode 100644
index 000000000000..ac331445af74
--- /dev/null
+++ b/Packs/MicrosoftGraphIdentityandAccess/ReleaseNotes/1_2_58.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Active Directory Identity And Access
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphIdentityandAccess/pack_metadata.json b/Packs/MicrosoftGraphIdentityandAccess/pack_metadata.json
index 4887bac2e014..9c7b4e694a5f 100644
--- a/Packs/MicrosoftGraphIdentityandAccess/pack_metadata.json
+++ b/Packs/MicrosoftGraphIdentityandAccess/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Identity and Access",
"description": "Use this pack to manage roles and members in Microsoft.",
"support": "xsoar",
- "currentVersion": "1.2.57",
+ "currentVersion": "1.2.58",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphMail/ReleaseNotes/1_6_24.md b/Packs/MicrosoftGraphMail/ReleaseNotes/1_6_24.md
new file mode 100644
index 000000000000..fa1a15709f74
--- /dev/null
+++ b/Packs/MicrosoftGraphMail/ReleaseNotes/1_6_24.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### O365 Outlook Mail (Using Graph API)
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphMail/pack_metadata.json b/Packs/MicrosoftGraphMail/pack_metadata.json
index a76676311cb9..193000932615 100644
--- a/Packs/MicrosoftGraphMail/pack_metadata.json
+++ b/Packs/MicrosoftGraphMail/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Mail",
"description": "Microsoft Graph lets your app get authorized access to a user's Outlook mail data in a personal or organization account.",
"support": "xsoar",
- "currentVersion": "1.6.23",
+ "currentVersion": "1.6.24",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphSearch/ReleaseNotes/1_0_17.md b/Packs/MicrosoftGraphSearch/ReleaseNotes/1_0_17.md
new file mode 100644
index 000000000000..6f2674655e76
--- /dev/null
+++ b/Packs/MicrosoftGraphSearch/ReleaseNotes/1_0_17.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Graph Search
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphSearch/pack_metadata.json b/Packs/MicrosoftGraphSearch/pack_metadata.json
index a843415ccf20..a0af07b9aff1 100644
--- a/Packs/MicrosoftGraphSearch/pack_metadata.json
+++ b/Packs/MicrosoftGraphSearch/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Search",
"description": "Use the Microsoft Search API in Microsoft Graph to search content stored in OneDrive or SharePoint: files, folders, lists, list items, or sites.",
"support": "community",
- "currentVersion": "1.0.16",
+ "currentVersion": "1.0.17",
"author": "randomizerxd",
"url": "",
"email": "",
diff --git a/Packs/MicrosoftGraphSecurity/ReleaseNotes/2_2_23.md b/Packs/MicrosoftGraphSecurity/ReleaseNotes/2_2_23.md
new file mode 100644
index 000000000000..bd8b1e3e381a
--- /dev/null
+++ b/Packs/MicrosoftGraphSecurity/ReleaseNotes/2_2_23.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Graph Security
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphSecurity/pack_metadata.json b/Packs/MicrosoftGraphSecurity/pack_metadata.json
index 836cb8580023..14740c016c58 100644
--- a/Packs/MicrosoftGraphSecurity/pack_metadata.json
+++ b/Packs/MicrosoftGraphSecurity/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph Security",
"description": "Unified gateway to security insights - all from a unified Microsoft Graph\n Security API.",
"support": "xsoar",
- "currentVersion": "2.2.22",
+ "currentVersion": "2.2.23",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftGraphTeams/ReleaseNotes/1_1_11.md b/Packs/MicrosoftGraphTeams/ReleaseNotes/1_1_11.md
new file mode 100644
index 000000000000..0982a6db59b1
--- /dev/null
+++ b/Packs/MicrosoftGraphTeams/ReleaseNotes/1_1_11.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### O365 Teams (Using Graph API)
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphTeams/pack_metadata.json b/Packs/MicrosoftGraphTeams/pack_metadata.json
index 89b312f1d86c..c856c9a00b73 100644
--- a/Packs/MicrosoftGraphTeams/pack_metadata.json
+++ b/Packs/MicrosoftGraphTeams/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "MicrosoftGraphTeams",
"description": "O365 Teams (Using Graph API) gives you authorized access to a user’s Teams enabling you to facilitate communication through teams as that user, or read conversations and/or messages of that user.",
"support": "community",
- "currentVersion": "1.1.10",
+ "currentVersion": "1.1.11",
"author": "Joachim Bockland",
"url": "",
"email": "",
diff --git a/Packs/MicrosoftGraphUser/ReleaseNotes/1_5_43.md b/Packs/MicrosoftGraphUser/ReleaseNotes/1_5_43.md
new file mode 100644
index 000000000000..9af2b97a1bc8
--- /dev/null
+++ b/Packs/MicrosoftGraphUser/ReleaseNotes/1_5_43.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Azure Active Directory Users
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftGraphUser/pack_metadata.json b/Packs/MicrosoftGraphUser/pack_metadata.json
index 23a6e50b8987..5923299b059a 100644
--- a/Packs/MicrosoftGraphUser/pack_metadata.json
+++ b/Packs/MicrosoftGraphUser/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Graph User",
"description": "Use the Microsoft Graph integration to connect to and interact with user objects on Microsoft Platforms.",
"support": "xsoar",
- "currentVersion": "1.5.42",
+ "currentVersion": "1.5.43",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftManagementActivity/ReleaseNotes/1_4_0.md b/Packs/MicrosoftManagementActivity/ReleaseNotes/1_4_0.md
new file mode 100644
index 000000000000..7611083fe179
--- /dev/null
+++ b/Packs/MicrosoftManagementActivity/ReleaseNotes/1_4_0.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Management Activity API (O365 Azure Events)
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftManagementActivity/pack_metadata.json b/Packs/MicrosoftManagementActivity/pack_metadata.json
index 0488d26cd868..ee5306425d43 100644
--- a/Packs/MicrosoftManagementActivity/pack_metadata.json
+++ b/Packs/MicrosoftManagementActivity/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Management Activity API (O365/Azure Events)",
"description": "An integration for Microsoft's management activity API, which enables you to fetch content records and manage your subscriptions.",
"support": "xsoar",
- "currentVersion": "1.3.52",
+ "currentVersion": "1.4.0",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
diff --git a/Packs/MicrosoftTeams/ReleaseNotes/1_5_15.md b/Packs/MicrosoftTeams/ReleaseNotes/1_5_15.md
new file mode 100644
index 000000000000..ed26a17603b0
--- /dev/null
+++ b/Packs/MicrosoftTeams/ReleaseNotes/1_5_15.md
@@ -0,0 +1,5 @@
+#### Integrations
+
+##### Microsoft Teams
+
+Added support for National Cloud in Microsoft 365 Defender, implemented in the Microsoft API Module.
diff --git a/Packs/MicrosoftTeams/pack_metadata.json b/Packs/MicrosoftTeams/pack_metadata.json
index 60113c5f7572..7f3a3fb69cbc 100644
--- a/Packs/MicrosoftTeams/pack_metadata.json
+++ b/Packs/MicrosoftTeams/pack_metadata.json
@@ -2,7 +2,7 @@
"name": "Microsoft Teams",
"description": "Send messages and notifications to your team members.",
"support": "xsoar",
- "currentVersion": "1.5.14",
+ "currentVersion": "1.5.15",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",