Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import json
from azure.storage.blob import BlobServiceClient
from azure.core.exceptions import ResourceNotFoundError
from azure.identity import DefaultAzureCredential
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential # Import ManagedIdentityCredential
from azure.keyvault.secrets import SecretClient
import os

Expand Down Expand Up @@ -154,7 +154,16 @@ def main(mytimer: func.TimerRequest) -> None:
raise Exception("Lookout: Invalid Log Analytics Uri.")
try:
logging.debug("Initializing Azure credentials and secret client.")
credential = DefaultAzureCredential()

# Read client ID from environment variable
client_id = os.environ.get('AZURE_CLIENT_ID')
if not client_id:
logging.warning("AZURE_CLIENT_ID environment variable not set. Falling back to DefaultAzureCredential.")
credential = DefaultAzureCredential()
else:
credential = ManagedIdentityCredential(client_id=client_id)
logging.info(f"Using ManagedIdentityCredential with client ID: {client_id}")

secret_client = SecretClient(vault_url=f"https://{key_vault_name}.vault.azure.net", credential=credential)
try:
secret_name = "environment-endpoint-url"
Expand Down Expand Up @@ -604,40 +613,78 @@ def fetch_file_details(job_id, subclient_id) -> tuple[list, list]:
"""

folders_list = []
if job_id is None:
try:
if job_id is None:
return [], []
files_list = []
try:
files_list = get_files_list(job_id)
except Exception as e:
logging.warning(f"Error in get_files_list for job_id {job_id}: {e}")
files_list = []
folder_response = []
try:
folder_response = get_subclient_content_list(subclient_id)
except Exception as e:
logging.warning(f"Error in get_subclient_content_list for subclient_id {subclient_id}: {e}")
folder_response = []
if folder_response:
for resp in folder_response:
try:
folders_list.append(resp.get(Constants.path_key, ""))
except Exception as e:
logging.warning(f"Error extracting path_key from folder_response: {e}")
return files_list, folders_list
except Exception as e:
logging.warning(f"Error in fetch_file_details: {e}")
return [], []
files_list = get_files_list(job_id)
folder_response = get_subclient_content_list(subclient_id)
if folder_response:
for resp in folder_response:
folders_list.append(resp[Constants.path_key])
return files_list, folders_list


def get_job_details(job_id, url, headers):
f_url = f"{url}/Job/{job_id}"
logging.info(f"Headers keys for Job details request: {list(headers.keys())}")
response = requests.get(f_url, headers=headers)
data = response.json()
if ("totalRecordsWithoutPaging" in data) and (
int(data["totalRecordsWithoutPaging"]) > 0
):
logging.info(f"Job Details for job_id : {job_id}")
logging.info(data)
return data
else:
logging.info(f"Failed to get Job Details for job_id : {job_id}")
logging.info(data)
try:
if not job_id:
logging.warning("job_id is missing or empty in get_job_details.")
return None
f_url = f"{url}/Job/{job_id}"
logging.info(f"Headers keys for Job details request: {list(headers.keys())}")
response = requests.get(f_url, headers=headers)
data = response.json()
if ("totalRecordsWithoutPaging" in data) and (
int(data["totalRecordsWithoutPaging"]) > 0
):
logging.info(f"Job Details for job_id : {job_id}")
logging.info(data)
return data
else:
logging.info(f"Failed to get Job Details for job_id : {job_id}")
logging.info(data)
return None
except Exception as e:
logging.warning(f"Error in get_job_details for job_id {job_id}: {e}")
return None


def get_user_details(client_name):
f_url = f"{url}/Client/byName(clientName='{client_name}')"
logging.info(f"Headers keys for Client byName request: {list(headers.keys())}")
response = requests.get(f_url, headers=headers).json()
user_id = response.get('clientProperties', [{}])[0].get('clientProps', {}).get('securityAssociations', {}).get('associations', [{}])[0].get('userOrGroup', [{}])[0].get('userId', None)
user_name = response.get('clientProperties', [{}])[0].get('clientProps', {}).get('securityAssociations', {}).get('associations', [{}])[0].get('userOrGroup', [{}])[0].get('userName', None)
return user_id, user_name
try:
if not client_name:
logging.warning("originating_client is missing or empty.")
return None, None
f_url = f"{url}/Client/byName(clientName='{client_name}')"
logging.info(f"Headers keys for Client byName request: {list(headers.keys())}")
response = requests.get(f_url, headers=headers).json()
user_id = None
user_name = None
try:
user_id = response.get('clientProperties', [{}])[0].get('clientProps', {}).get('securityAssociations', {}).get('associations', [{}])[0].get('userOrGroup', [{}])[0].get('userId', None)
except Exception as e:
logging.warning(f"Error extracting user_id: {e}")
user_id = None
try:
user_name = response.get('clientProperties', [{}])[0].get('clientProps', {}).get('securityAssociations', {}).get('associations', [{}])[0].get('userOrGroup', [{}])[0].get('userName', None)
except Exception as e:
logging.warning(f"Error extracting user_name: {e}")
user_name = None
return user_id, user_name
except Exception as e:
logging.warning(f"Error in get_user_details for client_name {client_name}: {e}")
return None, None


def get_incident_details(message: str) -> dict | None:
Expand Down Expand Up @@ -670,12 +717,11 @@ def get_incident_details(message: str) -> dict | None:
job_details = get_job_details(job_id,url,headers)
if job_details is None:
print(f"Invalid job [{job_id}]")
return None
job_start_time = int(
job_details.get("jobs", [{}])[0].get("jobSummary", {}).get("jobStartTime")
job_details.get("jobs", [{}])[0].get("jobSummary", {}).get("jobStartTime",0)
)
job_end_time = int(
job_details.get("jobs", [{}])[0].get("jobSummary", {}).get("jobEndTime")
job_details.get("jobs", [{}])[0].get("jobSummary", {}).get("jobEndTime",0)
)
subclient_id = (
job_details.get("jobs", [{}])[0]
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@
},
{
"title": "",
"description": ">**(Optional Step)** Securely store workspace and API authorization key(s) or token(s) in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
"description": ">Securely store workspace and API authorization key(s) or token(s) in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
},
{
"title": "",
"description": "**STEP 1 - Configuration steps for the Commvalut QSDK Token**\n\n[Follow these instructions](https://docs.microsoft.com/cloud-app-security/api-authentication) to create an API Token."
"description": "**STEP 1 - Configuration steps for the Commvalut QSDK Token**\n\n[Follow these instructions](https://documentation.commvault.com/2024e/essential/creating_access_token.html) to create an API Token."
},
{
"title": "",
"description": "**STEP 2 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Function**\n\n>**IMPORTANT:** Before deploying the CommvaultSecurityIQ data connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Commvault Endpoint URL and QSDK Token, readily available.",
"description": "**STEP 2 - Deploy the connector and the associated Azure Function**\n\n>**IMPORTANT:** Before deploying the CommvaultSecurityIQ data connector, have the Workspace ID and Workspace Primary Key (can be copied from the following), as well as the Commvault Endpoint URL and QSDK Token, readily available.",
"instructions": [
{
"parameters": {
Expand All @@ -111,23 +111,7 @@
},
{
"title": "",
"description": "**Option 1 - Azure Resource Manager (ARM) Template**\n\nUse this method for automated deployment of the Commvault Security IQ data connector.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-CommvaultSecurityIQ-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Location**. \n3. Enter the **Workspace ID**, **Workspace Key** 'and/or Other required fields'. \n>Note: If using Azure Key Vault secrets for any of the values above, use the`@Microsoft.KeyVault(SecretUri={Security Identifier})`schema in place of the string values. Refer to [Key Vault references documentation](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) for further details. \n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy."
},
{
"title": "",
"description": "**Option 2 - Manual Deployment of Azure Functions**\n\n Use the following step-by-step instructions to deploy the CommvaultSecurityIQ data connector manually with Azure Functions."
},
{
"title": "1. Create a Function App",
"description": "1. From the Azure Portal, navigate to [Function App](https://portal.azure.com/#blade/HubsExtension/BrowseResource/resourceType/Microsoft.Web%2Fsites/kind/functionapp).\n2. Click **+ Add** at the top.\n3. In the **Basics** tab, ensure Runtime stack is set to **'Add Required Language'**. \n4. In the **Hosting** tab, ensure **Plan type** is set to **'Add Plan Type'**.\n5. 'Add other required configurations'. \n5. 'Make other preferable configuration changes', if needed, then click **Create**."
},
{
"title": "2. Import Function App Code",
"description": "1. In the newly created Function App, select **Functions** from the navigation menu and click **+ Add**.\n2. Select **Timer Trigger**.\n3. Enter a unique Function **Name** in the New Function field and leave the default cron schedule of every 5 minutes, then click **Create Function**.\n4. Click on the function name and click **Code + Test** from the left pane.\n5. Copy the [Function App Code](<Add GitHub link to Function App code>) and paste into the Function App `run.ps1` editor.\n6. Click **Save**."
},
{
"title": "3. Configure the Function App",
"description": "1. In the Function App screen, click the Function App name and select **Configuration**.\n2. In the **Application settings** tab, select **+ New application setting**.\n3. Add each of the following 'x (number of)' application settings individually, under Name, with their respective string values (case-sensitive) under Value: \n\t\tapiUsername\n\t\tapipassword\n\t\tapiToken\n\t\tworkspaceID\n\t\tworkspaceKey\n\t\turi\n\t\tlogAnalyticsUri (optional)\n(add any other settings required by the Function App)\nSet the `uri` value to: `<add uri value>` \n>Note: If using Azure Key Vault secrets for any of the values above, use the`@Microsoft.KeyVault(SecretUri={Security Identifier})`schema in place of the string values. Refer to [Azure Key Vault references documentation](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) for further details.\n - Use logAnalyticsUri to override the log analytics API endpoint for dedicated cloud. For example, for public cloud, leave the value empty; for Azure GovUS cloud environment, specify the value in the following format: https://<CustomerId>.ods.opinsights.azure.us. \n4. Once all application settings have been entered, click **Save**."
"description": "**Azure Resource Manager (ARM) Template**\n\nUse this method for automated deployment of the Commvault Security IQ data connector.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[![Deploy To Azure](https://aka.ms/deploytoazurebutton)](https://aka.ms/sentinel-CommvaultSecurityIQ-azuredeploy)\n2. Select the preferred **Subscription**, **Resource Group** and **Region**. \n3. Enter the **Workspace ID**, **Workspace Key** 'and/or Other required fields' and click Next. \n4. Click **Create** to deploy."
}
]
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"config": {
"isWizard": false,
"basics": {
"description": "<img src=\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Logos/Commvault-Logo.svg\" width=\"75px\"height=\"75px\">\n\n**Note:** Please refer to the following before installing the solution: \n\n• Review the solution [Release Notes](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Commvault%20Security%20IQ/ReleaseNotes.md)\n\n • There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing.\n\nThis Microsoft Sentinel integration enables Commvault users to ingest alerts and other data into their Microsoft Sentinel instance. With Analytic Rules, Microsoft Sentinel can automatically create Microsoft Sentinel incidents.\n\n**Data Connectors:** 1, **Analytic Rules:** 4, **Playbooks:** 3\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)",
"description": "<img src=\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Logos/Commvault-Logo.svg\" width=\"75px\"height=\"75px\">\n\n**Note:** Please refer to the following before installing the solution: \n\n• Review the solution [Release Notes](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Commvault%20Security%20IQ/ReleaseNotes.md)\n\n • There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing.\n\nThis Microsoft Sentinel integration enables Commvault users to ingest alerts and other data into their Microsoft Sentinel instance. With Analytic Rules, Microsoft Sentinel can automatically create Microsoft Sentinel incidents.\n\n**Data Connectors:** 1, **Analytic Rules:** 1, **Playbooks:** 3\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)",
"subscription": {
"resourceProviders": [
"Microsoft.OperationsManagement/solutions",
Expand Down Expand Up @@ -114,48 +114,6 @@
}
}
]
},
{
"name": "analytic2",
"type": "Microsoft.Common.Section",
"label": "Data Alert",
"elements": [
{
"name": "analytic2-text",
"type": "Microsoft.Common.TextBlock",
"options": {
"text": "This query identifies clients or servers whose data has been compromised."
}
}
]
},
{
"name": "analytic3",
"type": "Microsoft.Common.Section",
"label": "IDP Alert",
"elements": [
{
"name": "analytic3-text",
"type": "Microsoft.Common.TextBlock",
"options": {
"text": "This query identifies indications of a potential security breach or unauthorized access to the systems and data of the Identity Provider."
}
}
]
},
{
"name": "analytic4",
"type": "Microsoft.Common.Section",
"label": "User Alert",
"elements": [
{
"name": "analytic4-text",
"type": "Microsoft.Common.TextBlock",
"options": {
"text": "This query identifies users whose user account or credentials have been compromised."
}
}
]
}
]
},
Expand Down
Loading
Loading