Azure Monitor / Sentinel — DCR, DCE, and managed-identity setup
The engine posts batches of traffic-log entries to the Azure Logs Ingestion REST API
using its VM’s system-assigned managed identity. No tokens are stored by Enforza.
This article walks through the Azure side; the steps are equivalent to the published
az vm extension tutorial but bypass the Azure Monitor Agent — the engine speaks the
API directly.
1. Log Analytics workspace
Section titled “1. Log Analytics workspace”- Create or pick a Log Analytics workspace. Note the
Workspace ID(also called Customer ID) on the workspace’s Overview blade. - (Optional) Enable Microsoft Sentinel on the workspace if you want Sentinel analytics, hunting, and incident management.
2. Custom table
Section titled “2. Custom table”The engine writes records under a custom table whose schema matches the engine’s traffic-log JSON.
az monitor log-analytics workspace table create \ --resource-group <rg> \ --workspace-name <workspace> \ --name EnforzaLogs_CL \ --columns TimeGenerated=datetime RawData=string \ --retention-time 30 \ --plan AnalyticsTwo columns are required: TimeGenerated (datetime) and RawData (string). The
engine posts each event as a JSON string in RawData; Sentinel / KQL queries can
parse it at read time. Future engine versions may emit a wider schema.
3. Data Collection Endpoint (DCE)
Section titled “3. Data Collection Endpoint (DCE)”- Create a DCE in the same region as the workspace.
- Note the Logs Ingestion endpoint URL, e.g.
https://enforza-dce-abcd.westeurope-1.ingest.monitor.azure.com.
4. Data Collection Rule (DCR)
Section titled “4. Data Collection Rule (DCR)”- Create a DCR that points at the DCE and the workspace, with one stream
Custom-EnforzaLogs_CLflowing to theEnforzaLogs_CLtable. - After creation, note the DCR’s
immutableId(under the DCR resource’s JSON view).
5. Grant the engine VM managed identity
Section titled “5. Grant the engine VM managed identity”- On the engine VM, enable System-assigned managed identity (Identity blade).
- Grant that identity the
Monitoring Metrics Publisherrole on the DCR resource (not the workspace).
az role assignment create \ --assignee <vm-managed-identity-principal-id> \ --role "Monitoring Metrics Publisher" \ --scope <dcr-resource-id>6. Configure the sink
Section titled “6. Configure the sink”On the Log Export page, click “New export” → Azure Monitor (or Azure Sentinel for the same flow). Fill in:
- Workspace ID — from step 1.
- Data Collection Endpoint — the full
https://...URL from step 3. - DCR Immutable ID — from step 4.
- Stream name —
Custom-EnforzaLogs_CLunless you renamed it in the DCR. - Region — the workspace / DCE region.
Advanced — copy-paste setup script
Section titled “Advanced — copy-paste setup script”Fill in these variables at the top, then paste the whole block into a shell (with
az already logged in) — or download the file and run
./enforza-azure-monitor-sink-setup.sh.
#!/usr/bin/env bash# ── FILL IN ─────────────────────────────────────────────────────SUB_ID="00000000-0000-0000-0000-000000000000" # Azure subscriptionRG="rg-enforza-monitoring" # resource group (will be created if missing)LOCATION="westeurope" # Azure region — workspace + DCE live hereWS_NAME="enforza-laws" # Log Analytics workspace nameDCE_NAME="enforza-dce" # Data Collection Endpoint nameDCR_NAME="enforza-ulog-dcr" # Data Collection Rule nameTABLE_NAME="EnforzaLogs_CL" # Custom table name (must end _CL)ENGINE_VM_NAME="enforza-edge-1" # VM whose managed identity gets publish accessENGINE_VM_RG="$RG" # RG of the engine VM (often same as $RG)# ────────────────────────────────────────────────────────────────
set -euo pipefailaz account set --subscription "$SUB_ID"
echo "▶ Ensuring resource group $RG ($LOCATION)…"az group create -n "$RG" -l "$LOCATION" -o none
echo "▶ Ensuring Log Analytics workspace ${WS_NAME}…"az monitor log-analytics workspace create \ -g "$RG" -n "$WS_NAME" -l "$LOCATION" -o none 2>/dev/null || trueWS_ID=$(az monitor log-analytics workspace show -g "$RG" -n "$WS_NAME" --query id -o tsv)WS_CUSTOMER_ID=$(az monitor log-analytics workspace show -g "$RG" -n "$WS_NAME" --query customerId -o tsv)
echo "▶ Ensuring custom table $TABLE_NAME (TimeGenerated + RawData)…"az monitor log-analytics workspace table create \ -g "$RG" --workspace-name "$WS_NAME" \ --name "$TABLE_NAME" \ --columns TimeGenerated=datetime RawData=string \ --retention-time 30 --plan Analytics -o none 2>/dev/null || true
echo "▶ Ensuring Data Collection Endpoint ${DCE_NAME}…"az monitor data-collection endpoint create \ -g "$RG" -n "$DCE_NAME" -l "$LOCATION" \ --public-network-access Enabled -o none 2>/dev/null || trueDCE_ID=$(az monitor data-collection endpoint show -g "$RG" -n "$DCE_NAME" --query id -o tsv)DCE_INGEST=$(az monitor data-collection endpoint show -g "$RG" -n "$DCE_NAME" \ --query 'logsIngestion.endpoint' -o tsv)
echo "▶ Building DCR template…"DCR_FILE=$(mktemp)cat > "$DCR_FILE" <<EOF{ "location": "$LOCATION", "properties": { "dataCollectionEndpointId": "$DCE_ID", "streamDeclarations": { "Custom-$TABLE_NAME": { "columns": [ { "name": "TimeGenerated", "type": "datetime" }, { "name": "RawData", "type": "string" } ] } }, "destinations": { "logAnalytics": [{ "name": "law", "workspaceResourceId": "$WS_ID" }] }, "dataFlows": [{ "streams": [ "Custom-$TABLE_NAME" ], "destinations": [ "law" ], "outputStream": "Custom-$TABLE_NAME" }] }}EOF
echo "▶ Ensuring Data Collection Rule ${DCR_NAME}…"az monitor data-collection rule create \ -g "$RG" -n "$DCR_NAME" --rule-file "$DCR_FILE" -o none 2>/dev/null \ || az monitor data-collection rule update \ -g "$RG" -n "$DCR_NAME" --rule-file "$DCR_FILE" -o noneDCR_ID=$(az monitor data-collection rule show -g "$RG" -n "$DCR_NAME" --query id -o tsv)DCR_IMMUTABLE=$(az monitor data-collection rule show -g "$RG" -n "$DCR_NAME" --query immutableId -o tsv)
echo "▶ Ensuring engine VM $ENGINE_VM_NAME has a system-assigned managed identity…"az vm identity assign -g "$ENGINE_VM_RG" -n "$ENGINE_VM_NAME" -o noneENGINE_PRINCIPAL=$(az vm show -g "$ENGINE_VM_RG" -n "$ENGINE_VM_NAME" \ --query identity.principalId -o tsv)
echo "▶ Granting 'Monitoring Metrics Publisher' on the DCR…"az role assignment create \ --assignee-object-id "$ENGINE_PRINCIPAL" \ --assignee-principal-type ServicePrincipal \ --role "Monitoring Metrics Publisher" \ --scope "$DCR_ID" -o none 2>/dev/null || true
echoecho "✔ Done. In the Enforza console → New export → Azure Monitor:"echo " Workspace ID: $WS_CUSTOMER_ID"echo " Data Collection Endpoint: $DCE_INGEST"echo " DCR Immutable ID: $DCR_IMMUTABLE"echo " Stream name: Custom-$TABLE_NAME"echo " Region: $LOCATION"Troubleshooting
Section titled “Troubleshooting”- 403 / “Forbidden” in the engine logs — the VM’s managed identity isn’t
Monitoring Metrics Publisheron the DCR (not the workspace). Re-check the scope. - No data in the workspace after 5–10 min — verify the DCR streams config matches
Custom-EnforzaLogs_CL, and the table actually has the two required columns. - “Bad Request” with column-validation error — the table schema drifted; recreate
EnforzaLogs_CLwith the two required columns.