Defender Security Dashboard Part 1: Workbooks in the Defender portal, and device health

Defender Security Dashboard Part 1: Workbooks in the Defender portal, and device health

Series: This is Part 1 of 3 in my series on building a Defender security dashboard with workbooks. New to the series? Start with the series guide, which explains what the dashboard shows and what you need.

In plain terms: Every device in the organisation should have a Defender sensor reporting in. A device that goes quiet is a blind spot: if it is attacked, nobody sees it, and Defender does not raise an alert to say it has stopped reporting. This part builds the first page of the dashboard, which answers two questions a manager can act on: are all our devices reporting, and are the servers that control our identities covered?

The series guide set out the goal: one page in the Defender portal that shows how healthy Contoso’s security posture is, built entirely on advanced hunting. This part creates the workbook and builds its first section - device health.

Workbooks in the Defender portal

A workbook is a dashboard page made of sections: text, parameters and queries. Each query section asks advanced hunting a question in KQL and shows the answer as a table, chart or tiles.

Workbooks in the Defender portal navigation
Workbooks in the Defender portal navigation

Workbooks sits in the Defender portal navigation. Tenants that use Microsoft Sentinel in the Defender portal reach it under Microsoft Sentinel; tenants on the Integrated Security Operations Center (ISOC) preview get it as a top-level item, as shown above, without needing a workspace.

Create the workbook

  1. Go to Workbooks and select + Add workbook, then Edit.
  2. Replace the default text with a heading such as Contoso Defender Dashboard.
The workbook in edit mode, with Advanced editor in the toolbar
The workbook in edit mode, with Advanced editor in the toolbar

Save once, then reopen. In testing, every Save on an unsaved new workbook page created a separate copy. Save the first time, then open the workbook from the Workbooks list before making further changes.

Add a parameter for Tier 0 servers

Domain controllers, AD CS, AD FS and Entra Connect servers deserve their own view, and only Contoso knows their names. Add a parameters section with one text parameter called IdentityServers, and enter the short host names separated by commas, for example dc01,dc02,adfs01,ca01,aadc01. Queries in this series read it as {IdentityServers}.

The IdentityServers parameter under the workbook header
The IdentityServers parameter under the workbook header

Device health

Each of the following is a query section: select Add, choose a query section, set Data source to Advanced hunting, leave Time range as Set in query, paste the query, select Run query, choose the visualisation and select Apply changes.

A query section in edit mode with Advanced hunting as the data source
A query section in edit mode with Advanced hunting as the data source

Why 30 days? A device that stops reporting also stops appearing in short time windows, so a 7-day query quietly drops the very devices you are looking for. These queries look back 30 days for each device’s last record, and flag any device not seen for more than 7 days.

1. Sensor health at a glance

The headline: how many devices are onboarded, how many are reporting normally, and how many have gone quiet or report a problem.

DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, *) by DeviceId
| where OnboardingStatus == "Onboarded"
| extend Health = iff(Timestamp < ago(7d), "Not seen for 7+ days", SensorHealthState)
| summarize Onboarded = count(),
            Active = countif(Health == "Active"),
            NotSeen = countif(Health == "Not seen for 7+ days"),
            Unhealthy = countif(Health has "Inactive" or Health has "Misconfigured" or Health has "Impaired")
| project Pairs = pack_array(pack("Status", "Onboarded", "Count", Onboarded), pack("Status", "Active", "Count", Active),
                             pack("Status", "Not seen for 7+ days", "Count", NotSeen), pack("Status", "Unhealthy", "Count", Unhealthy))
| mv-expand Pairs
| project Status = tostring(Pairs.Status), Count = toint(Pairs.Count)

Show this as tiles. The last two numbers should be close to zero. In the lab, the single test device has not reported for over a week, and the tile shows it immediately.

Sensor health tiles
Sensor health tiles

2. Sensor health by device class

The same numbers split by type of device, so it is clear where the problems sit.

DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, *) by DeviceId
| where OnboardingStatus == "Onboarded"
| extend Health = iff(Timestamp < ago(7d), "Not seen for 7+ days", SensorHealthState)
| extend Class = case(DeviceType == "Server" and OSPlatform startswith "Windows", "Windows server",
                      OSPlatform == "Linux", "Linux",
                      DeviceType == "Workstation", strcat("Workstation - ", OSPlatform), OSPlatform)
| summarize Devices = count(),
            Active = countif(Health == "Active"),
            NotSeen = countif(Health == "Not seen for 7+ days"),
            Inactive = countif(Health == "Inactive"),
            Misconfigured = countif(Health has "Misconfigured"),
            Impaired = countif(Health has "Impaired") by Class
| order by Devices desc

Workstations that have not been seen are often laptops left switched off; a server that has not been seen almost never has a good reason.

Sensor health by device class table
Sensor health by device class table

3. Devices that have stopped reporting

The list that turns a number into a task: every device that has gone quiet or reports a problem, and how long it has been silent.

DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, DeviceName, OSPlatform, DeviceType, SensorHealthState, OnboardingStatus, MachineGroup) by DeviceId
| where OnboardingStatus == "Onboarded"
| extend DaysSinceLastSeen = datetime_diff('day', now(), Timestamp)
| where SensorHealthState != "Active" or DaysSinceLastSeen >= 7
| project DeviceName, OSPlatform, DeviceType, SensorHealthState, LastSeen = Timestamp, DaysSinceLastSeen, DeviceGroup = MachineGroup
| order by DaysSinceLastSeen desc

The lab device still shows Active as its last sensor state - it simply stopped reporting, which is exactly why the 7-day check matters. Sort by DaysSinceLastSeen and work from the top: a device unseen for weeks is either retired, rebuilt under a new name, or a blind spot.

Devices not reporting a healthy sensor
Devices not reporting a healthy sensor

4. Tier 0 identity servers

Domain controllers and the other identity servers are the keys to the kingdom. This section checks each one by name, so a server that was never onboarded shows up as a gap instead of simply being missing from the results.

let names = split(tolower(replace_string("{IdentityServers}", " ", "")), ",");
let seen = DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, DeviceName, OSPlatform, ClientVersion, SensorHealthState, OnboardingStatus, MachineGroup) by DeviceId
| extend Host = tolower(tostring(split(DeviceName, ".")[0]))
| where Host in (names)
| project Host, DeviceName, OnboardingStatus, SensorHealthState, ClientVersion, DeviceGroup = MachineGroup, LastSeen = Timestamp;
print Host = names
| mv-expand Host to typeof(string)
| where isnotempty(Host)
| join kind=leftouter seen on Host
| extend Status = iff(OnboardingStatus == "Onboarded" and SensorHealthState == "Active" and LastSeen > ago(7d), "Healthy", "Needs attention")
| project Host, Status, OnboardingStatus, SensorHealthState, ClientVersion, DeviceGroup, LastSeen
| order by Status desc, Host asc

Show the result as a table, and use an icon format on the Status column so problems stand out. A server counts as healthy only if it is onboarded, its sensor is active and it has reported in the last 7 days. The lab has no domain controllers, so every name in the parameter shows Needs attention - exactly what a missing server would look like.

Tier 0 identity server coverage
Tier 0 identity server coverage

5. Devices that can be onboarded

Defender also discovers devices on the network that are not yet protected. This section counts them by platform - the unmanaged devices attackers look for first.

DeviceInfo
| where Timestamp > ago(30d)
| summarize arg_max(Timestamp, OnboardingStatus, OSPlatform, DeviceType) by DeviceId
| where OnboardingStatus == "Can be onboarded"
| summarize Devices = count() by OSPlatform, DeviceType
| order by Devices desc

An empty result is good news. In the lab, device discovery found nothing waiting to be onboarded.

Devices that can be onboarded, by platform
Devices that can be onboarded, by platform

Save it

Select Done editing, then Save, and enter a title. The workbook is saved for the whole organisation and appears in the Workbooks list for anyone with Workbooks Reader access.

Save Workbook dialog
Save Workbook dialog

Opening the dashboard

  1. Sign in to the Microsoft Defender portal at security.microsoft.com.
  2. Select Workbooks in the navigation (under Microsoft Sentinel in Sentinel tenants), or browse directly to security.microsoft.com/sentinel/workbooks.
  3. Under My workbooks, select Contoso Defender Dashboard, then View saved workbook in the side panel.
The saved dashboard in the Workbooks list
The saved dashboard in the Workbooks list

Select the star next to the workbook to add it to your favourites, so it stays at the top of the list. Anyone with Workbooks Reader access, or the Security Reader role, sees the same workbook in their own list.

The shortcut: import the finished workbook

The complete dashboard from all three parts is in the companion repository as DefenderDashboard.workbook. Create a new workbook, select Edit, then Advanced editor, replace the JSON with the file’s contents and select Apply. Every query section uses "queryType": "advancedHunting", so it runs in any tenant with the same tables.

The workbook Advanced editor
The workbook Advanced editor

Every query is also in the repository as its own .kql file, ready to run in advanced hunting. Those files cannot read a workbook parameter, so the two queries that use {IdentityServers} have a let line marked // EDIT instead, where you type your Tier 0 host names.

GitHub: github.com/cloud-secure-ai/Building-a-Defender-Security-Dashboard

Key takeaways

  • A device that stops reporting does not raise an alert - this section makes the silence visible.
  • Look back 30 days, not 7, or the devices you most need to find drop out of the results.
  • Check Tier 0 servers by name, so a server that was never onboarded shows up as a gap.

Next: Part 2

A device that reports is not the same as a device that is protected. Part 2 adds a single grid showing whether each protection is actually switched on, for every type of device.


The rest of the series

The workbook and every KQL query from the series are on GitHub: github.com/cloud-secure-ai/Building-a-Defender-Security-Dashboard

References

Seán O’Farrell | cloud-secure.ai

3 thoughts on “Defender Security Dashboard Part 1: Workbooks in the Defender portal, and device health”

  1. Pingback: Defender Security Dashboard Part 3: Alerts and attack surface reduction | Cloud-Secure.ai

  2. Pingback: Defender Security Dashboard Part 2: Are the protections actually on? | Cloud-Secure.ai

  3. Pingback: Building a Defender Security Dashboard with Workbooks: the series | Cloud-Secure.ai

Leave a Reply