IaC Azure

Terraform Module: Azure Microsoft Sentinel — One-shot SIEM onboarding with managed-RBAC governance

Quick take — A reusable Terraform module that onboards a Log Analytics workspace to Microsoft Sentinel, enforces customer-managed RBAC, and wires the SecurityInsights solution for production-grade SIEM/SOAR. New here? Jump to the Quickstart below to deploy it in minutes; read on for how it works and when to reach for it.

Quickstart (copy-paste)

Minimal, runnable configuration — drop this in a .tf file and fill in the "..." placeholders (each required input is commented):

provider "azurerm" {
  features {}
}

module "sentinel" {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-azure-sentinel?ref=v1.0.0"

  workspace_name      = "..."  # Name of the Log Analytics workspace to create or onboar…
  resource_group_name = "..."  # Resource group that holds (or will hold) the workspace.
  location            = "..."  # Azure region for the workspace and solution.
}

Then terraform init && terraform apply. Every other input has a sensible default — see Inputs below to override behaviour.

What this module is

Microsoft Sentinel is Azure’s cloud-native SIEM and SOAR platform. It does not run on its own dedicated store — it is enabled on top of a Log Analytics workspace (LAW), and that LAW becomes the security data lake where analytics rules, hunting queries, watchlists, and automation playbooks operate. “Turning Sentinel on” is therefore an onboarding operation against an existing workspace, not the creation of a separate Sentinel resource.

In the hashicorp/azurerm provider, that onboarding is expressed by azurerm_sentinel_log_analytics_workspace_onboarding. This single resource is what flips a plain Log Analytics workspace into a Sentinel-enabled one. Its two production-relevant knobs are the workspace_id association and the customer_managed_key_enabled flag, which opts the workspace into customer-managed RBAC so Sentinel-specific roles are governed explicitly rather than inherited solely from the underlying Log Analytics workspace.

Wrapping this in a reusable module matters because, in practice, “enabling Sentinel” is never a one-liner. A real deployment needs the workspace itself with a sane retention and SKU, the onboarding resource, the SecurityInsights solution so the Sentinel content and tables light up, and a consistent way to tag and name the security workspace across every subscription. A module gives every team the same compliant SIEM footprint — same retention floor, same RBAC posture, same diagnostic wiring — instead of a hand-clicked workspace that silently drops logs after 30 days.

When to use it

Do not reach for this module if you only need raw operational logging with no detections — a bare azurerm_log_analytics_workspace is cheaper and simpler. Sentinel adds per-GB ingestion cost on top of Log Analytics, so onboard deliberately.

Module structure

terraform-module-azure-sentinel/
├── versions.tf      # provider + Terraform version pins
├── main.tf          # workspace + Sentinel onboarding + SecurityInsights solution
├── variables.tf     # var-driven inputs with validation
└── outputs.tf       # ids/names + key attributes for downstream wiring
# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}
# main.tf

# Resolve a workspace: either create one, or bind onboarding to a pre-existing one.
resource "azurerm_log_analytics_workspace" "this" {
  count = var.create_workspace ? 1 : 0

  name                = var.workspace_name
  location            = var.location
  resource_group_name = var.resource_group_name

  sku               = var.workspace_sku
  retention_in_days = var.retention_in_days

  # Daily ingestion cap protects the budget; -1 disables it (use with care).
  daily_quota_gb = var.daily_quota_gb

  # Sentinel workspaces should not be deletable without a deliberate change.
  internet_ingestion_enabled = var.internet_ingestion_enabled
  internet_query_enabled     = var.internet_query_enabled

  tags = var.tags
}

data "azurerm_log_analytics_workspace" "existing" {
  count = var.create_workspace ? 0 : 1

  name                = var.workspace_name
  resource_group_name = var.resource_group_name
}

locals {
  workspace_id = var.create_workspace ? azurerm_log_analytics_workspace.this[0].id : data.azurerm_log_analytics_workspace.existing[0].id
  workspace_nm = var.create_workspace ? azurerm_log_analytics_workspace.this[0].name : data.azurerm_log_analytics_workspace.existing[0].name
}

# The core: flip the workspace into a Microsoft Sentinel workspace.
resource "azurerm_sentinel_log_analytics_workspace_onboarding" "this" {
  workspace_id = local.workspace_id

  # When true, Sentinel RBAC is managed explicitly (customer-managed RBAC)
  # rather than inherited only from Log Analytics workspace roles.
  customer_managed_key_enabled = var.customer_managed_rbac_enabled
}

