The Azure Audit Gap Nobody Talks About: Why Your 90-Day Logs Won't Survive a 7-Year Audit
2025-10-26 ยท ~30 min read
Most Azure environments are silently non-compliant with regulatory audit requirements. Here's the three-part problem everyone faces, how app registrations create blind spots, and the architecture to fix it before your next audit.
Update (October 27, 2025): Ready to fix your Activity Log retention? I've published a complete step-by-step implementation guide: SOC 2 Audit Prep: Activity Log Retention Setup (Step-by-Step). Every click, every command, every verification - the grill assembly manual version.
The Question That Starts the Panic
"Who created this storage account three months ago?"
You're sitting in a meeting. Security is investigating potential data exposure. Leadership wants answers. You confidently open Azure Portal, navigate to Activity Logs, and...
The data is gone.
Azure Activity Logs keep 90 days by default. Your regulatory requirements demand 5-7 years. And this moment - when you realize you can't answer basic audit questions - is how most organizations discover they're silently non-compliant.
The Three-Part Audit Problem
Let me break down what's actually happening in most Azure environments:
Part 1: Do You HAVE the Logs?
This is about retention configuration. Out of the box, Azure gives you:
- Activity Logs (ARM resources): 90 days in the portal
- Azure AD Audit Logs: 30 days in the portal
- That's it.
Meanwhile, regulatory frameworks demand:
- SOX: 7 years
- HIPAA: 6 years
- GDPR: Varies by data category
- PCI-DSS: 1 year online, 3 years archive
- State regulations: Often 5-7 years
The gap: You're configured for 90 days, but legally required to keep 5-7 years. Most organizations don't discover this until they're asked to produce logs they don't have.
Part 2: Do You KNOW WHERE the Logs Are?
Even if you've configured log retention properly, there's a documentation problem. Your logs might be scattered across:
- Activity Logs in the portal (90 days)
- Log Analytics Workspace (configured retention, maybe 30-730 days)
- Storage Account archive (hopefully years)
- Azure Monitor diagnostic settings (per-resource configuration)
- Azure AD Audit Logs (separate system entirely)
When the auditor asks "show me who accessed this storage account in Q2 2023," can you immediately answer:
1. WHERE that data lives?
2. HOW to query it?
3. WHO has access to retrieve it?
Most teams spend 4 hours scrambling to find WHERE the data is before they can even start answering the actual question.
Part 3: Can You QUERY the Logs?
You've exported logs to a Log Analytics Workspace. Great! Now what?
You need to:
- Know KQL (Kusto Query Language)
- Understand the schema differences between Activity Logs and Azure AD logs
- Build custom queries for common audit scenarios
- Document those queries so you're not rebuilding them every audit cycle
The reality: Most organizations have configured retention (Part 1), but lack the query skills (Part 3) to actually extract answers when needed.
The Two Separate Audit Systems
Here's what trips up most Azure admins: You're not dealing with one audit system. You're dealing with two.
System 1: Azure Activity Logs (ARM Resources)
This covers your ARM resources:
- Virtual machines
- Storage accounts
- Virtual networks
- Key vaults
- Managed disks
- Resource groups
Where it lives: Azure Resource Manager (ARM) / Azure Monitor
Query tool: Resource Graph (for resources) + Log Analytics (for activity)
Retention default: 90 days in portal
System 2: Azure AD Audit Logs (Identity Resources)
This covers your identity layer:
- App registrations
- Service principals
- Enterprise applications
- User sign-ins
- Admin consent grants
- Role assignments
Where it lives: Azure Active Directory / Microsoft Entra ID
Query tool: Microsoft Graph API or Log Analytics (if exported)
Retention default: 30 days in portal
The problem: Most people only monitor ONE of these systems and completely miss the other.
The App Registration Blind Spot
Let me tell you about the alert that drives every Azure admin crazy:
Tenable Scanner Alert: "Unused app registration detected with client secret expiring in 30 days"
You get 50 of these every month. And every single one triggers the same questions:
- Who created this app registration?
- What does it do?
- Is it still being used?
- Who owns it?
Here's why these alerts are so painful:
App Registrations Can't Be Tagged
App registrations are NOT ARM resources. They're Azure AD objects.
That means:
- โ No tags (can't apply Cost Center, Owner, Environment tags)
- โ No Resource Graph support (can't query them with KQL in Resource Graph)
- โ Different audit system (Azure AD logs, not Activity Logs)
- โ Different API (Microsoft Graph, not Azure Resource Manager)
The Ownership Problem
When you create a VM, you can tag it: Owner: john.doe@company.com
When you create an app registration, you get:
- A GUID
- A display name (if they bothered to make it descriptive)
- Maybe a description (if you're lucky)
- An "Owners" field... that's often empty
Three months later, when security asks "who created this?", you're digging through Azure AD audit logs hoping the data is still there.
The Real-World Scenario
Security Team Email: "We found an app registration 'MyApp-Test-042' with a client secret expiring in 2 days. Is this still needed?"
Your investigation process:
1. Search Azure AD Audit Logs (hoping it's within 30 days)
2. Find the creation event (maybe)
3. Look up the user who created it (probably someone who left 6 months ago)
4. Search Slack/Teams for any mention of 'MyApp-Test-042' (desperate times)
5. Ask around until someone recognizes it (maybe)
6. Make a judgment call (coin flip)
This happens every single day in large Azure environments.
The Day-to-Day Pain Points
Let me show you the questions you get constantly and why they're so painful to answer:
Question 1: "Who created this resource?"
Scenario: A storage account appears in a production resource group. Nobody recognizes it. Leadership wants to know who created it and why.
The pain:
- Check Activity Logs โ Only goes back 90 days, creation was 4 months ago
- Check Resource Tags โ No Owner tag applied
- Check Resource creation logs โ Not exported to Log Analytics
- Answer: "I don't know. The logs are gone."
Question 2: "Who accessed this storage account?"
Scenario: Compliance asks for a list of everyone who accessed a specific storage account during Q2 2024 for audit documentation.
The pain:
- Activity Logs show ARM operations (create, delete, update) but not data plane access
- Storage Analytics logs are separate and may not be enabled
- Diagnostic settings might not be configured
- Answer: "Let me get back to you... tomorrow... maybe."
Question 3: "What did John Doe create before he left?"
Scenario: Employee departed 6 months ago. ITSM ticket: Remove all resources created by john.doe@company.com
The pain:
- Activity Logs only go back 90 days
- Resource Graph shows current owners (tags) but not who created resources
- No historical audit trail unless exported
- Answer: "I can show you resources he currently owns, but not everything he created."
Question 4: "Who granted admin consent to this app registration?"
Scenario: Security scan finds an app with excessive permissions. Need to track down who approved it and why.
The pain:
- App registrations are in Azure AD, not ARM
- Requires Azure AD Audit Logs (30-day default retention)
- If it happened 31 days ago, the data is gone
- Answer: "The audit log doesn't go back that far."
Question 5: "Is this app registration still being used?"
Scenario: 50 app registrations flagged by Tenable. Security wants to delete unused ones.
The pain:
- No built-in "last used" metric for app registrations
- Sign-in logs might show service principal activity
- No correlation between client secret and actual usage
- Answer: "We'd have to monitor sign-in logs for 30-90 days to be sure."
The pattern: You spend 4 hours scrambling to find WHERE the data is, then realize it doesn't exist or isn't accessible.
The Solution Architecture
Here's how to fix this before your next audit. This is not theoretical - this is what I've implemented in production handling SOX compliance.
Step 1: Configure Log Export
You need to export logs from their 30/90-day defaults to long-term storage.
Export Activity Logs to Log Analytics
# Create or identify your Log Analytics Workspace
$workspaceName = "audit-logs-workspace"
$resourceGroup = "governance-rg"
$location = "eastus"
# Create workspace if it doesn't exist
az monitor log-analytics workspace create `
--resource-group $resourceGroup `
--workspace-name $workspaceName `
--location $location `
--retention-time 730 # 2 years retention in LAW
Configure Diagnostic Settings for Activity Logs
# Export subscription-level Activity Logs to Log Analytics
$subscriptionId = (az account show --query id -o tsv)
$workspaceId = (az monitor log-analytics workspace show `
--resource-group $resourceGroup `
--workspace-name $workspaceName `
--query id -o tsv)
az monitor diagnostic-settings create `
--name "export-activity-logs" `
--resource "/subscriptions/$subscriptionId" `
--workspace $workspaceId `
--logs '[
{
"category": "Administrative",
"enabled": true
},
{
"category": "Security",
"enabled": true
},
{
"category": "ServiceHealth",
"enabled": true
},
{
"category": "Alert",
"enabled": true
},
{
"category": "Recommendation",
"enabled": true
},
{
"category": "Policy",
"enabled": true
},
{
"category": "Autoscale",
"enabled": true
},
{
"category": "ResourceHealth",
"enabled": true
}
]'
Export Azure AD Audit Logs
This requires Azure AD Premium P1 or P2 licensing.
# Configure Azure AD diagnostic settings via Portal or API
# Navigate to: Azure AD > Diagnostic settings > Add diagnostic setting
# Select:
# - AuditLogs (who did what in Azure AD)
# - SignInLogs (authentication events)
# - NonInteractiveUserSignInLogs (service principal activity)
# - ServicePrincipalSignInLogs (app registration usage)
# - ManagedIdentitySignInLogs (managed identity activity)
# Destination: Same Log Analytics Workspace
PowerShell equivalent:
# Connect to Azure AD
Connect-AzureAD
# Get the Log Analytics Workspace resource ID
$workspaceResourceId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.OperationalInsights/workspaces/$workspaceName"
# Configure diagnostic settings for Azure AD
# Note: This requires Microsoft.Graph PowerShell module
Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes "AuditLog.Read.All", "Directory.Read.All"
# Create diagnostic setting
$params = @{
Name = "export-azuread-logs"
WorkspaceId = $workspaceResourceId
Logs = @(
@{ Category = "AuditLogs"; Enabled = $true }
@{ Category = "SignInLogs"; Enabled = $true }
@{ Category = "NonInteractiveUserSignInLogs"; Enabled = $true }
@{ Category = "ServicePrincipalSignInLogs"; Enabled = $true }
@{ Category = "ManagedIdentitySignInLogs"; Enabled = $true }
)
}
# Apply at tenant level
New-MgBetaDiagnosticSetting -BodyParameter $params
Export to Storage Account for Long-Term Archive
Log Analytics is great for querying (30-730 days), but expensive for 7-year retention. Use Storage Accounts for cheap archive.
# Create storage account for audit archive
$storageAccountName = "auditarchive$(Get-Random)"
az storage account create `
--name $storageAccountName `
--resource-group $resourceGroup `
--location $location `
--sku Standard_LRS `
--kind StorageV2 `
--access-tier Cool # Cool tier for infrequent access
# Get storage account resource ID
$storageId = (az storage account show `
--name $storageAccountName `
--resource-group $resourceGroup `
--query id -o tsv)
# Update diagnostic settings to include storage
az monitor diagnostic-settings create `
--name "export-activity-logs-storage" `
--resource "/subscriptions/$subscriptionId" `
--storage-account $storageId `
--logs '[
{
"category": "Administrative",
"enabled": true,
"retentionPolicy": {
"enabled": true,
"days": 2555
}
}
]'
Cost breakdown:
- Log Analytics: ~$2.50 per GB ingested + $0.12 per GB retention per month
- Storage Account (Cool tier): ~$0.01 per GB per month
- For 7-year retention: Storage is 95% cheaper than Log Analytics
Step 2: Build Your Query Library
You need pre-built KQL queries for common audit scenarios. Don't rebuild these every time.
Query 1: Who Created This Resource?
// Find who created a specific resource by name
AzureActivity
| where OperationNameValue == "MICROSOFT.RESOURCES/DEPLOYMENTS/WRITE"
or OperationNameValue contains "CREATE"
or OperationNameValue contains "WRITE"
| where ResourceId contains "your-resource-name" // Replace with actual resource name
| where ActivityStatusValue == "Success"
| project
TimeGenerated,
Caller, // Who did it
ResourceId, // What was created
OperationNameValue, // What action
ActivityStatusValue, // Success/Failure
Claims // Full identity context
| order by TimeGenerated desc
| take 100
Query 2: All Actions by a Specific User
// Find everything a user did (useful for offboarding investigations)
AzureActivity
| where Caller contains "john.doe@company.com" // Replace with actual user
| where TimeGenerated >= ago(90d) // Adjust time range as needed
| project
TimeGenerated,
ResourceGroup,
ResourceId,
OperationNameValue,
ActivityStatusValue
| order by TimeGenerated desc
Query 3: Resource Deletions
// Track all resource deletions (critical for audit trails)
AzureActivity
| where OperationNameValue contains "DELETE"
| where ActivityStatusValue == "Success"
| where TimeGenerated >= ago(90d)
| project
TimeGenerated,
Caller,
ResourceId,
ResourceGroup,
OperationNameValue
| order by TimeGenerated desc
Query 4: Who Granted Admin Consent to an App?
// Track admin consent grants (Azure AD Audit Logs)
AuditLogs
| where OperationName == "Consent to application"
| where Result == "success"
| extend
AppName = tostring(TargetResources[0].displayName),
ConsentedBy = tostring(InitiatedBy.user.userPrincipalName),
Permissions = tostring(TargetResources[0].modifiedProperties)
| project
TimeGenerated,
ConsentedBy,
AppName,
Permissions,
CorrelationId
| order by TimeGenerated desc
Query 5: App Registration Creation and Ownership
// Track app registration creation (Azure AD Audit Logs)
AuditLogs
| where OperationName == "Add application"
| where Result == "success"
| extend
AppName = tostring(TargetResources[0].displayName),
CreatedBy = tostring(InitiatedBy.user.userPrincipalName),
AppId = tostring(TargetResources[0].id)
| project
TimeGenerated,
CreatedBy,
AppName,
AppId,
CorrelationId
| order by TimeGenerated desc
Query 6: Service Principal Sign-In Activity
// Check if an app registration is actually being used
AADServicePrincipalSignInLogs
| where AppId == "your-app-id-guid" // Replace with actual App ID
| where TimeGenerated >= ago(90d)
| summarize
SignInCount = count(),
LastSignIn = max(TimeGenerated),
UniqueResources = dcount(ResourceId)
by AppDisplayName, AppId
| project
AppDisplayName,
AppId,
SignInCount,
LastSignIn,
UniqueResources
Query 7: Storage Account Access (Data Plane)
// Track who accessed storage account data (requires diagnostic settings on storage)
StorageBlobLogs
| where AccountName == "yourstorageaccount" // Replace with actual storage account
| where TimeGenerated >= ago(30d)
| where StatusCode == 200 // Successful operations
| extend CallerIdentity = tostring(parse_json(Identity).claims.upn)
| summarize
AccessCount = count(),
LastAccess = max(TimeGenerated)
by CallerIdentity, OperationName
| order by AccessCount desc
Step 3: Document Your Logging Architecture
Create a one-page reference document that answers:
Audit Log Documentation Template:
# Azure Audit Logging Architecture
## Log Analytics Workspace
- **Name:** audit-logs-workspace
- **Resource Group:** governance-rg
- **Retention:** 730 days (2 years)
- **Access:** Security team, Compliance team, IT leadership
## Storage Account Archive
- **Name:** auditarchiveXXXXXX
- **Resource Group:** governance-rg
- **Retention:** 2,555 days (7 years)
- **Access:** Compliance team only (RBAC locked down)
## What's Collected
### Activity Logs (ARM Resources)
- All subscriptions configured with diagnostic settings
- Categories: Administrative, Security, Policy, Alerts
- Retention: 730 days in LAW, 7 years in Storage
### Azure AD Audit Logs
- AuditLogs (who did what in Azure AD)
- SignInLogs (user authentication)
- ServicePrincipalSignInLogs (app registration usage)
- Retention: 730 days in LAW, 7 years in Storage
## How to Query
### Portal Access
1. Navigate to: Log Analytics Workspace > Logs
2. Select "audit-logs-workspace"
3. Use pre-built queries from: [Link to internal wiki/SharePoint]
### Common Queries
- Who created this resource? โ Query #1
- What did user X do? โ Query #2
- Resource deletions โ Query #3
- App consent grants โ Query #4
## Quarterly Audit Process
1. Compliance team requests specific queries
2. IT runs queries from template library
3. Export results to Excel/CSV
4. Compliance validates and archives
## Contacts
- **IT Governance:** your-team@company.com
- **Compliance:** compliance@company.com
- **Azure Admin:** azure-admins@company.com
Save this document where your team can find it. SharePoint, Confluence, internal wiki - wherever you keep runbooks.
Step 4: External Tracking for App Registrations
Since app registrations can't be tagged, you need external tracking.
Option 1: Spreadsheet (Quick Start)
Create a simple tracking sheet:
| App Name | App ID | Created By | Created Date | Purpose | Owner | Status | Last Review |
|---|---|---|---|---|---|---|---|
| MyApp-Prod | guid | john.doe | 2024-03-15 | Production API | Jane Smith | Active | 2024-10-01 |
| TestApp-042 | guid | old.user | 2023-08-22 | Testing | Unknown | Review | 2024-10-01 |
Update process:
- New app registration? Add row
- Quarterly review? Update Status and Last Review
- Tenable alert? Check this sheet first
Option 2: CMDB Integration (Enterprise)
If you have a CMDB (ServiceNow, Azure DevOps, Jira), create:
- CI Type: "Azure App Registration"
- Automated sync via Microsoft Graph API
- Link to resource owner records
Option 3: Automation Script
# Export all app registrations to CSV for tracking
Connect-AzureAD
$apps = Get-AzureADApplication -All $true
$report = @()
foreach ($app in $apps) {
$owners = Get-AzureADApplicationOwner -ObjectId $app.ObjectId
$report += [PSCustomObject]@{
DisplayName = $app.DisplayName
AppId = $app.AppId
ObjectId = $app.ObjectId
CreatedDateTime = $app.CreatedDateTime
Owners = ($owners.UserPrincipalName -join "; ")
SignInAudience = $app.SignInAudience
PublisherDomain = $app.PublisherDomain
}
}
$report | Export-Csv -Path "app-registrations-inventory.csv" -NoTypeInformation
Write-Host "Exported $($report.Count) app registrations to CSV"
Run this monthly. Compare to last month's export. Flag new apps without owners.
Step 5: Test Quarterly (Audit Drills)
Don't wait for a real audit to discover your logging is broken. Run drills.
Quarterly Audit Drill Checklist:
# Q4 2025 Audit Drill
## Test Date: October 26, 2025
## Tester: [Your Name]
### Test 1: Log Retention Verification
- [ ] Verify Activity Logs retention: Query logs from 90+ days ago
- [ ] Verify Azure AD logs retention: Query AuditLogs from 30+ days ago
- [ ] Verify Storage Account archive: Check oldest logs in storage
- [ ] **Result:** Pass/Fail + Notes
### Test 2: Query Execution
- [ ] Run Query #1: Who created resource X? (pick random VM)
- [ ] Run Query #2: What did user Y do? (pick random user)
- [ ] Run Query #4: Recent admin consent grants
- [ ] **Result:** Pass/Fail + Notes
### Test 3: Documentation Access
- [ ] Locate audit documentation
- [ ] Verify links to Log Analytics Workspace
- [ ] Verify query library is up to date
- [ ] **Result:** Pass/Fail + Notes
### Test 4: App Registration Tracking
- [ ] Open app registration inventory
- [ ] Verify last update date
- [ ] Spot check 5 random apps: do owners match Azure AD?
- [ ] **Result:** Pass/Fail + Notes
### Test 5: Access Verification
- [ ] Confirm compliance team has LAW access
- [ ] Confirm storage account access is restricted
- [ ] Verify audit log export is still running
- [ ] **Result:** Pass/Fail + Notes
## Issues Found:
[List any issues discovered during drill]
## Action Items:
- [ ] Fix issue #1
- [ ] Update documentation
- [ ] Retest in 30 days
## Next Drill: January 26, 2026
Why quarterly?
- Catches configuration drift before audit season
- Validates your team knows where logs are
- Proves your retention is actually working
What This Looks Like in Production
Let me show you what this setup looks like at scale.
Enterprise Bank Example (SOX Compliance)
Environment:
- 40+ Azure subscriptions
- Multiple Active Directory domains (consolidation project)
- 300+ applications
- SOX audit requirement: 7 years
What was implemented:
- Centralized Log Analytics Workspace
- One workspace for all subscriptions
- 730-day retention (balancing cost vs query access)
-
Configured via Azure Policy (automatic for new subscriptions)
-
Storage Account Archive
- Separate storage account per compliance zone
- 7-year retention
- Immutable storage (WORM) for tamper-proof logs
-
Lifecycle management (auto-archive to Cool tier after 90 days)
-
Query Library
- 25 pre-built queries for common audit scenarios
- Stored in internal wiki with examples
-
Updated quarterly based on actual audit requests
-
App Registration Tracking
- CSV export automated monthly via Azure Automation
- Stored in SharePoint with version history
- Quarterly review with security team
-
Tenable alerts cross-referenced against this inventory
-
Azure Policy Enforcement
- Diagnostic settings automatically applied to new subscriptions
- Activity Log export mandatory via policy
- Storage account lifecycle management enforced
Cost:
- Log Analytics: ~$800/month (all subscriptions)
- Storage Account: ~$50/month (7 years of logs)
- Total: ~$850/month for complete audit compliance
ROI:
- First SOX audit: 4 hours to produce all requested logs (vs. weeks previously)
- Security incidents: Immediate answers to "who did what when"
- Offboarding: Complete audit trail of departed employee actions
Small Environment Example (Startup, 1-3 Subscriptions)
Don't overthink it if you're small. Start simple:
Minimum Viable Setup:
- One Log Analytics Workspace
- 90-day retention (free tier: 5GB/month)
- Activity Logs exported from all subscriptions
-
Azure AD logs exported (requires Azure AD Premium P1)
-
One Storage Account
- Standard LRS, Cool tier
-
1-year retention (extend as budget allows)
-
Five Core Queries
- Who created this?
- What did this user do?
- Resource deletions
- Admin consent grants
-
App registration creation
-
Simple App Tracking
- Excel spreadsheet
- Updated when Tenable alerts fire
- Quarterly manual review
Cost:
- Log Analytics: $0-50/month (depending on log volume)
- Storage Account: $5-10/month
- Total: ~$60/month or less
When to upgrade:
- Growing past 5 subscriptions โ Enforce with Azure Policy
- Getting audited โ Extend retention to match requirements
- Multiple teams โ Add RBAC and centralized governance
The Business Case for Leadership
If you need budget approval, here's your talking points:
The Risk (What Happens If We Don't)
Scenario 1: Failed Audit
- Auditor requests logs from 18 months ago
- You can't produce them (90-day default)
- Result: Audit finding, potential compliance penalty, reputational damage
Scenario 2: Security Incident
- Breach detected, forensics team needs 6 months of logs
- Activity Logs only go back 90 days
- Result: Can't determine scope of breach, can't identify entry point, liability exposure
Scenario 3: Offboarding Investigation
- Employee leaves, manager wants to know what they had access to
- No historical audit trail
- Result: 40 hours of manual investigation, incomplete results
The Cost (What It Actually Takes)
Setup:
- Engineering time: 8-16 hours (one-time)
- Monthly cost: $50-1,000 depending on scale
- Quarterly testing: 2 hours per quarter
ROI:
- First audit: 80% time savings (hours vs. weeks)
- Security incidents: Immediate forensics capability
- Offboarding: 90% time savings (2 hours vs. 40 hours)
Budget ask:
- Small environment: $1,000/year
- Medium environment: $10,000/year
- Enterprise environment: $50,000/year
Comparison:
- One failed audit finding: $50,000-500,000 in remediation + penalties
- One security breach investigation: $100,000+ in consulting fees
- This solution: $1,000-50,000/year
Approval probability: Very high.
Common Mistakes to Avoid
I've seen these mistakes repeatedly. Don't repeat them.
Mistake 1: "We'll Set It Up When We Need It"
The problem: By the time you need audit logs, it's too late. You can't retroactively create historical data.
The fix: Set up retention BEFORE your next audit cycle. If you're audited in Q1 2026, configure this by end of Q4 2025.
Mistake 2: "We Only Need Activity Logs"
The problem: Activity Logs cover ARM resources (VMs, storage). You're missing the entire identity layer (app registrations, consent grants, role assignments).
The fix: Export BOTH Activity Logs and Azure AD Audit Logs. They're separate systems covering different types of actions.
Mistake 3: "Log Analytics Retention = Archive"
The problem: Log Analytics retention (30-730 days) is great for querying, but expensive for 5-7 year archive requirements.
The fix: Use Log Analytics for recent logs (90-730 days), then archive to Storage Account for long-term retention. It's 95% cheaper.
Mistake 4: "We'll Query the Storage Account When Needed"
The problem: Querying logs directly from Storage Account is painful. No KQL support, just raw JSON files.
The fix: Log Analytics for query access (recent logs), Storage Account for compliance archive (old logs you rarely need).
Mistake 5: "Tagging Solves Everything"
The problem: Tags are great for ARM resources, but app registrations can't be tagged. You need separate tracking.
The fix: External tracking system (spreadsheet, CMDB, automated export) for app registrations. Update it monthly.
Mistake 6: "Set It and Forget It"
The problem: Diagnostic settings get deleted. Log Analytics fills up. Storage accounts get misconfigured. No one notices until the audit.
The fix: Quarterly audit drills. Test your queries. Verify your retention. Update your documentation.
The 30-Minute Quick Start
If you're reading this during an audit crisis, here's your emergency process:
Hour 1: Verify What You Have
// Check Activity Log retention
AzureActivity
| summarize
OldestLog = min(TimeGenerated),
NewestLog = max(TimeGenerated),
DaysCovered = datetime_diff('day', max(TimeGenerated), min(TimeGenerated))
| project OldestLog, NewestLog, DaysCovered
// Check Azure AD log retention
AuditLogs
| summarize
OldestLog = min(TimeGenerated),
NewestLog = max(TimeGenerated),
DaysCovered = datetime_diff('day', max(TimeGenerated), min(TimeGenerated))
| project OldestLog, NewestLog, DaysCovered
If DaysCovered < 90: You're running on defaults. You need to configure exports NOW.
Hour 2: Configure Exports (Emergency Mode)
# Quick setup: Export logs to new Log Analytics Workspace
az monitor log-analytics workspace create \
--resource-group emergency-audit-rg \
--workspace-name emergency-audit-logs \
--location eastus \
--retention-time 730
# Get workspace ID
workspaceId=$(az monitor log-analytics workspace show \
--resource-group emergency-audit-rg \
--workspace-name emergency-audit-logs \
--query id -o tsv)
# Configure diagnostic settings for current subscription
subscriptionId=$(az account show --query id -o tsv)
az monitor diagnostic-settings create \
--name emergency-export \
--resource "/subscriptions/$subscriptionId" \
--workspace $workspaceId \
--logs '[{"category":"Administrative","enabled":true}]'
Important: This only captures NEW logs going forward. You can't recover old logs that already expired.
Hour 3: Document What You've Done
Create a one-page summary:
- What logs are being captured NOW
- Where they're going (workspace name, resource group)
- How long we'll keep them (retention setting)
- What logs we DON'T have (date ranges we lost)
Be honest in your audit: "We discovered a gap in our retention configuration. We've corrected it as of [date]. Historical logs prior to [date] are not available."
The Takeaway
Most Azure environments are silently non-compliant with audit requirements because nobody talks about the 90-day default.
The three-part problem:
1. Do you HAVE the logs? (retention configuration)
2. Do you KNOW WHERE they are? (documentation gap)
3. Can you QUERY them? (skills gap)
The solution:
1. Export Activity Logs + Azure AD logs to Log Analytics + Storage
2. Build a query library for common audit scenarios
3. Document your logging architecture
4. Track app registrations externally (they can't be tagged)
5. Test quarterly with audit drills
The cost: $50-1,000/month depending on scale.
The ROI: You'll know this was worth it the moment someone asks "who created this resource 6 months ago?" and you actually have an answer.
What's your audit logging setup look like? Are you running on the 90-day default, or have you built something more robust? Reply in the comments or shoot me an email - I'd love to hear how other organizations are handling this.
And if you found this useful, share it with your compliance team. They'll thank you during the next audit cycle.