Automating Azure Sentinel Workflows: A Deep Dive into Logic Apps, API Connections, and Custom Triggers

Tejas C S
KPMG UK Engineering
7 min readJan 17, 2024

Simplify Security with Logic: A Step-by-Step Journey into Azure Sentinel Automation

Overview

Azure Logic Apps is a cloud-based service that allows you to build and orchestrate scalable workflows for integrating applications, data, systems, and services across cloud and on-premises environments. Logic Apps provide a visual designer for creating workflows, making it easy to design, automate, and manage complex business processes.

Key Concepts:

Workflow: A Logic App workflow is a series of steps that define the automation process. It consists of triggers, actions, and control flow.

Triggers: Triggers define the event or condition that starts the execution of a Logic App. They can be based on various events, such as HTTP requests, timer schedules, or external services like Azure Sentinel incidents.

Actions: Actions are the individual steps within a Logic App workflow. Each action performs a specific operation, such as sending an email, updating a database, or making an HTTP request.

API Connections:

API connections in Azure Logic Apps provide a secure and standardized way to connect to external services, APIs, or systems. They encapsulate the connection details and authentication parameters required to interact with the target service.

Key Concepts:

Managed API: Azure Sentinel leverages managed APIs, which are pre-configured connectors to various services. In this case, the azuresentinel managed API are preconfigured connectors serve as gateways, streamlining the connection process and simplifying integration with Azure Sentinel.

Authentication: API connections handle authentication details, such as client ID, client secret, and tenant ID, providing a secure way to interact with external services.

Section 1: Setting the Foundation with Managed API

To kickstart our automation journey, we begin by referencing the necessary managed API and configuring the client settings. The following data blocks ensure that we have the essential groundwork for creating a seamless connection to Azure Sentinel.

data "azurerm_client_config" "current" {}

data "azurerm_managed_api" "azuresentinel" {
name = "azuresentinel"
location = var.location
}

In these blocks, we extract client configuration details and retrieve information about the Azure Sentinel managed API, setting the stage for subsequent resource creation.

Section 2: Establishing the API Connection

Establishing the API connection for Azure Sentinel, ensures a secure bridge between our Logic App and these services. In this process, we define authentication parameters and connection specifics, enabling seamless and authenticated interactions with Azure Sentinel

resource "azurerm_api_connection" "sentinel_api_connection" {
name = "qcyber-sentinel-api-connection"
resource_group_name = var.resource_group_name
managed_api_id = data.azurerm_managed_api.azuresentinel.id
display_name = "qcyber-sentinel-automation"

parameter_values = {
"token:TenantId" = data.azurerm_client_config.current.tenant_id,
"token:clientId" = data.azurerm_client_config.current.client_id,
"token:clientSecret" = var.client_secret,
"token:resourceUri" = "https://management.core.windows.net/",
"token:grantType" = "client_credentials"
}

lifecycle {
ignore_changes = [
parameter_values
]
}
}

This resource block encapsulates the configuration details for our API connection, ensuring a secure and authenticated link to Azure Sentinel.

Section 3: Crafting the Logic App Workflow

With the foundational elements in place, we proceed to construct our automated workflow using Azure Logic Apps through the definition of a primary resource block.

resource "azurerm_logic_app_workflow" "sentinel_case_log_la" {
name = "sentinel-case-log-la"
location = var.location
resource_group_name = var.resource_group_name
depends_on = [azurerm_api_connection.sentinel_api_connection]

workflow_parameters = {
"$connections" = jsonencode({
"defaultValue" : {},
"type" : "Object"
})
}

parameters = {
"$connections" = jsonencode({
"azuresentinel_1" : {
"connectionId" : "${azurerm_api_connection.sentinel_api_connection.id}",
"connectionName" : "${azurerm_api_connection.sentinel_api_connection.display_name}",
"id" : "${azurerm_api_connection.sentinel_api_connection.managed_api_id}",
"clarityApiDomain" : "${var.clarityApiDomain}",
"clarityWebhookCode" : "${var.clarityWebhookCode}",
"clarityCustomerCode" : "${var.clarityCustomerCode}",
"clarityCustomerSecret" : "${var.clarityCustomerSecret}"
}
})
}
}

This block outlines the fundamental parameters and dependencies that characterize the central structure of the Logic App. including API connection and specific variables crucial for seamless integration.

Section 4: Custom Trigger for Sentinel Incidents

A Logic App is only as powerful as its triggers. In this section, we define a custom trigger that responds specifically to Azure Sentinel incidents.

