modules/Devolutions.CIEM.Notifications/Public/Send-CIEMNotification.ps1
|
function Send-CIEMNotification { [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [int]$CurrentDiscoveryRunId, [Parameter(Mandatory)] [ValidateSet('Manual', 'ScheduledDiscovery')] [string]$InvocationSource, [Parameter()] [switch]$Test ) $ErrorActionPreference = 'Stop' if (-not $Test -and -not $PSBoundParameters.ContainsKey('CurrentDiscoveryRunId')) { throw 'CurrentDiscoveryRunId is required unless -Test is specified.' } $enabledNotifications = @(Get-CIEMNotification | Where-Object { $_.Enabled -and $_.Type -eq 'ExposureChange' }) if ($enabledNotifications.Count -eq 0) { return [PSCustomObject]@{ SentCount = 0 FailedCount = 0 SkippedCount = 0 } } if ($enabledNotifications.Count -ne 1) { throw 'CIEM notifications V1 supports exactly one enabled ExposureChange notification.' } $notification = $enabledNotifications[0] $scopeAllowsSend = switch ($notification.AutoSendScope) { 'AnyDiscovery' { $true } 'ScheduledDiscovery' { $InvocationSource -eq 'ScheduledDiscovery' } 'ManualOnly' { $InvocationSource -eq 'Manual' } } if (-not $scopeAllowsSend) { return [PSCustomObject]@{ SentCount = 0 FailedCount = 0 SkippedCount = 0 } } $enabledChannels = @(Get-CIEMNotificationChannel | Where-Object { $_.Enabled -and $_.Type -eq 'Email' }) if ($enabledChannels.Count -eq 0) { return [PSCustomObject]@{ SentCount = 0 FailedCount = 0 SkippedCount = 0 } } if ($enabledChannels.Count -ne 1) { throw 'CIEM notifications V1 supports exactly one enabled Email notification channel.' } $channel = $enabledChannels[0] $profile = GetCIEMAssignedAuthenticationProfile -UsageType 'NotificationChannel' -UsageId 'email-default' $changes = if ($Test) { @([PSCustomObject]@{ Id = 'test-notification' CurrentDiscoveryRunId = 0 ChangeType = 'NewRisk' Severity = 'Critical' SeverityRank = 1 Title = 'Test exposure change notification' Evidence = 'This is a test notification from the CIEM configuration page.' ImpactedIdentityName = 'Test Identity' ImpactedResourceName = 'Test Resource' }) } else { @(Get-CIEMExposureChange -CurrentDiscoveryRunId $CurrentDiscoveryRunId) } $minimumSeverityRank = GetCIEMNotificationSeverityRank -Severity $notification.MinimumSeverity $matchingChanges = @($changes | Where-Object { $notification.ChangeTypes -contains $_.ChangeType -and [int]$_.SeverityRank -le $minimumSeverityRank }) $notificationGroups = @($matchingChanges | Group-Object -Property ChangeType, ExposureType, Title | ForEach-Object { $groupChanges = @($_.Group | Sort-Object @{ Expression = { [int]$_.SeverityRank } }, ImpactedIdentityName, ImpactedResourceName, Id) $primaryChange = $groupChanges[0] $identities = @($groupChanges | ForEach-Object { [string]$_.ImpactedIdentityName } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique) $targets = @($groupChanges | ForEach-Object { [string]$_.ImpactedResourceName } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique) $signalIds = @($groupChanges | ForEach-Object { [string]$_.Id }) $groupLines = @(foreach ($groupChange in $groupChanges) { $lineParts = [System.Collections.Generic.List[string]]::new() if (-not [string]::IsNullOrWhiteSpace([string]$groupChange.ImpactedIdentityName)) { $lineParts.Add("Identity: $($groupChange.ImpactedIdentityName)") } if (-not [string]::IsNullOrWhiteSpace([string]$groupChange.ImpactedResourceName)) { $lineParts.Add("Target: $($groupChange.ImpactedResourceName)") } if (-not [string]::IsNullOrWhiteSpace([string]$groupChange.Evidence)) { $lineParts.Add("Evidence: $($groupChange.Evidence)") } $lineParts.Add("Signal: $($groupChange.Id)") "- $($lineParts -join '; ')" }) $changeCountLabel = if ($groupChanges.Count -eq 1) { '1 matching exposure change' } else { "$($groupChanges.Count) matching exposure changes" } [PSCustomObject]@{ Id = $signalIds -join ',' CurrentDiscoveryRunId = $primaryChange.CurrentDiscoveryRunId ChangeType = $primaryChange.ChangeType Severity = $primaryChange.Severity SeverityRank = $primaryChange.SeverityRank Title = $primaryChange.Title Evidence = "$changeCountLabel`n$($groupLines -join "`n")" ImpactedIdentityName = $identities -join ', ' ImpactedResourceName = $targets -join ', ' } }) $sentCount = 0 $failedCount = 0 $recipientSummary = "To: $($channel.ToRecipients -join ', ')" if ($channel.CcRecipients.Count -gt 0) { $recipientSummary += "; Cc: $($channel.CcRecipients -join ', ')" } if ($channel.BccRecipients.Count -gt 0) { $recipientSummary += "; Bcc: $($channel.BccRecipients -join ', ')" } foreach ($change in $notificationGroups) { $templateValues = @{ Severity = $change.Severity Title = $change.Title Evidence = $change.Evidence ChangeType = $change.ChangeType Identity = $change.ImpactedIdentityName Target = $change.ImpactedResourceName CurrentDiscoveryRunId = $change.CurrentDiscoveryRunId SourceSignalId = $change.Id } $subject = FormatCIEMNotificationTemplate -Template $notification.SubjectTemplate -Values $templateValues $textBody = FormatCIEMNotificationTemplate -Template $notification.TextBodyTemplate -Values $templateValues $htmlBody = FormatCIEMNotificationTemplate -Template $notification.HtmlBodyTemplate -Values $templateValues $attemptedAt = (Get-Date).ToString('o') try { $sendResult = SendCIEMEmailMessage -AuthenticationProfile $profile -Channel $channel -Subject $subject -TextBody $textBody -HtmlBody $htmlBody SaveCIEMNotificationHistory ` -NotificationId $notification.Id ` -ChannelId $channel.Id ` -SourceSignalId ([string]$change.Id) ` -SourceSignalType 'ExposureChange' ` -InvocationSource $InvocationSource ` -Status 'Succeeded' ` -AttemptedAt $attemptedAt ` -MessageId $sendResult.MessageId ` -RecipientSummary $recipientSummary $sentCount++ } catch { $failedCount++ SaveCIEMNotificationHistory ` -NotificationId $notification.Id ` -ChannelId $channel.Id ` -SourceSignalId ([string]$change.Id) ` -SourceSignalType 'ExposureChange' ` -InvocationSource $InvocationSource ` -Status 'Failed' ` -AttemptedAt $attemptedAt ` -RecipientSummary $recipientSummary ` -ErrorMessage $_.Exception.Message } } if ($failedCount -gt 0) { throw "notification send failed: $failedCount failure(s)" } [PSCustomObject]@{ SentCount = $sentCount FailedCount = $failedCount SkippedCount = $changes.Count - $matchingChanges.Count } } |