Tenant device registration policy changed
Description
Identifies changes to the tenant device registration and join policy, who made them and when, and shows whether the same person also changed the authentication methods policy or the default user permissions in the same hour.
Query · kql
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let TenantPolicyOperations = dynamic([
"Authentication Methods Policy Update",
"Update authorization policy",
"Set device registration policies"
]);
let TenantPolicyChanges = AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName in~ (TenantPolicyOperations)
| where Result =~ "success"
| extend ActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorApp = tostring(InitiatedBy.app.displayName)
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| extend ActorAppId = tostring(InitiatedBy.app.servicePrincipalId)
// External accounts are written as "name_domain#EXT#@tenant.onmicrosoft.com" by some services
// and as "name@domain" by others, so both forms are brought to "name@domain" (matched without regard to case).
// Applications are keyed by service principal id, never by display name, which is not unique; an event with
// neither a user nor a service principal id is only grouped with itself.
| extend ActorKey = case(
isnotempty(ActorUpn), tolower(replace_regex(ActorUpn, @"(?i)^(.+)_([^_#]+)#EXT#@.*$", @"\1@\2")),
isnotempty(ActorAppId), strcat("app:", ActorAppId),
strcat("event:", Id))
| extend Hour = bin(TimeGenerated, 1h);
let ChangesPerActorHour = TenantPolicyChanges
| summarize
PolicyChangesInHour = countif(OperationName !~ "Set device registration policies"),
PolicyOperationsInHour = make_set(OperationName)
by ActorKey, Hour;
TenantPolicyChanges
| where OperationName =~ "Set device registration policies"
| join kind=leftouter ChangesPerActorHour on ActorKey, Hour
| extend OtherPolicyOperationsInHour = set_difference(PolicyOperationsInHour, dynamic(["Set device registration policies"]))
| extend ActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| extend AccountName = tostring(split(ActorUpn, "@")[0])
| extend AccountUPNSuffix = tostring(split(ActorUpn, "@")[1])
| project
TimeGenerated,
OperationName,
Actor,
ActorUpn,
AccountName,
AccountUPNSuffix,
ActorIp,
PolicyChangesInHour,
OtherPolicyOperationsInHour,
CorrelationId
| sort by array_length(OtherPolicyOperationsInHour) desc, TimeGenerated desc