CoursesAzure securityMicrosoft Sentinel SIEM/SOAR

Microsoft Sentinel SIEM/SOAR

Analytics rules, UEBA, playbooks that respond.

Advanced35 min · lesson 11 of 15

A security operations centre (a SOC, the team that watches for attacks) runs like one control room. Sign-in records, firewall logs, cloud alerts, and audit trails all stream onto a single wall of screens. Something on that wall reads the feeds together and boils thousands of raw events down to a short list of cases worth a person's time. A duty officer then acts on the worst ones without calling a meeting first. That wall is a SIEM (Security Information and Event Management: it gathers logs from everywhere and correlates them into alerts). The duty officer is SOAR (Security Orchestration, Automation and Response: it runs the workflow that contains a threat). Microsoft Sentinel is both, in one service. You do not run a cluster for it. It sits on top of Azure's logging stack and bills you by how much data you feed it. This lesson stands Sentinel up and runs it from the az command line, front to back: onboard a workspace, pull signals in, write a detection that fires, and wire a playbook that responds.

One workspace where every signal lands

Sentinel stores nothing on its own. It is a layer on top of a Log Analytics workspace, which you can picture as one warehouse ledger: every log line gets written in, time-stamped, and kept for its retention period. Data connectors are the loading docks. You install them as solutions from the Content Hub, and each one pulls a category of logs into the warehouse: Microsoft Entra ID (Microsoft's identity service, the current name for what used to be Azure AD) sign-in and audit logs, Defender for Cloud alerts (Azure's built-in security scanner, previously called Security Center), the Azure Activity Log, Office 365 audit events, and network and firewall logs. Some streams cost nothing to ingest (Azure Activity, Microsoft Defender alerts, and Office 365 audit logs are free). Others, including Entra sign-in logs, are billed by the gigabyte. And some sources will not appear at all until you point their diagnostic settings at the workspace. A Key Vault (Azure's managed store for secrets and keys) stays invisible in Sentinel until its AuditEvent logs are routed there.

With the logs in one place, you query them in KQL (Kusto Query Language, a read-only query language built for logs), and it is the same language whether you are hunting by hand or saving a detection that runs forever. One workspace exists for one reason: correlation. A lone failed sign-in, a sudden burst of Key Vault secret reads, and a firewall rule that opened a port each looks harmless on its own screen. Line them up in one ledger, sorted by time and tied to the same account, and they turn into a single story: an account got phished, then used to read secrets, then used to widen network access.

Turning Sentinel on registers the workspace with the Microsoft.SecurityInsights resource provider and records one onboarding-state object. Do that before you wire a single connector, because rules and connectors have nothing to attach to until Sentinel owns the workspace. That onboarding state is a single named object, and the name is always default.

terminal
# Sentinel is a layer on a Log Analytics workspace. Create the workspace,
# then onboard Sentinel (registers the workspace with Microsoft.SecurityInsights).
# az sentinel ships in an extension: run `az extension add -n sentinel` if prompted.
az monitor log-analytics workspace create \
--resource-group soc-rg --name soc-law --location eastus
az sentinel onboarding-state create \
--resource-group soc-rg --workspace-name soc-law --name default
output
{
"customerId": "b2f9c1e4-7a3d-4c8b-9e21-6d0f5a2c7b14",
"id": "/subscriptions/.../resourceGroups/soc-rg/providers/Microsoft.OperationalInsights/workspaces/soc-law",
"location": "eastus",
"name": "soc-law",
"provisioningState": "Succeeded",
"retentionInDays": 30,
"sku": {
"name": "PerGB2018"
}
}
{
"id": "/subscriptions/.../resourceGroups/soc-rg/providers/Microsoft.OperationalInsights/workspaces/soc-law/providers/Microsoft.SecurityInsights/onboardingStates/default",
"name": "default",
"resourceGroup": "soc-rg",
"type": "Microsoft.SecurityInsights/onboardingStates"
}
terminal
# Confirm Sentinel is actually enabled on the workspace:
az sentinel onboarding-state show \
--resource-group soc-rg --workspace-name soc-law \
--name default --query name --output tsv
output
default
An installed connector is not the same as data arriving
Installing a connector does not guarantee logs. Many Azure resources only emit them once you create a diagnostic setting that forwards them to the workspace, and Sentinel raises no error for a source it was never told to expect. A Key Vault, a storage account, or a firewall stays dark, your detection runs against an empty table, and it finds nothing because the event it hunts for was never written. After wiring any source, prove data is landing: run a small KQL count against its table before you trust a rule built on it.

Detect: analytics rules, near-real-time, and behaviour

An analytics rule is a standing order to the night-shift clerk: every few minutes, run this exact check against the ledger, and if the count crosses a line, open a case. In Sentinel it is a saved KQL query with four settings that carry the weight. The query period is how far back each run looks. The frequency is how often it runs, as often as every 5 minutes. The threshold is the count that trips it. The entity mapping tells Sentinel which columns are the user, the IP address, and the host. When a run crosses the threshold, Sentinel raises an incident: a grouped, investigable case with those entities attached and, when you set it, a MITRE ATT&CK tactic tagged (MITRE ATT&CK is a public catalogue of attacker techniques) so a responder reads the technique at a glance.

Entity mapping is not paperwork you can skip. An incident with a mapped user and IP can be triaged in seconds and can drive an automated response aimed at that exact account. An incident with no mapped entities is a wall of text a human has to decode, and no playbook can disable a user it was never handed. Map entities on every rule you write.

Two rule types are worth knowing past the standard scheduled one. Near-real-time rules (NRT) run once a minute, and instead of asking when an event happened they ask when it arrived in the workspace. That skips the wait for the scheduled engine's window to close and gives the lowest detection lag Sentinel offers. You are capped at 50 NRT rules per workspace, so keep them for the few break-glass detections where a single minute changes the outcome. UEBA (User and Entity Behaviour Analytics) is the other one. It quietly learns what normal looks like for each identity and device over weeks, then flags the drift: a sign-in from a country the user has never worked from, a first-ever use of a privileged action, a reach into a resource this account never touches. A fixed threshold cannot express 'strange for this specific person'; UEBA is built for that.

Do not start from a blank query. Sentinel ships hundreds of rule templates tied to known techniques, so clone one and shape it to your data. And before any query becomes a rule that can page people at 3 a.m., run it by hand and read exactly what it fires on. The query command wants the workspace GUID (its customerId), not the friendly name, which is the mistake almost everyone makes the first time.

terminal
# Run the detection by hand before it becomes a rule that pages people.
# --workspace takes the workspace GUID (customerId), NOT the resource name.
# `az monitor log-analytics query` ships in the log-analytics extension.
WS=$(az monitor log-analytics workspace show \
--resource-group soc-rg --name soc-law \
--query customerId --output tsv)
az monitor log-analytics query --workspace "$WS" --output table \
--analytics-query '
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where OperationName == "SecretGet"
| extend caller = column_ifexists(
"identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s", "")
| summarize reads = count(), secrets = dcount(id_s)
by caller, CallerIPAddress, bin(TimeGenerated, 5m)
| where reads > 50'
output
Caller CallerIPAddress TimeGenerated Reads Secrets
---------------- --------------- --------------------- ----- -------
[email protected] 52.170.24.11 2026-07-14T02:15:00Z 214 58
[email protected] 52.170.24.11 2026-07-14T02:20:00Z 187 57

Two back-to-back five-minute windows, one account, one IP, more than two hundred secret reads in the first window and close to sixty distinct secrets touched. A reporting job does not behave like that. This is the shape a stolen token makes when someone points it at your secrets and loops. That pattern earns a saved rule, with caller mapped to the Account entity and CallerIPAddress mapped to the IP entity so the incident arrives carrying both.

Respond: incidents, automation rules, and playbooks

A detection that ends at an alert is a smoke detector wired to no sprinklers. The response half of Sentinel is the playbook. A playbook is an Azure Logic App (a low-code workflow where each step is a ready-made action) that carries out containment: disable the account in Entra and revoke its live sessions, drop a VM behind a deny-all network security group, post to the SecOps channel, open a ticket. You build it once and reuse it across many detections.

Here is the wiring that catches people. An analytics rule does not call a playbook. A dispatcher sits in between, called an automation rule. It watches incidents as they are created, matches them against conditions you define (severity equals High, tactic is Credential Access, a certain title, a particular entity type present), and runs the playbook on the first rule that matches, in the order you set. That layer of indirection is what makes response predictable instead of improvised. One playbook can serve twenty detections, and you can reorder or switch off a response without touching the rule that detects.

How a signal becomes a contained incident
1Signal ingested
logs land in the Log Analytics workspace
2Analytics rule
scheduled KQL crosses its threshold
3Incident raised
entities mapped, MITRE tactic tagged
4Automation rule
matches by condition, runs in order
5Playbook (Logic App)
acts through its managed identity
6Contained
user disabled, IP blocked, SecOps paged
The automation rule, not the analytics rule, picks and runs the playbook. Break that link and detections keep firing while nothing responds.

Triage still begins at the incident queue, and you can run all of it from the command line: list, filter, assign, comment, close. That is the part you script for bulk cleanup and for your own enrichment. One quirk to learn before you write a filter: the CLI unwraps each incident's properties bag, so the fields you filter on (severity, status, title, incidentNumber) sit at the top level, not under properties. The case number a human quotes on the phone is incidentNumber.

terminal
# High-severity incidents that are still open. The CLI flattens each
# incident's properties, so filter on top-level severity/status and
# project top-level incidentNumber/title. Human-quoted case = incidentNumber.
az sentinel incident list \
--resource-group soc-rg --workspace-name soc-law \
--query "[?severity=='High' && status!='Closed'].{num:incidentNumber, title:title, sev:severity, status:status}" \
--output table
output
Num Title Sev Status
----- ------------------------------------------- ----- ------
142 Mass Key Vault secret reads (single caller) High New

Wire the containment through an automation rule. The command below reads: when an incident is created at severity High, run the pb-disable-user Logic App, and evaluate this rule first.

terminal
# The automation rule matches the incident and runs the playbook.
# The analytics rule never calls the playbook itself.
az sentinel automation-rule create \
--resource-group soc-rg --workspace-name soc-law \
--name disable-user-high \
--display-name "Disable user on high-severity incident" \
--order 1 \
--triggering-logic '{
"isEnabled": true,
"triggersOn": "Incidents",
"triggersWhen": "Created",
"conditions": [
{ "conditionType": "Property",
"conditionProperties": {
"propertyName": "IncidentSeverity",
"operator": "Equals",
"propertyValues": ["High"] } } ] }' \
--actions '[
{ "order": 1,
"actionType": "RunPlaybook",
"actionConfiguration": {
"logicAppResourceId": "/subscriptions/<sub-id>/resourceGroups/soc-rg/providers/Microsoft.Logic/workflows/pb-disable-user",
"tenantId": "<tenant-id>" } } ]'
# Verify it is registered and ordered (fields are top-level, not under properties):
az sentinel automation-rule list \
--resource-group soc-rg --workspace-name soc-law \
--query "[].{name:displayName, order:order}" \
--output table
output
Name Order
-------------------------------------- -----
Disable user on high-severity incident 1
A playbook with no permission fails silently, right when it counts
Every playbook acts through its Logic App's managed identity (an Azure account attached to the app, with no password you handle). If that identity was never granted the role its actions need (a User Administrator or a security role in Entra to disable an account, Network Contributor on the VM's network security group to isolate it), the whole chain still looks healthy: the incident fires, the automation rule matches, the playbook runs, and the disable step returns HTTP 403 while the account stays live. Grant and test the identity before an incident, scope it to only what it touches, and alert on playbook run failures. Otherwise your rehearsed response does nothing during the one event you built it for.

Tune for signal, and price the rest on purpose

Two forces decide whether Sentinel helps you or buries you: noise and cost. Noise you beat with tuning. Raise thresholds off their defaults, suppress the known-benign pattern instead of re-reading it every morning, group related alerts into one incident, and let an automation rule auto-close the categories that are reliably false. This is a safety control, not housekeeping. A queue stuffed with low-confidence incidents trains responders to ignore the queue, and the one real attack drowns the same way a car alarm nobody looks at anymore fails to protect the car.

Cost comes almost entirely from one number: gigabytes ingested and retained. A chatty, low-value stream like verbose firewall or network flow logs can cost more per month than the breach it might one day catch. You have levers. Route high-volume, low-fidelity data to the cheaper Basic or Auxiliary table plans instead of the full Analytics tier you detect on. Drop columns you will never query at ingestion time with a Data Collection Rule transformation (a short KQL step applied as the data arrives). Buy a commitment tier when your daily volume is steady, which discounts the per-gigabyte rate against pay-as-you-go. Push old data into the cheaper archive tier past the window you actively hunt in. Sentinel-enabled workspaces include 90 days of retention at no retention charge, so spend that runway on purpose rather than paying to keep logs no rule ever reads.

Quick check
01Your analytics rule raises a high-severity incident, the playbook itself is known-good, yet the playbook never runs. What is the most likely reason?
Incorrect — Analytics rules never invoke playbooks directly in the current model, so this cannot be the missing configuration.
Correct — the automation rule is the dispatcher between an incident and a playbook, and without a match the playbook is never triggered.
Incorrect — UEBA adds behavioural signals but does not launch playbooks, and missing entities would not block the trigger.
Incorrect — That mistake breaks an ad-hoc KQL query, not the incident-to-playbook path.
02Why does a near-real-time (NRT) rule detect faster than a standard scheduled rule?
Incorrect — NRT rules still evaluate their conditions; the speed does not come from dropping the threshold.
Incorrect — Every Sentinel rule, NRT included, runs against the Log Analytics workspace, not the source.
Correct — running on ingestion time each minute removes the wait for the scheduled engine's aggregation window.
Incorrect — NRT runs once a minute and no faster, and you are limited to 50 such rules per workspace.
03An incident fires, the automation rule matches, and the playbook's run history shows it executed, but the disable-user step returned HTTP 403 and the account is still active. What happened?
Incorrect — Order decides which rule runs, but a 403 on the disable step means this playbook did run and was refused, not pre-empted.
Incorrect — A missing IP entity would not produce a 403 on a user-disable action that already received the account.
Incorrect — The NRT cap governs how many rules you can create, not whether a running playbook is authorized to act.
Correct — a 403 is an authorization failure, so grant the Logic App's identity a role such as User Administrator and re-test.

Sentinel is only ever as good as the logs beneath it. A rule cannot fire on an event that was never written, and an attacker who can reach into the workspace and delete the trail can erase the incident before anyone opens it. The next lesson, Activity Log and immutable trails, locks that foundation down, so the records Sentinel reads are ones nobody can quietly rewrite.

Try this

Run by caller, CallerIPAddress, bin(TimeGenerated, 5m) on a scratch host or disposable cluster and read the output against what this lesson described. Then change one input so it fails, and re-run: the error you get is the one you will meet in production.

Takeaway

The trap worth remembering here: an installed connector is not the same as data arriving. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related