Single-pane visibility into your Tailscale tailnet on Personal (Free), Starter and Standard tiers: who, what, when, where, and what changed. Scope every panel with the time range below; the Investigate tab adds Actor and Device pickers for drilldown. Premium-tier panels (network flow logs, posture integrations) live in the separate Tailscale Operations (Premium) workbook.
\"},\"name\":\"div-tailnet-at-a-glance-04e62b\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let DEV = Tailscale_Devices_CL | summarize arg_max(TimeGenerated, *) by DeviceId;\\nlet USR = Tailscale_Users_CL | summarize arg_max(TimeGenerated, *) by UserId;\\nlet KEY = Tailscale_Keys_CL | summarize arg_max(TimeGenerated, *) by KeyId;\\nunion\\n (DEV | summarize V=toreal(count()) | extend Metric=\\\"Devices\\\", Order=1),\\n (DEV | where Authorized == true | summarize V=toreal(count()) | extend Metric=\\\"Authorized\\\", Order=2),\\n (DEV | where UpdateAvailable == true | summarize V=toreal(count()) | extend Metric=\\\"Updates Available\\\", Order=3),\\n (DEV | where SshEnabled == true | summarize V=toreal(count()) | extend Metric=\\\"SSH-Enabled\\\", Order=4),\\n (USR | summarize V=toreal(count()) | extend Metric=\\\"Users\\\", Order=5),\\n (USR | where Role =~ \\\"admin\\\" or Role =~ \\\"owner\\\" or Role =~ \\\"network-admin\\\" | summarize V=toreal(count()) | extend Metric=\\\"Admins\\\", Order=6),\\n (KEY | where isnull(Revoked) and (isnull(Expires) or Expires > now()) | summarize V=toreal(count()) | extend Metric=\\\"Active Keys\\\", Order=7),\\n (Tailscale_Audit_CL | where TimeGenerated {TimeRange} | summarize V=toreal(count()) | extend Metric=\\\"Audit Events ({TimeRange:label})\\\", Order=8)\\n| order by Order asc | project Metric, Value=V\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":false}},\"name\":\"q-4e9d7cff\"},{\"type\":1,\"content\":{\"json\":\"
Audit activity over time
\"},\"name\":\"div-audit-activity-over--546688\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| summarize EventCount = count() by bin(TimeGenerated, 1h), Action\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Audit events by action\",\"noDataMessage\":\"No audit events in the selected window. Widen the time range; remember the Tailscale audit poll runs every ~30 min.\",\"noDataMessageStyle\":5},\"name\":\"q-effcd498\"},{\"type\":1,\"content\":{\"json\":\"
Who's doing what
\"},\"name\":\"div-who's-doing-what-52144e\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Actor=tostring(coalesce(Actor.loginName, Actor.displayName, Actor.type))\\n| where isnotempty(Actor)\\n| summarize Events=count(), DistinctActions=dcount(Action), LastSeen=max(TimeGenerated) by Actor\\n| order by Events desc | take 15\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Top actors (by event count)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Events\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"LastSeen\",\"formatter\":6}]}},\"name\":\"q-34c77fa9\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend TargetType=tostring(Target.type)\\n| where isnotempty(TargetType)\\n| summarize Events=count() by TargetType\\n| order by Events desc | take 15\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Activity by target type\"},\"name\":\"q-4b3b709d\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"
Recent admin events
\"},\"name\":\"div-recent-admin-events-87c9e0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Actor=tostring(coalesce(Actor.loginName, Actor.displayName, Actor.type))\\n| extend TargetType=tostring(Target.type), TargetName=tostring(coalesce(Target.name, Target.id))\\n| project TimeGenerated, Action, Actor, TargetType, TargetName, Origin\\n| order by TimeGenerated desc | take 30\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Most recent 30 audit events\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]}},\"name\":\"q-5e7d6306\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"overview\"},\"name\":\"group-overview\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"b83a25c1-da18-49a2-a444-6517f13d891c\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SelectedActor\",\"label\":\"Actor\",\"type\":2,\"isRequired\":false,\"query\":\"let opts = Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where isnotempty(ActorLogin)\\n| summarize Events=count() by ActorLogin\\n| project value=ActorLogin, label=strcat(ActorLogin, \\\" (\\\", tostring(Events), \\\" events)\\\");\\n(print value=\\\"__ALL__\\\", label=\\\"(All actors)\\\")\\n| union opts\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"__ALL__\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"investigate-picker-actor\"},{\"type\":1,\"content\":{\"json\":\"
Actor activity timeline
\"},\"name\":\"div-actor-activity-timel-5ec305\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where \\\"{SelectedActor}\\\" == \\\"__ALL__\\\" or ActorLogin == \\\"{SelectedActor}\\\"\\n| summarize Events=count() by bin(TimeGenerated, 1h), Action\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Actions over time -- actor: {SelectedActor:label}\",\"noDataMessage\":\"Select an actor from the Actor dropdown above, or leave on 'All' to see total activity.\",\"noDataMessageStyle\":5},\"name\":\"q-32d40848\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where \\\"{SelectedActor}\\\" == \\\"__ALL__\\\" or ActorLogin == \\\"{SelectedActor}\\\"\\n| extend TargetType=tostring(Target.type), TargetName=tostring(coalesce(Target.name, Target.id))\\n| project TimeGenerated, ActorLogin, Action, TargetType, TargetName, Origin\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent events for actor: {SelectedActor:label}\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No events for this actor in the selected window.\",\"noDataMessageStyle\":5},\"name\":\"q-a742f6fd\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"a9cf7907-f201-4725-a072-a8bd34bef74e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SelectedDevice\",\"label\":\"Device\",\"type\":2,\"isRequired\":false,\"query\":\"let opts = Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| order by LastSeen desc | take 100\\n| project value=DeviceName, label=strcat(coalesce(DeviceName, Hostname), \\\" (\\\", User, \\\")\\\");\\n(print value=\\\"__ALL__\\\", label=\\\"(All devices)\\\")\\n| union opts\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"__ALL__\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"investigate-picker-device\"},{\"type\":1,\"content\":{\"json\":\"
Selected device timeline
\"},\"name\":\"div-selected-device-time-00bead\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| where \\\"{SelectedDevice}\\\" == \\\"__ALL__\\\" or DeviceName == \\\"{SelectedDevice}\\\"\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| extend OnlineNow = ClientConnectivity.endpoints != \\\"\\\" or ConnectedToControl == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, UpdateAvailable, Authorized, IsExternal, SshEnabled, LastSeen, Expires, KeyExpiryDisabled, OnlineNow, Addresses, Tags, AdvertisedRoutes\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Summary for device: {SelectedDevice:label}\",\"noDataMessage\":\"Select a device from the Device dropdown above. Defaults to 'All'.\",\"noDataMessageStyle\":5},\"name\":\"q-11094dc8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend TargetType=tostring(Target.type), TargetName=tostring(Target.name), TargetId=tostring(Target.id)\\n| where (\\\"{SelectedDevice}\\\" == \\\"__ALL__\\\" and TargetType == \\\"NODE\\\") or TargetName == \\\"{SelectedDevice}\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| project TimeGenerated, Action, ActorLogin, TargetName, TargetId, Origin\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Audit events touching device: {SelectedDevice:label}\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No audit events recorded against the selected device in this window. Tailscale tags device events with Target.type=NODE; the audit feed only emits NODE events on create/update/delete, so quiet devices stay quiet here.\",\"noDataMessageStyle\":5},\"name\":\"q-ead106ce\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"investigate\"},\"name\":\"group-investigate\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
First-seen actors in the last 24h
\"},\"name\":\"div-first-seen-actors-in-ce38d9\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let recent = Tailscale_Audit_CL | where TimeGenerated > ago(24h) | extend A=tostring(coalesce(Actor.loginName, Actor.displayName)) | summarize FirstSeen24h=min(TimeGenerated), Events=count() by A;\\nlet historical = Tailscale_Audit_CL | where TimeGenerated between(ago(30d) .. ago(24h)) | extend A=tostring(coalesce(Actor.loginName, Actor.displayName)) | distinct A;\\nrecent | join kind=leftanti historical on A | where isnotempty(A) | project FirstSeen24h, Actor=A, Events | order by Events desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Actors who have NEVER appeared before (30d baseline)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"FirstSeen24h\",\"formatter\":6},{\"columnMatch\":\"Events\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orange\"}}]},\"noDataMessage\":\"Every actor seen in the last 24h has appeared at least once in the prior 30d. Healthy state.\",\"noDataMessageStyle\":1},\"name\":\"q-9ba7b85e\"},{\"type\":1,\"content\":{\"json\":\"
Off-hours configuration changes
\"},\"name\":\"div-off-hours-configurat-3c3787\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Hour=hourofday(TimeGenerated), DayOfWeek=dayofweek(TimeGenerated)/1d\\n| where Hour < 7 or Hour > 19 or DayOfWeek in (0, 6)\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetType=tostring(Target.type)\\n| where Action !in (\\\"LOGIN\\\", \\\"LOGOUT\\\")\\n| project TimeGenerated, ActorLogin, Action, TargetType, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Admin actions outside 07:00-19:00 weekdays\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No off-hours admin changes recorded - healthy state for an organisation working business hours.\",\"noDataMessageStyle\":1},\"name\":\"q-721ee490\"},{\"type\":1,\"content\":{\"json\":\"
Devices with key expiry disabled
\"},\"name\":\"div-devices-with-key-exp-dfe9c1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where KeyExpiryDisabled == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Authorized, Tags\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices that will never re-authenticate (high-risk drift)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No devices have key expiry disabled - good. Disabling key expiry creates devices that never re-auth, drifting from policy.\",\"noDataMessageStyle\":1},\"name\":\"q-8a8e2fb5\"},{\"type\":1,\"content\":{\"json\":\"
Auth keys with no expiry
\"},\"name\":\"div-auth-keys-with-no-ex-b11207\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| where isnull(Revoked) and (isnull(Expires) or ExpirySeconds == 0)\\n| project KeyId, Description, UserId, KeyType, Created, Capabilities\\n| order by Created desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Active keys that never expire\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6}]},\"noDataMessage\":\"No never-expiring auth keys - rotation hygiene is good.\",\"noDataMessageStyle\":1},\"name\":\"q-1372740c\"},{\"type\":1,\"content\":{\"json\":\"
Devices running outdated clients
\"},\"name\":\"div-devices-running-outd-bcd077\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where UpdateAvailable == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Tags\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices flagged update-available by Tailscale\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"All devices on current client - nothing to patch.\",\"noDataMessageStyle\":1},\"name\":\"q-ecebf1f7\"},{\"type\":1,\"content\":{\"json\":\"
Dormant devices (LastSeen > 30 days)
\"},\"name\":\"div-dormant-devices-(las-761156\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where LastSeen < ago(30d)\\n| extend DaysIdle = toint((now() - LastSeen) / 1d)\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, DaysIdle, Authorized, Tags\\n| order by DaysIdle desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices idle 30+ days - candidates for retirement\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"DaysIdle\",\"formatter\":8,\"formatOptions\":{\"palette\":\"redBright\"}}]},\"noDataMessage\":\"No devices idle 30+ days - inventory is fresh.\",\"noDataMessageStyle\":1},\"name\":\"q-fb6c1fcc\"},{\"type\":1,\"content\":{\"json\":\"
Subnet route exposure
\"},\"name\":\"div-subnet-route-exposur-289c42\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where array_length(AdvertisedRoutes) > 0 or array_length(EnabledRoutes) > 0\\n| extend Routes = tostring(EnabledRoutes), Advertised = tostring(AdvertisedRoutes)\\n| project DeviceName, Hostname, User, Os, Advertised, Routes, LastSeen, SshEnabled, Authorized\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices advertising or running subnet routes / exit-node duty\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No devices advertising subnet routes. Pure mesh topology.\",\"noDataMessageStyle\":1},\"name\":\"q-9533b081\"},{\"type\":1,\"content\":{\"json\":\"
Devices with SSH enabled
\"},\"name\":\"div-devices-with-ssh-ena-285238\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where SshEnabled == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Authorized, Tags\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices with Tailscale SSH enabled (Tailscale-managed remote-shell access)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No devices have Tailscale SSH enabled - no SSH-via-Tailscale risk surface.\",\"noDataMessageStyle\":1},\"name\":\"q-735368fb\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"hunts\"},\"name\":\"group-hunts\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
User inventory snapshot
\"},\"name\":\"div-user-inventory-snaps-9dc96c\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let U = Tailscale_Users_CL | summarize arg_max(TimeGenerated, *) by UserId;\\nunion\\n (U | summarize V=toreal(count()) | extend Metric=\\\"Total users\\\", Order=1),\\n (U | where Role in~ (\\\"admin\\\",\\\"owner\\\",\\\"network-admin\\\",\\\"it-admin\\\",\\\"billing-admin\\\") | summarize V=toreal(count()) | extend Metric=\\\"Admin-tier users\\\", Order=2),\\n (U | where Status =~ \\\"active\\\" | summarize V=toreal(count()) | extend Metric=\\\"Active\\\", Order=3),\\n (U | where CurrentlyConnected == true | summarize V=toreal(count()) | extend Metric=\\\"Connected now\\\", Order=4),\\n (U | where Status =~ \\\"idle\\\" or LastSeen < ago(30d) | summarize V=toreal(count()) | extend Metric=\\\"Idle / dormant\\\", Order=5),\\n (U | where UserType =~ \\\"shared\\\" | summarize V=toreal(count()) | extend Metric=\\\"Shared (external)\\\", Order=6)\\n| order by Order asc | project Metric, Value=V\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":false}},\"name\":\"q-5689d9e8\"},{\"type\":1,\"content\":{\"json\":\"
Distribution
\"},\"name\":\"div-distribution-de67ec\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| summarize Count=count() by Role\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Users by role\"},\"name\":\"q-0c47912a\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| summarize Count=count() by Status\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Users by status\"},\"name\":\"q-e8c20a69\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| summarize Count=count() by UserType\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Users by type (member / shared)\"},\"name\":\"q-2c51776d\",\"customWidth\":\"33\"},{\"type\":1,\"content\":{\"json\":\"
Activity heatmap
\"},\"name\":\"div-activity-heatmap-ca6a21\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| extend DaysSinceLogin = toint((now() - LastSeen) / 1d)\\n| extend Bucket = case(\\n DaysSinceLogin < 1, \\\"Today\\\",\\n DaysSinceLogin < 7, \\\"This week\\\",\\n DaysSinceLogin < 30, \\\"This month\\\",\\n DaysSinceLogin < 90, \\\"Past quarter\\\",\\n \\\"90+ days\\\")\\n| summarize Users=count() by Bucket\\n| order by case(Bucket==\\\"Today\\\",1, Bucket==\\\"This week\\\",2, Bucket==\\\"This month\\\",3, Bucket==\\\"Past quarter\\\",4, 5) asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Users by recency of last login\"},\"name\":\"q-350e118d\"},{\"type\":1,\"content\":{\"json\":\"
Full user list
\"},\"name\":\"div-full-user-list-136aac\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| project DisplayName, LoginName, Role, Status, UserType, DeviceCount, CurrentlyConnected, Created, LastSeen\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"All users (latest snapshot per user ID)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6},{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"Role\",\"formatter\":1},{\"columnMatch\":\"Status\",\"formatter\":1},{\"columnMatch\":\"DeviceCount\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"name\":\"q-cee23d3c\"},{\"type\":1,\"content\":{\"json\":\"
Orphaned users (active but no devices)
\"},\"name\":\"div-orphaned-users-(acti-56d6f8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| where Status =~ \\\"active\\\" and DeviceCount == 0\\n| project DisplayName, LoginName, Role, UserType, Created, LastSeen\\n| order by Created desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Active accounts with zero devices - candidates for offboarding review\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6},{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"Every active account has at least one device - good hygiene.\",\"noDataMessageStyle\":1},\"name\":\"q-83fcd942\"},{\"type\":1,\"content\":{\"json\":\"
Role escalation history
\"},\"name\":\"div-role-escalation-hist-bd8df4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where Action == \\\"USER_ROLE_UPDATE\\\" or Action == \\\"USER_ROLES_ASSIGNED\\\" or Action contains \\\"ROLE\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetName=tostring(coalesce(Target.name, Target.id))\\n| extend FromRole=tostring(Old.role), ToRole=tostring(New.role)\\n| project TimeGenerated, ActorLogin, Action, TargetName, FromRole, ToRole, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent role changes\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"ToRole\",\"formatter\":1}]},\"noDataMessage\":\"No role changes in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-f6c8358a\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"identity\"},\"name\":\"group-identity\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
\"},\"name\":\"div-distribution-396d03\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| summarize Count=count() by Os\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Devices by OS\"},\"name\":\"q-0c8f5988\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| summarize Count=count() by ClientVersion\\n| order by Count desc | take 10\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Top 10 client versions\"},\"name\":\"q-af1c45cd\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| mv-expand Tag = Tags to typeof(string)\\n| summarize Devices=dcount(DeviceId) by Tag=iff(isempty(Tag), \\\"(untagged)\\\", Tag)\\n| order by Devices desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Devices by tag\"},\"name\":\"q-c3a0ef5b\",\"customWidth\":\"33\"},{\"type\":1,\"content\":{\"json\":\"
Devices needing attention
\"},\"name\":\"div-devices-needing-atte-f04a47\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where UpdateAvailable == true or KeyExpiryDisabled == true or LastSeen < ago(30d) or Authorized == false\\n| extend Issues = strcat_array(pack_array(\\n iff(UpdateAvailable == true, \\\"needs-update\\\", \\\"\\\"),\\n iff(KeyExpiryDisabled == true, \\\"key-never-expires\\\", \\\"\\\"),\\n iff(LastSeen < ago(30d), \\\"stale\\\", \\\"\\\"),\\n iff(Authorized == false, \\\"unauthorized\\\", \\\"\\\")), \\\",\\\")\\n| extend Issues = trim(\\\",\\\", trim_start(\\\",\\\", trim_end(\\\",\\\", replace_string(Issues, \\\",,\\\", \\\",\\\"))))\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Issues\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices flagged with one or more issues\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"Issues\",\"formatter\":1}]},\"noDataMessage\":\"No devices need attention - all updated, fresh, authorized, and key-rotating.\",\"noDataMessageStyle\":1},\"name\":\"q-dc5db84c\"},{\"type\":1,\"content\":{\"json\":\"
Full device inventory
\"},\"name\":\"div-full-device-inventor-b70642\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| project DeviceName, Hostname, User, Os, ClientVersion, UpdateAvailable, Authorized, IsExternal, SshEnabled, LastSeen, KeyExpiryDisabled, Tags, AdvertisedRoutes\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"All devices (latest snapshot per device ID)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"Os\",\"formatter\":1},{\"columnMatch\":\"ClientVersion\",\"formatter\":1}]}},\"name\":\"q-7f7e7a9a\"},{\"type\":1,\"content\":{\"json\":\"
Subnet routers / exit nodes
\"},\"name\":\"div-subnet-routers-/-exi-b082d6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where array_length(AdvertisedRoutes) > 0\\n| extend AdvertisedSummary = tostring(AdvertisedRoutes), EnabledSummary = tostring(EnabledRoutes)\\n| project DeviceName, Hostname, User, Os, AdvertisedSummary, EnabledSummary, LastSeen, Authorized\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices advertising subnet routes or exit-node capability\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No subnet routers in this tailnet - pure mesh topology.\",\"noDataMessageStyle\":1},\"name\":\"q-5004df25\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"devices\"},\"name\":\"group-devices\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Credentials snapshot
\"},\"name\":\"div-credentials-snapshot-fd464a\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let K = Tailscale_Keys_CL | summarize arg_max(TimeGenerated, *) by KeyId;\\nunion\\n (K | summarize V=toreal(count()) | extend Metric=\\\"Total keys\\\", Order=1),\\n (K | where isnull(Revoked) and (isnull(Expires) or Expires > now()) | summarize V=toreal(count()) | extend Metric=\\\"Active\\\", Order=2),\\n (K | where isnotnull(Revoked) | summarize V=toreal(count()) | extend Metric=\\\"Revoked\\\", Order=3),\\n (K | where Expires < now() and isnull(Revoked) | summarize V=toreal(count()) | extend Metric=\\\"Expired\\\", Order=4),\\n (K | where isnull(Revoked) and Expires between(now() .. ago(-7d)) | summarize V=toreal(count()) | extend Metric=\\\"Expiring in 7d\\\", Order=5),\\n (K | where isnull(Revoked) and (isnull(Expires) or ExpirySeconds==0) | summarize V=toreal(count()) | extend Metric=\\\"Never expire\\\", Order=6),\\n (K | where KeyType =~ \\\"auth\\\" | summarize V=toreal(count()) | extend Metric=\\\"Auth keys\\\", Order=7),\\n (K | where KeyType =~ \\\"api\\\" or KeyType contains \\\"oauth\\\" | summarize V=toreal(count()) | extend Metric=\\\"API / OAuth\\\", Order=8)\\n| order by Order asc | project Metric, Value=V\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":false}},\"name\":\"q-79398bc0\"},{\"type\":1,\"content\":{\"json\":\"
Distribution
\"},\"name\":\"div-distribution-15f665\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| summarize Count=count() by KeyType\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Keys by type\"},\"name\":\"q-23e6618b\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| where isnull(Revoked)\\n| extend Bucket = case(\\n isnull(Expires) or ExpirySeconds == 0, \\\"Never\\\",\\n Expires < now(), \\\"Already expired\\\",\\n Expires < ago(-1d), \\\"<24h\\\",\\n Expires < ago(-7d), \\\"1-7d\\\",\\n Expires < ago(-30d), \\\"8-30d\\\",\\n Expires < ago(-90d), \\\"31-90d\\\",\\n \\\"90+d\\\")\\n| summarize Keys=count() by Bucket\\n| order by case(Bucket==\\\"Already expired\\\",1, Bucket==\\\"<24h\\\",2, Bucket==\\\"1-7d\\\",3, Bucket==\\\"8-30d\\\",4, Bucket==\\\"31-90d\\\",5, Bucket==\\\"90+d\\\",6, Bucket==\\\"Never\\\",7, 8) asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Active key expiry distribution\"},\"name\":\"q-7484d1a0\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"
Active credential register
\"},\"name\":\"div-active-credential-re-c27539\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| where isnull(Revoked)\\n| extend ExpiryStatus = case(\\n isnull(Expires) or ExpirySeconds == 0, \\\"Never expires\\\",\\n Expires < now(), \\\"Expired\\\",\\n Expires < ago(-7d), \\\"Expires in 7d\\\",\\n Expires < ago(-30d), \\\"Expires in 30d\\\",\\n \\\"OK\\\")\\n| project KeyId, KeyType, Description, UserId, Created, Expires, ExpiryStatus, Capabilities\\n| order by Created desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"All active credentials with computed expiry status\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6},{\"columnMatch\":\"Expires\",\"formatter\":6},{\"columnMatch\":\"ExpiryStatus\",\"formatter\":1},{\"columnMatch\":\"KeyType\",\"formatter\":1}]}},\"name\":\"q-4b27a750\"},{\"type\":1,\"content\":{\"json\":\"
Credential CRUD events
\"},\"name\":\"div-credential-crud-even-e455b0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where Action contains \\\"API_KEY\\\" or Action contains \\\"AUTH_KEY\\\" or Action contains \\\"OAUTH\\\" or Action contains \\\"KEY_CREATE\\\" or Action contains \\\"KEY_REVOKE\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetId=tostring(Target.id), TargetType=tostring(Target.type)\\n| project TimeGenerated, Action, ActorLogin, TargetType, TargetId, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent credential create / revoke / rotate events\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No credential CRUD activity in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-777693bd\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"credentials\"},\"name\":\"group-credentials\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Audit volume
\"},\"name\":\"div-audit-volume-de29a2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| summarize Events=count() by bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Audit events per hour\",\"noDataMessage\":\"No audit events in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-f5eee265\"},{\"type\":1,\"content\":{\"json\":\"
Action heatmap by hour of day
\"},\"name\":\"div-action-heatmap-by-ho-c8bd5e\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Hour=hourofday(TimeGenerated)\\n| summarize Events=count() by Hour, Action\\n| order by Hour asc, Events desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"categoricalbar\",\"title\":\"When are admin actions happening?\"},\"name\":\"q-c6f45d95\"},{\"type\":1,\"content\":{\"json\":\"
Actor / Action heatmap
\"},\"name\":\"div-actor-/-action-heatm-d820ba\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where isnotempty(ActorLogin)\\n| summarize Events=count() by ActorLogin, Action\\n| order by Events desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Who is firing which action\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Events\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]},\"noDataMessage\":\"No audit events in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-06c13b3b\"},{\"type\":1,\"content\":{\"json\":\"
Recent activity
\"},\"name\":\"div-recent-activity-63b210\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetType=tostring(Target.type), TargetName=tostring(coalesce(Target.name, Target.id))\\n| project TimeGenerated, Action, ActorLogin, TargetType, TargetName, Origin, EventGroupID\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Last 100 audit events\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"Action\",\"formatter\":1}]},\"noDataMessage\":\"No audit events in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-07a25a40\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"audit\"},\"name\":\"group-audit\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
DNS configuration (current state)
\"},\"name\":\"div-dns-configuration-(c-5f2e1e\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Dns_CL\\n| summarize arg_max(TimeGenerated, *) by ConfigType\\n| project ConfigType, Nameservers, MagicDNS, SearchPaths, LastSnapshot=TimeGenerated\\n| order by ConfigType asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"MagicDNS, nameservers, search paths\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSnapshot\",\"formatter\":6},{\"columnMatch\":\"ConfigType\",\"formatter\":1}]},\"noDataMessage\":\"No DNS snapshots in the workspace yet. DNS polls runs at ~30 min cadence.\",\"noDataMessageStyle\":5},\"name\":\"q-d6a6a358\"},{\"type\":1,\"content\":{\"json\":\"
\"},\"name\":\"div-dns-change-history-9dd376\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend TargetProperty=tostring(Target.property)\\n| where Action contains \\\"DNS\\\" or TargetProperty has_any (\\\"DNS_NAMESERVERS\\\", \\\"DNS_SPLIT_DNS\\\", \\\"MAGICDNS\\\", \\\"DNS_SEARCH_PATHS\\\")\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| project TimeGenerated, ActorLogin, Action, TargetProperty, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent DNS-related admin changes\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No DNS changes in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-a132ff6a\"},{\"type\":1,\"content\":{\"json\":\"
ACL policy changes
\"},\"name\":\"div-acl-policy-changes-ff0e68\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where Action == \\\"ACL_UPDATE\\\" or Action contains \\\"ACL\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| project TimeGenerated, ActorLogin, Action, Origin, EventGroupID\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent ACL / policy file modifications\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No ACL changes in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-94818c53\"},{\"type\":1,\"content\":{\"json\":\"
Subnet routes & exit nodes
\"},\"name\":\"div-subnet-routes-and-ex-eef47f\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where array_length(EnabledRoutes) > 0\\n| project DeviceName, User, Os, EnabledRoutes=tostring(EnabledRoutes), AdvertisedRoutes=tostring(AdvertisedRoutes), LastSeen\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Routes currently being served from devices\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No subnet routers active in this tailnet.\",\"noDataMessageStyle\":1},\"name\":\"q-61a792cd\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"network\"},\"name\":\"group-network\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Ingest rate per table
\"},\"name\":\"div-ingest-rate-per-tabl-24e5f6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"union withsource=Table Tailscale_Audit_CL, Tailscale_Devices_CL, Tailscale_Users_CL, Tailscale_Keys_CL, Tailscale_Dns_CL, Tailscale_Settings_CL\\n| where TimeGenerated > ago(24h)\\n| summarize Rows=count() by Table, bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Rows ingested per Tailscale table per hour (last 24h)\",\"noDataMessage\":\"No Tailscale data ingested in the last 24h - check the connector card under Sentinel Data Connectors.\",\"noDataMessageStyle\":5},\"name\":\"q-c6fd0143\"},{\"type\":1,\"content\":{\"json\":\"
\"},\"name\":\"div-log-analytics-operat-630749\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"_LogOperation\\n| where TimeGenerated > ago(24h)\\n| where _ResourceId contains \\\"tailscale\\\" or Detail contains \\\"Tailscale_\\\"\\n| project TimeGenerated, Operation, Level, Detail\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Log Analytics operational events touching Tailscale tables\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"Level\",\"formatter\":1}]},\"noDataMessage\":\"No operational issues recorded in the last 24h.\",\"noDataMessageStyle\":1},\"name\":\"q-b492f150\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"pipeline\"},\"name\":\"group-pipeline\"},{\"type\":1,\"content\":{\"json\":\"
Tailscale Operations (Standard) (CCF) - Microsoft Sentinel content from the Tailscale (CCF) solution, Standard-tier surface. Tables polled from the Tailscale REST API: audit, devices, users, keys, dns, settings. Filter every panel via the time range above; the Investigate tab adds Actor and Device pickers for drilldown. For network flow logs and posture integrations, install the companion Tailscale Operations (Premium) workbook on a Premium / Enterprise tailnet.
Single-pane visibility into your Tailscale tailnet on Personal (Free), Starter and Premium tiers: who, what, when, where, and what changed. Scope every panel with the time range below; the Investigate tab adds Actor and Device pickers for drilldown. Premium-tier panels (network flow logs, posture integrations) live in the separate Tailscale Operations (Premium) workbook.
\"},\"name\":\"div-tailnet-at-a-glance-04e62b\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let DEV = Tailscale_Devices_CL | summarize arg_max(TimeGenerated, *) by DeviceId;\\nlet USR = Tailscale_Users_CL | summarize arg_max(TimeGenerated, *) by UserId;\\nlet KEY = Tailscale_Keys_CL | summarize arg_max(TimeGenerated, *) by KeyId;\\nunion\\n (DEV | summarize V=toreal(count()) | extend Metric=\\\"Devices\\\", Order=1),\\n (DEV | where Authorized == true | summarize V=toreal(count()) | extend Metric=\\\"Authorized\\\", Order=2),\\n (DEV | where UpdateAvailable == true | summarize V=toreal(count()) | extend Metric=\\\"Updates Available\\\", Order=3),\\n (DEV | where SshEnabled == true | summarize V=toreal(count()) | extend Metric=\\\"SSH-Enabled\\\", Order=4),\\n (USR | summarize V=toreal(count()) | extend Metric=\\\"Users\\\", Order=5),\\n (USR | where Role =~ \\\"admin\\\" or Role =~ \\\"owner\\\" or Role =~ \\\"network-admin\\\" | summarize V=toreal(count()) | extend Metric=\\\"Admins\\\", Order=6),\\n (KEY | where isnull(Revoked) and (isnull(Expires) or Expires > now()) | summarize V=toreal(count()) | extend Metric=\\\"Active Keys\\\", Order=7),\\n (Tailscale_Audit_CL | where TimeGenerated {TimeRange} | summarize V=toreal(count()) | extend Metric=\\\"Audit Events ({TimeRange:label})\\\", Order=8)\\n,\\n (Tailscale_Network_CL | where TimeGenerated {TimeRange} | summarize V=toreal(count()) | extend Metric=\\\"Flows ({TimeRange:label})\\\", Order=9),\\n (Tailscale_Network_CL | where TimeGenerated {TimeRange} | summarize V=toreal(dcount(SrcNodeName)) | extend Metric=\\\"Active Talkers\\\", Order=10),\\n (Tailscale_Network_CL | where TimeGenerated {TimeRange} | summarize V=toreal(iff(count()==0, 0.0, 100.0 * countif(IsRelayed) / count())) | extend Metric=\\\"DERP Relayed %\\\", Order=11),\\n (Tailscale_PostureIntegrations_CL | where TimeGenerated {TimeRange} | summarize arg_max(TimeGenerated, *) by IntegrationId | summarize V=toreal(count()) | extend Metric=\\\"Posture Integrations\\\", Order=12)\\n| order by Order asc | project Metric, Value=V\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":false}},\"name\":\"q-4e9d7cff\"},{\"type\":1,\"content\":{\"json\":\"
Audit activity over time
\"},\"name\":\"div-audit-activity-over--546688\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| summarize EventCount = count() by bin(TimeGenerated, 1h), Action\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Audit events by action\",\"noDataMessage\":\"No audit events in the selected window. Widen the time range; remember the Tailscale audit poll runs every ~30 min.\",\"noDataMessageStyle\":5},\"name\":\"q-effcd498\"},{\"type\":1,\"content\":{\"json\":\"
Who's doing what
\"},\"name\":\"div-who's-doing-what-52144e\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Actor=tostring(coalesce(Actor.loginName, Actor.displayName, Actor.type))\\n| where isnotempty(Actor)\\n| summarize Events=count(), DistinctActions=dcount(Action), LastSeen=max(TimeGenerated) by Actor\\n| order by Events desc | take 15\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Top actors (by event count)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Events\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"LastSeen\",\"formatter\":6}]}},\"name\":\"q-34c77fa9\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend TargetType=tostring(Target.type)\\n| where isnotempty(TargetType)\\n| summarize Events=count() by TargetType\\n| order by Events desc | take 15\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Activity by target type\"},\"name\":\"q-4b3b709d\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"
Recent admin events
\"},\"name\":\"div-recent-admin-events-87c9e0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Actor=tostring(coalesce(Actor.loginName, Actor.displayName, Actor.type))\\n| extend TargetType=tostring(Target.type), TargetName=tostring(coalesce(Target.name, Target.id))\\n| project TimeGenerated, Action, Actor, TargetType, TargetName, Origin\\n| order by TimeGenerated desc | take 30\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Most recent 30 audit events\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]}},\"name\":\"q-5e7d6306\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"overview\"},\"name\":\"group-overview\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"b83a25c1-da18-49a2-a444-6517f13d891c\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SelectedActor\",\"label\":\"Actor\",\"type\":2,\"isRequired\":false,\"query\":\"let opts = Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where isnotempty(ActorLogin)\\n| summarize Events=count() by ActorLogin\\n| project value=ActorLogin, label=strcat(ActorLogin, \\\" (\\\", tostring(Events), \\\" events)\\\");\\n(print value=\\\"__ALL__\\\", label=\\\"(All actors)\\\")\\n| union opts\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"__ALL__\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"investigate-picker-actor\"},{\"type\":1,\"content\":{\"json\":\"
Actor activity timeline
\"},\"name\":\"div-actor-activity-timel-5ec305\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where \\\"{SelectedActor}\\\" == \\\"__ALL__\\\" or ActorLogin == \\\"{SelectedActor}\\\"\\n| summarize Events=count() by bin(TimeGenerated, 1h), Action\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Actions over time -- actor: {SelectedActor:label}\",\"noDataMessage\":\"Select an actor from the Actor dropdown above, or leave on 'All' to see total activity.\",\"noDataMessageStyle\":5},\"name\":\"q-32d40848\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where \\\"{SelectedActor}\\\" == \\\"__ALL__\\\" or ActorLogin == \\\"{SelectedActor}\\\"\\n| extend TargetType=tostring(Target.type), TargetName=tostring(coalesce(Target.name, Target.id))\\n| project TimeGenerated, ActorLogin, Action, TargetType, TargetName, Origin\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent events for actor: {SelectedActor:label}\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No events for this actor in the selected window.\",\"noDataMessageStyle\":5},\"name\":\"q-a742f6fd\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"a9cf7907-f201-4725-a072-a8bd34bef74e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SelectedDevice\",\"label\":\"Device\",\"type\":2,\"isRequired\":false,\"query\":\"let opts = Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| order by LastSeen desc | take 100\\n| project value=DeviceName, label=strcat(coalesce(DeviceName, Hostname), \\\" (\\\", User, \\\")\\\");\\n(print value=\\\"__ALL__\\\", label=\\\"(All devices)\\\")\\n| union opts\",\"typeSettings\":{\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":\"__ALL__\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"investigate-picker-device\"},{\"type\":1,\"content\":{\"json\":\"
Selected device timeline
\"},\"name\":\"div-selected-device-time-00bead\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| where \\\"{SelectedDevice}\\\" == \\\"__ALL__\\\" or DeviceName == \\\"{SelectedDevice}\\\"\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| extend OnlineNow = ClientConnectivity.endpoints != \\\"\\\" or ConnectedToControl == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, UpdateAvailable, Authorized, IsExternal, SshEnabled, LastSeen, Expires, KeyExpiryDisabled, OnlineNow, Addresses, Tags, AdvertisedRoutes\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Summary for device: {SelectedDevice:label}\",\"noDataMessage\":\"Select a device from the Device dropdown above. Defaults to 'All'.\",\"noDataMessageStyle\":5},\"name\":\"q-11094dc8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend TargetType=tostring(Target.type), TargetName=tostring(Target.name), TargetId=tostring(Target.id)\\n| where (\\\"{SelectedDevice}\\\" == \\\"__ALL__\\\" and TargetType == \\\"NODE\\\") or TargetName == \\\"{SelectedDevice}\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| project TimeGenerated, Action, ActorLogin, TargetName, TargetId, Origin\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Audit events touching device: {SelectedDevice:label}\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No audit events recorded against the selected device in this window. Tailscale tags device events with Target.type=NODE; the audit feed only emits NODE events on create/update/delete, so quiet devices stay quiet here.\",\"noDataMessageStyle\":5},\"name\":\"q-ead106ce\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"investigate\"},\"name\":\"group-investigate\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
First-seen actors in the last 24h
\"},\"name\":\"div-first-seen-actors-in-ce38d9\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let recent = Tailscale_Audit_CL | where TimeGenerated > ago(24h) | extend A=tostring(coalesce(Actor.loginName, Actor.displayName)) | summarize FirstSeen24h=min(TimeGenerated), Events=count() by A;\\nlet historical = Tailscale_Audit_CL | where TimeGenerated between(ago(30d) .. ago(24h)) | extend A=tostring(coalesce(Actor.loginName, Actor.displayName)) | distinct A;\\nrecent | join kind=leftanti historical on A | where isnotempty(A) | project FirstSeen24h, Actor=A, Events | order by Events desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Actors who have NEVER appeared before (30d baseline)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"FirstSeen24h\",\"formatter\":6},{\"columnMatch\":\"Events\",\"formatter\":8,\"formatOptions\":{\"palette\":\"orange\"}}]},\"noDataMessage\":\"Every actor seen in the last 24h has appeared at least once in the prior 30d. Healthy state.\",\"noDataMessageStyle\":1},\"name\":\"q-9ba7b85e\"},{\"type\":1,\"content\":{\"json\":\"
Off-hours configuration changes
\"},\"name\":\"div-off-hours-configurat-3c3787\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Hour=hourofday(TimeGenerated), DayOfWeek=dayofweek(TimeGenerated)/1d\\n| where Hour < 7 or Hour > 19 or DayOfWeek in (0, 6)\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetType=tostring(Target.type)\\n| where Action !in (\\\"LOGIN\\\", \\\"LOGOUT\\\")\\n| project TimeGenerated, ActorLogin, Action, TargetType, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Admin actions outside 07:00-19:00 weekdays\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No off-hours admin changes recorded - healthy state for an organisation working business hours.\",\"noDataMessageStyle\":1},\"name\":\"q-721ee490\"},{\"type\":1,\"content\":{\"json\":\"
Devices with key expiry disabled
\"},\"name\":\"div-devices-with-key-exp-dfe9c1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where KeyExpiryDisabled == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Authorized, Tags\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices that will never re-authenticate (high-risk drift)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No devices have key expiry disabled - good. Disabling key expiry creates devices that never re-auth, drifting from policy.\",\"noDataMessageStyle\":1},\"name\":\"q-8a8e2fb5\"},{\"type\":1,\"content\":{\"json\":\"
Auth keys with no expiry
\"},\"name\":\"div-auth-keys-with-no-ex-b11207\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| where isnull(Revoked) and (isnull(Expires) or ExpirySeconds == 0)\\n| project KeyId, Description, UserId, KeyType, Created, Capabilities\\n| order by Created desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Active keys that never expire\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6}]},\"noDataMessage\":\"No never-expiring auth keys - rotation hygiene is good.\",\"noDataMessageStyle\":1},\"name\":\"q-1372740c\"},{\"type\":1,\"content\":{\"json\":\"
Devices running outdated clients
\"},\"name\":\"div-devices-running-outd-bcd077\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where UpdateAvailable == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Tags\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices flagged update-available by Tailscale\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"All devices on current client - nothing to patch.\",\"noDataMessageStyle\":1},\"name\":\"q-ecebf1f7\"},{\"type\":1,\"content\":{\"json\":\"
Dormant devices (LastSeen > 30 days)
\"},\"name\":\"div-dormant-devices-(las-761156\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where LastSeen < ago(30d)\\n| extend DaysIdle = toint((now() - LastSeen) / 1d)\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, DaysIdle, Authorized, Tags\\n| order by DaysIdle desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices idle 30+ days - candidates for retirement\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"DaysIdle\",\"formatter\":8,\"formatOptions\":{\"palette\":\"redBright\"}}]},\"noDataMessage\":\"No devices idle 30+ days - inventory is fresh.\",\"noDataMessageStyle\":1},\"name\":\"q-fb6c1fcc\"},{\"type\":1,\"content\":{\"json\":\"
Subnet route exposure
\"},\"name\":\"div-subnet-route-exposur-289c42\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where array_length(AdvertisedRoutes) > 0 or array_length(EnabledRoutes) > 0\\n| extend Routes = tostring(EnabledRoutes), Advertised = tostring(AdvertisedRoutes)\\n| project DeviceName, Hostname, User, Os, Advertised, Routes, LastSeen, SshEnabled, Authorized\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices advertising or running subnet routes / exit-node duty\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No devices advertising subnet routes. Pure mesh topology.\",\"noDataMessageStyle\":1},\"name\":\"q-9533b081\"},{\"type\":1,\"content\":{\"json\":\"
Devices with SSH enabled
\"},\"name\":\"div-devices-with-ssh-ena-285238\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where SshEnabled == true\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Authorized, Tags\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices with Tailscale SSH enabled (Tailscale-managed remote-shell access)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No devices have Tailscale SSH enabled - no SSH-via-Tailscale risk surface.\",\"noDataMessageStyle\":1},\"name\":\"q-735368fb\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"hunts\"},\"name\":\"group-hunts\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
User inventory snapshot
\"},\"name\":\"div-user-inventory-snaps-9dc96c\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let U = Tailscale_Users_CL | summarize arg_max(TimeGenerated, *) by UserId;\\nunion\\n (U | summarize V=toreal(count()) | extend Metric=\\\"Total users\\\", Order=1),\\n (U | where Role in~ (\\\"admin\\\",\\\"owner\\\",\\\"network-admin\\\",\\\"it-admin\\\",\\\"billing-admin\\\") | summarize V=toreal(count()) | extend Metric=\\\"Admin-tier users\\\", Order=2),\\n (U | where Status =~ \\\"active\\\" | summarize V=toreal(count()) | extend Metric=\\\"Active\\\", Order=3),\\n (U | where CurrentlyConnected == true | summarize V=toreal(count()) | extend Metric=\\\"Connected now\\\", Order=4),\\n (U | where Status =~ \\\"idle\\\" or LastSeen < ago(30d) | summarize V=toreal(count()) | extend Metric=\\\"Idle / dormant\\\", Order=5),\\n (U | where UserType =~ \\\"shared\\\" | summarize V=toreal(count()) | extend Metric=\\\"Shared (external)\\\", Order=6)\\n| order by Order asc | project Metric, Value=V\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":false}},\"name\":\"q-5689d9e8\"},{\"type\":1,\"content\":{\"json\":\"
Distribution
\"},\"name\":\"div-distribution-de67ec\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| summarize Count=count() by Role\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Users by role\"},\"name\":\"q-0c47912a\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| summarize Count=count() by Status\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Users by status\"},\"name\":\"q-e8c20a69\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| summarize Count=count() by UserType\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Users by type (member / shared)\"},\"name\":\"q-2c51776d\",\"customWidth\":\"33\"},{\"type\":1,\"content\":{\"json\":\"
Activity heatmap
\"},\"name\":\"div-activity-heatmap-ca6a21\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| extend DaysSinceLogin = toint((now() - LastSeen) / 1d)\\n| extend Bucket = case(\\n DaysSinceLogin < 1, \\\"Today\\\",\\n DaysSinceLogin < 7, \\\"This week\\\",\\n DaysSinceLogin < 30, \\\"This month\\\",\\n DaysSinceLogin < 90, \\\"Past quarter\\\",\\n \\\"90+ days\\\")\\n| summarize Users=count() by Bucket\\n| order by case(Bucket==\\\"Today\\\",1, Bucket==\\\"This week\\\",2, Bucket==\\\"This month\\\",3, Bucket==\\\"Past quarter\\\",4, 5) asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Users by recency of last login\"},\"name\":\"q-350e118d\"},{\"type\":1,\"content\":{\"json\":\"
Full user list
\"},\"name\":\"div-full-user-list-136aac\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| project DisplayName, LoginName, Role, Status, UserType, DeviceCount, CurrentlyConnected, Created, LastSeen\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"All users (latest snapshot per user ID)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6},{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"Role\",\"formatter\":1},{\"columnMatch\":\"Status\",\"formatter\":1},{\"columnMatch\":\"DeviceCount\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"name\":\"q-cee23d3c\"},{\"type\":1,\"content\":{\"json\":\"
Orphaned users (active but no devices)
\"},\"name\":\"div-orphaned-users-(acti-56d6f8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Users_CL\\n| summarize arg_max(TimeGenerated, *) by UserId\\n| where Status =~ \\\"active\\\" and DeviceCount == 0\\n| project DisplayName, LoginName, Role, UserType, Created, LastSeen\\n| order by Created desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Active accounts with zero devices - candidates for offboarding review\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6},{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"Every active account has at least one device - good hygiene.\",\"noDataMessageStyle\":1},\"name\":\"q-83fcd942\"},{\"type\":1,\"content\":{\"json\":\"
Role escalation history
\"},\"name\":\"div-role-escalation-hist-bd8df4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where Action == \\\"USER_ROLE_UPDATE\\\" or Action == \\\"USER_ROLES_ASSIGNED\\\" or Action contains \\\"ROLE\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetName=tostring(coalesce(Target.name, Target.id))\\n| extend FromRole=tostring(Old.role), ToRole=tostring(New.role)\\n| project TimeGenerated, ActorLogin, Action, TargetName, FromRole, ToRole, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent role changes\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"ToRole\",\"formatter\":1}]},\"noDataMessage\":\"No role changes in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-f6c8358a\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"identity\"},\"name\":\"group-identity\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
\"},\"name\":\"div-distribution-396d03\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| summarize Count=count() by Os\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Devices by OS\"},\"name\":\"q-0c8f5988\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| summarize Count=count() by ClientVersion\\n| order by Count desc | take 10\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Top 10 client versions\"},\"name\":\"q-af1c45cd\",\"customWidth\":\"33\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| mv-expand Tag = Tags to typeof(string)\\n| summarize Devices=dcount(DeviceId) by Tag=iff(isempty(Tag), \\\"(untagged)\\\", Tag)\\n| order by Devices desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Devices by tag\"},\"name\":\"q-c3a0ef5b\",\"customWidth\":\"33\"},{\"type\":1,\"content\":{\"json\":\"
Devices needing attention
\"},\"name\":\"div-devices-needing-atte-f04a47\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where UpdateAvailable == true or KeyExpiryDisabled == true or LastSeen < ago(30d) or Authorized == false\\n| extend Issues = strcat_array(pack_array(\\n iff(UpdateAvailable == true, \\\"needs-update\\\", \\\"\\\"),\\n iff(KeyExpiryDisabled == true, \\\"key-never-expires\\\", \\\"\\\"),\\n iff(LastSeen < ago(30d), \\\"stale\\\", \\\"\\\"),\\n iff(Authorized == false, \\\"unauthorized\\\", \\\"\\\")), \\\",\\\")\\n| extend Issues = trim(\\\",\\\", trim_start(\\\",\\\", trim_end(\\\",\\\", replace_string(Issues, \\\",,\\\", \\\",\\\"))))\\n| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Issues\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices flagged with one or more issues\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"Issues\",\"formatter\":1}]},\"noDataMessage\":\"No devices need attention - all updated, fresh, authorized, and key-rotating.\",\"noDataMessageStyle\":1},\"name\":\"q-dc5db84c\"},{\"type\":1,\"content\":{\"json\":\"
Full device inventory
\"},\"name\":\"div-full-device-inventor-b70642\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| project DeviceName, Hostname, User, Os, ClientVersion, UpdateAvailable, Authorized, IsExternal, SshEnabled, LastSeen, KeyExpiryDisabled, Tags, AdvertisedRoutes\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"All devices (latest snapshot per device ID)\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6},{\"columnMatch\":\"Os\",\"formatter\":1},{\"columnMatch\":\"ClientVersion\",\"formatter\":1}]}},\"name\":\"q-7f7e7a9a\"},{\"type\":1,\"content\":{\"json\":\"
Subnet routers / exit nodes
\"},\"name\":\"div-subnet-routers-/-exi-b082d6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where array_length(AdvertisedRoutes) > 0\\n| extend AdvertisedSummary = tostring(AdvertisedRoutes), EnabledSummary = tostring(EnabledRoutes)\\n| project DeviceName, Hostname, User, Os, AdvertisedSummary, EnabledSummary, LastSeen, Authorized\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices advertising subnet routes or exit-node capability\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No subnet routers in this tailnet - pure mesh topology.\",\"noDataMessageStyle\":1},\"name\":\"q-5004df25\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"devices\"},\"name\":\"group-devices\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Credentials snapshot
\"},\"name\":\"div-credentials-snapshot-fd464a\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let K = Tailscale_Keys_CL | summarize arg_max(TimeGenerated, *) by KeyId;\\nunion\\n (K | summarize V=toreal(count()) | extend Metric=\\\"Total keys\\\", Order=1),\\n (K | where isnull(Revoked) and (isnull(Expires) or Expires > now()) | summarize V=toreal(count()) | extend Metric=\\\"Active\\\", Order=2),\\n (K | where isnotnull(Revoked) | summarize V=toreal(count()) | extend Metric=\\\"Revoked\\\", Order=3),\\n (K | where Expires < now() and isnull(Revoked) | summarize V=toreal(count()) | extend Metric=\\\"Expired\\\", Order=4),\\n (K | where isnull(Revoked) and Expires between(now() .. ago(-7d)) | summarize V=toreal(count()) | extend Metric=\\\"Expiring in 7d\\\", Order=5),\\n (K | where isnull(Revoked) and (isnull(Expires) or ExpirySeconds==0) | summarize V=toreal(count()) | extend Metric=\\\"Never expire\\\", Order=6),\\n (K | where KeyType =~ \\\"auth\\\" | summarize V=toreal(count()) | extend Metric=\\\"Auth keys\\\", Order=7),\\n (K | where KeyType =~ \\\"api\\\" or KeyType contains \\\"oauth\\\" | summarize V=toreal(count()) | extend Metric=\\\"API / OAuth\\\", Order=8)\\n| order by Order asc | project Metric, Value=V\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":false}},\"name\":\"q-79398bc0\"},{\"type\":1,\"content\":{\"json\":\"
Distribution
\"},\"name\":\"div-distribution-15f665\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| summarize Count=count() by KeyType\\n| order by Count desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Keys by type\"},\"name\":\"q-23e6618b\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| where isnull(Revoked)\\n| extend Bucket = case(\\n isnull(Expires) or ExpirySeconds == 0, \\\"Never\\\",\\n Expires < now(), \\\"Already expired\\\",\\n Expires < ago(-1d), \\\"<24h\\\",\\n Expires < ago(-7d), \\\"1-7d\\\",\\n Expires < ago(-30d), \\\"8-30d\\\",\\n Expires < ago(-90d), \\\"31-90d\\\",\\n \\\"90+d\\\")\\n| summarize Keys=count() by Bucket\\n| order by case(Bucket==\\\"Already expired\\\",1, Bucket==\\\"<24h\\\",2, Bucket==\\\"1-7d\\\",3, Bucket==\\\"8-30d\\\",4, Bucket==\\\"31-90d\\\",5, Bucket==\\\"90+d\\\",6, Bucket==\\\"Never\\\",7, 8) asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Active key expiry distribution\"},\"name\":\"q-7484d1a0\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"
Active credential register
\"},\"name\":\"div-active-credential-re-c27539\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Keys_CL\\n| summarize arg_max(TimeGenerated, *) by KeyId\\n| where isnull(Revoked)\\n| extend ExpiryStatus = case(\\n isnull(Expires) or ExpirySeconds == 0, \\\"Never expires\\\",\\n Expires < now(), \\\"Expired\\\",\\n Expires < ago(-7d), \\\"Expires in 7d\\\",\\n Expires < ago(-30d), \\\"Expires in 30d\\\",\\n \\\"OK\\\")\\n| project KeyId, KeyType, Description, UserId, Created, Expires, ExpiryStatus, Capabilities\\n| order by Created desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"All active credentials with computed expiry status\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Created\",\"formatter\":6},{\"columnMatch\":\"Expires\",\"formatter\":6},{\"columnMatch\":\"ExpiryStatus\",\"formatter\":1},{\"columnMatch\":\"KeyType\",\"formatter\":1}]}},\"name\":\"q-4b27a750\"},{\"type\":1,\"content\":{\"json\":\"
Credential CRUD events
\"},\"name\":\"div-credential-crud-even-e455b0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where Action contains \\\"API_KEY\\\" or Action contains \\\"AUTH_KEY\\\" or Action contains \\\"OAUTH\\\" or Action contains \\\"KEY_CREATE\\\" or Action contains \\\"KEY_REVOKE\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetId=tostring(Target.id), TargetType=tostring(Target.type)\\n| project TimeGenerated, Action, ActorLogin, TargetType, TargetId, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent credential create / revoke / rotate events\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No credential CRUD activity in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-777693bd\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"credentials\"},\"name\":\"group-credentials\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Audit volume
\"},\"name\":\"div-audit-volume-de29a2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| summarize Events=count() by bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Audit events per hour\",\"noDataMessage\":\"No audit events in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-f5eee265\"},{\"type\":1,\"content\":{\"json\":\"
Action heatmap by hour of day
\"},\"name\":\"div-action-heatmap-by-ho-c8bd5e\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend Hour=hourofday(TimeGenerated)\\n| summarize Events=count() by Hour, Action\\n| order by Hour asc, Events desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"categoricalbar\",\"title\":\"When are admin actions happening?\"},\"name\":\"q-c6f45d95\"},{\"type\":1,\"content\":{\"json\":\"
Actor / Action heatmap
\"},\"name\":\"div-actor-/-action-heatm-d820ba\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| where isnotempty(ActorLogin)\\n| summarize Events=count() by ActorLogin, Action\\n| order by Events desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Who is firing which action\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Events\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]},\"noDataMessage\":\"No audit events in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-06c13b3b\"},{\"type\":1,\"content\":{\"json\":\"
Recent activity
\"},\"name\":\"div-recent-activity-63b210\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| extend TargetType=tostring(Target.type), TargetName=tostring(coalesce(Target.name, Target.id))\\n| project TimeGenerated, Action, ActorLogin, TargetType, TargetName, Origin, EventGroupID\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Last 100 audit events\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"Action\",\"formatter\":1}]},\"noDataMessage\":\"No audit events in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-07a25a40\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"audit\"},\"name\":\"group-audit\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
DNS configuration (current state)
\"},\"name\":\"div-dns-configuration-(c-5f2e1e\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Dns_CL\\n| summarize arg_max(TimeGenerated, *) by ConfigType\\n| project ConfigType, Nameservers, MagicDNS, SearchPaths, LastSnapshot=TimeGenerated\\n| order by ConfigType asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"MagicDNS, nameservers, search paths\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSnapshot\",\"formatter\":6},{\"columnMatch\":\"ConfigType\",\"formatter\":1}]},\"noDataMessage\":\"No DNS snapshots in the workspace yet. DNS polls runs at ~30 min cadence.\",\"noDataMessageStyle\":5},\"name\":\"q-d6a6a358\"},{\"type\":1,\"content\":{\"json\":\"
\"},\"name\":\"div-dns-change-history-9dd376\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| extend TargetProperty=tostring(Target.property)\\n| where Action contains \\\"DNS\\\" or TargetProperty has_any (\\\"DNS_NAMESERVERS\\\", \\\"DNS_SPLIT_DNS\\\", \\\"MAGICDNS\\\", \\\"DNS_SEARCH_PATHS\\\")\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| project TimeGenerated, ActorLogin, Action, TargetProperty, Origin\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent DNS-related admin changes\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No DNS changes in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-a132ff6a\"},{\"type\":1,\"content\":{\"json\":\"
ACL policy changes
\"},\"name\":\"div-acl-policy-changes-ff0e68\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where Action == \\\"ACL_UPDATE\\\" or Action contains \\\"ACL\\\"\\n| extend ActorLogin=tostring(coalesce(Actor.loginName, Actor.displayName))\\n| project TimeGenerated, ActorLogin, Action, Origin, EventGroupID\\n| order by TimeGenerated desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent ACL / policy file modifications\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6}]},\"noDataMessage\":\"No ACL changes in this window.\",\"noDataMessageStyle\":1},\"name\":\"q-94818c53\"},{\"type\":1,\"content\":{\"json\":\"
Subnet routes & exit nodes
\"},\"name\":\"div-subnet-routes-and-ex-eef47f\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Devices_CL\\n| summarize arg_max(TimeGenerated, *) by DeviceId\\n| where array_length(EnabledRoutes) > 0\\n| project DeviceName, User, Os, EnabledRoutes=tostring(EnabledRoutes), AdvertisedRoutes=tostring(AdvertisedRoutes), LastSeen\\n| order by LastSeen desc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Routes currently being served from devices\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"LastSeen\",\"formatter\":6}]},\"noDataMessage\":\"No subnet routers active in this tailnet.\",\"noDataMessageStyle\":1},\"name\":\"q-61a792cd\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"network\"},\"name\":\"group-network\"},{\"type\":12,\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"network-flows\"},\"name\":\"group-network-flows\",\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Activity snapshot
\"},\"name\":\"div-flow-snapshot\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NET = Tailscale_Network_CL | where TimeGenerated {TimeRange};\\nunion\\n (NET | summarize V=toreal(count()) | extend Metric=\\\"Total flows\\\", Order=1),\\n (NET | summarize V=toreal(dcount(SrcNodeName)) | extend Metric=\\\"Src nodes\\\", Order=2),\\n (NET | summarize V=toreal(dcount(DstNodeName)) | extend Metric=\\\"Dst nodes\\\", Order=3),\\n (NET | where HasVirtualTraffic | summarize V=toreal(count())| extend Metric=\\\"Virtual\\\", Order=4),\\n (NET | where HasSubnetTraffic | summarize V=toreal(count())| extend Metric=\\\"Subnet\\\", Order=5),\\n (NET | where HasExitTraffic | summarize V=toreal(count())| extend Metric=\\\"Exit\\\", Order=6),\\n (NET | where IsRelayed | summarize V=toreal(count())| extend Metric=\\\"Relayed (DERP)\\\", Order=7)\\n| order by Order asc | project Metric, Value=V\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"title\":\"Activity (selected time range)\",\"noDataMessage\":\"No data in this window.\",\"noDataMessageStyle\":5,\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":0}}},\"showBorder\":false}},\"name\":\"q-flow-tiles\"},{\"type\":1,\"content\":{\"json\":\"
Top talkers
\"},\"name\":\"div-flow-top-talkers\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| extend SrcLabel = case(\\n isnotempty(SrcUser), strcat(SrcNodeName, \\\" - \\\", SrcUser),\\n isnotempty(SrcTags), strcat(SrcNodeName, \\\" \\\", tostring(SrcTags)),\\n SrcNodeName)\\n| summarize Flows=count() by SrcLabel\\n| top 10 by Flows\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"title\":\"Top source nodes by flow count\",\"noDataMessage\":\"No flow records in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-flow-top-src\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| extend DstLabel = case(\\n isnotempty(DstUser), strcat(DstNodeName, \\\" - \\\", DstUser),\\n isnotempty(DstTags), strcat(DstNodeName, \\\" \\\", tostring(DstTags)),\\n DstNodeName)\\n| extend DstKind = case(\\n isnotempty(DstUser), \\\"User device\\\",\\n isnotempty(DstTags), \\\"Tagged service\\\",\\n \\\"Other\\\")\\n| summarize Flows=count() by DstLabel, DstKind\\n| top 10 by Flows\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"categoricalbar\",\"title\":\"Top destination nodes (split by user vs tagged service)\",\"noDataMessage\":\"No flow records in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-flow-top-dst\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"
Traffic mix over time (stacked area: Virtual / Subnet / Exit / Physical)
\"},\"name\":\"div-flow-time-mix\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NET = Tailscale_Network_CL | where TimeGenerated {TimeRange};\\nunion\\n (NET | where HasVirtualTraffic | summarize Flows=count() by bin(TimeGenerated, 10m) | extend Kind=\\\"Virtual\\\"),\\n (NET | where HasSubnetTraffic | summarize Flows=count() by bin(TimeGenerated, 10m) | extend Kind=\\\"Subnet\\\"),\\n (NET | where HasExitTraffic | summarize Flows=count() by bin(TimeGenerated, 10m) | extend Kind=\\\"Exit\\\"),\\n (NET | where HasPhysicalTraffic | summarize Flows=count() by bin(TimeGenerated, 10m) | extend Kind=\\\"Physical\\\")\\n| project TimeGenerated, Kind, Flows\\n| order by TimeGenerated asc\",\"size\":4,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Flow count by traffic kind over time\",\"noDataMessage\":\"No flow records.\",\"noDataMessageStyle\":5,\"chartSettings\":{\"group\":\"Kind\",\"createOtherGroup\":10,\"showLegend\":true,\"ySettings\":{\"min\":0},\"chartType\":\"Area\"}},\"name\":\"q-flow-traffic-mix\"},{\"type\":1,\"content\":{\"json\":\"
DERP relay health
\"},\"name\":\"div-flow-derp-watch\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| summarize Count=count() by Path = iff(IsRelayed, \\\"Relayed (DERP)\\\", \\\"Direct (P2P)\\\")\\n| project Path, Count\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Direct vs relayed\",\"noDataMessage\":\"No flow records.\",\"noDataMessageStyle\":5},\"name\":\"q-flow-derp-pie\",\"customWidth\":\"40\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| where IsRelayed\\n| extend SrcLabel = strcat(SrcNodeName, iff(isempty(SrcUser),\\\"\\\",strcat(\\\" (\\\",SrcUser,\\\")\\\")))\\n| summarize RelayedFlows=count(), LastRelay=max(TimeGenerated) by SrcLabel, SrcOs\\n| top 10 by RelayedFlows\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Devices stuck on DERP (potential NAT/firewall issue)\",\"noDataMessage\":\"No data in this window.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"RelayedFlows\",\"formatter\":4,\"formatOptions\":{\"palette\":\"orange\"}},{\"columnMatch\":\"LastRelay\",\"formatter\":6}]}},\"name\":\"q-flow-derp-devices\",\"customWidth\":\"60\"},{\"type\":1,\"content\":{\"json\":\"
Tagged-service flows + anomaly hunt
\"},\"name\":\"div-flow-tag-anomaly\"},{\"type\":12,\"name\":\"row-tag-anomaly-row\",\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| extend SrcKind = case(\\n isnotempty(SrcTags), tostring(SrcTags),\\n isnotempty(SrcUser), \\\"\\\",\\n \\\"\\\")\\n| extend DstKind = case(\\n isnotempty(DstTags), tostring(DstTags),\\n isnotempty(DstUser), \\\"\\\",\\n \\\"\\\")\\n| summarize Flows=count() by SrcKind, DstKind\\n| order by Flows desc\",\"size\":1,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Tagged-service flow matrix\",\"noDataMessage\":\"No data in this window.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Flows\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"name\":\"q-flow-tag-matrix\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let baseline = Tailscale_Network_CL\\n | where TimeGenerated between (ago(7d) .. ago(1h))\\n | distinct SrcNodeName, DstNodeName;\\nTailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| where HasVirtualTraffic or HasSubnetTraffic or HasExitTraffic\\n| extend ShortSrc = tostring(split(SrcNodeName, \\\".\\\")[0])\\n| extend ShortDst = tostring(split(DstNodeName, \\\".\\\")[0])\\n| extend ShortSrcUser = tostring(split(SrcUser, \\\"@\\\")[0])\\n| extend ShortDstUser = tostring(split(DstUser, \\\"@\\\")[0])\\n| extend Src = strcat(ShortSrc, iff(isempty(ShortSrcUser),\\\"\\\",strcat(\\\" - \\\",ShortSrcUser)))\\n| extend Dst = strcat(ShortDst, iff(isempty(ShortDstUser),\\\"\\\",strcat(\\\" - \\\",ShortDstUser)))\\n| summarize FirstSeen=min(TimeGenerated), Flows=count() by Src, Dst, SrcNodeName, DstNodeName\\n| join kind=leftanti baseline on SrcNodeName, DstNodeName\\n| project FirstSeen, Src, Dst, Flows\\n| order by FirstSeen desc\",\"size\":1,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Anomaly: pairs NOT seen in past 7 days\",\"noDataMessage\":\"No new src/dst pairs - all flows match the 7-day baseline.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"FirstSeen\",\"formatter\":6},{\"columnMatch\":\"Flows\",\"formatter\":4,\"formatOptions\":{\"palette\":\"red\"}}]}},\"name\":\"q-flow-new-pairs\",\"customWidth\":\"50\"}]}},{\"type\":1,\"content\":{\"json\":\"
Egress destinations - exit nodes + subnet routes
\"},\"name\":\"div-flow-egress\"},{\"type\":12,\"name\":\"row-egress-row\",\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| where HasExitTraffic\\n| extend ExitTag = iff(isempty(DstTags), DstNodeName, tostring(DstTags))\\n| summarize Flows=count() by ExitTag\",\"size\":1,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Exit-node tag distribution\",\"noDataMessage\":\"No exit-node traffic in this window.\",\"noDataMessageStyle\":5},\"name\":\"q-flow-exit-providers\",\"customWidth\":\"40\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| where HasSubnetTraffic\\n| mv-expand s=SubnetTraffic\\n| extend DstHost = tostring(split(tostring(s.dst), \\\":\\\")[0])\\n| extend Bytes = toint(coalesce(s.txBytes,0)) + toint(coalesce(s.rxBytes,0))\\n| extend Pkts = toint(coalesce(s.txPkts,0)) + toint(coalesce(s.rxPkts,0))\\n| summarize TotalBytes=sum(Bytes), TotalPkts=sum(Pkts), Talkers=dcount(SrcNodeName) by DstHost\\n| top 10 by TotalBytes\\n| project DstHost, TotalBytes, TotalPkts, Talkers\",\"size\":1,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Top subnet route destinations\",\"noDataMessage\":\"No subnet-route traffic in this window.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TotalBytes\",\"formatter\":4,\"formatOptions\":{\"palette\":\"green\"}},{\"columnMatch\":\"TotalPkts\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Talkers\",\"formatter\":4,\"formatOptions\":{\"palette\":\"purple\"}}]}},\"name\":\"q-flow-subnet-dests\",\"customWidth\":\"60\"}]}},{\"type\":1,\"content\":{\"json\":\"
Recent flow detail (last 100)
\"},\"name\":\"div-flow-recent-detail\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Network_CL\\n| where TimeGenerated {TimeRange}\\n| order by TimeGenerated desc\\n| take 100\\n| project TimeGenerated,\\n Src=SrcNodeName, SrcUser, SrcTags=tostring(SrcTags),\\n Dst=DstNodeName, DstUser, DstTags=tostring(DstTags),\\n Vir=HasVirtualTraffic, Sub=HasSubnetTraffic, Exi=HasExitTraffic, Phy=HasPhysicalTraffic, IsRelayed\",\"size\":4,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Last 100 flow records in time range\",\"noDataMessage\":\"No data in this window.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"IsRelayed\",\"formatter\":11}]}},\"name\":\"q-flow-recent\"}]}},{\"type\":12,\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"posture\"},\"name\":\"group-posture\",\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Posture integrations - MDM/EDR providers configured for device posture
\"},\"name\":\"div-posture-overview\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let CUR = Tailscale_PostureIntegrations_CL\\n | where TimeGenerated {TimeRange}\\n | summarize arg_max(TimeGenerated, *) by IntegrationId;\\nunion\\n (CUR | summarize V=toreal(count()) | extend Metric=\\\"Integrations\\\", Order=1),\\n (CUR | summarize V=toreal(dcount(Provider)) | extend Metric=\\\"Distinct providers\\\", Order=2),\\n (CUR | where tostring(Status) has \\\"healthy\\\" or tostring(Status) has \\\"ok\\\"\\n | summarize V=toreal(count()) | extend Metric=\\\"Healthy\\\", Order=3),\\n (CUR | where not(tostring(Status) has \\\"healthy\\\" or tostring(Status) has \\\"ok\\\")\\n | summarize V=toreal(count()) | extend Metric=\\\"Unhealthy / unknown\\\", Order=4)\\n| order by Order asc | project Metric, Value=V\",\"size\":3,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"title\":\"Integration inventory (selected time range)\",\"noDataMessage\":\"No posture integrations configured. Set them up at https://login.tailscale.com/admin/settings/posture-integrations.\",\"noDataMessageStyle\":5,\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"Metric\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"Value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":0}}},\"showBorder\":false}},\"name\":\"q-posture-tile\"},{\"type\":1,\"content\":{\"json\":\"
Distribution
\"},\"name\":\"div-posture-distribution\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_PostureIntegrations_CL\\n| where TimeGenerated {TimeRange}\\n| summarize arg_max(TimeGenerated, *) by IntegrationId\\n| summarize Count=count() by Provider\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Integrations by provider\",\"noDataMessage\":\"No posture integrations configured.\",\"noDataMessageStyle\":5},\"name\":\"q-posture-by-provider\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_PostureIntegrations_CL\\n| where TimeGenerated {TimeRange}\\n| summarize arg_max(TimeGenerated, *) by IntegrationId\\n| extend Health = case(\\n tostring(Status) has \\\"healthy\\\" or tostring(Status) has \\\"ok\\\", \\\"Healthy\\\",\\n tostring(Status) has \\\"error\\\" or tostring(Status) has \\\"failed\\\", \\\"Error\\\",\\n isnotempty(tostring(Status)), \\\"Other\\\",\\n \\\"Unknown\\\")\\n| summarize Count=count() by Health\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\",\"title\":\"Health status across integrations\",\"noDataMessage\":\"No posture integrations configured.\",\"noDataMessageStyle\":5},\"name\":\"q-posture-by-status\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"
Inventory detail
\"},\"name\":\"div-posture-inventory\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_PostureIntegrations_CL\\n| where TimeGenerated {TimeRange}\\n| summarize arg_max(TimeGenerated, *) by IntegrationId\\n| project IntegrationId, Provider, CloudId, ClientId, TenantId_Provider, Status, LastSnapshot=TimeGenerated\",\"size\":4,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Configured integrations (latest snapshot per IntegrationId)\",\"noDataMessage\":\"No posture integrations configured.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Provider\",\"formatter\":11},{\"columnMatch\":\"LastSnapshot\",\"formatter\":6}]}},\"name\":\"q-posture-inventory\"},{\"type\":1,\"content\":{\"json\":\"
Lifecycle (from audit log)
\"},\"name\":\"div-posture-lifecycle\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"Tailscale_Audit_CL\\n| where TimeGenerated {TimeRange}\\n| where EventType == \\\"CONFIG\\\"\\n| where tostring(Target.type) contains \\\"POSTURE\\\" or tostring(Target.type) contains \\\"INTEGRATION\\\"\\n| project TimeGenerated, Actor=tostring(Actor.loginName), Action,\\n Target=tostring(Target.name), TargetType=tostring(Target.type), ActionDetails\\n| order by TimeGenerated desc\",\"size\":4,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Recent posture integration create/update/delete events\",\"noDataMessage\":\"No posture-integration audit events in this window.\",\"noDataMessageStyle\":5,\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"Action\",\"formatter\":11}]}},\"name\":\"q-posture-audit\"}]}},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
Ingest rate per table
\"},\"name\":\"div-ingest-rate-per-tabl-24e5f6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"union withsource=Table Tailscale_Audit_CL, Tailscale_Devices_CL, Tailscale_Users_CL, Tailscale_Keys_CL, Tailscale_Dns_CL, Tailscale_Settings_CL\\n| where TimeGenerated > ago(24h)\\n| summarize Rows=count() by Table, bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"timechart\",\"title\":\"Rows ingested per Tailscale table per hour (last 24h)\",\"noDataMessage\":\"No Tailscale data ingested in the last 24h - check the connector card under Sentinel Data Connectors.\",\"noDataMessageStyle\":5},\"name\":\"q-c6fd0143\"},{\"type\":1,\"content\":{\"json\":\"
\"},\"name\":\"div-log-analytics-operat-630749\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"_LogOperation\\n| where TimeGenerated > ago(24h)\\n| where _ResourceId contains \\\"tailscale\\\" or Detail contains \\\"Tailscale_\\\"\\n| project TimeGenerated, Operation, Level, Detail\\n| order by TimeGenerated desc | take 100\",\"size\":0,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"table\",\"title\":\"Log Analytics operational events touching Tailscale tables\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"TimeGenerated\",\"formatter\":6},{\"columnMatch\":\"Level\",\"formatter\":1}]},\"noDataMessage\":\"No operational issues recorded in the last 24h.\",\"noDataMessageStyle\":1},\"name\":\"q-b492f150\"}]},\"conditionalVisibility\":{\"parameterName\":\"selectedTab\",\"comparison\":\"isEqualTo\",\"value\":\"pipeline\"},\"name\":\"group-pipeline\"},{\"type\":1,\"content\":{\"json\":\"
Tailscale Operations (Premium) (CCF) - Microsoft Sentinel content from the Tailscale (CCF) solution, Premium-tier surface. Tables polled from the Tailscale REST API: audit, devices, users, keys, dns, settings, network flows (/logging/network), posture integrations. Filter every panel via the time range above; the Investigate tab adds Actor and Device pickers for drilldown. The Network Flows and Posture tabs use the 15 promoted columns added in 3.1.0 (SrcUser, SrcTags, DstUser, DstTags, HasVirtualTraffic, HasSubnetTraffic, HasExitTraffic, HasPhysicalTraffic, IsRelayed, etc.). Companion workbook: Tailscale Operations (Standard) for Personal / Starter tailnets.
\u2022 There may be known issues pertaining to this Solution, please refer to them before installing.
\n
The Tailscale solution for Microsoft Sentinel ingests Tailscale identity, device, configuration, audit and (Premium) network-flow telemetry via OAuth2-secured APIs. Built on the Codeless Connector Framework (CCF) - no Function App or container required.
\n
Data connectors in this solution (install the one matching your Tailscale plan):
\n
\n
Tailscale Standard (CCF) - Configuration audit, devices, users, keys, webhooks, DNS, settings. Use on Personal (Free), Starter and Premium tailnets.
\n
Tailscale Premium (CCF) - Everything in Standard plus network flow logs and posture integrations. Use on Premium and Enterprise tailnets for full coverage.
\n",
+ "contentKind": "Solution",
+ "contentProductId": "[variables('_solutioncontentProductId')]",
+ "id": "[variables('_solutioncontentProductId')]",
+ "icon": "",
+ "contentId": "[variables('_solutionId')]",
+ "parentId": "[variables('_solutionId')]",
+ "source": {
+ "kind": "Solution",
+ "name": "Tailscale (CCF)",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "noodlemctwoodle",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Tailscale (CCF)",
+ "email": "ccfconnectors.county118@passmail.com",
+ "tier": "Community",
+ "link": "https://github.com/Azure/Azure-Sentinel/issues"
+ },
+ "dependencies": {
+ "operator": "AND",
+ "criteria": [
+ {
+ "kind": "DataConnector",
+ "contentId": "[variables('_dataConnectorContentIdConnections1')]",
+ "version": "[variables('dataConnectorCCPVersion')]"
+ },
+ {
+ "kind": "DataConnector",
+ "contentId": "[variables('_dataConnectorContentIdConnections2')]",
+ "version": "[variables('dataConnectorCCPVersion')]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]",
+ "version": "[variables('analyticRuleObject1').analyticRuleVersion1]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]",
+ "version": "[variables('analyticRuleObject2').analyticRuleVersion2]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]",
+ "version": "[variables('analyticRuleObject3').analyticRuleVersion3]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]",
+ "version": "[variables('analyticRuleObject4').analyticRuleVersion4]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]",
+ "version": "[variables('analyticRuleObject5').analyticRuleVersion5]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]",
+ "version": "[variables('analyticRuleObject6').analyticRuleVersion6]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]",
+ "version": "[variables('analyticRuleObject7').analyticRuleVersion7]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]",
+ "version": "[variables('analyticRuleObject8').analyticRuleVersion8]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]",
+ "version": "[variables('analyticRuleObject9').analyticRuleVersion9]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]",
+ "version": "[variables('analyticRuleObject10').analyticRuleVersion10]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]",
+ "version": "[variables('analyticRuleObject11').analyticRuleVersion11]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]",
+ "version": "[variables('analyticRuleObject12').analyticRuleVersion12]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]",
+ "version": "[variables('analyticRuleObject13').analyticRuleVersion13]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]",
+ "version": "[variables('analyticRuleObject14').analyticRuleVersion14]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]",
+ "version": "[variables('analyticRuleObject15').analyticRuleVersion15]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]",
+ "version": "[variables('analyticRuleObject16').analyticRuleVersion16]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]",
+ "version": "[variables('analyticRuleObject17').analyticRuleVersion17]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]",
+ "version": "[variables('analyticRuleObject18').analyticRuleVersion18]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]",
+ "version": "[variables('analyticRuleObject19').analyticRuleVersion19]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject20')._analyticRulecontentId20]",
+ "version": "[variables('analyticRuleObject20').analyticRuleVersion20]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject21')._analyticRulecontentId21]",
+ "version": "[variables('analyticRuleObject21').analyticRuleVersion21]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject22')._analyticRulecontentId22]",
+ "version": "[variables('analyticRuleObject22').analyticRuleVersion22]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject23')._analyticRulecontentId23]",
+ "version": "[variables('analyticRuleObject23').analyticRuleVersion23]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject24')._analyticRulecontentId24]",
+ "version": "[variables('analyticRuleObject24').analyticRuleVersion24]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]",
+ "version": "[variables('huntingQueryObject1').huntingQueryVersion1]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]",
+ "version": "[variables('huntingQueryObject2').huntingQueryVersion2]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]",
+ "version": "[variables('huntingQueryObject3').huntingQueryVersion3]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]",
+ "version": "[variables('huntingQueryObject4').huntingQueryVersion4]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]",
+ "version": "[variables('huntingQueryObject5').huntingQueryVersion5]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]",
+ "version": "[variables('huntingQueryObject6').huntingQueryVersion6]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]",
+ "version": "[variables('huntingQueryObject7').huntingQueryVersion7]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]",
+ "version": "[variables('huntingQueryObject8').huntingQueryVersion8]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]",
+ "version": "[variables('huntingQueryObject9').huntingQueryVersion9]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]",
+ "version": "[variables('huntingQueryObject10').huntingQueryVersion10]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]",
+ "version": "[variables('huntingQueryObject11').huntingQueryVersion11]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]",
+ "version": "[variables('huntingQueryObject12').huntingQueryVersion12]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]",
+ "version": "[variables('huntingQueryObject13').huntingQueryVersion13]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]",
+ "version": "[variables('huntingQueryObject14').huntingQueryVersion14]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]",
+ "version": "[variables('huntingQueryObject15').huntingQueryVersion15]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]",
+ "version": "[variables('huntingQueryObject16').huntingQueryVersion16]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]",
+ "version": "[variables('huntingQueryObject17').huntingQueryVersion17]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]",
+ "version": "[variables('huntingQueryObject18').huntingQueryVersion18]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]",
+ "version": "[variables('huntingQueryObject19').huntingQueryVersion19]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]",
+ "version": "[variables('huntingQueryObject20').huntingQueryVersion20]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]",
+ "version": "[variables('huntingQueryObject21').huntingQueryVersion21]"
+ },
+ {
+ "kind": "HuntingQuery",
+ "contentId": "[variables('huntingQueryObject22')._huntingQuerycontentId22]",
+ "version": "[variables('huntingQueryObject22').huntingQueryVersion22]"
+ },
+ {
+ "kind": "Workbook",
+ "contentId": "[variables('_workbookContentId1')]",
+ "version": "[variables('workbookVersion1')]"
+ },
+ {
+ "kind": "Workbook",
+ "contentId": "[variables('_workbookContentId2')]",
+ "version": "[variables('workbookVersion2')]"
+ },
+ {
+ "kind": "Parser",
+ "contentId": "[variables('parserObject1').parserContentId1]",
+ "version": "[variables('parserObject1').parserVersion1]"
+ },
+ {
+ "kind": "Parser",
+ "contentId": "[variables('parserObject2').parserContentId2]",
+ "version": "[variables('parserObject2').parserVersion2]"
+ }
+ ]
+ },
+ "firstPublishDate": "2026-05-19",
+ "lastPublishDate": "2026-05-19",
+ "providers": [
+ "Community"
+ ],
+ "categories": {
+ "domains": [
+ "Networking",
+ "Security - Network",
+ "Identity"
+ ]
+ }
+ },
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]"
+ }
+ ],
+ "outputs": {}
+}
\ No newline at end of file
diff --git a/Solutions/Tailscale (CCF)/Package/testParameters.json b/Solutions/Tailscale (CCF)/Package/testParameters.json
new file mode 100644
index 00000000000..8e1e27a3c33
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/Package/testParameters.json
@@ -0,0 +1,54 @@
+{
+ "location": {
+ "type": "string",
+ "minLength": 1,
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace"
+ }
+ },
+ "workspace-location": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]"
+ }
+ },
+ "workspace": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup"
+ }
+ },
+ "resourceGroupName": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().name]",
+ "metadata": {
+ "description": "resource group name where Microsoft Sentinel is setup"
+ }
+ },
+ "subscription": {
+ "type": "string",
+ "defaultValue": "[last(split(subscription().id, '/'))]",
+ "metadata": {
+ "description": "subscription id where Microsoft Sentinel is setup"
+ }
+ },
+ "workbook1-name": {
+ "type": "string",
+ "defaultValue": "Tailscale Operations (Standard)",
+ "minLength": 1,
+ "metadata": {
+ "description": "Name for the workbook"
+ }
+ },
+ "workbook2-name": {
+ "type": "string",
+ "defaultValue": "Tailscale Operations (Premium)",
+ "minLength": 1,
+ "metadata": {
+ "description": "Name for the workbook"
+ }
+ }
+}
diff --git a/Solutions/Tailscale (CCF)/Parsers/ASimNetworkSessionTailscale.yaml b/Solutions/Tailscale (CCF)/Parsers/ASimNetworkSessionTailscale.yaml
new file mode 100644
index 00000000000..4b8af4fb11a
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/Parsers/ASimNetworkSessionTailscale.yaml
@@ -0,0 +1,96 @@
+id: 2b9c1f5a-7d3e-4f8b-a456-c1d2e3f4a5b6
+Function:
+ Title: ASIM Network Session parser for Tailscale (no-prefix wrapper)
+ Version: '1.0.0'
+ LastUpdated: '2026-05-19'
+Category: Microsoft Sentinel Parser
+FunctionName: ASimNetworkSessionTailscale
+FunctionAlias: ASimNetworkSessionTailscale
+FunctionQuery: |
+ let NetworkProtocolLookup = datatable(proto_lookup: int, NetworkProtocol_lookup: string)
+ [
+ 1, "ICMP",
+ 6, "TCP",
+ 17, "UDP",
+ 58, "ICMPv6"
+ ];
+ let baseEvents = Tailscale_Network_CL;
+ let virtualFlows = baseEvents
+ | where HasVirtualTraffic
+ | mv-expand t = VirtualTraffic
+ | extend NetworkSessionType = "Virtual", NetworkDirection = "Local";
+ let subnetFlows = baseEvents
+ | where HasSubnetTraffic
+ | mv-expand t = SubnetTraffic
+ | extend NetworkSessionType = "Subnet", NetworkDirection = "Outbound";
+ let exitFlows = baseEvents
+ | where HasExitTraffic
+ | mv-expand t = ExitTraffic
+ | extend NetworkSessionType = "Exit", NetworkDirection = "Outbound";
+ union virtualFlows, subnetFlows, exitFlows
+ | extend
+ SrcIpAddrAndPort = tostring(t.src),
+ DstIpAddrAndPort = tostring(t.dst),
+ proto_lookup = toint(t.proto),
+ SrcBytes = tolong(t.txBytes),
+ DstBytes = tolong(t.rxBytes),
+ SrcPackets = tolong(t.txPkts),
+ DstPackets = tolong(t.rxPkts)
+ | extend
+ SrcRawHost = extract(@"^(.+):([0-9]+)$", 1, SrcIpAddrAndPort),
+ SrcPortNumber = toint(extract(@"^(.+):([0-9]+)$", 2, SrcIpAddrAndPort)),
+ DstRawHost = extract(@"^(.+):([0-9]+)$", 1, DstIpAddrAndPort),
+ DstPortNumber = toint(extract(@"^(.+):([0-9]+)$", 2, DstIpAddrAndPort))
+ | extend
+ SrcIpAddr = case(
+ isempty(SrcRawHost), SrcIpAddrAndPort,
+ SrcRawHost startswith "[", substring(SrcRawHost, 1, strlen(SrcRawHost) - 2),
+ SrcRawHost
+ ),
+ DstIpAddr = case(
+ isempty(DstRawHost), DstIpAddrAndPort,
+ DstRawHost startswith "[", substring(DstRawHost, 1, strlen(DstRawHost) - 2),
+ DstRawHost
+ )
+ | lookup NetworkProtocolLookup on proto_lookup
+ | extend
+ NetworkProtocol = coalesce(NetworkProtocol_lookup, tostring(proto_lookup)),
+ NetworkBytes = SrcBytes + DstBytes,
+ NetworkPackets = SrcPackets + DstPackets
+ | extend
+ EventStartTime = todatetime(FlowStart),
+ EventEndTime = todatetime(FlowEnd),
+ EventProduct = "Tailscale",
+ EventVendor = "Tailscale",
+ EventSchema = "NetworkSession",
+ EventSchemaVersion = "0.2.6",
+ EventType = "NetworkSession",
+ EventResult = "Success",
+ EventCount = int(1),
+ EventSeverity = "Informational",
+ DvcAction = "Allow"
+ | extend
+ SrcHostname = SrcNodeName,
+ SrcUsername = SrcUser,
+ SrcUsernameType = iff(isnotempty(SrcUser), "Simple", ""),
+ SrcDvcOs = SrcOs,
+ DstHostname = DstNodeName,
+ DstUsername = DstUser,
+ DstUsernameType = iff(isnotempty(DstUser), "Simple", ""),
+ DstDvcOs = DstOs,
+ Dvc = SrcNodeName,
+ Src = SrcIpAddr,
+ Dst = DstIpAddr,
+ IpAddr = SrcIpAddr,
+ User = SrcUser,
+ Hostname = SrcNodeName
+ | project-away
+ t, NetworkProtocol_lookup, proto_lookup,
+ VirtualTraffic, SubnetTraffic, ExitTraffic, PhysicalTraffic,
+ HasVirtualTraffic, HasSubnetTraffic, HasExitTraffic, HasPhysicalTraffic,
+ SrcAddresses, DstAddresses, SrcTags, DstTags,
+ SrcOs, DstOs, SrcUser, DstUser, SrcNodeName, DstNodeName,
+ DstCount, DstNodeId, NodeId, IsRelayed,
+ SrcIpAddrAndPort, DstIpAddrAndPort, SrcRawHost, DstRawHost,
+ FlowStart, FlowEnd,
+ TenantId, _ResourceId, Type
diff --git a/Solutions/Tailscale (CCF)/Parsers/vimNetworkSessionTailscale.yaml b/Solutions/Tailscale (CCF)/Parsers/vimNetworkSessionTailscale.yaml
new file mode 100644
index 00000000000..9f4faca048a
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/Parsers/vimNetworkSessionTailscale.yaml
@@ -0,0 +1,96 @@
+id: 0d8fe6c1-3a4f-4f5b-9c8d-1a2b3c4d5e6f
+Function:
+ Title: ASIM Network Session parser for Tailscale
+ Version: '1.0.0'
+ LastUpdated: '2026-05-19'
+Category: Microsoft Sentinel Parser
+FunctionName: vimNetworkSessionTailscale
+FunctionAlias: vimNetworkSessionTailscale
+FunctionQuery: |
+ let NetworkProtocolLookup = datatable(proto_lookup: int, NetworkProtocol_lookup: string)
+ [
+ 1, "ICMP",
+ 6, "TCP",
+ 17, "UDP",
+ 58, "ICMPv6"
+ ];
+ let baseEvents = Tailscale_Network_CL;
+ let virtualFlows = baseEvents
+ | where HasVirtualTraffic
+ | mv-expand t = VirtualTraffic
+ | extend NetworkSessionType = "Virtual", NetworkDirection = "Local";
+ let subnetFlows = baseEvents
+ | where HasSubnetTraffic
+ | mv-expand t = SubnetTraffic
+ | extend NetworkSessionType = "Subnet", NetworkDirection = "Outbound";
+ let exitFlows = baseEvents
+ | where HasExitTraffic
+ | mv-expand t = ExitTraffic
+ | extend NetworkSessionType = "Exit", NetworkDirection = "Outbound";
+ union virtualFlows, subnetFlows, exitFlows
+ | extend
+ SrcIpAddrAndPort = tostring(t.src),
+ DstIpAddrAndPort = tostring(t.dst),
+ proto_lookup = toint(t.proto),
+ SrcBytes = tolong(t.txBytes),
+ DstBytes = tolong(t.rxBytes),
+ SrcPackets = tolong(t.txPkts),
+ DstPackets = tolong(t.rxPkts)
+ | extend
+ SrcRawHost = extract(@"^(.+):([0-9]+)$", 1, SrcIpAddrAndPort),
+ SrcPortNumber = toint(extract(@"^(.+):([0-9]+)$", 2, SrcIpAddrAndPort)),
+ DstRawHost = extract(@"^(.+):([0-9]+)$", 1, DstIpAddrAndPort),
+ DstPortNumber = toint(extract(@"^(.+):([0-9]+)$", 2, DstIpAddrAndPort))
+ | extend
+ SrcIpAddr = case(
+ isempty(SrcRawHost), SrcIpAddrAndPort,
+ SrcRawHost startswith "[", substring(SrcRawHost, 1, strlen(SrcRawHost) - 2),
+ SrcRawHost
+ ),
+ DstIpAddr = case(
+ isempty(DstRawHost), DstIpAddrAndPort,
+ DstRawHost startswith "[", substring(DstRawHost, 1, strlen(DstRawHost) - 2),
+ DstRawHost
+ )
+ | lookup NetworkProtocolLookup on proto_lookup
+ | extend
+ NetworkProtocol = coalesce(NetworkProtocol_lookup, tostring(proto_lookup)),
+ NetworkBytes = SrcBytes + DstBytes,
+ NetworkPackets = SrcPackets + DstPackets
+ | extend
+ EventStartTime = todatetime(FlowStart),
+ EventEndTime = todatetime(FlowEnd),
+ EventProduct = "Tailscale",
+ EventVendor = "Tailscale",
+ EventSchema = "NetworkSession",
+ EventSchemaVersion = "0.2.6",
+ EventType = "NetworkSession",
+ EventResult = "Success",
+ EventCount = int(1),
+ EventSeverity = "Informational",
+ DvcAction = "Allow"
+ | extend
+ SrcHostname = SrcNodeName,
+ SrcUsername = SrcUser,
+ SrcUsernameType = iff(isnotempty(SrcUser), "Simple", ""),
+ SrcDvcOs = SrcOs,
+ DstHostname = DstNodeName,
+ DstUsername = DstUser,
+ DstUsernameType = iff(isnotempty(DstUser), "Simple", ""),
+ DstDvcOs = DstOs,
+ Dvc = SrcNodeName,
+ Src = SrcIpAddr,
+ Dst = DstIpAddr,
+ IpAddr = SrcIpAddr,
+ User = SrcUser,
+ Hostname = SrcNodeName
+ | project-away
+ t, NetworkProtocol_lookup, proto_lookup,
+ VirtualTraffic, SubnetTraffic, ExitTraffic, PhysicalTraffic,
+ HasVirtualTraffic, HasSubnetTraffic, HasExitTraffic, HasPhysicalTraffic,
+ SrcAddresses, DstAddresses, SrcTags, DstTags,
+ SrcOs, DstOs, SrcUser, DstUser, SrcNodeName, DstNodeName,
+ DstCount, DstNodeId, NodeId, IsRelayed,
+ SrcIpAddrAndPort, DstIpAddrAndPort, SrcRawHost, DstRawHost,
+ FlowStart, FlowEnd,
+ TenantId, _ResourceId, Type
diff --git a/Solutions/Tailscale (CCF)/README.md b/Solutions/Tailscale (CCF)/README.md
new file mode 100644
index 00000000000..38512f55518
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/README.md
@@ -0,0 +1,368 @@
+# Tailscale (CCF)
+
+Microsoft Sentinel solution that ingests Tailscale identity, device, configuration, audit and (Premium) network-flow telemetry via the OAuth2-secured Tailscale API. Built on the Codeless Connector Framework (CCF) - no Function App or container required.
+
+- **2 data connectors** (Standard, Premium) - install whichever matches your Tailscale plan
+- **24 analytic rules** (16 Standard + 8 Premium-only)
+- **22 hunting queries** (12 Standard + 10 Premium-only)
+- **2 workbooks** (Standard Operations, Premium Operations)
+- **2 ASIM NetworkSession parsers** (`vimNetworkSessionTailscale` + `ASimNetworkSessionTailscale` wrapper) - Premium only
+- **9 custom tables** ingested via 9-11 polling rules behind a single Connect button
+
+---
+
+## Table of contents
+
+1. [Pick your tier](#1-pick-your-tier)
+2. [Pre-requisites](#2-pre-requisites)
+3. [Installation](#3-installation)
+4. [Verification](#4-verification)
+5. [Custom tables](#5-custom-tables)
+6. [Analytic rules](#6-analytic-rules)
+7. [Hunting queries](#7-hunting-queries)
+8. [Workbooks](#8-workbooks)
+9. [Architecture notes](#9-architecture-notes)
+10. [Limitations](#10-limitations)
+11. [Troubleshooting](#11-troubleshooting)
+12. [Support](#12-support)
+13. [Acknowledgements](#13-acknowledgements)
+
+---
+
+## 1. Pick your tier
+
+Install **one** of the two connectors based on your Tailscale plan. The split mirrors what the Tailscale API actually exposes per tier - network flow logs are only available on Premium and Enterprise tailnets.
+
+| | Tailscale Standard (CCF) | Tailscale Premium (CCF) |
+|---|---|---|
+| **Tailscale plan** | Personal (Free), Starter, Premium\* | Premium, Enterprise |
+| **Pollers behind one Connect** | 9 | 11 |
+| **Custom tables created** | 7 | 9 |
+| **Analytic rules wired** | 16 | 24 (Standard 16 + Premium 8) |
+| **Hunting queries wired** | 12 | 22 (Standard 12 + Premium 10) |
+| **Workbook** | Standard Operations | Premium Operations |
+| **Network flow logs** | not exposed by API | `Tailscale_Network_CL` |
+| **Posture integrations** | not exposed by API | `Tailscale_PostureIntegrations_CL` |
+| **Required OAuth scopes** | `logs:configuration:read`, `devices:read`, `users:read`, `keys:read`, `webhooks:read`, `dns:read`, `settings:read` | All of Standard plus `logs:network:read`, `posture-integrations:read` |
+
+\* Premium tailnets can use the Standard connector if you don't want network-flow data, but the Premium connector is the recommended path.
+
+---
+
+## 2. Pre-requisites
+
+You need four things before clicking Connect:
+
+1. **A Microsoft Sentinel-enabled Log Analytics workspace** in any region.
+2. **A Data Collection Endpoint (DCE)** in the same region as the workspace. The Sentinel Content Hub installer creates one automatically if you don't already have a shared DCE.
+3. **A Tailscale OAuth client** generated at . Personal API tokens (`tskey-api-...`) do **not** work - see [Architecture notes](#9-architecture-notes) for the reason.
+ - Tick the scopes listed in the table above for your tier.
+ - Copy the **Client ID** and **Client Secret** when prompted - the secret is shown only once.
+4. **Your tailnet name** (e.g. `tailb094d7.ts.net`). Find it on the [Keys page](https://login.tailscale.com/admin/settings/keys) or in your Tailscale admin URL.
+
+### OAuth scope checklist
+
+Tick exactly the scopes for your tier - extra scopes don't hurt but the connector won't ask for them, and missing a scope means the corresponding poller will return `200 OK` with no data (Tailscale doesn't error on missing scope, it just returns empty).
+
+**Standard (7 scopes)**
+
+- `logs:configuration:read` - audit log
+- `devices:read` - device inventory, including tailnet-lock state, SSH enablement, advertised routes
+- `users:read` - user roles and last-seen
+- `keys:read` - auth keys and OAuth clients (for sprawl/expiry detection)
+- `webhooks:read` - webhook configuration
+- `dns:read` - nameservers, MagicDNS, split-DNS, search paths
+- `settings:read` - tailnet-wide settings
+
+**Premium adds (2 scopes)**
+
+- `logs:network:read` - network flow logs
+- `posture-integrations:read` - device posture integration list and status
+
+---
+
+## 3. Installation
+
+1. Open **Microsoft Sentinel** -> **Content hub**, search for "Tailscale" and install **Tailscale (CCF)**.
+2. Go to **Data connectors**, search "Tailscale", and open either **Tailscale Standard (CCF)** or **Tailscale Premium (CCF)**.
+3. Supply:
+ - **Tailnet name** (e.g. `tailb094d7.ts.net`)
+ - **OAuth Client ID**
+ - **OAuth Client Secret**
+4. Click **Connect**. The connector page shows "Connected" within ~30 seconds; the first audit poll completes within 5 minutes and the first snapshot pollers (devices, users, ...) within 60 minutes.
+
+That single Connect click deploys 9 (Standard) or 11 (Premium) Sentinel `RestApiPoller` data connectors behind the scenes - see [Architecture notes](#9-architecture-notes) for how that works.
+
+---
+
+## 4. Verification
+
+Run these in **Sentinel** -> **Logs** after the first poll cycle completes (~5 min for audit, ~60 min for snapshots).
+
+```kql
+// Audit logs received in the last 15 min (should be > 0 if any config activity happened)
+Tailscale_Audit_CL
+| where TimeGenerated > ago(15m)
+| project TimeGenerated, EventTime, Action, Actor, Target
+
+// Snapshot of every tailnet device on the latest poll
+Tailscale_Devices_CL
+| summarize arg_max(TimeGenerated, *) by DeviceId
+| project DeviceName, Hostname, User, Os, ClientVersion, LastSeen, Authorized, ConnectedToControl
+
+// All tables receiving data in the last 2 hours
+union
+ (Tailscale_Audit_CL | extend _T = "Tailscale_Audit_CL"),
+ (Tailscale_Devices_CL | extend _T = "Tailscale_Devices_CL"),
+ (Tailscale_Users_CL | extend _T = "Tailscale_Users_CL"),
+ (Tailscale_Keys_CL | extend _T = "Tailscale_Keys_CL"),
+ (Tailscale_Webhooks_CL | extend _T = "Tailscale_Webhooks_CL"),
+ (Tailscale_Settings_CL | extend _T = "Tailscale_Settings_CL"),
+ (Tailscale_Dns_CL | extend _T = "Tailscale_Dns_CL"),
+ (Tailscale_Network_CL | extend _T = "Tailscale_Network_CL"),
+ (Tailscale_PostureIntegrations_CL | extend _T = "Tailscale_PostureIntegrations_CL")
+| where TimeGenerated > ago(2h)
+| summarize Rows = count(), Latest = max(TimeGenerated) by _T
+| order by _T asc
+```
+
+A working Standard tier should return rows for 7 tables; Premium should return rows for 9. `Tailscale_Network_CL` and `Tailscale_PostureIntegrations_CL` are Premium-only.
+
+---
+
+## 5. Custom tables
+
+All tables are Log Analytics custom tables (`_CL`) populated via Sentinel CCF poller -> DCE -> DCR transform.
+
+| Table | Cols | Cadence | Source endpoint | Tier |
+|---|---|---|---|---|
+| `Tailscale_Audit_CL` | 11 | 5 min | `/logging/configuration` | Standard + Premium |
+| `Tailscale_Devices_CL` | 27 | 60 min | `/devices?fields=all` | Standard + Premium |
+| `Tailscale_Users_CL` | 13 | 60 min | `/users` | Standard + Premium |
+| `Tailscale_Keys_CL` | 10 | 60 min | `/keys?all=true` | Standard + Premium |
+| `Tailscale_Webhooks_CL` | 8 | 60 min | `/webhooks` | Standard + Premium |
+| `Tailscale_Settings_CL` | 9 | 60 min | `/settings` | Standard + Premium |
+| `Tailscale_Dns_CL` | 5 | 60 min | merged from `/dns/nameservers`, `/dns/preferences`, `/dns/searchpaths` | Standard + Premium |
+| `Tailscale_Network_CL` | 27 | 5 min | `/logging/network` | Premium only |
+| `Tailscale_PostureIntegrations_CL` | 8 | 60 min | `/posture/integrations` | Premium only |
+
+**Snapshot semantics.** All `_CL` tables except `Audit` and `Network` are snapshot tables - each poll writes the full current state of the endpoint. Use `summarize arg_max(TimeGenerated, *) by ` to get the latest snapshot. The 5-min audit and network tables are append-only event streams.
+
+**`Tailscale_Devices_CL` is the richest snapshot table** at 27 columns. The 5 most interesting columns for detection are surfaced via `?fields=all`:
+
+- `AdvertisedRoutes` / `EnabledRoutes` - dynamic arrays of CIDRs the device offers/has approved
+- `SshEnabled` - bool, is Tailscale SSH active on this device
+- `ConnectedToControl` / `Authorized` - control-plane state pair (unauthorized + connected = the rule trigger)
+- `TailnetLockKey` / `TailnetLockError` - cryptographic node-key validation state
+- `UpdateAvailable` - bool, client behind latest release
+
+**`Tailscale_Network_CL` is the richest event table** (Premium only) at 27 columns. The DCR transform promotes the most useful inner-object fields out of the source `srcNode` / `dstNodes` dynamics so hunting queries don't have to traverse dynamic JSON:
+
+- `SrcUser` / `SrcNodeName` / `SrcOs` / `SrcTags` / `SrcAddresses` - src device identity + tagging
+- `DstCount` / `DstNodeId` / `DstNodeName` / `DstUser` / `DstOs` / `DstTags` / `DstAddresses` - dst device identity + tagging
+- `HasVirtualTraffic` / `HasSubnetTraffic` / `HasExitTraffic` / `HasPhysicalTraffic` - traffic-shape flags
+- `IsRelayed` - bool, true when the flow used a DERP relay (detected via `127.3.3.40` in `physicalTraffic`)
+
+---
+
+## 6. Analytic rules
+
+### Standard tier (16 rules)
+
+**Identity & access (5)**
+
+| Rule | Severity | Tactics | What it watches |
+|---|---|---|---|
+| New API access token or OAuth client created | Medium | Persistence, CredentialAccess | New `API_ACCESS_TOKEN_CREATE` / `OAUTH_CLIENT_CREATE` audit events |
+| OAuth client or API key created with write scopes | High | Persistence, PrivilegeEscalation | New OAuth client or API key whose granted scopes include any `:write` permission |
+| Auth key created | Low | Persistence | Any new auth key (incl. ephemeral / reusable / preauthorized) |
+| User role elevated to admin or owner | High | PrivilegeEscalation, Persistence | `USER_ROLE_UPDATE` audit events targeting `admin` or `owner` |
+| Unauthorized device connected to control plane | High | InitialAccess, Persistence | `Authorized=false AND ConnectedToControl=true` in devices snapshot |
+
+**Configuration (3)**
+
+| Rule | Severity | Tactics | What it watches |
+|---|---|---|---|
+| Policy file (ACL) modified | Medium | DefenseEvasion, Persistence | `ACL_FILE_UPDATE` events |
+| Mass credential revocation in short window | High | DefenseEvasion, Impact | More than N delete-key events in a 30-min sliding window |
+| External (shared-in) device added | Medium | InitialAccess | New `IsExternal=true` device vs 24-hour baseline |
+
+**Devices (3)**
+
+| Rule | Severity | Tactics | What it watches |
+|---|---|---|---|
+| Device started advertising subnet routes | Medium | LateralMovement, Persistence | Non-exit-node CIDRs newly appear in `AdvertisedRoutes` |
+| Device key expiring within 7 days | Medium | InitialAccess | Devices whose key expiry is within 7 days |
+| Device Tailscale SSH newly enabled | Medium | Persistence, LateralMovement | `SshEnabled` transition from false to true vs 24-hour baseline |
+
+**Network & exit (2)**
+
+| Rule | Severity | Tactics | What it watches |
+|---|---|---|---|
+| Exit node advertised or approved | Low | CommandAndControl, Exfiltration | `0.0.0.0/0` or `::/0` newly appears in `AdvertisedRoutes` / `EnabledRoutes` |
+| Tailnet lock validation failed | High | DefenseEvasion, InitialAccess | Non-empty `TailnetLockError` field in devices snapshot |
+
+**DNS (3)**
+
+| Rule | Severity | Tactics | What it watches |
+|---|---|---|---|
+| DNS nameservers modified | High | DefenseEvasion, CommandAndControl | `DNS_UPDATE` audit events affecting global nameservers |
+| MagicDNS disabled | Medium | DefenseEvasion | `MAGICDNS_DISABLE` audit event |
+| Split-DNS configuration modified | High | DefenseEvasion, CommandAndControl | `SPLIT_DNS_UPDATE` audit events (per-domain DNS override) |
+
+### Premium tier (additional 8 rules)
+
+These require `Tailscale_Network_CL` (flow logs) or `Tailscale_PostureIntegrations_CL`.
+
+| Rule | Severity | Tactics | What it watches |
+|---|---|---|---|
+| Network flow beaconing detected | Medium | CommandAndControl, Exfiltration | Regular periodic flows from a single source over 24h (jitter-tolerant) |
+| DERP relay traffic surge | Low | CommandAndControl | More than 75% of a source node's recent flows fell back to DERP relay (`IsRelayed=true`); signals NAT/firewall failure, UDP-blocking middlebox, or attempted evasion |
+| Large outbound transfer over tailnet | Medium | Exfiltration, Collection | Single flow tx-bytes > 1GB in any 5-min window |
+| Mass fan-out from single node | High | Discovery, LateralMovement | Source node initiated flows to N+ distinct destinations within 5 min |
+| Subnet router throughput anomaly | Low | Exfiltration, CommandAndControl | Subnet-router src->dst throughput exceeds 3-sigma of its 7-day baseline |
+| Unexpected exit-node egress | Medium | CommandAndControl, Exfiltration | Egress through a node that wasn't approved as exit node in the last hour |
+| New posture integration added | Medium | Persistence | New entry in `Tailscale_PostureIntegrations_CL` snapshot vs prior |
+| Posture integration disabled or removed | High | DefenseEvasion, Persistence | Posture integration disappeared or status changed to disabled |
+
+---
+
+## 7. Hunting queries
+
+### Standard (12)
+
+| Query | Tactics | Use case |
+|---|---|---|
+| First-seen actor making configuration changes | InitialAccess, Persistence | New principal performing privileged audit actions |
+| ACL policy churn | DefenseEvasion, PrivilegeEscalation | How often is the policy file edited - high churn = governance risk |
+| Off-hours configuration changes | InitialAccess, Persistence | Privileged audit actions outside business hours |
+| Auth key sprawl | Persistence, CredentialAccess | Users with many active reusable auth keys |
+| Auth keys with no expiry | Persistence, CredentialAccess | Long-lived auth keys (compliance / hygiene) |
+| Devices not seen in 30+ days | Discovery | Stale devices - candidates for offboarding |
+| Devices with outdated client version | DefenseEvasion | Client behind latest release |
+| Users with zero devices | InitialAccess | Orphaned user accounts |
+| Split-DNS per-domain change history | DefenseEvasion, CommandAndControl | Audit-log slice of per-domain DNS routing changes |
+| Devices with Tailscale SSH enabled | LateralMovement, Persistence | Cross-reference with the SSH ACL block |
+| External (shared-in) device inventory | InitialAccess | Devices admitted via Tailscale sharing |
+| Subnet router CIDR exposure inventory | LateralMovement | Every CIDR currently bridged into the tailnet |
+
+### Premium (10)
+
+| Query | Tactics | Use case |
+|---|---|---|
+| Beaconing candidates (regular periodic flows) | CommandAndControl, Exfiltration | Looser threshold than the analytic rule - investigation aid |
+| Cross-tag flow matrix | LateralMovement, Discovery | Flows pivoted by src-tag x dst-tag over 7 days; surfaces tag-to-same-tag loops as worm/service-mesh signal |
+| Devices with persistent DERP relay usage | CommandAndControl | Devices that consistently fall back to DERP relay over 24h; long-window companion to the surge rule |
+| Exit-node usage patterns | CommandAndControl, Exfiltration | Who uses which exit node, how often, how much data |
+| New src->dst node pairs (lateral movement candidates) | LateralMovement, Discovery | First-time observed flow pair vs 7-day baseline |
+| Network flows outside business hours | Exfiltration, CommandAndControl | Off-hours flows with `TaggedSource` discriminator to separate service-account from human activity |
+| Tagged services with broad inbound exposure | LateralMovement, InitialAccess | Tagged services ranked by inbound `DistinctSrcDevices` / `DistinctSrcUsers` / `DistinctSrcOs` - surfaces ACL drift |
+| Top talkers by bytes (virtual traffic) | Exfiltration, Collection | Highest tx/rx nodes over time window |
+| Users generating traffic from multiple devices | InitialAccess, Persistence | Multi-device users joined against `Tailscale_Devices_CL.Created` to flag newly-added devices |
+| Current posture integration inventory | DefenseEvasion | Snapshot of every posture integration and its enabled state |
+
+---
+
+## 8. Workbooks
+
+Both workbooks are wired automatically when you install the matching connector. Open Sentinel -> **Workbooks** -> search "Tailscale".
+
+**Tailscale Standard Operations**
+
+Tabs for Devices, Users, Keys, DNS, Audit and Health. Quick-look tiles for total devices, devices with updates available, devices with SSH enabled, subnet routers and exit nodes, dormant devices, tailnet-lock state. All KQL validated against live data.
+
+**Tailscale Premium Operations**
+
+Everything in Standard plus Network tab (top talkers, src->dst pairs, exit-node egress, beaconing candidates) and Posture tab (integration inventory, posture state per device).
+
+---
+
+## 9. Architecture notes
+
+### Why two connectors
+
+Tailscale's `/logging/network` endpoint is gated to Premium and Enterprise tailnets. We could put a single connector behind a single Connect button and let pollers silently fail on Free/Standard, but that would generate noisy errors and confuse operators. Splitting the connector by tier means each card only registers pollers the user's API tier actually supports.
+
+### Why one Connect button per connector deploys N pollers
+
+Sentinel CCF normally maps one Connect card to one polling rule. To fan out to 9-11 pollers from a single click, the polling-rule contentTemplate uses the **Proofpoint TAP** pattern - a `guidValue` parameter (`defaultValue: '[newGuid()]'`) and an `innerWorkspace` parameter that defer evaluation to inner-deploy scope (Connect-click time), producing one shared GUID across every poller resource name. Without this exact shape, Sentinel silently deploys only the first poller - the most painful bug we hit in this project.
+
+### Why OAuth clients are required (not personal API tokens)
+
+Tailscale's `/logging/configuration`, `/logging/network`, posture, and DNS endpoints require scoped credentials. Personal API tokens (`tskey-api-...`) are unscoped - against scope-gated endpoints they return HTTP 200 with `"logs": null` (or empty arrays) rather than 401, so the misconfiguration is **silent**. OAuth client credentials carry explicit scopes and fail closed.
+
+### How three DNS endpoints become one table
+
+Tailscale exposes DNS configuration across three endpoints (`/dns/nameservers`, `/dns/preferences`, `/dns/searchpaths`) but the Sentinel UX is cleaner with one logical table. Both connectors land all three into a single `Tailscale_Dns_CL` table with a `ConfigType` discriminator column - the mechanism differs by connector because Azure DCRs are capped at 10 `dataFlows` and the Premium connector is already at the limit:
+
+- **Standard connector** (7 tables, 9 dataFlows): each DNS poller writes to its own input stream (`Custom-Tailscale_DnsNameservers_CL`, `Custom-Tailscale_DnsPreferences_CL`, `Custom-Tailscale_DnsSearchPaths_CL`) and three separate `dataFlows` apply per-source transforms before merging into the shared `Tailscale_Dns_CL` output.
+- **Premium connector** (9 tables, 9 dataFlows): all three DNS pollers write to a single unified input stream (`Custom-Tailscale_DnsConfig_CL`) so the DCR uses just one `dataFlow` for DNS. Without this consolidation the Premium DCR would be at 11 dataFlows and Azure would reject the deployment.
+
+Net effect for the operator is identical: one table, filter by `ConfigType`.
+
+### Cadence rationale
+
+- **5 min** for `/logging/configuration` (Standard + Premium) and `/logging/network` (Premium only) - these are event streams with `start` / `end` query parameters
+- **60 min** for snapshot endpoints (devices, users, keys, ...) - the data doesn't change frequently and a 1-hour granularity is enough for snapshot-based rules; reducing this would mostly burn ingestion cost without improving detection
+
+---
+
+## 10. Limitations
+
+- **Network flow logs are Premium-only.** The eight Premium rules and ten Premium hunts depend on `Tailscale_Network_CL` and won't run on a Standard install.
+- **Microsoft pre-built "Network Session Essentials" detections require a clone-and-rewire.** This solution ships a `vimNetworkSessionTailscale` ASIM NetworkSession parser (and the param-less `ASimNetworkSessionTailscale` wrapper) that maps `Tailscale_Network_CL` to the ASIM NetworkSession schema. Microsoft's pre-built detections call the sealed workspace function `_Im_NetworkSession`, which can't be extended from a Solution - so to apply those detections to Tailscale data, clone the rule and replace `_Im_NetworkSession(...)` with `vimNetworkSessionTailscale(...)` (or `union vimNetworkSessionTailscale(...), _Im_NetworkSession(...)` if you want both sources in one query).
+- **Snapshot tables overwrite, they don't diff.** Each 60-min snapshot is a complete current-state poll, not a delta. Rules that need transition detection (e.g. "SSH newly enabled") compare the latest snapshot against a 24-hour baseline.
+
+---
+
+## 11. Troubleshooting
+
+### "Connected" but no rows in any `_CL` table after 30 min
+
+```kql
+// Were any pollers dispatched? (DCR ingestion is logged by the workspace)
+union AzureDiagnostics
+| where Category == "DataCollectionRuleLogs"
+| where _ResourceId contains "dcr-tailscale"
+| where TimeGenerated > ago(1h)
+| project TimeGenerated, Status_s, ResultDescription_s
+```
+
+If you see no entries, the connector hasn't been dispatched yet - wait the 5-minute or 60-minute cadence. If you see entries with `Status_s != "Succeeded"`, paste `ResultDescription_s` into a support thread.
+
+### "Connected" but only `Tailscale_Audit_CL` has rows
+
+The remaining endpoints poll on 60-min cadence - the first non-audit snapshot won't land until 60 minutes after Connect. Wait an hour.
+
+### Audit has rows but specific endpoints (devices, dns, ...) don't
+
+Almost always a missing OAuth scope. Tailscale returns HTTP 200 with empty data when a scope is missing, so the poller doesn't error. Re-check the OAuth client at against the [scope checklist](#oauth-scope-checklist) above.
+
+### `Tailscale_Devices_CL` is missing `AdvertisedRoutes`, `SshEnabled`, etc.
+
+The connector polls `/devices?fields=all`. If those columns are empty in your workspace, the most likely cause is that the DCR transform isn't projecting them - confirm the deployed `dcr-tailscale*` DCR `transformKql` matches the version in this solution and re-Connect.
+
+### OAuth deployment fails with "Invalid Token Endpoint query parameters"
+
+You hit the Sentinel CCF reserved-key bug - the connector definition is including OAuth reserved keys (`client_id`, `client_secret`, `grant_type`, `scope`) inside `TokenEndpointQueryParameters`. The shipped connector definition omits these correctly; if you're seeing this error after edits, remove those keys from the connector definition JSON and redeploy.
+
+### Premium rules silently never fire
+
+Confirm you installed the **Premium** connector (not Standard) and that `Tailscale_Network_CL` and `Tailscale_PostureIntegrations_CL` are receiving rows (see the verification query in [section 4](#4-verification)). Premium rules query those two tables exclusively.
+
+---
+
+## 12. Support
+
+This is a Community-tier solution. Bugs, feature requests, and PRs:
+
+- **GitHub Issues**: (tag the title with `[Tailscale (CCF)]`)
+- **Maintainer**: noodlemctwoodle
+
+No SLA - the maintainer responds when convenient. PRs that include tests + a reproducer trip the response time considerably.
+
+---
+
+## 13. Acknowledgements
+
+Thanks to [Tailscale](https://tailscale.com/) for the support that made the Premium-tier features of this solution (network flow log ingestion, posture integration inventory, the seven Premium analytic rules, the five Premium hunting queries, and the Premium Operations workbook) buildable and verifiable against live data.
diff --git a/Solutions/Tailscale (CCF)/ReleaseNotes.md b/Solutions/Tailscale (CCF)/ReleaseNotes.md
new file mode 100644
index 00000000000..b62de57a294
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/ReleaseNotes.md
@@ -0,0 +1,3 @@
+| **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** |
+|---|---|---|
+| 3.0.0 | 19-05-2026 | Initial Solution Release |
diff --git a/Solutions/Tailscale (CCF)/SolutionMetadata.json b/Solutions/Tailscale (CCF)/SolutionMetadata.json
new file mode 100644
index 00000000000..d619d1ac7cf
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/SolutionMetadata.json
@@ -0,0 +1,22 @@
+{
+ "publisherId": "noodlemctwoodle",
+ "offerId": "azure-sentinel-solution-tailscale-ccf",
+ "firstPublishDate": "2026-05-19",
+ "lastPublishDate": "2026-05-19",
+ "providers": [
+ "Community"
+ ],
+ "categories": {
+ "domains": [
+ "Networking",
+ "Security - Network",
+ "Identity"
+ ]
+ },
+ "support": {
+ "name": "Tailscale (CCF)",
+ "tier": "Community",
+ "email": "ccfconnectors.county118@passmail.com",
+ "link": "https://github.com/Azure/Azure-Sentinel/issues"
+ }
+}
diff --git a/Solutions/Tailscale (CCF)/Workbooks/TailscalePremiumOperations.json b/Solutions/Tailscale (CCF)/Workbooks/TailscalePremiumOperations.json
new file mode 100644
index 00000000000..7a3e59eadcc
--- /dev/null
+++ b/Solutions/Tailscale (CCF)/Workbooks/TailscalePremiumOperations.json
@@ -0,0 +1,2259 @@
+{
+ "version": "Notebook/1.0",
+ "items": [
+ {
+ "type": 9,
+ "content": {
+ "version": "KqlParameterItem/1.0",
+ "parameters": [
+ {
+ "id": "7b05a598-5120-43f4-bf5d-576c2a7ff28d",
+ "version": "KqlParameterItem/1.0",
+ "name": "TimeRange",
+ "type": 4,
+ "isRequired": true,
+ "value": {
+ "durationMs": 86400000
+ },
+ "typeSettings": {
+ "selectableValues": [
+ {
+ "durationMs": 3600000
+ },
+ {
+ "durationMs": 14400000
+ },
+ {
+ "durationMs": 43200000
+ },
+ {
+ "durationMs": 86400000
+ },
+ {
+ "durationMs": 172800000
+ },
+ {
+ "durationMs": 604800000
+ },
+ {
+ "durationMs": 2592000000
+ }
+ ]
+ }
+ }
+ ],
+ "style": "pills",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces"
+ },
+ "name": "parameters"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "
Tailscale Operations (Premium)
Single-pane visibility into your Tailscale tailnet on Personal (Free), Starter and Premium tiers: who, what, when, where, and what changed. Scope every panel with the time range below; the Investigate tab adds Actor and Device pickers for drilldown. Premium-tier panels (network flow logs, posture integrations) live in the separate Tailscale Operations (Premium) workbook.
Tailscale Operations (Premium) (CCF) - Microsoft Sentinel content from the Tailscale (CCF) solution, Premium-tier surface. Tables polled from the Tailscale REST API: audit, devices, users, keys, dns, settings, network flows (/logging/network), posture integrations. Filter every panel via the time range above; the Investigate tab adds Actor and Device pickers for drilldown. The Network Flows and Posture tabs use the 15 promoted columns added in 3.1.0 (SrcUser, SrcTags, DstUser, DstTags, HasVirtualTraffic, HasSubnetTraffic, HasExitTraffic, HasPhysicalTraffic, IsRelayed, etc.). Companion workbook: Tailscale Operations (Standard) for Personal / Starter tailnets.
Single-pane visibility into your Tailscale tailnet on Personal (Free), Starter and Standard tiers: who, what, when, where, and what changed. Scope every panel with the time range below; the Investigate tab adds Actor and Device pickers for drilldown. Premium-tier panels (network flow logs, posture integrations) live in the separate Tailscale Operations (Premium) workbook.
Tailscale Operations (Standard) (CCF) - Microsoft Sentinel content from the Tailscale (CCF) solution, Standard-tier surface. Tables polled from the Tailscale REST API: audit, devices, users, keys, dns, settings. Filter every panel via the time range above; the Investigate tab adds Actor and Device pickers for drilldown. For network flow logs and posture integrations, install the companion Tailscale Operations (Premium) workbook on a Premium / Enterprise tailnet.
"
+ },
+ "name": "footer"
+ }
+ ],
+ "fallbackResourceIds": [
+ "Azure Monitor"
+ ],
+ "fromTemplateId": "sentinel-Tailscale-CCF",
+ "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
+}
\ No newline at end of file
diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json
index 143ab9dc8e7..5e23a612b73 100644
--- a/Workbooks/WorkbooksMetadata.json
+++ b/Workbooks/WorkbooksMetadata.json
@@ -329,7 +329,7 @@
"tier": "Community"
},
"author": {
- "name": "Tomáš Kubica"
+ "name": "Tom\u00e1\u0161 Kubica"
},
"source": {
"kind": "Community"
@@ -5184,7 +5184,7 @@
{
"workbookKey": "ThreatAnalysis&Response",
"logoFileName": "Azure_Sentinel.svg",
- "description": "The Defenders for IoT workbook provide guided investigations for OT entities based on open incidents, alert notifications, and activities for OT assets. They also provide a hunting experience across the MITRE ATT&CK® framework for ICS, and are designed to enable analysts, security engineers, and MSSPs to gain situational awareness of OT security posture.",
+ "description": "The Defenders for IoT workbook provide guided investigations for OT entities based on open incidents, alert notifications, and activities for OT assets. They also provide a hunting experience across the MITRE ATT&CK\u00ae framework for ICS, and are designed to enable analysts, security engineers, and MSSPs to gain situational awareness of OT security posture.",
"dataTypesDependencies": [
"SecurityAlert"
],
@@ -6392,7 +6392,7 @@
{
"workbookKey": "MicrosoftExchangeSearchAdminAuditLog",
"logoFileName": "Azure_Sentinel.svg",
- "description": "This workbook is dedicated to On-Premises Exchange organizations. It uses the MSExchange Management event logs to give you a simple way to view administrators’ activities in your Exchange environment with Cmdlets usage statistics and multiple pivots to understand who and/or what is affected to modifications on your environment. Required Data Connector: Exchange Audit Event logs via Legacy Agent.",
+ "description": "This workbook is dedicated to On-Premises Exchange organizations. It uses the MSExchange Management event logs to give you a simple way to view administrators\u2019 activities in your Exchange environment with Cmdlets usage statistics and multiple pivots to understand who and/or what is affected to modifications on your environment. Required Data Connector: Exchange Audit Event logs via Legacy Agent.",
"dataTypesDependencies": [
"ESIExchangeConfig_CL"
],
@@ -6413,7 +6413,7 @@
{
"workbookKey": "MicrosoftExchangeSearchAdminAuditLog-Online",
"logoFileName": "Azure_Sentinel.svg",
- "description": "This workbook is dedicated to Online Exchange organizations. It uses the Office Activity logs to give you a simple way to view administrators’ activities in your Exchange environment with Cmdlets usage statistics and multiple pivots to understand who and/or what is affected to modifications on your environment. Required Data Connector: Microsoft 365 (Exchange).",
+ "description": "This workbook is dedicated to Online Exchange organizations. It uses the Office Activity logs to give you a simple way to view administrators\u2019 activities in your Exchange environment with Cmdlets usage statistics and multiple pivots to understand who and/or what is affected to modifications on your environment. Required Data Connector: Microsoft 365 (Exchange).",
"dataTypesDependencies": [
"OfficeActivity"
],
@@ -6935,7 +6935,7 @@
{
"workbookKey": "MicrosoftSentinelCostGBP",
"logoFileName": "Azure_Sentinel.svg",
- "description": "This workbook provides an estimated cost in GBP (£) across the main billed items in Microsoft Sentinel: ingestion, retention and automation. It also provides insight about the possible impact of the Microsoft 365 E5 offer.",
+ "description": "This workbook provides an estimated cost in GBP (\u00a3) across the main billed items in Microsoft Sentinel: ingestion, retention and automation. It also provides insight about the possible impact of the Microsoft 365 E5 offer.",
"dataTypesDependencies": [],
"dataConnectorsDependencies": [],
"previewImagesFileNames": [
@@ -7091,7 +7091,7 @@
{
"workbookKey": "MicrosoftSentinelCostEUR",
"logoFileName": "Azure_Sentinel.svg",
- "description": "This workbook provides an estimated cost in EUR (€) across the main billed items in Microsoft Sentinel: ingestion, retention and automation. It also provides insight about the possible impact of the Microsoft 365 E5 offer.",
+ "description": "This workbook provides an estimated cost in EUR (\u20ac) across the main billed items in Microsoft Sentinel: ingestion, retention and automation. It also provides insight about the possible impact of the Microsoft 365 E5 offer.",
"dataTypesDependencies": [],
"dataConnectorsDependencies": [],
"previewImagesFileNames": [
@@ -7607,7 +7607,7 @@
{
"workbookKey": "InsiderRiskManagementWorkbook",
"logoFileName": "Azure_Sentinel.svg",
- "description": "The Microsoft Insider Risk Management Workbook integrates telemetry from 25+ Microsoft security products to provide actionable insights into insider risk management. Reporting tools provide “Go to Alert” links to provide deeper integration between products and a simplified user experience for exploring alerts. ",
+ "description": "The Microsoft Insider Risk Management Workbook integrates telemetry from 25+ Microsoft security products to provide actionable insights into insider risk management. Reporting tools provide \u201cGo to Alert\u201d links to provide deeper integration between products and a simplified user experience for exploring alerts. ",
"dataTypesDependencies": [
"SigninLogsSigninLogs",
"AuditLogs",
@@ -10354,9 +10354,7 @@
"SentinelBehaviorInfo",
"SentinelBehaviorEntities"
],
- "dataConnectorsDependencies": [
-
- ],
+ "dataConnectorsDependencies": [],
"previewImagesFileNames": [
"UEBABehaviorsAnalysisWorkbookBlack1.png",
"UEBABehaviorsAnalysisWorkbookBlack2.png",
@@ -10389,70 +10387,72 @@
"provider": "KnowBe4"
},
{
- "workbookKey": "VTIFeedDashboard",
- "logoFileName": "Visa_VTI_Logo.svg",
- "description": "Visa Threat Intelligence Feed Dashboard",
- "dataTypesDependencies": [ "VisaThreatIntelligenceIOC_CL" ],
- "dataConnectorsDependencies": [
- "VisaThreatIntelligence"
- ],
- "previewImagesFileNames": [
- "VTIOverview_black.png",
- "VTIOverview_white.png"
- ],
- "version": "1.0",
- "title": "Visa Threat Intelligence Feed Overview",
- "templateRelativePath": "VTI_IOC_Feed.json",
- "subtitle": "",
- "provider": "Visa"
- },
- {
- "workbookKey": "Censys",
- "logoFileName": "Censys.svg",
- "description": "This Workbook provides immediate insight into the data coming from Censys.",
- "dataTypesDependencies": [
- "Censys_Host_Services_CL",
- "Censys_Host_IOC_CL",
- "Censys_Web_Property_IOC_CL",
- "Censys_Web_Property_Vuln_CL",
- "Censys_Web_Property_Endpoint_CL",
- "Censys_Web_Property_Threat_CL",
- "Censys_Certificate_IOC_Temp_CL",
- "CensysHost_CL",
- "Censyswebproperty_CL",
- "CensysWebProperty_CL",
- "CensysCert_CL",
- "CensysCertificate_CL",
- "CensysHostAlert_CL",
- "CensysCertificateAlert_CL",
- "CensysWebPropertyAlert_CL",
- "CensysRescanHost_CL",
- "CensysRescanWebProperty_CL",
- "CensysRescanHostAlert_CL",
- "CensysRescanWebPropertyAlert_CL",
- "CensysRelatedInfrastructure_CL",
- "Censys_Host_History_Data_CL",
- "Incident_Enrich_Data_CL"
- ],
- "previewImagesFileNames": [
- "CensysWhite1.png",
- "CensysWhite2.png",
- "CensysWhite3.png",
- "CensysWhite4.png",
- "CensysWhite5.png",
- "CensysWhite6.png",
- "CensysBlack1.png",
- "CensysBlack2.png",
- "CensysBlack3.png",
- "CensysBlack4.png",
- "CensysBlack5.png",
- "CensysBlack6.png"
- ],
- "version": "1.0",
- "title": "Censys",
- "templateRelativePath": "Censys.json",
- "subtitle": "",
- "provider": "Censys"
+ "workbookKey": "VTIFeedDashboard",
+ "logoFileName": "Visa_VTI_Logo.svg",
+ "description": "Visa Threat Intelligence Feed Dashboard",
+ "dataTypesDependencies": [
+ "VisaThreatIntelligenceIOC_CL"
+ ],
+ "dataConnectorsDependencies": [
+ "VisaThreatIntelligence"
+ ],
+ "previewImagesFileNames": [
+ "VTIOverview_black.png",
+ "VTIOverview_white.png"
+ ],
+ "version": "1.0",
+ "title": "Visa Threat Intelligence Feed Overview",
+ "templateRelativePath": "VTI_IOC_Feed.json",
+ "subtitle": "",
+ "provider": "Visa"
+ },
+ {
+ "workbookKey": "Censys",
+ "logoFileName": "Censys.svg",
+ "description": "This Workbook provides immediate insight into the data coming from Censys.",
+ "dataTypesDependencies": [
+ "Censys_Host_Services_CL",
+ "Censys_Host_IOC_CL",
+ "Censys_Web_Property_IOC_CL",
+ "Censys_Web_Property_Vuln_CL",
+ "Censys_Web_Property_Endpoint_CL",
+ "Censys_Web_Property_Threat_CL",
+ "Censys_Certificate_IOC_Temp_CL",
+ "CensysHost_CL",
+ "Censyswebproperty_CL",
+ "CensysWebProperty_CL",
+ "CensysCert_CL",
+ "CensysCertificate_CL",
+ "CensysHostAlert_CL",
+ "CensysCertificateAlert_CL",
+ "CensysWebPropertyAlert_CL",
+ "CensysRescanHost_CL",
+ "CensysRescanWebProperty_CL",
+ "CensysRescanHostAlert_CL",
+ "CensysRescanWebPropertyAlert_CL",
+ "CensysRelatedInfrastructure_CL",
+ "Censys_Host_History_Data_CL",
+ "Incident_Enrich_Data_CL"
+ ],
+ "previewImagesFileNames": [
+ "CensysWhite1.png",
+ "CensysWhite2.png",
+ "CensysWhite3.png",
+ "CensysWhite4.png",
+ "CensysWhite5.png",
+ "CensysWhite6.png",
+ "CensysBlack1.png",
+ "CensysBlack2.png",
+ "CensysBlack3.png",
+ "CensysBlack4.png",
+ "CensysBlack5.png",
+ "CensysBlack6.png"
+ ],
+ "version": "1.0",
+ "title": "Censys",
+ "templateRelativePath": "Censys.json",
+ "subtitle": "",
+ "provider": "Censys"
},
{
"workbookKey": "NetskopeWebTransactionsWorkbook",
@@ -10466,13 +10466,13 @@
"NetskopeWebtxOverviewWhite01.png",
"NetskopeWebtxOverviewBlack02.png",
"NetskopeWebtxOverviewWhite02.png"
- ],
+ ],
"version": "1.0.0",
"title": "Netskope Web Transactions",
"templateRelativePath": "NetskopeWebTx_Workbook.json",
"subtitle": "Web Traffic Analysis and Security Monitoring",
"provider": "Netskope"
-},
+ },
{
"workbookKey": "MicrosoftCopilotActivityMonitoring",
"logoFileName": "Copilot_logo.svg",
@@ -10493,22 +10493,22 @@
"subtitle": "",
"provider": "Microsoft Sentinel Community"
},
- {
+ {
"workbookKey": "ExtraHopDetectionsOverview",
"logoFileName": "ExtraHop.svg",
"description": "This workbook provides immediate insight into detection data ingested from ExtraHop.",
"dataTypesDependencies": [
"ExtraHop_Detections_CL"
- ],
+ ],
"dataConnectorsDependencies": [
"ExtraHopDataConnector"
- ],
+ ],
"previewImagesFileNames": [
"ExtraHopDetectionsOverviewBlack1.png",
"ExtraHopDetectionsOverviewWhite1.png",
- "ExtraHopDetectionsOverviewBlack2.png",
- "ExtraHopDetectionsOverviewWhite2.png"
- ],
+ "ExtraHopDetectionsOverviewBlack2.png",
+ "ExtraHopDetectionsOverviewWhite2.png"
+ ],
"version": "1.0.0",
"title": "ExtraHop Detections Overview",
"templateRelativePath": "ExtraHopDetectionsOverview.json",
@@ -10609,44 +10609,6 @@
"provider": "archTIS"
},
{
- "workbookKey": "UnifiSiteManager",
- "logoFileName": "UnifiSiteManager.svg",
- "description": "The UniFi Site Manager workbook provides comprehensive visibility into your UniFi network infrastructure. Monitor device status, host connections, site health, and ISP performance metrics across all your UniFi deployments.",
- "dataTypesDependencies": [
- "Unifi_SiteManager_Devices_CL",
- "Unifi_SiteManager_Hosts_CL",
- "Unifi_SiteManager_Sites_CL",
- "Unifi_SiteManager_ISPMetrics_CL"
- ],
- "dataConnectorsDependencies": [
- "UniFiSiteManagerConnectorDefinition"
- ],
- "previewImagesFileNames": [
- "UnifiSiteManagerBlack1.png",
- "UnifiSiteManagerBlack2.png",
- "UnifiSiteManagerBlack3.png",
- "UnifiSiteManagerWhite1.png",
- "UnifiSiteManagerWhite2.png",
- "UnifiSiteManagerWhite3.png"
- ],
- "version": "1.0.0",
- "title": "UniFi Site Manager Network Dashboard",
- "templateRelativePath": "UnifiSiteManager.json",
- "subtitle": "",
- "provider": "Community",
- "support": {
- "tier": "Community"
- },
- "source": {
- "kind": "Solution",
- "name": "UniFi Site Manager (CCF)"
- },
- "categories": {
- "domains": [
- "Networking",
- "Security - Network"
- ]
- },
"workbookKey": "stealthTalkAnomalousAuthMonitor",
"logoFileName": "st-ms-def-hub.svg",
"description": "The StealthTalk Anomalous Auth Monitor workbook provides a real-time SOC dashboard for the four classes of anomalous authentication events forwarded by StealthTalk into Microsoft Sentinel. Across 17 panels organised into Overview, Off-Hours, New Devices, Geo Anomaly and Brute Force sections, the workbook surfaces a composite User Risk Leaderboard (High=10 / Medium=5 / Low=1 per event), a Multi-Vector Correlation view that flags users triggering 2 or more anomaly types simultaneously, an interactive World Map of anomalous login locations, and chronological raw-event logs for incident investigation. Three global filters (Time Range, Severity and User ID) scope every panel.",
@@ -10664,31 +10626,31 @@
"StealthTalkDataConnector_black.png"
],
"version": "1.0.0",
- "lastPublishDate": "2026-05-19",
"title": "StealthTalk Anomalous Auth Monitor",
"templateRelativePath": "StealthTalkAnomalousAuthMonitor.json",
"subtitle": "",
"provider": "StealthTalk",
- "source": {
- "kind": "Solution",
- "name": "StealthTalk Anomalous Authentication"
- },
- "author": {
- "name": "StealthTalk",
- "email": "support@stealthtalk.com"
- },
"support": {
"name": "StealthTalk Support",
"email": "support@stealthtalk.com",
"tier": "Partner",
"link": "https://stealthtalk.com/support"
},
+ "source": {
+ "kind": "Solution",
+ "name": "StealthTalk Anomalous Authentication"
+ },
"categories": {
"domains": [
"Security - Threat Protection",
"Identity",
"Security - Insider Threat"
]
+ },
+ "lastPublishDate": "2026-05-19",
+ "author": {
+ "name": "StealthTalk",
+ "email": "support@stealthtalk.com"
}
},
{
@@ -10708,26 +10670,6 @@
"provider": "AWS Security Hub"
},
{
- "workbookKey": "ESKMworkbook",
- "logoFileName": "UtimacoLogoSVG.svg",
- "description": "Gain insights into Utimaco Enterprise Secure Key Manager (ESKM) KMIP server activity. This workbook visualizes authentication events, key management operations, client IPs, and operation outcomes to help detect anomalies, misuse, and configuration issues.",
- "dataTypesDependencies": [
- "UtimacoESKMKmipServerLogs_CL"
- ],
- "dataConnectorsDependencies": [
- "UtimacoESKMConnector"
- ],
- "previewImagesFileNames": [
- "UtimacoESKMBlack1.png",
- "UtimacoESKMBlack2.png",
- "UtimacoESKMWhite1.png",
- "UtimacoESKMWhite2.png"
- ],
- "version": "1.0.0",
- "title": "Utimaco Enterprise Secure Key Manager",
- "templateRelativePath": "ESKMworkbook.json",
- "subtitle": "",
- "provider": "Utimaco",
"workbookKey": "GuardicoreInfoWorkbook",
"logoFileName": "akamai-guardicore.svg",
"description": "Gain insights into Guardicore workload protection coverage, policy enforcement effectiveness, and security posture. This workbook provides visibility into protected workloads, policy rules by action type, blocking rulesets, and application status trends over time.",
@@ -10769,5 +10711,87 @@
"templateRelativePath": "GuardicoreIncident.json",
"subtitle": "",
"provider": "Akamai Guardicore"
+ },
+ {
+ "workbookKey": "TailscaleStandardOperationsWorkbook",
+ "logoFileName": "Tailscale.svg",
+ "description": "Tailscale Operations workbook for Standard tier. Nine tabs covering an at-a-glance KPI hero row, audit activity overview, an actor + device drilldown (Investigate), embedded hunting queries (first-seen actors, off-hours changes, key-expiry-disabled devices, never-expire auth keys, outdated clients, dormant devices, subnet route exposure, SSH-enabled devices), identity (user roles, status, last login recency, role escalation history, orphaned users), devices (OS / version / tag distribution, devices needing attention, full inventory, subnet routers), credentials (expiry timeline, never-expire flag, CRUD events), admin audit (action heatmap, actor x action heatmap, recent 100), network and DNS (current snapshot, tailnet policy gates, ACL change history), and pipeline health (per-table freshness, ingest rate, operational events). Driven by data polled from the Tailscale REST API.",
+ "dataTypesDependencies": [
+ "Tailscale_Audit_CL",
+ "Tailscale_Devices_CL",
+ "Tailscale_Users_CL",
+ "Tailscale_Keys_CL",
+ "Tailscale_Webhooks_CL",
+ "Tailscale_Settings_CL",
+ "Tailscale_Dns_CL"
+ ],
+ "dataConnectorsDependencies": [
+ "TailscaleCCF"
+ ],
+ "previewImagesFileNames": [],
+ "version": "1.0.0",
+ "title": "Tailscale Operations (Standard)",
+ "templateRelativePath": "TailscaleStandardOperations.json",
+ "subtitle": "",
+ "provider": "Community",
+ "support": {
+ "tier": "Community"
+ },
+ "source": {
+ "kind": "Solution",
+ "name": "Tailscale (CCF)"
+ },
+ "categories": {
+ "domains": [
+ "Networking",
+ "Security - Network",
+ "Identity"
+ ]
+ },
+ "author": {
+ "name": "noodlemctwoodle"
+ }
+ },
+ {
+ "workbookKey": "TailscalePremiumOperationsWorkbook",
+ "logoFileName": "Tailscale.svg",
+ "description": "Tailscale Operations workbook for Premium / Enterprise tier - everything in the Standard workbook plus network flow analysis (top talkers, src-dst pairs, exit-node egress, beaconing candidates) and posture integration inventory.",
+ "dataTypesDependencies": [
+ "Tailscale_Audit_CL",
+ "Tailscale_Devices_CL",
+ "Tailscale_Users_CL",
+ "Tailscale_Keys_CL",
+ "Tailscale_Webhooks_CL",
+ "Tailscale_Settings_CL",
+ "Tailscale_Dns_CL",
+ "Tailscale_Network_CL",
+ "Tailscale_PostureIntegrations_CL"
+ ],
+ "dataConnectorsDependencies": [
+ "TailscalePremiumCCF"
+ ],
+ "previewImagesFileNames": [],
+ "version": "1.0.0",
+ "title": "Tailscale Operations (Premium)",
+ "templateRelativePath": "TailscalePremiumOperations.json",
+ "subtitle": "",
+ "provider": "Community",
+ "support": {
+ "tier": "Community"
+ },
+ "source": {
+ "kind": "Solution",
+ "name": "Tailscale (CCF)"
+ },
+ "categories": {
+ "domains": [
+ "Networking",
+ "Security - Network",
+ "Identity"
+ ]
+ },
+ "author": {
+ "name": "noodlemctwoodle"
+ }
}
]