# Install the SecurityInsights solution so Sentinel content/tables are available.
# Depends on onboarding so the solution attaches to an enabled Sentinel workspace.
resource "azurerm_log_analytics_solution" "security_insights" {
  count = var.deploy_security_insights_solution ? 1 : 0

  solution_name         = "SecurityInsights"
  location              = var.location
  resource_group_name   = var.resource_group_name
  workspace_resource_id = local.workspace_id
  workspace_name        = local.workspace_nm

  plan {
    publisher = "Microsoft"
    product   = "OMSGallery/SecurityInsights"
  }

  tags = var.tags

  depends_on = [azurerm_sentinel_log_analytics_workspace_onboarding.this]
}
# variables.tf

variable "workspace_name" {
  description = "Name of the Log Analytics workspace to create or onboard to Sentinel."
  type        = string

  validation {
    condition     = can(regex("^[A-Za-z0-9][A-Za-z0-9-]{2,61}[A-Za-z0-9]$", var.workspace_name))
    error_message = "workspace_name must be 4-63 chars, alphanumeric or hyphen, and not start/end with a hyphen."
  }
}

variable "resource_group_name" {
  description = "Resource group that holds (or will hold) the workspace."
  type        = string
}

variable "location" {
  description = "Azure region for the workspace and solution (e.g. centralindia, eastus)."
  type        = string
}

variable "create_workspace" {
  description = "Create a new Log Analytics workspace (true) or onboard an existing one (false)."
  type        = bool
  default     = true
}

variable "workspace_sku" {
  description = "Log Analytics SKU. PerGB2018 is the standard pay-as-you-go SKU for Sentinel."
  type        = string
  default     = "PerGB2018"

  validation {
    condition     = contains(["PerGB2018", "CapacityReservation", "Free", "Standalone"], var.workspace_sku)
    error_message = "workspace_sku must be one of PerGB2018, CapacityReservation, Free, or Standalone."
  }
}

variable "retention_in_days" {
  description = "Interactive retention for the security workspace. Compliance often requires >= 90 days."
  type        = number
  default     = 90

  validation {
    condition     = var.retention_in_days >= 30 && var.retention_in_days <= 730
    error_message = "retention_in_days must be between 30 and 730 (use a data export / archive tier for longer)."
  }
}

variable "daily_quota_gb" {
  description = "Daily ingestion cap in GB to protect spend. Use -1 to disable the cap."
  type        = number
  default     = -1

  validation {
    condition     = var.daily_quota_gb == -1 || var.daily_quota_gb > 0
    error_message = "daily_quota_gb must be -1 (unlimited) or a positive number of GB."
  }
}

variable "customer_managed_rbac_enabled" {
  description = "Enable customer-managed RBAC for Sentinel instead of relying solely on inherited workspace roles."
  type        = bool
  default     = false
}

variable "deploy_security_insights_solution" {
  description = "Install the SecurityInsights solution so Sentinel content and tables are provisioned."
  type        = bool
  default     = true
}

variable "internet_ingestion_enabled" {
  description = "Allow log ingestion from public networks. Set false when using Private Link / AMPLS."
  type        = bool
  default     = true
}

variable "internet_query_enabled" {
  description = "Allow workspace queries from public networks. Set false to force private-only access."
  type        = bool
  default     = true
}

variable "tags" {
  description = "Tags applied to the workspace and solution."
  type        = map(string)
  default     = {}
}
# outputs.tf

output "workspace_id" {
  description = "Resource ID of the Sentinel-enabled Log Analytics workspace."
  value       = local.workspace_id
}

output "workspace_name" {
  description = "Name of the Sentinel-enabled Log Analytics workspace."
  value       = local.workspace_nm
}

output "sentinel_onboarding_id" {
  description = "Resource ID of the Sentinel workspace onboarding resource."
  value       = azurerm_sentinel_log_analytics_workspace_onboarding.this.id
}

output "customer_managed_rbac_enabled" {
  description = "Whether customer-managed RBAC was enabled on the Sentinel workspace."
  value       = azurerm_sentinel_log_analytics_workspace_onboarding.this.customer_managed_key_enabled
}

output "security_insights_solution_id" {
  description = "Resource ID of the SecurityInsights solution, or null if not deployed."
  value       = try(azurerm_log_analytics_solution.security_insights[0].id, null)
}

How to use it

module "microsoft_sentinel" {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-azure-sentinel?ref=v1.0.0"

  workspace_name      = "law-sec-prod-cin"
  resource_group_name = azurerm_resource_group.security.name
  location            = "centralindia"

