Private/Entra/Checks/Invoke-M365AuditChecks.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Invoke-M365AuditChecks { [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$AuditData ) $checkDefs = Get-AuditCategoryDefinitions -Category 'M365AuditChecks' $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($check in $checkDefs.checks) { $funcName = "Test-Infiltration$($check.id -replace '-', '')" if (Get-Command $funcName -ErrorAction SilentlyContinue) { try { $finding = & $funcName -AuditData $AuditData -CheckDefinition $check if ($finding) { $findings.Add($finding) } } catch { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' ` -CurrentValue "Check failed: $_")) } } else { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' ` -CurrentValue 'Check not yet implemented')) } } return @($findings) } # ── M365AUDIT-001: Unified Audit Log Enabled ───────────────────────── function Test-InfiltrationM365AUDIT001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $audit = $AuditData.M365Services.AuditConfig if (-not $audit) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Audit configuration data not available (EXO module not connected)' } $ualEnabled = $audit.UnifiedAuditLogIngestionEnabled if ($null -eq $ualEnabled) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'UnifiedAuditLogIngestionEnabled property not available in audit configuration' } $status = if ($ualEnabled -eq $true) { 'PASS' } else { 'FAIL' } $description = if ($ualEnabled -eq $true) { 'Unified Audit Log ingestion is enabled' } else { 'Unified Audit Log ingestion is DISABLED — critical security logging is not active' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $description ` -Details @{ UnifiedAuditLogIngestionEnabled = $ualEnabled } } # ── M365AUDIT-002: Audit Log Retention ─────────────────────────────── function Test-InfiltrationM365AUDIT002 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $audit = $AuditData.M365Services.AuditConfig if (-not $audit) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Audit configuration data not available (EXO module not connected)' } $retentionDays = $audit.AuditLogAgeLimit $adminAuditAgeLimit = $audit.AdminAuditLogAgeLimit # Parse retention if it is a timespan string (e.g., "90.00:00:00") $retentionNumeric = $null if ($retentionDays -is [int] -or $retentionDays -is [double]) { $retentionNumeric = [int]$retentionDays } elseif ($retentionDays -is [string] -and $retentionDays -match '^(\d+)') { $retentionNumeric = [int]$Matches[1] } elseif ($retentionDays -is [timespan]) { $retentionNumeric = [int]$retentionDays.TotalDays } if ($null -eq $retentionNumeric -and $null -eq $adminAuditAgeLimit) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Audit log retention settings not available — unable to determine retention period' } # Default M365 E3 retention is 180 days, E5 is 365 days $status = if ($null -eq $retentionNumeric) { 'WARN' } elseif ($retentionNumeric -ge 365) { 'PASS' } elseif ($retentionNumeric -ge 180) { 'WARN' } else { 'FAIL' } $description = if ($null -eq $retentionNumeric) { "Audit log retention: AdminAuditLogAgeLimit=$adminAuditAgeLimit (numeric retention not determined)" } elseif ($retentionNumeric -ge 365) { "Audit log retention set to $retentionNumeric days (meets 365-day recommendation)" } elseif ($retentionNumeric -ge 180) { "Audit log retention set to $retentionNumeric days (default E3 — consider extending to 365 days with E5 or add-on)" } else { "Audit log retention set to $retentionNumeric days — below recommended minimum of 180 days" } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $description ` -Details @{ AuditLogAgeLimit = $retentionDays AdminAuditLogAgeLimit = $adminAuditAgeLimit RetentionDays = $retentionNumeric RecommendedMinDays = 180 RecommendedOptimalDays = 365 } } # ── M365AUDIT-003: Audit Log Search ────────────────────────────────── function Test-InfiltrationM365AUDIT003 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $audit = $AuditData.M365Services.AuditConfig if (-not $audit) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Audit configuration data not available (EXO module not connected)' } # Verify UAL is enabled (prerequisite for search) $ualEnabled = $audit.UnifiedAuditLogIngestionEnabled $adminAuditEnabled = $audit.AdminAuditLogEnabled if ($ualEnabled -eq $false) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue 'Audit log search unavailable — Unified Audit Log ingestion is disabled' ` -Details @{ UnifiedAuditLogIngestionEnabled = $false AdminAuditLogEnabled = $adminAuditEnabled } } # Check admin audit log is also enabled $status = if ($ualEnabled -eq $true -and $adminAuditEnabled -eq $true) { 'PASS' } elseif ($ualEnabled -eq $true) { 'PASS' } elseif ($adminAuditEnabled -eq $true) { 'WARN' } else { 'FAIL' } $description = if ($ualEnabled -eq $true -and $adminAuditEnabled -eq $true) { 'Audit log search is operational — UAL and admin audit logging are both enabled' } elseif ($ualEnabled -eq $true) { 'Unified Audit Log is enabled. Admin audit log status could not be confirmed' } else { 'Audit log search capability may be limited — verify UAL and admin audit settings' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue $description ` -Details @{ UnifiedAuditLogIngestionEnabled = $ualEnabled AdminAuditLogEnabled = $adminAuditEnabled LogLevel = $audit.LogLevel } } |