- π Key Highlights
- π οΈ Getting Started
- ποΈ Architecture
- π― Framework Overview
- π Framework Status
- π§ Configuration
- π Quick Start
- ποΈ Component Workflow Diagrams
- π Data Collection Rules (DCRs) & Logs Ingestion API
- π‘οΈ Auto-Generated Detection Wiki
- π Security Considerations
- π Environment Strategy
- ποΈ 11 Core Components: Detection rules (Scheduled, NRT, Microsoft), automation rules, watchlists, playbooks, custom tables, data collection (DCEs/DCRs), data connectors, threat intel rules
- π― Framework Scope: Focuses on Terraform-manageable components for programmatic control
- π Modular Architecture: Individual modules in
modules/directories with comprehensive documentation - π Content Management: Individual YAML files in
content/directories for granular control - βοΈ Single Configuration: Simplified
terraform.tfvarsapproach for easy management - π Auto-Generated Documentation: Comprehensive Detection Wiki with MITRE ATT&CK mapping
- π Data Collection Endpoints: API ingestion capabilities similar to Splunk HEC
- π Remote State Management: Terraform Cloud integration with cross-workspace dependencies
Before deploying the SentinelDaC framework, you'll need to complete the following setup steps:
Create Core Azure Resources:
# 1. Create Azure Subscription (if needed)
# Use Azure Portal or contact your Azure administrator
# 2. Create Resource Group
az group create --name "rg-sentinel-yourenv" --location "Australia East"
# 3. Create Log Analytics Workspace
az monitor log-analytics workspace create --resource-group "rg-sentinel-yourenv" --workspace-name "law-sentinel-yourenv" --location "Australia East" --sku "PerGB2018"
# 4. Enable Microsoft Sentinel
az sentinel workspace create --resource-group "rg-sentinel-yourenv" --workspace-name "law-sentinel-yourenv"Create App Registration & Service Principal:
# 1. Create App Registration
az ad app create --display-name "terraform-sentineldac-yourenv" --query "appId" -o tsv
# Save the returned Application ID
# 2. Create Service Principal
az ad sp create --id "YOUR_APPLICATION_ID_FROM_STEP_1"
# 3. Generate Client Secret
az ad app credential reset --id "YOUR_APPLICATION_ID" --display-name "terraform-secret" --query "password" -o tsv
# Save the returned secret securely
# 4. Assign Contributor Permissions to Resource Group
az role assignment create --assignee "YOUR_APPLICATION_ID" --role "Contributor" --scope "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-sentinel-yourenv"
# 5. Assign Sentinel Contributor Permissions
az role assignment create --assignee "YOUR_APPLICATION_ID" --role "Microsoft Sentinel Contributor" --scope "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-sentinel-yourenv"Set up Terraform Cloud (Free for up to 5 users):
- Create Account: Sign up at https://app.terraform.io
- Create Organization: Choose a unique organization name
- Create Workspace:
- Choose "Version control workflow"
- Connect your forked GitHub repository
- Name your workspace (e.g.,
sentinel-content-yourenv)
Configure Environment Variables (mark as Sensitive):
ARM_CLIENT_ID = "your-service-principal-application-id"
ARM_CLIENT_SECRET = "your-service-principal-secret"
ARM_SUBSCRIPTION_ID = "your-azure-subscription-id"
ARM_TENANT_ID = "your-azure-tenant-id"
Fork and Configure Repository:
# 1. Fork this repository to your GitHub account
# 2. Clone your fork
git clone https://github.com/YOUR_USERNAME/SentinelxTerraformDaC.git
cd SentinelxTerraformDaC
# 3. Update terraform.tfvars with your values
# See SECURITY-SETUP.md for secure configuration guidance
# 4. Update main.tf with your Terraform Cloud organization
# Replace "Kaibersec" with your organization nameRequired terraform.tfvars Configuration:
# Infrastructure Settings - Update with your values
subscription_id = "[YOUR_AZURE_SUBSCRIPTION_ID]"
resource_group_name = "rg-sentinel-yourenv"
log_analytics_workspace_name = "law-sentinel-yourenv"
# Environment Configuration
environment = "dev" # or "staging", "prod"
# Feature Toggles
enable_scheduled_rules = true
enable_nrt_rules = true
enable_microsoft_rules = true
enable_playbooks = true
enable_hunting_queries = true
enable_automation_rules = true
enable_watchlists = true
enable_data_collection = true
enable_data_connectors = trueFor PowerShell Scripts & CI/CD (See SECURITY-SETUP.md for details):
AZURE_TENANT_ID
AZURE_SUBSCRIPTION_ID
AZURE_CLIENT_ID
AZURE_CLIENT_SECRET
# 1. Commit your configuration changes
git add terraform.tfvars
git commit -m "Initial configuration for my environment"
git push origin main
# 2. Terraform Cloud will automatically trigger a plan
# Review the plan in the Terraform Cloud UI
# 3. Approve the apply in Terraform Cloud UI
# This will deploy all 11 framework components
# 4. Verify deployment in Azure Portal
# Check Microsoft Sentinel > Analytics rules, Automation rules, etc.You'll need to open up each of the pipelines and adjust the scheduling also if you want them to run as a cron job.
.github\workflows\detection-wiki-generator.yml
.github\workflows\pull-request-sentinel-templates.yml
This one is manually triggered though, once a pull request is accepted from the detection wiki generator. You would run this to push it to Confluence. Feel free to automate it though if the appetite is there.
.github\workflows\push-wiki-to-confluence.yml
# Test data ingestion (optional)
.\scripts\post-events-to-dce.ps1 -ClientSecret $env:AZURE_CLIENT_SECRET -ShowBody
# Generate detection wiki
.\scripts\generate-detection-wiki-clean.ps1
# Validate in Azure Portal:
# - Analytics Rules: Should see 10 detection rules
# - Automation Rules: Should see 1 active rule
# - Watchlists: Should see 3 lists with data
# - Custom Tables: Should see 11 custom tablesπ Congratulations! Your SentinelDaC framework is now deployed and ready for customization.
Next Steps:
- Review the Quick Start section for common operations
- Explore Component Workflow Diagrams for adding new content
- Check the Auto-Generated Detection Wiki for documentation
SentinelxTerraform-DaC/
βββ modules/ # Reusable Terraform modules
β βββ analytics-rules/ # Scheduled analytics rules module β
β βββ nrt-rules/ # Near Real Time rules module β
β βββ microsoft-rules/ # Microsoft security rules module β
β βββ automation-rules/ # Automation rules module β
β βββ watchlists/ # Watchlists module β
β βββ playbooks/ # Logic Apps (playbooks) module β
β βββ hunting-queries/ # Hunting queries module β
β βββ custom-tables/ # Custom Log Analytics tables module β
β βββ data-collection/ # Data Collection Rules & Endpoints module β
β βββ data-connectors/ # Data connectors module β
β βββ threat-intel-rules/ # Threat intelligence rules module β
βββ content/ # Detection content definitions
β βββ scheduled-rules/ # 5 scheduled analytics rules (YAML)
β βββ nrt-rules/ # 1 near real-time rule (YAML)
β βββ microsoft-rules/ # 5 Microsoft security rules (YAML)
β βββ automation-rules/ # 1 automation rule + 2 disabled (YAML)
β βββ watchlists/ # 3 watchlists (metadata YAML + CSV data files)
β βββ playbooks/ # 3 active playbooks (YAML)
β βββ hunting-queries/ # 1 active hunting query (YAML)
βββ scripts/ # PowerShell deployment & validation scripts
βββ DetectionWiki/ # Auto-generated detection documentation β
β βββ DetectionWiki.md # Comprehensive rule documentation with MITRE ATT&CK mapping
β βββ wiki-metadata.json # Wiki generation metadata and statistics
βββ .github/workflows/ # CI/CD pipelines for automation β
β βββ generate-detection-wiki-clean.ps1 # Wiki generation script
β βββ detection-wiki-generator.yml # Automated wiki generation workflow
βββ *.tf # Terraform configuration files
βββ terraform.tfvars # Single configuration file
This framework follows core principles of modularity, scalability, and centralised variable management to enable efficient deployment and management of Microsoft Sentinel detection content. The framework focuses on 11 core components that are natively manageable through Terraform, providing programmatic control over detection and response capabilities.
Terraform-Managed Components (Active):
- Scheduled Rules - Custom analytics rules with KQL queries (
analytics-rulesmodule) - NRT Rules - Near Real Time detection rules (
nrt-rulesmodule) - Microsoft Rules - Microsoft security product integration rules (
microsoft-rulesmodule) - Automation Rules - Incident assignment and response automation (
automation-rulesmodule) - Watchlists - Threat intelligence and reference data lists (
watchlistsmodule) - Playbooks - Logic Apps for automated response workflows (
playbooksmodule) - Hunting Queries - Proactive threat hunting queries (
hunting-queriesmodule) - Custom Tables - Log Analytics custom tables for data ingestion (
custom-tablesmodule) - Data Collection - Data Collection Rules (DCRs) and Endpoints (DCEs) for log ingestion (
data-collectionmodule) - Data Connectors - Third-party and Microsoft service integrations (
data-connectorsmodule) - Threat Intel Rules - Threat intelligence correlation rules (
threat-intel-rulesmodule)
Architectural Decision: The framework focuses on components that provide the best Terraform integration experience and are most suitable for programmatic management through Infrastructure as Code principles.
- Detection Rules: 10 active (5 scheduled + 1 NRT + 5 Microsoft)
- Automation Rules: 1 active (2 disabled)
- Watchlists: 3 active with CSV data
- Playbooks: 3 active (PowerShell, Python, Logic App)
- Hunting Queries: 1 active
- Custom Tables: 11 active (SecurityEvents_CL, MinimalLogs_CL + 8 ASIM schema tables)
- Data Collection Rules: 11 active (Linux syslog, Windows security, API security events + 8 ASIM schema DCRs)
- Data Collection Endpoints: 2 active (API ingestion, private ingestion)
- Data Connectors: 1 active (Microsoft Threat Intelligence)
- Threat Intel Rules: 0 active (module ready for deployment)
- ASIM Data Ingestion: Complete 8-schema implementation with Custom-Asim* stream naming
- Detection Wiki: Auto-generated documentation with MITRE ATT&CK mapping β
- Framework Components: 11/11 operational β
- Infrastructure: Remote state management with cross-workspace dependencies
The framework uses a single terraform.tfvars file for simplified configuration management. The previous multi-environment directory structure has been removed in favor of a clean, single-configuration approach.
- Organisation: Kaibersec
- Workspace: sentinel-content
- Project: Azure Infrastructure
- Remote State: Managed by Terraform Cloud
- Authentication: Service Principal (ARM_* environment variables)
- Cross-Workspace Dependencies: Consumes infrastructure state from
sentinellab-devworkspace
- Infrastructure Workspace: sentinellab-dev
- Log Analytics Workspace: law-sentinel-dev-2 (deployed via infrastructure workspace)
- Sentinel Instance: Enabled on Log Analytics workspace
- Resource Group: rg-sentinel-dev-2
- Remote State Sharing: Cross-workspace data source integration
π New to SentinelDaC? Start with the comprehensive Getting Started Guide above for complete setup instructions.
For existing deployments, here are common operational tasks:
-
Deploy framework changes:
# Review changes terraform plan # Apply changes terraform apply
-
Add a new detection rule: Create a YAML file in the appropriate
content/directory and redeploy -
Modify existing rules: Edit the YAML files and re-run
terraform applyterraform plan # Apply changes terraform apply # View deployment status terraform output
-
Modify individual rules by editing their YAML files and re-running
terraform apply -
Test ASIM data ingestion with the newly deployed DCRs:
# Test ASIM Network Session ingestion (validate only) .\scripts\post-events-to-dce-asim.ps1 -AsimSchema "NetworkSession" -InputJsonPath "Examples\Logs\network-session-sample.json" -ValidateOnly -ShowBody # Test ASIM Authentication events .\scripts\post-events-to-dce-asim.ps1 -AsimSchema "Authentication" -InputJsonPath "Examples\Logs\auth-sample.json" -ValidateOnly # Ingest real data to Custom-AsimNetworkSession stream .\scripts\post-events-to-dce-asim.ps1 -AsimSchema "NetworkSession" -InputJsonPath "Examples\Logs\network-session-sample.json" -ClientSecret "YOUR_SECRET" # Test all 8 ASIM schemas with retry protection .\scripts\test-all-asim-schemas.ps1 -ClientSecret "YOUR_SECRET" -ValidateOnly
-
Query ASIM data in Log Analytics:
// Query Custom-AsimNetworkSession events AsimNetworkSession_CL | where TimeGenerated > ago(24h) | summarize count() by EventVendor, EventProduct // Query Custom-AsimAuthentication events AsimAuthentication_CL | where TimeGenerated > ago(24h) | where EventResult == "Failure" | project TimeGenerated, ActorUsername, SrcIpAddr, LogonMethod
The framework includes complete Microsoft ASIM (Azure Sentinel Information Model) implementation with dedicated DCRs for optimal performance:
Network & Communication:
- π Network Session (
Custom-AsimNetworkSession) - Network connection events - π DNS Activity (
Custom-AsimDnsActivity) - DNS query and response events - π Web Session (
Custom-AsimWebSession) - HTTP/HTTPS web session events
Authentication & Access:
- π Authentication (
Custom-AsimAuthentication) - Login and authentication events
System Activity:
- βοΈ Process Events (
Custom-AsimProcessEvent) - Process creation and termination - π File Events (
Custom-AsimFileEvent) - File access and modification events - ποΈ Registry Events (
Custom-AsimRegistryEvent) - Windows registry operations
Governance & Compliance:
- π Audit Events (
Custom-AsimAuditEvent) - System and user audit trails
- Dedicated DCRs: Each schema has its own DCR for maximum throughput (2GB/min per schema)
- Custom Stream Naming: Follows Azure Monitor requirements with
Custom-Asim*prefix - Custom Table Integration: Maps to dedicated custom tables with 90-day retention
- API Ingestion Ready: All DCRs use the API ingestion endpoint for external data sources
- Transform Standardization: Simple
sourcetransform for direct data ingestion - Scalable Design: Independent schema processing prevents bottlenecks
These workflows show how to add new components after initial setup. For first-time setup, see Getting Started.
graph LR
A["π<br/>Define<br/>Connector"] --> B["π§<br/>Edit<br/>terraform.tfvars"]
B --> C["π<br/>Add to<br/>connector_configs"]
C --> D["βοΈ<br/>Set Type<br/>& Settings"]
D --> E["π<br/>terraform<br/>apply"]
E --> F["β
<br/>Connector<br/>Active"]
classDef start fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef config fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef action fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000
classDef deploy fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#000
class A start
class B,C config
class D action
class E deploy
class F success
graph LR
A["π<br/>Define<br/>DCR Config"] --> B["π§<br/>Edit<br/>terraform.tfvars"]
B --> C["π<br/>Add to<br/>dcr_configs"]
C --> D["βοΈ<br/>Set Data Type<br/>& Sources"]
D --> E["π―<br/>Configure<br/>Destinations"]
E --> F["π<br/>terraform<br/>apply"]
F --> G["β
<br/>DCR<br/>Deployed"]
classDef start fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef config fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef setup fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000
classDef action fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000
classDef deploy fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#000
class A start
class B,C config
class D,E setup
class F deploy
class G success
graph LR
A["π<br/>Define<br/>DCE Config"] --> B["π§<br/>Edit<br/>terraform.tfvars"]
B --> C["π<br/>Add to<br/>dce_configs"]
C --> D["π<br/>Set Network<br/>Access"]
D --> E["π<br/>Add<br/>Description"]
E --> F["π<br/>terraform<br/>apply"]
F --> G["π<br/>API Endpoint<br/>Ready"]
classDef start fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef config fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef setup fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000
classDef deploy fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#000
class A start
class B,C config
class D,E setup
class F deploy
class G success
graph LR
A["π<br/>Design<br/>Table Schema"] --> B["π§<br/>Edit<br/>terraform.tfvars"]
B --> C["π<br/>Add to<br/>custom_tables"]
C --> D["βοΈ<br/>Define Columns<br/>& Types"]
D --> E["π
<br/>Set Retention<br/>Period"]
E --> F["π<br/>terraform<br/>apply"]
F --> G["β
<br/>Custom Table<br/>Ready"]
classDef start fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef config fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef setup fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000
classDef deploy fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#000
class A start
class B,C config
class D,E setup
class F deploy
class G success
graph LR
A["π<br/>Design<br/>KQL Parser"] --> B["π§ͺ<br/>Test in<br/>Sentinel"]
B --> C["π<br/>Define Stream<br/>Schema"]
C --> D["π§<br/>Update<br/>DCR Config"]
D --> E["βοΈ<br/>Add Stream<br/>Declarations"]
E --> F["π―<br/>Configure<br/>Transformations"]
F --> G["π<br/>terraform<br/>apply"]
G --> H["β
<br/>Parser<br/>Active"]
classDef start fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef test fill:#f1f8e9,stroke:#689f38,stroke-width:2px,color:#000
classDef design fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000
classDef config fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef setup fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000
classDef deploy fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000
classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#000
class A start
class B test
class C design
class D config
class E,F setup
class G deploy
class H success
graph LR
A["π―<br/>Start:<br/>Design Log Schema"] --> B["π<br/>1. Create Custom Table<br/>(modules/custom-tables)"]
B --> C["π<br/>2. Create Data Collection<br/>Endpoint (DCE)"]
C --> D["βοΈ<br/>3. Create Data Collection<br/>Rule (DCR)"]
D --> E["π‘οΈ<br/>4. Create App Registration<br/>& Permissions"]
E --> F["π§<br/>5. Update terraform.tfvars<br/>Configuration"]
F --> G["π<br/>6. Deploy Infrastructure<br/>(terraform apply)"]
G --> H["π<br/>7. Configure PowerShell<br/>Ingestion Script"]
H --> I["π§ͺ<br/>8. Test with Sample<br/>JSON Data"]
I --> J["β
<br/>9. Validate Data Flow<br/>in Log Analytics"]
J --> K["π<br/>10. Query Data<br/>in Sentinel"]
classDef start fill:#e3f2fd,stroke:#1976d2,stroke-width:3px,color:#000
classDef phase1 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef phase2 fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000
classDef phase3 fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000
classDef success fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px,color:#000
class A start
class B,C,D phase1
class E,F,G phase2
class H,I,J phase3
class K success
Standard Operating Procedure Steps:
- π― Design Log Schema: Define data structure, column types, and retention requirements
- π Create Custom Table: Use azapi provider to create custom table with
_CLsuffix - π Create DCE: Deploy Data Collection Endpoint for HTTP ingestion
- βοΈ Create DCR: Configure Data Collection Rule with stream declarations
- π‘οΈ App Registration: Create Azure AD app with "Monitoring Metrics Publisher" role
- π§ Update Configuration: Add all resources to terraform.tfvars
- π Deploy Infrastructure: Execute terraform apply with dependency ordering
- π Configure Script: Set up PowerShell with OAuth2 authentication
- π§ͺ Test Ingestion: Validate with JSON sample data
- β Validate Flow: Confirm data appears in Log Analytics
- π Query in Sentinel: Verify data is available for detection rules
This section covers comprehensive learnings about Azure Monitor Data Collection Rules, custom tables, and the Logs Ingestion API - essential for building scalable data ingestion pipelines similar to Splunk HEC.
Following Microsoft's best practices research, we've implemented one DCR per ASIM schema for optimal performance and scalability:
- β
asim_network_session:
Custom-AsimNetworkSessionβMicrosoft-ASimNetworkSessionLogs - β
asim_dns_activity:
Custom-AsimDnsActivityβMicrosoft-ASimDnsActivityLogs - β
asim_authentication:
Custom-AsimAuthenticationβMicrosoft-ASimAuthenticationEventLogs - β
asim_process_event:
Custom-AsimProcessEventβMicrosoft-ASimProcessEventLogs - β
asim_file_event:
Custom-AsimFileEventβMicrosoft-ASimFileEventLogs - β
asim_registry_event:
Custom-AsimRegistryEventβMicrosoft-ASimRegistryEventLogs - β
asim_web_session:
Custom-AsimWebSessionβMicrosoft-ASimWebSessionLogs - β
asim_audit_event:
Custom-AsimAuditEventβMicrosoft-ASimAuditEventLogs
- π 8x Throughput: Each DCR gets dedicated 2GB/min + 12K requests/min limits (16GB/min total)
- π― Schema Isolation: Independent monitoring, troubleshooting, and access control per schema
- π Microsoft Pattern: Follows Azure Sentinel GitHub repository implementation patterns
- β‘ ASIM Script Ready: Compatible with
post-events-to-dce-asim.ps1auto-stream naming
# Automatic stream resolution in ASIM script
$streamName = "Custom-Asim$schemaName" # e.g., Custom-AsimNetworkSession
$outputStream = "Microsoft-ASim${schemaName}Logs" # e.g., Microsoft-ASimNetworkSessionLogsData Collection Rules (DCRs) are Azure Monitor resources that define:
- What data to collect (stream declarations)
- How to transform it (KQL transformations)
- Where to send it (destinations)
Custom Tables are Log Analytics workspace tables with user-defined schemas that must exist before creating DCRs that target them.
Logs Ingestion API enables programmatic data submission to Azure Monitor via HTTP POST requests with OAuth2 authentication.
graph LR
A["π<br/>JSON Logs<br/>(Applications)"] --> B["π<br/>Data Collection<br/>Endpoint (DCE)"]
B --> C["βοΈ<br/>Data Collection<br/>Rule (DCR)"]
C --> D["π<br/>KQL<br/>Transformation"]
D --> E["π<br/>Custom Table<br/>(Log Analytics)"]
E --> F["π‘οΈ<br/>Microsoft<br/>Sentinel"]
classDef source fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000
classDef endpoint fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef processing fill:#fff8e1,stroke:#f9a825,stroke-width:2px,color:#000
classDef storage fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000
classDef security fill:#ffebee,stroke:#d32f2f,stroke-width:2px,color:#000
class A source
class B endpoint
class C,D processing
class E storage
class F security
Custom tables must be created before DCRs. They define the schema and retention for your data.
# Custom Tables Module (modules/custom-tables/)
resource "azapi_resource" "custom_tables" {
for_each = var.custom_tables
type = "Microsoft.OperationalInsights/workspaces/tables@2022-10-01"
name = each.value.name
parent_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${var.resource_group_name}/providers/Microsoft.OperationalInsights/workspaces/${var.workspace_name}"
body = jsonencode({
properties = {
schema = {
name = each.value.name
description = each.value.description
columns = each.value.columns
}
retentionInDays = each.value.retention_days
plan = "Analytics"
}
})
}custom_tables = {
security_events = {
name = "SecurityEvents_CL"
description = "Custom table for security events ingested via DCR API"
columns = [
{ name = "TimeGenerated", type = "datetime" },
{ name = "EventType", type = "string" },
{ name = "SourceIP", type = "string" },
{ name = "DestinationIP", type = "string" },
{ name = "Protocol", type = "string" },
{ name = "Action", type = "string" },
{ name = "Severity", type = "string" },
{ name = "Message", type = "string" }
]
retention_days = 90
}
}- β
Naming Convention: Must end with
_CL(Custom Log) - β TimeGenerated Column: Required for all custom tables
- β
Column Types:
datetime,string,int,long,real,bool,dynamic - β azapi Provider: Required as AzureRM provider doesn't support custom tables
β οΈ Create First: Tables must exist before DCRs can reference them
DCEs provide HTTP endpoints for data ingestion, similar to Splunk's HTTP Event Collector (HEC).
- Public API Endpoint: External applications, public internet access
- Private Endpoint: Internal systems, VNet integration, enhanced security
dce_configs = {
api_ingestion = {
name = "dce-api-ingestion-dev"
description = "API endpoint for custom application data ingestion"
public_network_access_enabled = true
}
private_ingestion = {
name = "dce-private-ingestion-dev"
description = "Private endpoint for internal system data ingestion"
public_network_access_enabled = false
}
}- API Ingestion:
https://dce-api-ingestion-dev-1tty.australiaeast-1.ingest.monitor.azure.com - Private Ingestion:
https://dce-private-ingestion-dev-am22.australiaeast-1.ingest.monitor.azure.com
DCRs define the data processing pipeline from endpoint to destination.
- Stream Declarations: Define input data schema
- Data Flows: Map streams to destinations with transformations
- Destinations: Target Log Analytics workspaces
- Transformations: KQL queries to modify data
api_logs_security = {
name = "dcr-api-security-events-dev"
data_type = "custom"
destinations = ["primary"]
data_collection_endpoint_key = "api_ingestion"
# Stream declarations - define input schema
stream_declarations = {
"Custom-SecurityEvents" = {
columns = [
{ name = "TimeGenerated", type = "datetime" },
{ name = "EventType", type = "string" },
{ name = "SourceIP", type = "string" },
{ name = "DestinationIP", type = "string" },
{ name = "Protocol", type = "string" },
{ name = "Action", type = "string" },
{ name = "Severity", type = "string" },
{ name = "Message", type = "string" }
]
}
}
# Output stream - target custom table
output_stream = "Custom-SecurityEvents_CL"
# KQL transformation
transform_kql = "source | extend TimeGenerated = now()"
}- β Stream Schema Match: Stream columns must match custom table schema exactly
- β Unique Stream Names: Each DCR needs unique stream names even for same table
- β
Output Stream Format: Must match custom table name (e.g.,
Custom-SecurityEvents_CL) - β DCE Reference: Must reference an existing Data Collection Endpoint
β οΈ Table Dependency: Custom table must exist before DCR creation
# 1. Create App Registration
az ad app create --display-name "DCR-API-Ingestion-App" --query "appId" -o tsv
# 2. Create Service Principal
az ad sp create --id "APP_ID_FROM_STEP_1"
# 3. Generate Client Secret
az ad app credential reset --id "APP_ID" --display-name "DCR-Ingestion-Secret" --query "password" -o tsv
# 4. Assign Required Role
az role assignment create --assignee "APP_ID" --role "Monitoring Metrics Publisher" --scope "RESOURCE_GROUP_SCOPE"# PowerShell Authentication Example
$tokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
$body = @{
grant_type = "client_credentials"
client_id = $ClientId
client_secret = $ClientSecret
scope = "https://monitor.azure.com/.default" # Critical: monitor.azure.com scope
}
$resp = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $body -ContentType "application/x-www-form-urlencoded"
$token = $resp.access_tokenPOST https://{DCE_ENDPOINT}/dataCollectionRules/{DCR_IMMUTABLE_ID}/streams/{STREAM_NAME}?api-version=2023-01-01
Headers:
- Authorization: Bearer {OAuth2_Token}
- Content-Type: application/json
- Content-Encoding: gzip (optional)
Body: JSON array of events
# Complete PowerShell script for data ingestion
param(
[string]$DceBaseUrl = "https://your-dce-url-here.australiaeast-1.ingest.monitor.azure.com",
[string]$DcrImmutableId = "your-immutable-id-here",
[string]$StreamName = "Custom-SecurityEvents",
[string]$InputJsonPath
)
# Authentication
$token = Get-OAuthToken -TenantId $TenantId -ClientId $ClientId -ClientSecret $ClientSecret
# Build URI
$uri = "$DceBaseUrl/dataCollectionRules/$DcrImmutableId/streams/$StreamName" + "?api-version=2023-01-01"
# Prepare headers
$headers = @{
Authorization = "Bearer $token"
'Content-Type' = 'application/json'
}
# Send data
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $jsonPayload -UseBasicParsingThe framework automatically maps common JSON fields to custom table schema:
# Example: security-events.json β SecurityEvents_CL mapping
$processedEvents += @{
"TimeGenerated" = if ($eventItem.timestamp) { $eventItem.timestamp } else { (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss.fffZ") }
"EventType" = if ($eventItem.source) { $eventItem.source } else { "SecurityEvent" }
"SourceIP" = if ($eventItem.sourceIP) { $eventItem.sourceIP } else { "" }
"DestinationIP" = if ($eventItem.destinationIP) { $eventItem.destinationIP } else { "" }
"Protocol" = if ($eventItem.protocol) { $eventItem.protocol.ToUpper() } else { "" }
"Action" = if ($eventItem.action) { $eventItem.action } else { "" }
"Severity" = if ($eventItem.severity) { $eventItem.severity } else { "Medium" }
"Message" = if ($eventItem.message) { $eventItem.message } else { ($eventItem | ConvertTo-Json -Compress) }
}DCRs support KQL transformations to enrich or modify data:
// Basic transformation - add ingestion timestamp
source | extend TimeGenerated = now()
// Advanced transformation - enrich with metadata
source
| extend TimeGenerated = now()
| extend DataSource = 'API_Ingestion'
| extend ProcessedBy = 'DCR_SecurityEvents'
| where isnotempty(SourceIP)You can have multiple DCRs writing to the same custom table with different stream names:
# DCR 1: API ingestion
api_logs_security = {
stream_declarations = {
"Custom-SecurityEvents" = { ... } # Stream name 1
}
output_stream = "Custom-SecurityEvents_CL" # Same table
}
# DCR 2: File ingestion
file_logs_security = {
stream_declarations = {
"Custom-SecurityEventsFile" = { ... } # Different stream name
}
output_stream = "Custom-SecurityEvents_CL" # Same table
}Requirements for Same Table:
- β Identical Schema: All stream declarations must have exact same columns
- β Unique Stream Names: Each DCR needs different stream names
- β Compatible Transformations: KQL transformations should produce same output schema
# β
Correct: One DCR per ASIM schema (Microsoft best practice)
asim_network_session = {
stream_declarations = {
"Custom-AsimNetworkSession" = { columns = [...] }
}
output_stream = "Microsoft-ASimNetworkSessionLogs"
}
asim_dns_activity = {
stream_declarations = {
"Custom-AsimDnsActivity" = { columns = [...] }
}
output_stream = "Microsoft-ASimDnsActivityLogs"
}
# ... 6 more ASIM DCRs for complete coverage# Fix: Correct azapi provider source
terraform {
required_providers {
azapi = {
source = "azure/azapi" # Not "hashicorp/azapi"
version = "~> 1.0"
}
}
}# Fix: Explicit dependency ordering
module "custom_tables" {
source = "./modules/custom-tables"
# ... configuration
}
module "data_collection" {
source = "./modules/data-collection"
depends_on = [module.custom_tables] # Ensure tables exist first
# ... configuration
}# Fix: Correct OAuth2 scope
scope = "https://monitor.azure.com/.default" # Not .azure.com# Fix: Ensure JSON array format
$bodyJson = ($processedEvents | ConvertTo-Json -Depth 64 -AsArray) # -AsArray is critical- β Recommended: 1MB - 25MB per request
- β Max Events: 1000 events per batch
- β Compression: Use gzip for payloads > 1KB
Critical Rate Limiting Behavior:
- β HTTP 204: Success (No Content)
β οΈ HTTP 429: Rate limiting - RECOVERABLE (temporary throttling)- β HTTP 400: Invalid data format
- β HTTP 401: Authentication failure
- β HTTP 403: Permission denied
- β HTTP 413: Payload too large
π¨ Data Loss Risk - HTTP 429 Handling:
When DCR limits are exceeded (2GB/min or 12K requests/min per DCR), Azure Monitor returns HTTP 429 with Retry-After header. Without proper retry logic, data is permanently lost.
# β BAD: Data loss occurs on rate limiting
try {
Invoke-WebRequest -Uri $uri -Method Post -Body $data
Write-Host "Success"
} catch {
Write-Error "Failed - DATA LOST!" # 429 errors lose data forever
}
# β
GOOD: Data preserved with retry logic
$result = Send-DataWithRetry -Uri $uri -Body $data -MaxRetries 5
if (-not $result.Success) {
# Data preserved for manual retry or queue persistence
Save-DataForRetry -Data $data -Reason $result.Error
}Enhanced ASIM Script Features:
- β Automatic Retry: Up to 5 attempts with exponential backoff
- β Retry-After Compliance: Honors Azure's specified wait times
- β Data Preservation: No data loss on temporary rate limiting
- β Error Classification: Distinguishes recoverable vs permanent failures
- β Detailed Logging: Retry attempts, wait times, and failure reasons
DCR Rate Limiting Recovery:
# When rate limited, Azure responds with:
HTTP 429 Too Many Requests
Retry-After: 60
# Script automatically waits 60 seconds and retries
# Data is preserved throughout retry process// Monitor ingestion success rates
SecurityEvents_CL
| where TimeGenerated >= ago(1h)
| summarize count() by bin(TimeGenerated, 5m)
| render timechart- β Custom Tables: Created with proper schema and retention
- β DCEs: Deployed with appropriate network access settings
- β DCRs: Configured with correct stream declarations and transformations
- β ASIM DCRs: 8 schema-specific DCRs following Microsoft best practices
- β Authentication: App registration with Monitoring Metrics Publisher role
- β Testing: PowerShell scripts validated with sample data
- β Monitoring: Log Analytics queries for ingestion validation
- β Documentation: Field mappings and data flow documented
- β Dependencies: Terraform module ordering enforced
This comprehensive DCR implementation enables Azure Monitor to function as a scalable data ingestion platform similar to Splunk, with full Infrastructure as Code management through Terraform. The 8 ASIM schema DCRs provide dedicated performance limits and follow Microsoft's recommended architecture patterns for optimal scalability.
The framework includes a comprehensive Detection Wiki system that automatically generates documentation from your detection rules:
A comprehensive system that generates Confluence-compatible documentation from Microsoft Sentinel detection rules.
Professional dashboard with statistics, MITRE ATT&CK coverage analysis, and rule distribution
Complete rule inventory with severity, status, tactics, techniques, and GitHub links
Comprehensive rule details with MITRE mappings, source links, and technical specifications
Wiki Features:
- β Auto-Generated: Powered by PowerShell script reading YAML configurations
- β MITRE ATT&CK Coverage: Simplified dashboard-style coverage analysis with all 14 tactics
- β Rule Statistics: Enabled/disabled counts, severity distribution, coverage density
- β GitHub Integration: Direct links to rule source files
- β CI/CD Integration: Automated regeneration on rule changes via GitHub Actions
- β Confluence Integration: Successfully deployed (Version 30) with optimized HTML formatting
- β Intelligent Metadata: Rule-type-specific handling eliminating inappropriate UNK values
- β Kind Badges: Visual rule categorization with emojis (π Scheduled, β‘ NRT, π‘οΈ Microsoft)
Generated Documentation:
DetectionWiki/
βββ DetectionWiki.md # Main documentation (22.77 KB, 12 rules, dashboard-style MITRE coverage)
βββ wiki-metadata.json # Generation metadata and statistics
Key Wiki Content:
- MITRE ATT&CK Coverage: Clean single-row dashboard format showing all 14 tactics horizontally
- Rules count per tactic (e.g., InitialAccess: 2, Execution: 1)
- Top techniques with counts (e.g., "T1078 +2 more")
- Rule Summary Table: Kind badges, intelligent severity, status, tactics, techniques, and GitHub links
- Detailed Rule Information: Individual rule documentation with rule-type-specific details
- Coverage Statistics: 7 of 14 tactics covered, 0.73 techniques per enabled rule
- Auto-Generated Links: Direct GitHub repository source links
Automation:
- PowerShell Generator:
generate-detection-wiki-clean.ps1with intelligent metadata functions - GitHub Actions: Automated wiki regeneration on content changes
- Confluence Publishing: Live deployment enabling analysts to quickly search analytics descriptions, use cases, and configurations.
Watchlists use a hybrid approach combining metadata and data files for optimal management:
File Structure:
content/watchlists/
βββ known-bad-ips-metadata.yaml # Watchlist metadata (name, description, search_key)
βββ known-bad-ips.csv # Tabular data (IP addresses, threat types, etc.)
βββ suspicious-domains-metadata.yaml
βββ suspicious-domains.csv
βββ vip-users-metadata.yaml
βββ vip-users.csv
Benefits of CSV Format:
- β Industry Standard: Native format for threat intelligence feeds
- β Easy Management: Import/export from Excel, databases, threat feeds
- β Simple Structure: Column-based format matches Sentinel's expectations
- β Analyst Friendly: Security teams can easily maintain IOC lists
- β
Terraform Native: Uses built-in
csvdecode()function for parsing
Example CSV Structure:
IPAddress,ThreatType,Confidence,FirstSeen,Source
192.0.2.1,Malware,High,2025-01-15T10:30:00Z,Internal Analysis
203.0.113.5,Phishing,High,2024-02-10,External Feed- All sensitive variables stored in Terraform Cloud workspace variables
- State file managed remotely in Terraform Cloud
- Access controlled through Azure RBAC and Terraform Cloud permissions
The framework uses a simplified single-configuration approach. For multiple environments, consider:
Workspace Configuration (Recommended)
- Use Terraform Cloud workspaces with environment-specific variable sets
- Single repository, multiple workspaces approach
This prevents accidental deployments and allows environment-specific customizations.
Key Documentation Resources:
- π Getting Started Guide: Complete setup instructions above
- ποΈ Architecture: Individual modules in
modules/directories with comprehensive documentation - π Content Management: Individual YAML files in
content/directories for granular control - π Auto-Generated Wiki: Comprehensive Detection Wiki with MITRE ATT&CK mapping
- π Data Collection: DCRs & DCEs documentation in sections above
- π Remote State Management: Cross-workspace Terraform Cloud integration
External References:
- Terraform AzureRM Provider Documentation
- Azure Sentinel GitHub
- Microsoft ASIM Documentation
- Confluence REST API
- Storage Format Documentation
- Authentication Methods
Last Updated: August 5, 2025



