Skip to content

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. Create or pick a Log Analytics workspace. Note the Workspace ID (also called Customer ID) on the workspace’s Overview blade.
  2. (Optional) Enable Microsoft Sentinel on the workspace if you want Sentinel analytics, hunting, and incident management.

The engine writes records under a custom table whose schema matches the engine’s traffic-log JSON.

Terminal window
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 Analytics

Two 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.

  1. Create a DCE in the same region as the workspace.
  2. Note the Logs Ingestion endpoint URL, e.g. https://enforza-dce-abcd.westeurope-1.ingest.monitor.azure.com.
  1. Create a DCR that points at the DCE and the workspace, with one stream Custom-EnforzaLogs_CL flowing to the EnforzaLogs_CL table.
  2. After creation, note the DCR’s immutableId (under the DCR resource’s JSON view).
  1. On the engine VM, enable System-assigned managed identity (Identity blade).
  2. Grant that identity the Monitoring Metrics Publisher role on the DCR resource (not the workspace).
Terminal window
az role assignment create \
--assignee <vm-managed-identity-principal-id> \
--role "Monitoring Metrics Publisher" \
--scope <dcr-resource-id>

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 nameCustom-EnforzaLogs_CL unless you renamed it in the DCR.
  • Region — the workspace / DCE region.

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 subscription
RG="rg-enforza-monitoring" # resource group (will be created if missing)
LOCATION="westeurope" # Azure region — workspace + DCE live here
WS_NAME="enforza-laws" # Log Analytics workspace name
DCE_NAME="enforza-dce" # Data Collection Endpoint name
DCR_NAME="enforza-ulog-dcr" # Data Collection Rule name
TABLE_NAME="EnforzaLogs_CL" # Custom table name (must end _CL)
ENGINE_VM_NAME="enforza-edge-1" # VM whose managed identity gets publish access
ENGINE_VM_RG="$RG" # RG of the engine VM (often same as $RG)
# ────────────────────────────────────────────────────────────────
set -euo pipefail
az 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 || true
WS_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 || true
DCE_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 none
DCR_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 none
ENGINE_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
echo
echo "✔ 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"
  • 403 / “Forbidden” in the engine logs — the VM’s managed identity isn’t Monitoring Metrics Publisher on 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_CL with the two required columns.

Enforza is a trading name of Synvu Limited, a company registered (15761962) in the United Kingdom. Registered office address: 71–75 Shelton Street, Covent Garden, London, WC2H 9JQ, United Kingdom.