resource "azurerm_logic_app_trigger_custom" "sentinel_case_log_la_trigger" {
name = "When_Azure_Sentinel_incident_creation_rule_was_triggered"
logic_app_id = azurerm_logic_app_workflow.sentinel_case_log_la.id
depends_on = [azurerm_logic_app_workflow.sentinel_case_log_la_la]

body = <<BODY
{
"type": "ApiConnectionWebhook",
"inputs": {
"body": {
"callback_url": "@{listCallbackUrl()}"
},
"host": {
"connection": {
"name": "@parameters('$connections')['azuresentinel_1']['connectionId']"
}
},
"path": "/incident-creation"
}
}
BODY
}

This resource block defines a trigger which is a custom webhook triggered by Azure Sentinel incidents. The Logic App listens for specific events and initiates the workflow when an incident is created. by utilizing an API connection webhook, ensuring a targeted and efficient workflow.

Section 5: Custom Actions for Intelligent Response

Automating incident response requires intelligent actions. Here, we define custom actions within our Logic App to handle incidents based on specified conditions.

resource "azurerm_logic_app_action_custom" "should_be_ingested_action" {
name = "Should_be_ingested"
logic_app_id = azurerm_logic_app_workflow.sentinel_case_log_la.id
depends_on = [azurerm_logic_app_workflow.sentinel_case_log_la]

body = <<BODY
{
"expression": {
"and": [
{
"not": {
"equals": [
"@triggerBody()?['object']?['properties']?['severity']",
"Informational"
]
}
}
]
},
"runAfter": {},
"type": "If",
"actions": {
"Add_comment_to_incident_(V3)": {
"runAfter": {
"Compose": [
"Succeeded"
]
},
"type": "ApiConnection",
"inputs": {
"body": {
"incidentArmId": "@triggerBody()?['object']?['id']",
"message": "<p>This Azure Sentinel incident has been logged for review by the Cyber Security Operations Centre, ticket number @{outputs('Compose')}. Please see <url> for more information.</p>"
},
"host": {
"connection": {
"name": "@parameters('$connections')['azuresentinel_1']['connectionId']"
}
},
"method": "post",
"path": "/Incidents/Comment"
}
},
"Compose": {
"runAfter": {
"Parse_JSON": [
"Succeeded"
]
},
"type": "Compose",
"inputs": "@body('Parse_JSON')?['jira']?['key']"
},
"HTTP_Webhook": {
"runAfter": {},
"type": "HttpWebhook",
"runtimeConfiguration": {
"secureData": {
"properties": [
"inputs",
"outputs"
]
}
},
"inputs": {
"subscribe": {
"body": {
"CallBackURL": "@listCallbackUrl()",
"Incident": "@triggerBody()"
},
"method": "POST",
"uri": "${var.uri}"
},
"unsubscribe": {}
}
},
"Parse_JSON": {
"runAfter": {
"HTTP_Webhook": [
"Succeeded"
]
},
"type": "ParseJson",
"inputs": {
"content": "@body('HTTP_Webhook')",
"schema": {
"properties": {
"clarity": {
"properties": {
"id": {
"type": "string"
}
},
"type": "object"
},
"jira": {
"properties": {
"id": {
"type": "string"
},
"key": {
"type": "string"
},
"self": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
}
}
}
}
BODY
}

This block defines intelligent actions within the Logic App, ensuring a dynamic and context-aware incident response.

  • Checks the severity of the incident and proceeds only if the severity is not “Informational.”
  • Posts a comment to the incident, providing information for review by the Cyber Security Operations Centre (SOC).
  • Makes an HTTP request to an external system, subscribing and unsubscribing to a webhook.

Section 6: Monitoring and Diagnostics

The final piece of the code involves setting up monitoring and diagnostics for our Logic App. This resource block configures diagnostic settings to capture runtime behavior logs and metrics.

resource "azurerm_monitor_diagnostic_setting" "sentinel_case_log_la_diagnostics_settings" {
depends_on = [azurerm_logic_app_workflow.sentinel_case_log_la]
name = "sentinel_case_log_la_diagnostics_settings"
target_resource_id = azurerm_logic_app_workflow.sentinel_case_log_la.id
log_analytics_workspace_id = var.log_analytics_workspace_id

enabled_log {
category = "WorkflowRuntime"
}

metric {
category = "AllMetrics"
}
}

This resource block ensures that we have comprehensive insights into the performance and behavior of our Logic App.

Finalized sentinel-case-log-la logic app workflow

Section 7: Enhancing Incident Closure Automation

To complete our automated incident response framework, let’s introduce an additional Logic App tailored for incident closure.

resource "azurerm_logic_app_workflow" "sentinel_case_close_la" {
name = "sentinel-case-close-la"
location = var.location
resource_group_name = var.resource_group_name
depends_on = [azurerm_api_connection.sentinel_api_connection]

workflow_parameters = {
"$connections" = jsonencode({
"defaultValue" : {},
"type" : "Object"
})
}

parameters = {
"$connections" = jsonencode({
"azuresentinel_1" : {
"connectionId" : "${azurerm_api_connection.sentinel_api_connection.id}",
"connectionName" : "${azurerm_api_connection.sentinel_api_connection.display_name}",
"id" : "${azurerm_api_connection.sentinel_api_connection.managed_api_id}"
}
})
}
}

This block introduces a new Logic App specifically designed for incident closure, providing a comprehensive solution for end-to-end incident management.

Section 8: Custom Trigger for Incident Closure

As with incident creation, incident closure requires a trigger. Here, we define a custom trigger that responds to HTTP requests for incident closure.

resource "azurerm_logic_app_trigger_custom" "sentinel_case_close_la_trigger" {
name = "When_a_HTTP_request_is_received"
logic_app_id = azurerm_logic_app_workflow.sentinel_case_close_la.id
depends_on = [azurerm_logic_app_workflow.sentinel_case_close_la]

body = <<BODY
{
"type": "Request",
"kind": "Http",
"inputs": {
"schema": {
"properties": {
"alertId": {
"type": "string"
},
"classification": {
"type": "string"
},
"reason": {
"type": "string"
},
"resourceGroup": {
"type": "string"
},
"subscriptionId": {
"type": "string"
},
"workspaceId": {
"type": "string"
}
},
"type": "object"
}
}
}
BODY
}

This resource block defines a trigger for incident closure, ensuring that the Logic App responds appropriately to external requests for closing incidents.

Section 9: Custom Action for Incident Closure

Closing an incident involves specific actions. Let’s define a custom action within our Logic App to handle incident closure based on the provided parameters.

resource "azurerm_logic_app_action_custom" "sentinel_case_close_la_action" {
name = "Update_incident"
logic_app_id = azurerm_logic_app_workflow.sentinel_case_close_la.id
depends_on = [azurerm_logic_app_workflow.sentinel_case_close_la]

body = <<BODY
{
"runAfter": {},
"type": "ApiConnection",
"inputs": {
"body": {
"classification": {
"ClassificationAndReason": "@triggerBody()?['classification']",
"ClassificationReasonText": "@triggerBody()?['reason']"
},
"incidentArmId": "@triggerBody()?['alertId']",
"ownerAction": "Unassign",
"status": "Closed"
},
"host": {
"connection": {
"name": "@parameters('$connections')['azuresentinel_1']['connectionId']"
}
},
"method": "put",
"path": "/Incidents"
}
}
BODY
}

This block defines a custom action for incident closure, ensuring that the Logic App updates the incident status and classification based on the provided parameters.

  • Updates the status and classification of the incident to “Closed” based on parameters provided in the HTTP request.
  • Performs actions such as unassigning the incident and updating the classification reasons.

Section 10: Monitoring and Diagnostics for Incident Closure

To complete our incident closure framework, let’s configure monitoring and diagnostics for the Logic App handling incident closure.

resource "azurerm_monitor_diagnostic_setting" "sentinel_case_close_la_diagnostics_settings" {
depends_on = [azurerm_logic_app_workflow.sentinel_case_close_la]
name = "sentinel_case_close_la_diagnostics_settings"
target_resource_id = azurerm_logic_app_workflow.sentinel_case_close_la.id
log_analytics_workspace_id = var.log_analytics_workspace_id

enabled_log {
category = "WorkflowRuntime"
}

metric {
category = "AllMetrics"
}
}

This resource block ensures that we have comprehensive insights into the performance and behavior of the Logic App handling incident closure.

Finalized sentinel-case-close-la logic app workflow

In summary, this guide provides a detailed exploration of automating Azure Sentinel workflows with Logic Apps, custom triggers, and API connections. Leveraging these tools empowers security operations to enhance efficiency and responsiveness and overall cybersecurity

Thanks for your time. Happy Learning !

Follow me on LinkedIn:
https://www.linkedin.com/in/tejas-c-s-439a021b1/

--

--

Tejas C S
KPMG UK Engineering

DevOps | DevSecOps | Microsoft Azure Passionate DevOps engineer specializing in Azure cloud infrastructure, Terraform, GitHub Actions & PowerShell scripting