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
- You are standing up a central security workspace in a dedicated
sec/managementsubscription and need it Sentinel-enabled the moment it is created. - You run a landing-zone / platform model and want every spoke or environment to onboard to Sentinel identically via CI, with no portal clicks.
- You need customer-managed RBAC for the SOC so that Sentinel roles are governed explicitly rather than implicitly through workspace contributors.
- You want retention and SKU guardrails baked in (e.g. a minimum retention so compliance evidence is not lost) and enforced through
terraform plan. - You are migrating a long-lived, hand-built workspace into IaC and want to adopt the existing Sentinel onboarding without re-creating data.
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 config — live/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 config — live/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
- Set a retention floor and an ingestion cap deliberately. Sentinel bills per GB ingested and on top of Log Analytics analytics-tier costs; pair a compliance-driven
retention_in_days(e.g. 90–180) with a sensibledaily_quota_gbso a misbehaving log source cannot blow the budget overnight. For data you must keep for years, use long-term retention / archive and data export instead of inflating interactive retention. - One Sentinel workspace per tenant where you can. Cross-workspace hunting is possible but adds query cost and complexity; centralize into a single security workspace in a dedicated management/security subscription and fan diagnostics into it.
- Enable customer-managed RBAC and least privilege. Turn on
customer_managed_rbac_enabled, then grant the SOC the purpose-built Microsoft Sentinel roles (Reader, Responder, Contributor) rather than Log Analytics Contributor — analysts who only triage should never be able to delete the workspace or its tables. - Lock the workspace down at the network edge. For sensitive environments set
internet_ingestion_enabledandinternet_query_enabledto false and front the workspace with Azure Monitor Private Link Scope (AMPLS) so security logs never traverse the public internet. - Deploy the SecurityInsights solution with the onboarding, not after. Keep
deploy_security_insights_solution = trueso detections, watchlists, and Sentinel tables are present the moment the workspace is enabled; the module’sdepends_onordering guarantees the solution attaches to an already-onboarded workspace. - Name and tag for SOC ownership. Use a clear, regioned convention such as
law-sec-<env>-<region>and tag withworkload = securityand an explicitowner, so the security workspace is unmistakable in cost reports and access reviews and is never mistaken for an app-team logging workspace.