LoFP LoFP / administrators may delete resource groups during decommissioning, lab cleanup, or organizational restructuring. verify the actor, change ticket, and whether the deleted group was non-production before treating the event as malicious.

Techniques

Sample rules

Azure Resource Group Deleted by Unusual Principal

Description

Identifies Azure resource group deletions by a principal that has not performed this operation in the prior 7 days. Deletion of a resource group is permanent and removes all resources in the group. This rule uses an ES|QL first-seen (new terms) approach keyed on the acting principal so routine infrastructure-as-code teardown is suppressed after the first occurrence, while still surfacing first-time or unusual identities performing the delete.

Detection logic

FROM logs-azure.activitylogs-*, filebeat-*
| WHERE data_stream.dataset == "azure.activitylogs"
    AND event.action == "MICROSOFT.RESOURCES/SUBSCRIPTIONS/RESOURCEGROUPS/DELETE"
    AND event.outcome IN ("success", "Success")
    AND azure.activitylogs.identity.authorization.evidence.principal_id IS NOT NULL
| STATS Esql.first_time_seen = MIN(@timestamp),
        Esql.last_time_seen = MAX(@timestamp),
        Esql.event_count = COUNT(*),
        Esql.resource_group_values = VALUES(azure.resource.group),
        Esql.resource_name_values = VALUES(azure.resource.name),
        Esql.resource_id_values = VALUES(azure.resource.id),
        Esql.principal_type_values = VALUES(azure.activitylogs.identity.authorization.evidence.principal_type),
        Esql.initiated_by_user_values = VALUES(azure.activitylogs.identity.claims_initiated_by_user.name),
        Esql.appid_values = VALUES(azure.activitylogs.identity.claims.appid),
        Esql.session_id_values = VALUES(azure.activitylogs.identity.claims.sid),
        Esql.unique_token_id_values = VALUES(azure.activitylogs.identity.claims.uti),
        Esql.correlation_id_values = VALUES(azure.correlation_id),
        Esql.role_values = VALUES(azure.activitylogs.identity.authorization.evidence.role),
        Esql.role_assignment_id_values = VALUES(azure.activitylogs.identity.authorization.evidence.role_assignment_id),
        Esql.source_ip_values = VALUES(source.ip),
        Esql.source_country_values = VALUES(source.geo.country_name),
        Esql.source_as_number_values = VALUES(source.`as`.number),
        Esql.subscription_id_values = VALUES(azure.subscription_id)
        BY azure.activitylogs.identity.authorization.evidence.principal_id
// new terms emulation: fire when this principal's first_seen falls in the current interval
// (schedule + ingest-lag buffer), i.e. it was absent from the rest of the 7-day window
| EVAL Esql.recent_minutes = DATE_DIFF("minute", Esql.first_time_seen, NOW())
| WHERE Esql.recent_minutes <= 10
| KEEP azure.*, Esql.*