Skip to content

Commit e704077

Browse files
committed
fix(auditlogs): treat token denial as disable
Detect AADSTS7000229 token-acquisition failures in audit search creation and classify them as `AccessDenied` instead of generic transient errors. Update the webhook flow to handle `AccessDenied` like `AuditingDisabled`: cache the tenant in `AuditLogDisabledTenants` for 24 hours, stop further processing in the cycle, and defer remaining windows with a consistent bail reason.
1 parent 206f9b1 commit e704077

2 files changed

Lines changed: 26 additions & 8 deletions

File tree

Modules/CIPPCore/Public/AuditLogs/New-CippAuditLogSearchV2.ps1

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function New-CippAuditLogSearchV2 {
1616
.PARAMETER RecordTypeFilters
1717
Record types to capture. Defaults to the four the V1 pipeline used.
1818
.OUTPUTS
19-
[pscustomobject]@{ Id; Status; Outcome; Message } Outcome in 'Created','AuditingDisabled','Transient'.
19+
[pscustomobject]@{ Id; Status; Outcome; Message } Outcome in 'Created','AuditingDisabled','AccessDenied','Transient'.
2020
.FUNCTIONALITY
2121
Internal
2222
#>
@@ -50,6 +50,15 @@ function New-CippAuditLogSearchV2 {
5050
$Raw = $_.Exception.Data['RawErrorBody']
5151
if (-not $Raw) { $Raw = $_.ErrorDetails.Message }
5252
if (-not $Raw) { $Raw = $_.Exception.Message }
53+
54+
# Token-acquisition failures that mean we cannot access the tenant at all (e.g.
55+
# AADSTS7000229 = CIPP's service principal is missing from the tenant). These arrive as a
56+
# plain "Could not get token: ..." string from Get-GraphToken, not JSON, and won't clear
57+
# on retry - the caller disables the tenant for 24h, same as AuditingDisabled.
58+
if ([string]$Raw -match 'AADSTS7000229') {
59+
return [pscustomobject]@{ Id = $null; Status = 'MissingServicePrincipal'; Outcome = 'AccessDenied'; Message = [string]$Raw; Throttled = $false }
60+
}
61+
5362
$Parsed = $null
5463
if ($Raw) { try { $Parsed = ([string]$Raw) | ConvertFrom-Json -ErrorAction Stop } catch {} }
5564

Modules/CIPPCore/Public/Webhooks/Push-AuditLogSearchCreationV2.ps1

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ function Push-AuditLogSearchCreationV2 {
1414
limit, so on a 429 we stop and defer the current window AND all remaining queued windows to
1515
the next cycle (no Attempts increment - a cap is not a failure, so it never dead-letters).
1616
Other transient errors (UnknownError, 5xx, gateway) retry the individual window with backoff
17-
and dead-letter at MaxAttempts. AuditingDisabled caches the tenant for 24h and stops.
17+
and dead-letter at MaxAttempts. AuditingDisabled and AccessDenied (token failure - e.g.
18+
AADSTS7000229 missing service principal) cache the tenant as disabled for 24h and stop.
1819
1920
Capping at 6/cycle keeps us well under the ~10 concurrent-search ceiling (they complete within
2021
the cycle and free slots). Oldest-first means gaps/backlog/reconciliation drain before the
@@ -77,15 +78,17 @@ function Push-AuditLogSearchCreationV2 {
7778
$Batch = @($Due | Select-Object -First $MaxPerCycle)
7879
}
7980

80-
# 3) Create searches (no auto-retry). On 429, defer current + remaining to next cycle.
81+
# 3) Create searches (no auto-retry). On 429 or a tenant-level disable, defer current +
82+
# remaining to next cycle.
8183
$Bail = $false
84+
$BailReason = ''
8285
foreach ($Row in $Batch) {
8386
if ($Bail) {
8487
# Deferred (not attempted): just set NextAttemptUtc, leave Attempts/State as Planned.
8588
Add-CIPPAzDataTableEntity @Ledger -Entity @{
8689
PartitionKey = $TenantFilter; RowKey = $Row.RowKey; State = 'Planned'
8790
NextAttemptUtc = (Get-CippAuditLogNextAttempt -Attempts 1)
88-
ThrottleCount = ([int]$Row.ThrottleCount + 1); LastError = 'Deferred: tenant search cap (429)'; LastErrorUtc = $Now
91+
ThrottleCount = ([int]$Row.ThrottleCount + 1); LastError = $BailReason; LastErrorUtc = $Now
8992
} -OperationType UpsertMerge
9093
continue
9194
}
@@ -105,23 +108,29 @@ function Push-AuditLogSearchCreationV2 {
105108
} elseif ($Result.Throttled) {
106109
# 429 = tenant cap full. Defer this window (no Attempts bump - a cap isn't a failure) and bail.
107110
$Bail = $true
111+
$BailReason = 'Deferred: tenant search cap (429)'
108112
Add-CIPPAzDataTableEntity @Ledger -Entity @{
109113
PartitionKey = $TenantFilter; RowKey = $Row.RowKey; State = 'Planned'
110114
NextAttemptUtc = (Get-CippAuditLogNextAttempt -Attempts 1)
111115
ThrottleCount = ([int]$Row.ThrottleCount + 1); LastError = 'Tenant search cap (429)'; LastErrorUtc = $Now
112116
} -OperationType UpsertMerge
113117
Write-Information "AuditLogV2: 429 for $TenantFilter - deferring this + remaining windows to next cycle"
114-
} elseif ($Result.Outcome -eq 'AuditingDisabled') {
118+
} elseif ($Result.Outcome -eq 'AuditingDisabled' -or $Result.Outcome -eq 'AccessDenied') {
119+
# AuditingDisabled = unified auditing off; AccessDenied = we can't get a token for the
120+
# tenant at all (e.g. missing service principal). Either way: disable for 24h and stop.
115121
$Bail = $true
122+
$BailReason = 'Deferred: tenant audit log collection disabled'
123+
$DisableStatus = if ($Result.Outcome -eq 'AccessDenied') { [string]$Result.Status } else { 'AuditingDisabledTenant' }
124+
$LedgerError = if ($Result.Message) { [string]$Result.Message } else { $DisableStatus }
116125
try {
117126
$AuditDisabledTable = Get-CIPPTable -TableName 'AuditLogDisabledTenants'
118127
Add-CIPPAzDataTableEntity @AuditDisabledTable -Entity @{
119128
PartitionKey = 'AuditDisabledTenant'; RowKey = [string]$TenantFilter; TenantFilter = [string]$TenantFilter
120-
Status = 'AuditingDisabledTenant'; ExpiresAtUnix = [int64]([datetimeoffset]::UtcNow.AddHours(24).ToUnixTimeSeconds())
129+
Status = $DisableStatus; ExpiresAtUnix = [int64]([datetimeoffset]::UtcNow.AddHours(24).ToUnixTimeSeconds())
121130
} -Force
122131
} catch {}
123-
Add-CIPPAzDataTableEntity @Ledger -Entity @{ PartitionKey = $TenantFilter; RowKey = $Row.RowKey; State = 'Skipped'; LastError = 'AuditingDisabledTenant'; LastErrorUtc = $Now } -OperationType UpsertMerge
124-
Write-Information "AuditLogV2: auditing disabled for $TenantFilter; skipping"
132+
Add-CIPPAzDataTableEntity @Ledger -Entity @{ PartitionKey = $TenantFilter; RowKey = $Row.RowKey; State = 'Skipped'; LastError = $LedgerError; LastErrorUtc = $Now } -OperationType UpsertMerge
133+
Write-Information "AuditLogV2: $DisableStatus for $TenantFilter; disabled for 24h"
125134
} else {
126135
# Other transient: retry this window next cycle; dead-letter at cap.
127136
$RetryTotal = [int]$Row.RetryCount + 1

0 commit comments

Comments
 (0)