  create_workspace                  = true
  workspace_sku                     = "PerGB2018"
  retention_in_days                 = 180
  daily_quota_gb                    = 25
  customer_managed_rbac_enabled     = true
  deploy_security_insights_solution = true

  # Private-only security workspace fronted by AMPLS.
  internet_ingestion_enabled = false
  internet_query_enabled     = false

  tags = {
    environment = "prod"
    workload    = "security"
    owner       = "soc-team"
    cost_center = "secops"
  }
}

# Downstream: route a key vault's diagnostics into the Sentinel workspace,
# consuming the module's workspace_id output.
resource "azurerm_monitor_diagnostic_setting" "kv_to_sentinel" {
  name                       = "kv-diag-to-sentinel"
  target_resource_id         = azurerm_key_vault.app.id
  log_analytics_workspace_id = module.microsoft_sentinel.workspace_id

  enabled_log {
    category = "AuditEvent"
  }

  metric {
    category = "AllMetrics"
  }
}

With Terragrunt

Terragrunt keeps this module DRY across environments — define the backend and provider once in a root config, then a thin terragrunt.hcl per environment supplies only the inputs that differ.

1. Root configlive/terragrunt.hcl (inherited by every module):

remote_state {
  backend = "azurerm"
  generate = { path = "backend.tf", if_exists = "overwrite" }
  config = {
    # ...azurerm state bucket/container + key per path...
  }
}

2. Module configlive/prod/sentinel/terragrunt.hcl:

include "root" {
  path = find_in_parent_folders()
}

terraform {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-azure-sentinel?ref=v1.0.0"
}

inputs = {
  workspace_name = "..."
  resource_group_name = "..."
  location = "..."
}

3. Deploy one environment, or roll out all modules together:

cd live/prod/sentinel && terragrunt apply        # this module
terragrunt run-all apply                      # every module under live/prod

Why Terragrunt here: the backend and provider live in one place instead of being copy-pasted into every module; inputs is overridden per environment (dev / stage / prod) without forking the module; and run-all orchestrates dependencies across modules. Reach for it once you have more than one environment or more than a handful of modules — for a single stack, the plain Quickstart above is enough.

Inputs

Name Type Default Required Description
workspace_name string Yes Name of the Log Analytics workspace to create or onboard to Sentinel (4–63 chars, validated).
resource_group_name string Yes Resource group that holds (or will hold) the workspace.
location string Yes Azure region for the workspace and solution.
create_workspace bool true No Create a new workspace (true) or onboard an existing one (false).
workspace_sku string "PerGB2018" No Log Analytics SKU; validated against the supported set.
retention_in_days number 90 No Interactive retention in days (validated 30–730).
daily_quota_gb number -1 No Daily ingestion cap in GB; -1 disables the cap.
customer_managed_rbac_enabled bool false No Enable customer-managed RBAC for Sentinel instead of inherited workspace roles.
deploy_security_insights_solution bool true No Install the SecurityInsights solution to provision Sentinel content/tables.
internet_ingestion_enabled bool true No Allow log ingestion from public networks; set false for Private Link / AMPLS.
internet_query_enabled bool true No Allow workspace queries from public networks; set false to force private-only access.
tags map(string) {} No Tags applied to the workspace and solution.

Outputs

Name Description
workspace_id Resource ID of the Sentinel-enabled Log Analytics workspace.
workspace_name Name of the Sentinel-enabled Log Analytics workspace.
sentinel_onboarding_id Resource ID of the Sentinel workspace onboarding resource.
customer_managed_rbac_enabled Whether customer-managed RBAC was enabled on the Sentinel workspace.
security_insights_solution_id Resource ID of the SecurityInsights solution, or null if not deployed.

Enterprise scenario

A regulated fintech runs a hub-and-spoke landing zone and consolidates all security telemetry into a single workspace in the sub-mgmt-security subscription. The platform team consumes this module in their pipeline to stand up law-sec-prod-cin with 180-day retention, a 25 GB/day ingestion cap, customer-managed RBAC for the SOC, and a private-only network posture behind Azure Monitor Private Link Scope. Every spoke subscription then points its diagnostic settings at the exported workspace_id, so Defender, NSG flow, Key Vault audit, and Entra ID sign-in logs all land in one Sentinel instance that the SOC can hunt across — with the retention floor and cost cap guaranteed by terraform plan rather than tribal knowledge.

Best practices

TerraformAzureMicrosoft SentinelModuleIaC
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading