-
Notifications
You must be signed in to change notification settings - Fork 0
Docs: WEC Collector and Sentinel KQL pages #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| # Sentinel KQL | ||
|
|
||
| Verifying the last hop: a collector (or any host) shipping events to a Log | ||
| Analytics workspace with the Azure Monitor Agent (AMA), and the KQL that | ||
| proves the whole chain works. Generic Microsoft Sentinel / Azure Monitor | ||
| material - nothing here is specific to this kit, but every query assumes | ||
| the [WEC page's](wec.md) architecture: sources push to a collector's | ||
| ForwardedEvents log, AMA collects that log. | ||
|
|
||
| ## Which table the events land in | ||
|
|
||
| Forwarded events collected by AMA land in the | ||
| [**WindowsEvent**](https://learn.microsoft.com/azure/azure-monitor/reference/tables/windowsevent) | ||
| table - that is what the | ||
| [Windows Forwarded Events connector](https://learn.microsoft.com/azure/sentinel/data-connectors/windows-forwarded-events) | ||
| configures: a | ||
| [Data Collection Rule](https://learn.microsoft.com/azure/azure-monitor/agents/data-collection-windows-events) | ||
| whose XPath list reads the `ForwardedEvents` channel. | ||
|
|
||
| Three things people trip over: | ||
|
|
||
| - **`SecurityEvent` is a different table.** The *Windows Security Events | ||
| via AMA* connector reads a machine's **own** Security channel into | ||
| `SecurityEvent`. Pointed at a collector, it ingests the collector's own | ||
| logs - not the forwarded fleet. For WEF you need the DCR reading | ||
| `ForwardedEvents!*`. A detection written only against `SecurityEvent` | ||
| will not see WEF-collected events (ASIM parsers union both tables - | ||
| see [normalisation](https://learn.microsoft.com/azure/sentinel/normalization)). | ||
| - **`Computer` is the original source**, not the collector. Forwarded | ||
| events keep the generating machine's name, which is what makes fleet | ||
| verification possible from the workspace end. | ||
| - **`Channel` is the original channel** (e.g. `Security`), not | ||
| `ForwardedEvents` - you cannot filter on the transport. The payload sits | ||
| in `EventData` as a dynamic bag (`EventData.CommandLine`), unlike | ||
| `SecurityEvent`'s flattened columns. | ||
|
|
||
| ## Confirming AMA actually collects ForwardedEvents | ||
|
|
||
| Four layers; a green connector page proves none of them individually. | ||
|
|
||
| **1. The DCR names the channel:** | ||
|
|
||
| ```bash | ||
| az monitor data-collection rule show --resource-group <rg> --name <dcr> \ | ||
| --query "dataSources.windowsEventLogs[].xPathQueries" -o json | ||
| ``` | ||
|
|
||
| (Depending on CLI version the payload may nest under `properties` - if the | ||
| query returns nothing, retry with | ||
| `properties.dataSources.windowsEventLogs[].xPathQueries`. The portal | ||
| equivalent is the DCR's **Data sources** blade.) | ||
|
|
||
| Look for `ForwardedEvents!*`. Only `Security!*` / `System!*` means the DCR | ||
| collects the collector's own logs - the classic looks-healthy failure. | ||
|
|
||
| **2. The DCR is associated with the collector:** | ||
|
|
||
| ```bash | ||
| az monitor data-collection rule association list --resource "<collector resource ID>" -o table | ||
| ``` | ||
|
|
||
| **3. The agent received that config** (on the collector; AMA caches its | ||
| delivered DCRs locally): | ||
|
|
||
| ```powershell | ||
| Get-Service AzureMonitorAgent | ||
| Get-ChildItem "C:\WindowsAzure\Resources\AMADataStore.*\mcs\configchunks" -Recurse | | ||
| Select-String -Pattern "ForwardedEvents" -List | ||
| ``` | ||
|
|
||
| (Arc-enabled servers cache under `C:\Resources\Directory\AMADataStore`.) | ||
| No hit after 10-15 minutes means the delivered config lacks the data | ||
| source - the fault can sit in the DCR content, the association, or the | ||
| agent's connectivity; work back up the layers. Agent liveness from the | ||
| workspace: | ||
|
|
||
| ```kusto | ||
| Heartbeat | ||
| | where Computer == "<collector>" and Category == "Azure Monitor Agent" | ||
| | summarize max(TimeGenerated) | ||
| ``` | ||
|
|
||
| **4. End-to-end tracer.** Generate a known harmless event on a **member | ||
| server** (create and delete a test scheduled task = 4698/4699 in its | ||
| Security log - which | ||
| [requires Success auditing on the *Other Object Access Events* subcategory](https://learn.microsoft.com/windows/security/threat-protection/auditing/audit-other-object-access-events), | ||
| part of this kit's Core tier; confirm it | ||
| first or the tracer reports a false forwarding failure), then watch it | ||
| cross each hop: source Security log -> collector ForwardedEvents -> | ||
| workspace: | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(1h) and EventID == 4698 | ||
| | where Computer == "<member server fqdn>" | ||
| | project TimeGenerated, Computer, Channel, EventData | ||
| ``` | ||
|
|
||
| A one-sentence acceptance criterion that exercises all four layers: *a | ||
| tracer event generated on a nominated source appears in WindowsEvent with | ||
| the source's Computer name within [delivery-mode floor + margin] minutes.* | ||
|
|
||
| ## Query pack | ||
|
|
||
| **Fleet inventory** - who is arriving, how much, how fresh. If only the | ||
| collector's own name appears, the DCR reads the wrong channel: | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(24h) | ||
| | summarize Events = count(), Channels = dcount(Channel), LastSeen = max(TimeGenerated) by Computer | ||
| | order by Events desc | ||
| ``` | ||
|
|
||
| **Agent presence** - a machine with its own AMA heartbeats; a | ||
| forwarded-only source does not. Neither direction is absolute proof of | ||
| path: an agented machine can be collected directly *and* forward through | ||
| a subscription, and a missing heartbeat can also mean a | ||
| [broken agent or ingestion failure](https://learn.microsoft.com/azure/azure-monitor/agents/azure-monitor-agent-troubleshoot-windows-vm) | ||
| rather than no agent. Use this to see whether WEF is likely in play, then | ||
| confirm any suspected path against the machine's own DCR associations: | ||
|
|
||
| ```kusto | ||
| let agented = Heartbeat | ||
| | where TimeGenerated > ago(24h) and Category == "Azure Monitor Agent" | ||
| | distinct Computer; | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(24h) | ||
| | summarize Events = count(), Channels = dcount(Channel) by Computer | ||
| | extend HasAgent = iff(Computer in (agented), "heartbeat present (direct collection possible)", "no heartbeat observed (forwarding likely)") | ||
| | order by Events desc | ||
| ``` | ||
|
|
||
| **Channel and event mix** - compare against the subscription query and the | ||
| source baseline (the [Reference page](reference.md) lists what each kit | ||
| setting emits): | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(24h) | ||
| | summarize Computers = dcount(Computer), Events = count() by Channel, EventID | ||
| | order by Events desc | ||
| ``` | ||
|
|
||
| **Collection-policy fingerprinting** - machines sharing an identical | ||
| channel set are almost certainly under the same DCR or subscription; the | ||
| distinct fingerprints recover the collection design from the data alone: | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(24h) | ||
| | summarize ChannelSet = make_set(Channel) by Computer | ||
| | extend Fingerprint = hash_sha256(tostring(array_sort_asc(ChannelSet))) | ||
| | summarize Machines = make_set(Computer), Count = dcount(Computer) by Fingerprint | ||
| | order by Count desc | ||
| ``` | ||
|
|
||
| **Silent sources** - previously seen, gone quiet (the workspace-side twin | ||
| of `wecutil gr`): | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(7d) | ||
| | summarize LastSeen = max(TimeGenerated) by Computer | ||
| | where LastSeen < ago(2h) | ||
| | order by LastSeen asc | ||
| ``` | ||
|
|
||
| **Never-seen sources** - diff the expected fleet against reality. | ||
| `set_difference` compares exact strings and `Computer` usually carries the | ||
| FQDN, so list the expected fleet as FQDNs (or normalise both sides): | ||
|
|
||
| ```kusto | ||
| let expected = dynamic(["server1.corp.example", "server2.corp.example"]); | ||
| let seen = toscalar(WindowsEvent | where TimeGenerated > ago(24h) | summarize make_set(Computer)); | ||
| print missing = set_difference(expected, seen) | ||
| ``` | ||
|
|
||
| **Ingestion latency** - against whatever target applies, remembering the | ||
| subscription delivery mode sets the floor before Azure is involved. One | ||
| caveat: if the DCR sets `UseTimeReceivedForForwardedEvents`, AMA stamps | ||
| `TimeGenerated` with the collector's receipt time, so this measures only | ||
| the post-receipt hop; leave that setting off to measure source-to-table: | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(24h) | ||
| | extend lag = ingestion_time() - TimeGenerated | ||
| | summarize p50 = percentile(lag, 50), p95 = percentile(lag, 95) by Computer | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| | order by p95 desc | ||
| ``` | ||
|
|
||
| **Volume and cost attribution** - the evidence for filtering further left | ||
| (source config, subscription query, or a | ||
| [DCR transform](https://learn.microsoft.com/azure/azure-monitor/data-collection/data-collection-transformations)): | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(7d) and _IsBillable == true | ||
| | summarize GB = sum(_BilledSize) / 1e9 by bin(TimeGenerated, 1d) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(24h) and _IsBillable == true | ||
| | summarize GB = sum(_BilledSize) / 1e9 by Computer, Channel | ||
| | order by GB desc | ||
| ``` | ||
|
|
||
| **Payload spot check** - pulling fields from the dynamic bag (4688 with | ||
| command line, assuming the source enables the kit's | ||
| [HighVolume tier](baselines.md#tiers)): | ||
|
|
||
| ```kusto | ||
| WindowsEvent | ||
| | where TimeGenerated > ago(1h) | ||
| | where Channel == "Security" and EventID == 4688 | ||
| | extend NewProcess = tostring(EventData.NewProcessName), CmdLine = tostring(EventData.CommandLine) | ||
| | project TimeGenerated, Computer, NewProcess, CmdLine | ||
| | take 20 | ||
| ``` | ||
|
|
||
| ## The reconciliation that matters | ||
|
|
||
| The single most useful standing assertion is a three-list comparison, and | ||
| none of the lists comes from a status page: | ||
|
|
||
| 1. the intended fleet (the AD group scoping the subscription), | ||
| 2. the collector's registered Active sources (`wecutil gr`), | ||
| 3. distinct `Computer` values in `WindowsEvent` over 24 hours. | ||
|
|
||
| Equal counts and matching names show the chain is delivering for those | ||
| machines. Every gap has at least one broken hop behind it, and the | ||
| queries above narrow down which. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: spydisec/WinLogKit
Length of output: 1077
🌐 Web query:
Microsoft Azure CLI data collection rule examples properties.dataSources windowsEventLogs xPathQueries response💡 Result:
In Azure Data Collection Rules (DCR), the
xPathQueriesproperty within thewindowsEventLogsdata source allows you to define granular filters for event collection [1][2]. These queries are formatted asLogName!XPathQuery[3][4]. JSON Structure Example Within a DCR JSON definition, the property is an array of strings [1][5]: { "name": "eventLogsDataSource", "streams": ["Microsoft-Event"], "xPathQueries": [ "System![System[(Level = 1 or Level = 2 or Level = 3)]]", "Application![System[(Level = 1 or Level = 2 or Level = 3)]]" ] } Azure CLI Usage You can manage these queries using the Azure CLIaz monitor data-collection rule windows-event-logcommands [6][7]. Adding a Data Source: az monitor data-collection rule windows-event-log add \ --rule-name "myCollectionRule" \ --resource-group "myResourceGroup" \ --name "appTeam1AppEvents" \ --streams "Microsoft-WindowsEvent" \ --x-path-queries "Application![System[(Level = 1 or Level = 2 or Level = 3)]]" "System![System[(Level = 1 or Level = 2 or Level = 3)]]" Updating a Data Source: az monitor data-collection rule windows-event-log update \ --rule-name "myCollectionRule" \ --resource-group "myResourceGroup" \ --name "appTeam1AppEvents" \ --x-path-queries "Application![System[(Level = 1 or Level = 2 or Level = 3)]]" Key Constraints and Tips - Format: Use theLogName!XPathQuerysyntax (e.g.,Security!*[System[EventID=4648]]) [3][4]. - Limits: Azure Monitor DCRs support up to 20 XPath expressions per data source [3][4]. - Testing: You can validate XPath queries locally on a Windows machine using the PowerShell cmdletGet-WinEvent -LogName 'LogName' -FilterXPath 'YourXPath'before deploying them to your DCR [3][4]. - Optimization: Use XPaths to filter data at the agent level whenever possible to improve efficiency and reduce potential ingestion costs [1][5].Citations:
🏁 Script executed:
Repository: spydisec/WinLogKit
Length of output: 2288
🌐 Web query:
site:learn.microsoft.com Azure Monitor data collection rule JSON properties dataSources windowsEventLogs xPathQueries az monitor data-collection rule show💡 Result:
In Azure Monitor Data Collection Rules (DCRs), Windows event log collection is configured within the dataSources section of the JSON definition using the windowsEventLogs property [1][2][3]. The windowsEventLogs property is an array of objects, where each object defines a specific data source configuration [4][5]. Key JSON properties for these objects include: name: A friendly, unique identifier for the data source [4][5]. streams: A list of streams that define the destination schema, typically Microsoft-Event or Microsoft-WindowsEvent [4][5][6]. xPathQueries: A list of strings, where each entry is a Windows Event Log query in XPath format [4][5]. XPath Query Syntax Queries follow the format LogName!XPathQuery [6]. For example, to collect specific events from the System log, you might use System!*[System[(Level=1 or Level=2 or Level=3)]] [1][2][6]. These queries provide granular control over event collection, allowing filtering by event ID, level, or other properties directly at the source [3][6]. Management via Azure CLI While you can inspect the full DCR JSON structure using az monitor data-collection rule show [7], this command retrieves the entire rule configuration [7][8]. For managing specific Windows event log data sources via the Azure CLI, you can use specialized subcommands such as: az monitor data-collection rule windows-event-log add: Adds a new Windows event log data source to an existing DCR [9]. az monitor data-collection rule windows-event-log update: Updates an existing Windows event log data source [9]. These CLI commands accept --x-path-queries as a parameter to define the collection logic without requiring manual JSON editing [9]. However, for complex configurations, working directly with the JSON definition via the REST API or Azure portal JSON view is often recommended [10][8].
Citations:
Fix the DCR JMESPath root.
az monitor data-collection rule showreturnsdataSourcesunderproperties; the current query therefore returns no XPath values. Useproperties.dataSources.windowsEventLogs[].xPathQueries.🤖 Prompt for AI Agents