Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit logs #1130

Merged
merged 33 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fc9a7f7
New audit logs
JohnDuprey Oct 1, 2024
78d3c9a
audit logs
JohnDuprey Oct 1, 2024
c4b1f35
try / catch
JohnDuprey Oct 1, 2024
c986c60
Update Push-AuditLogTenant.ps1
JohnDuprey Oct 1, 2024
6e12e15
audit logs
JohnDuprey Oct 1, 2024
90adeed
audit log tweaks
JohnDuprey Oct 1, 2024
aa01d28
Update Push-AuditLogTenant.ps1
JohnDuprey Oct 1, 2024
c6eaf49
Update Push-AuditLogTenant.ps1
JohnDuprey Oct 1, 2024
92f17cb
audit logs
JohnDuprey Oct 1, 2024
820d4a4
Update Push-AuditLogTenant.ps1
JohnDuprey Oct 1, 2024
51e3bf7
audit logs fixes
JohnDuprey Oct 1, 2024
609bcfb
Fix api role permissions
JohnDuprey Oct 1, 2024
cbda16f
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 1, 2024
ce39b56
Update Push-AuditLogTenant.ps1
JohnDuprey Oct 1, 2024
540e90c
Update Push-AuditLogTenant.ps1
JohnDuprey Oct 1, 2024
74a2de5
Fix issues with graph requests missing data
JohnDuprey Oct 1, 2024
f603d0f
Add sharepoint admin url endpoint
JohnDuprey Oct 1, 2024
28b7254
Create Get-CippLastAuditLogSearch.ps1
JohnDuprey Oct 1, 2024
2583b6b
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 1, 2024
a99bf17
tweak timing
JohnDuprey Oct 1, 2024
3c1f852
audit logs
JohnDuprey Oct 1, 2024
d34be3c
Merge branch 'dev' of github.com:JohnDuprey/CIPP-API into dev
JohnDuprey Oct 1, 2024
f35dca1
Merge pull request #242 from KelvinTegelaar/dev
JohnDuprey Oct 1, 2024
f8b88e5
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
d0778b2
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
dd1c649
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
30b6f85
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
499a206
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
593cf41
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
35e9320
update timer
JohnDuprey Oct 2, 2024
7f9e952
Update Get-CIPPAlertQuotaUsed.ps1
JohnDuprey Oct 2, 2024
891d989
Update Start-AuditLogOrchestrator.ps1
JohnDuprey Oct 2, 2024
a915505
Update Get-CippAuditLogSearchResults.ps1
JohnDuprey Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CIPPTimers.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"Cron": "0 */15 * * * *",
"Priority": 2,
"RunOnProcessor": true,
"PreferredProcessor": "auditlog"
"PreferredProcessor": "auditlog",
"IsSystem": true
},
{
"Command": "Start-WebhookOrchestrator",
Expand Down
6 changes: 4 additions & 2 deletions Modules/CIPPCore/Public/Alerts/Get-CIPPAlertQuotaUsed.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ function Get-CIPPAlertQuotaUsed {
}
$OverQuota = $AlertData | ForEach-Object {
if ($_.StorageUsedInBytes -eq 0 -or $_.prohibitSendReceiveQuotaInBytes -eq 0) { return }
$PercentLeft = [math]::round(($_.storageUsedInBytes / $_.prohibitSendReceiveQuotaInBytes) * 100)
try {
$PercentLeft = [math]::round(($_.storageUsedInBytes / $_.prohibitSendReceiveQuotaInBytes) * 100)
} catch { $PercentLeft = 100 }
try {
if ([int]$InputValue -gt 0) {
$Value = [int]$InputValue
Expand All @@ -34,4 +36,4 @@ function Get-CIPPAlertQuotaUsed {

}
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $OverQuota
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function Get-CippAuditLogSearchResults {
<#
.SYNOPSIS
Get the results of an audit log search
.DESCRIPTION
Get the results of an audit log search from the Graph API
.PARAMETER TenantFilter
The tenant to filter on.
.PARAMETER QueryId
The ID of the query to get the results for.
#>
param (
[Parameter(Mandatory = $true)]
[string]$TenantFilter,
[Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
[Alias('id')]
[string]$QueryId
)

process {
New-GraphGetRequest -uri ('https://graph.microsoft.com/beta/security/auditLog/queries/{0}/records?$top=999' -f $QueryId) -AsApp $true -tenantid $TenantFilter
}
}
23 changes: 23 additions & 0 deletions Modules/CIPPCore/Public/AuditLogs/Get-CippAuditLogSearches.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function Get-CippAuditLogSearches {
<#
.SYNOPSIS
Get the available audit log searches
.DESCRIPTION
Query the Graph API for available audit log searches.
.PARAMETER TenantFilter
The tenant to filter on.
#>
param (
[Parameter(Mandatory = $true)]
[string]$TenantFilter,
[Parameter()]
[switch]$ReadyToProcess
)
$Queries = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/auditLog/queries' -AsApp $true -tenantid $TenantFilter
if ($ReadyToProcess.IsPresent) {
$AuditLogSearchesTable = Get-CippTable -TableName 'AuditLogSearches'
$PendingQueries = Get-CIPPAzDataTableEntity @AuditLogSearchesTable -Filter "Tenant eq '$TenantFilter' and CippStatus eq 'Pending'"
$Queries = $Queries | Where-Object { $PendingQueries.RowKey -contains $_.id -and $_.status -eq 'succeeded' }
}
return $Queries
}
19 changes: 19 additions & 0 deletions Modules/CIPPCore/Public/AuditLogs/Get-CippLastAuditLogSearch.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function Get-CippLastAuditLogSearch {
<#
.SYNOPSIS
Get the last audit log search
.DESCRIPTION
Query the Graph API for the last audit log search.
.PARAMETER TenantFilter
The tenant to filter on.
#>
param (
[Parameter(Mandatory = $true)]
[string]$TenantFilter
)

$Table = Get-CIPPTable -TableName AuditLogSearches
$LastHour = (Get-Date).AddHours(-1).ToString('yyyy-MM-ddTHH:mm:ssZ')
$LastSearch = Get-AzDataTableEntity @Table -Filter "Tenant eq '$TenantFilter' and Timestamp ge datetime'$LastHour'" | Sort-Object Timestamp -Descending | Select-Object -First 1
return $LastSearch
}
24 changes: 22 additions & 2 deletions Modules/CIPPCore/Public/AuditLogs/New-CippAuditLogSearch.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ function New-CippAuditLogSearch {
The object IDs to filter on.
.PARAMETER AdministrativeUnitFilters
The administrative units to filter on.
.PARAMETER ProcessLogs
Store the search in the CIPP AuditLogSearches table for alert processing.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
Expand Down Expand Up @@ -117,7 +119,9 @@ function New-CippAuditLogSearch {
[Parameter()]
[string[]]$ObjectIdFilters,
[Parameter()]
[string[]]$AdministrativeUnitFilters
[string[]]$AdministrativeUnitFilters,
[Parameter()]
[switch]$ProcessLogs
)

$SearchParams = @{
Expand Down Expand Up @@ -151,6 +155,22 @@ function New-CippAuditLogSearch {
}

if ($PSCmdlet.ShouldProcess('Create a new audit log search for tenant ' + $TenantFilter)) {
New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/security/auditLog/queries' -body ($SearchParams | ConvertTo-Json -Compress) -tenantid $TenantFilter -AsApp $true
$Query = New-GraphPOSTRequest -uri 'https://graph.microsoft.com/beta/security/auditLog/queries' -body ($SearchParams | ConvertTo-Json -Compress) -tenantid $TenantFilter -AsApp $true

if ($ProcessLogs.IsPresent -and $Query.id) {
$Entity = [PSCustomObject]@{
PartitionKey = [string]'Search'
RowKey = [string]$Query.id
Tenant = [string]$TenantFilter
DisplayName = [string]$DisplayName
StartTime = [datetime]$StartTime.ToUniversalTime()
EndTime = [datetime]$EndTime.ToUniversalTime()
Query = [string]($Query | ConvertTo-Json -Compress)
CippStatus = [string]'Pending'
}
$Table = Get-CIPPTable -TableName 'AuditLogSearches'
Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force | Out-Null
}
return $Query
}
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,60 @@
function Push-AuditLogTenant {
Param($Item)

# Get Table contexts
$AuditBundleTable = Get-CippTable -tablename 'AuditLogBundles'
$SchedulerConfig = Get-CippTable -TableName 'SchedulerConfig'
$WebhookTable = Get-CippTable -tablename 'webhookTable'
$ConfigTable = Get-CippTable -TableName 'WebhookRules'
#$Tenant = Get-Tenants -TenantFilter $Item.customerId -IncludeErrors
$TenantFilter = $Item.TenantFilter

Write-Information "Audit Logs: Processing $($TenantFilter)"
# Query CIPPURL for linking
$CIPPURL = Get-CIPPAzDataTableEntity @SchedulerConfig -Filter "PartitionKey eq 'webhookcreation'" | Select-Object -First 1 -ExpandProperty CIPPURL

# Get all webhooks for the tenant
$Webhooks = Get-CIPPAzDataTableEntity @WebhookTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and Version eq '3'" | Where-Object { $_.Resource -match '^Audit' }

# Get webhook rules
$ConfigEntries = Get-CIPPAzDataTableEntity @ConfigTable
$LogSearchesTable = Get-CippTable -TableName 'AuditLogSearches'

# Date filter for existing bundles
$LastHour = (Get-Date).AddHours(-1).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss')

$NewBundles = [System.Collections.Generic.List[object]]::new()
foreach ($Webhook in $Webhooks) {
# only process webhooks that are configured in the webhookrules table
$Configuration = $ConfigEntries | Where-Object { ($_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants') }
if ($Configuration.Type -notcontains $Webhook.Resource) {
continue
}

$TenantFilter = $Webhook.PartitionKey
$LogType = $Webhook.Resource
Write-Information "Querying for $LogType on $TenantFilter"
$ContentBundleQuery = @{
TenantFilter = $TenantFilter
ContentType = $LogType
StartTime = $Item.StartTime
EndTime = $Item.EndTime
}
$Configuration = $ConfigEntries | Where-Object { ($_.Tenants -match $TenantFilter -or $_.Tenants -match 'AllTenants') }
if ($Configuration) {
try {
$LogBundles = Get-CIPPAuditLogContentBundles @ContentBundleQuery
$ExistingBundles = Get-CIPPAzDataTableEntity @AuditBundleTable -Filter "PartitionKey eq '$($Item.TenantFilter)' and ContentType eq '$LogType' and Timestamp ge datetime'$($LastHour)'"

foreach ($Bundle in $LogBundles) {
if ($ExistingBundles.RowKey -notcontains $Bundle.contentId) {
$NewBundles.Add([PSCustomObject]@{
PartitionKey = $TenantFilter
RowKey = $Bundle.contentId
DefaultDomainName = $TenantFilter
ContentType = $Bundle.contentType
ContentUri = $Bundle.contentUri
ContentCreated = $Bundle.contentCreated
ContentExpiration = $Bundle.contentExpiration
CIPPURL = [string]$CIPPURL
ProcessingStatus = 'Pending'
MatchedRules = ''
MatchedLogs = 0
})
$LogSearches = Get-CippAuditLogSearches -TenantFilter $TenantFilter -ReadyToProcess
Write-Information ('Audit Logs: Found {0} searches, begin processing' -f $LogSearches.Count)
foreach ($Search in $LogSearches) {
$SearchEntity = Get-CIPPAzDataTableEntity @LogSearchesTable -Filter "Tenant eq '$($TenantFilter)' and RowKey eq '$($Search.id)'"
$SearchEntity.CippStatus = 'Processing'
Add-CIPPAzDataTableEntity @LogSearchesTable -Entity $SearchEntity -Force
try {
# Test the audit log rules against the search results
$AuditLogTest = Test-CIPPAuditLogRules -TenantFilter $TenantFilter -SearchId $Search.id

$SearchEntity.CippStatus = 'Completed'
$MatchedRules = [string](ConvertTo-Json -Compress -InputObject $AuditLogTest.MatchedRules)
$SearchEntity | Add-Member -MemberType NoteProperty -Name MatchedRules -Value $MatchedRules
$SearchEntity | Add-Member -MemberType NoteProperty -Name MatchedLogs -Value $AuditLogTest.MatchedLogs
$SearchEntity | Add-Member -MemberType NoteProperty -Name TotalLogs -Value $AuditLogTest.TotalLogs
} catch {
$SearchEntity.CippStatus = 'Failed'
Write-Information "Error processing audit log rules: $($_.Exception.Message)"
$Exception = [string](ConvertTo-Json -Compress -InputObject (Get-CippException -Exception $_))
$SearchEntity | Add-Member -MemberType NoteProperty -Name Error -Value $Exception
}
Add-CIPPAzDataTableEntity @LogSearchesTable -Entity $SearchEntity -Force
$DataToProcess = ($AuditLogTest).DataToProcess
Write-Information "Audit Logs: Data to process found: $($DataToProcess.count) items"
if ($DataToProcess) {
foreach ($AuditLog in $DataToProcess) {
Write-Information "Processing $($AuditLog.operation)"
$Webhook = @{
Data = $AuditLog
CIPPURL = [string]$CIPPURL
TenantFilter = $TenantFilter
}
Invoke-CippWebhookProcessing @Webhook
}
}
}
} catch {
Write-Information "Could not get audit log content bundles for $TenantFilter - $LogType, $($_.Exception.Message)"
}
}

if (($NewBundles | Measure-Object).Count -gt 0) {
Add-CIPPAzDataTableEntity @AuditBundleTable -Entity $NewBundles -Force
Write-Information ($NewBundles | ConvertTo-Json -Depth 5 -Compress)

$Batch = $NewBundles | Select-Object @{Name = 'ContentId'; Expression = { $_.RowKey } }, @{Name = 'TenantFilter'; Expression = { $_.PartitionKey } }, @{Name = 'FunctionName'; Expression = { 'AuditLogBundleProcessing' } }
$InputObject = [PSCustomObject]@{
OrchestratorName = 'AuditLogs'
Batch = @($Batch)
SkipLog = $true
Write-Information ( 'Audit Logs: Error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message)
}
$InstanceId = Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
Write-Host "Started orchestration with ID = '$InstanceId'"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Function Invoke-AddSite {
.FUNCTIONALITY
Entrypoint
.ROLE
Teams.Group.ReadWrite
Sharepoint.Site.ReadWrite
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Function Invoke-AddSiteBulk {
.FUNCTIONALITY
Entrypoint
.ROLE
Teams.Group.ReadWrite
Sharepoint.Site.ReadWrite
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
function Invoke-ListSharepointAdminUrl {
<#
.FUNCTIONALITY
Entrypoint
.ROLE
CIPP.Core.Read
#>
[CmdletBinding()]
param(
$Request,
$TriggerMetadata
)

if ($Request.Query.TenantFilter) {
$TenantFilter = $Request.Query.TenantFilter

$Tenant = Get-Tenants -TenantFilter $TenantFilter

if ($Tenant.SharepointAdminUrl) {
$AdminUrl = $Tenant.SharepointAdminUrl
} else {
$tenantName = (New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/sites/root' -asApp $true -tenantid $TenantFilter).id.Split('.')[0]
$AdminUrl = "https://$($tenantName)-admin.sharepoint.com"
$Tenant | Add-Member -MemberType NoteProperty -Name SharepointAdminUrl -Value $AdminUrl
$Table = Get-CIPPTable -TableName 'Tenants'
Add-CIPPAzDataTableEntity @Table -Entity $Tenant -Force
}

if ($Request.Query.ReturnUrl) {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = @{
AdminUrl = $AdminUrl
}
})
} else {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::Found
Headers = @{
Location = $AdminUrl
}
})
}
} else {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::BadRequest
Body = 'TenantFilter is required'
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,41 @@ function Start-AuditLogOrchestrator {
[CmdletBinding(SupportsShouldProcess = $true)]
param()
try {
$webhookTable = Get-CIPPTable -tablename webhookTable
$Webhooks = Get-CIPPAzDataTableEntity @webhookTable -Filter "Version eq '3'" | Where-Object { $_.Resource -match '^Audit' -and $_.Status -ne 'Disabled' }
if (($Webhooks | Measure-Object).Count -eq 0) {
Write-Information 'No webhook subscriptions found. Exiting.'
return
}

$StartTime = (Get-Date).AddMinutes(-30)
$EndTime = Get-Date
$AuditLogSearchesTable = Get-CIPPTable -TableName 'AuditLogSearches'
$AuditLogSearches = Get-CIPPAzDataTableEntity @AuditLogSearchesTable -Filter "CippStatus eq 'Pending'"
$TenantList = Get-Tenants -IncludeErrors
# Round time down to nearest minute
$Now = Get-Date
$StartTime = ($Now.AddSeconds(-$Now.Seconds)).AddMinutes(-30)
$EndTime = $Now.AddSeconds(-$Now.Seconds)

$Queue = New-CippQueueEntry -Name 'Audit Log Collection' -Reference 'AuditLogCollection' -TotalTasks ($Webhooks | Sort-Object -Property PartitionKey -Unique | Measure-Object).Count
if (($AuditLogSearches | Measure-Object).Count -eq 0) {
Write-Information 'No audit log searches available'
} else {
$Queue = New-CippQueueEntry -Name 'Audit Log Collection' -Reference 'AuditLogCollection' -TotalTasks ($AuditLogSearches).Count
$Batch = $AuditLogSearches | Sort-Object -Property Tenant -Unique | Select-Object @{Name = 'TenantFilter'; Expression = { $_.Tenant } }, @{Name = 'QueueId'; Expression = { $Queue.RowKey } }, @{Name = 'FunctionName'; Expression = { 'AuditLogTenant' } }

$Batch = $Webhooks | Sort-Object -Property PartitionKey -Unique | Select-Object @{Name = 'TenantFilter'; Expression = { $_.PartitionKey } }, @{Name = 'QueueId'; Expression = { $Queue.RowKey } }, @{Name = 'FunctionName'; Expression = { 'AuditLogTenant' } }, @{Name = 'StartTime'; Expression = { $StartTime } }, @{Name = 'EndTime'; Expression = { $EndTime } }
$InputObject = [PSCustomObject]@{
OrchestratorName = 'AuditLogs'
Batch = @($Batch)
SkipLog = $true
$InputObject = [PSCustomObject]@{
OrchestratorName = 'AuditLogs'
Batch = @($Batch)
SkipLog = $true
}
if ($PSCmdlet.ShouldProcess('Start-AuditLogOrchestrator', 'Starting Audit Log Polling')) {
Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)
}
}
if ($PSCmdlet.ShouldProcess('Start-AuditLogPolling', 'Starting Audit Log Polling')) {
Start-NewOrchestration -FunctionName 'CIPPOrchestrator' -InputObject ($InputObject | ConvertTo-Json -Depth 5 -Compress)

Write-Information 'Audit Logs: Creating new searches'
foreach ($Tenant in $TenantList) {
try {
$NewSearch = New-CippAuditLogSearch -TenantFilter $Tenant.defaultDomainName -StartTime $StartTime -EndTime $EndTime -ProcessLogs
Write-Information "Created audit log search $($Tenant.defaultDomainName) - $($NewSearch.displayName)"
} catch {
Write-Information "Error creating audit log search $($Tenant.defaultDomainName) - $($_.Exception.Message)"
}
}
} catch {
Write-LogMessage -API 'Webhooks' -message 'Error processing webhooks' -sev Error -LogData (Get-CippException -Exception $_)
Write-Information ( 'Webhook error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message)
Write-LogMessage -API 'Audit Logs' -message 'Error processing audit logs' -sev Error -LogData (Get-CippException -Exception $_)
Write-Information ( 'Audit logs error {0} line {1} - {2}' -f $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message)
}
}
Loading