Having recently completed a PCI-DSS Level 1 audit for my organization, I spent a considerable amount of time architecting a log retention and filtering strategy for Microsoft Entra ID (formerly Azure AD) audit logs. The verbosity is indeed a significant challenge, not just for storage costs but for effective query performance and signal-to-noise ratio during an actual compliance review. The "bare minimum" is inherently tied to your specific regulatory or contractual obligations (PCI-DSS, HIPAA, SOC 2, ISO 27001, internal governance), but there are common, foundational categories you can derive from most frameworks.
Based on a cross-reference of common control requirements, I've distilled the following event categories as the critical core. These should be considered the absolute baseline, assuming you have separate, more detailed logs for application-specific auditing.
**Core Categories & Key `activityDisplayName` Values to Retain:**
* **User Account Lifecycle:**
* `Add user`
* `Delete user`
* `Update user`
* `Change user password`
* `Update user credentials`
* **Privileged Access & Role Management:**
* `Add member to role`
* `Remove member from role`
* Any event where `targetResources.any(tr => tr.resourceType eq 'Role')`
* **Authentication & Security:**
* `Sign-in error` (Specifically for anomaly detection)
* `Risky sign-in`
* `Risky user detected`
* `Risky state change` (e.g., user confirmed safe, dismissed risk)
* **Application & Service Principal Management:**
* `Add service principal`
* `Update application`
* `Add owner to application`
* `Add delegated permission grant`
* **Group Membership Changes (Crucial for data access):**
* `Add member to group`
* `Remove member from group`
* `Update group`
Operationally, you should not rely on the Azure portal's filtering for your compliance archive. You must implement a pipeline to export these logs to a durable, immutable storage. The following Kusto Query Language (KQL) snippet represents a filter I apply in a Logic App *before* ingesting logs into our cold storage (and a separate, filtered set into our hot analytics platform). This drastically reduces volume.
```kql
AuditLogs
| where TimeGenerated >= {your_time_range}
| where Category in~ ("UserManagement", "GroupManagement", "ApplicationManagement", "RoleManagement", "Authentication")
| where OperationName in~ (
"Add user", "Delete user", "Update user", "Change user password",
"Add member to role", "Remove member from role",
"Add member to group", "Remove member from group",
"Add service principal", "Update application",
"Sign-in error", "Risky sign-in"
)
// Additionally, capture any high-risk operations regardless of category
| or (ResultType !in ("success") and ResultType startswith "5") // Client-side errors may be benign, server errors (5xx) are critical
| or (tostring(InitiatedBy.user).contains("admin") or tostring(InitiatedBy.app).contains("servicePrincipal")) // Example: flag all admin/service principal actions
```
A final, critical point: your retention policy must account for **log integrity**. Simply exporting to a blob store is insufficient. You must implement Write-Once-Read-Many (WORM) protection, hash-chain verification, or use a dedicated compliance platform that provides these guarantees. The filtered log set must be tamper-evident. The volume reduction from filtering enables you to afford more robust integrity controls on the remaining, crucial data.
I'm interested to hear how others have mapped specific regulatory controls to these raw event types, particularly for GDPR data subject requests or FedRAMP continuous monitoring requirements.
-- elliot
Data first, decisions later.