Monitoring with PowerShell: Monitoring O365 alerts

So today we’re tackling two blogs in one again, we’re going to be focussing on two different types of alerting policies.

The first getting alerts for M365 or Azure P1/P2 users via the Graph API, these alerts are pretty cool as you can see if users are connecting from tor nodes/VPN nodes, or from stuff like malware linked IPs. Monitoring these alerts actively via your RMM helps you in making sure that users are as safe as they can be.

Unfortunately, I know not everyone has the luxury of a M365, or P1 subscription. To help those users out we’re going get the existing protection alerts for all tenants, and forward them from “TenantsAdmin” to an actual e-mail address managed by you. So, lets get started!

Monitoring Alerts with Graph

This script uses the Secure Application Model, You’ll also need to add some extra permissions to your Secure App. To do that execute the following steps:

  • Go to the Azure Portal.
  • Click on Azure Active Directory, now click on “App Registrations”.
  • Find your Secure App Model application. You can search based on the ApplicationID.
  • Go to “API Permissions” and click Add a permission.
  • Choose “Microsoft Graph” and “Application permission”.
  • Search for “Security” and click on “SecurityEvents.Read.All”. Click on add permission.
  • Search for “Security” and click on “and SecurityEvents.ReadWrite.All”. Click on add permission.
  • Do the same for “Delegate Permissions”.
  • Finally, click on “Grant Admin Consent for Company Name.

After you’ve done this, execute the script below using your RMM, from there you’ll be able to see all open alerts. 🙂

######### Secrets #########
$ApplicationId = 'YourAppID'
$ApplicationSecret = 'YourAppSecret' | ConvertTo-SecureString -Force -AsPlainText
$RefreshToken = 'YourVeryLongRefreshToken'
$SkipList = "Bla.onmicrosoft.com", "Bla2.onmicrosoft.com"
######### Secrets #########


$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)


Try {
    $aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal 
    $graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default'
}
catch {
    write-DRRMAlert "Could not get tokens: $($_.Exception.Message)"
    exit 1
}
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken

$customers = Get-MsolPartnerContract -All | Where-Object { $_.DefaultDomainName -notin $skiplist }

$OpenAlerts = foreach ($customer in $customers) {
    write-host "Getting started for $($Customer.name)" -foregroundcolor green
    try {
        $graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $customer.TenantID
        $Header = @{
            Authorization = "Bearer $($graphToken.AccessToken)"
        }
    }
    catch {
        "Could not connect to tenant $($customer.name). Error:  $($_.Exception.Message)"
        continue
    }
    (Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/security/Alerts" -Headers $Header -Method Get -ContentType "application/json").value | Select-Object title,category, description, createddatetime,userstates,status, @{label="Username";expression={$_.userstates.userprincipalname}}

}

if(!$OpenAlerts){
    $OpenAlerts = "Healthy - No open alerts found"
}

$OpenAlerts 

Now lets move on to the e-mail alerts.

Setting Protection Alerts

So the biggest problem with the default protection alerts is that they can only be sent to “Tenant Admins” a group of administrators in the tenant. For Microsoft Partners this group often does not have licensed user.

To tackle this issue we’re going to grab all rules that reference the TenantsAdmins group, we’re going to copy that rule, set the e-mail address to one of our choosing. We’re leaving the original rule intact because those might be used by local administrators or in co-managed environments.

######### Secrets #########
$ApplicationId         = 'ApplicationID'
$ApplicationSecret     = 'AppSecret'
$TenantID              = 'YourTenantID'
$RefreshToken          = 'RefreshToken'
$ExchangeRefreshToken = 'ExchangeRefreshToken'
$UPN = "YourUPN"
$AlertingEmail = "O365Alerts@YourDomain.com"
######### Secrets #########

$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)
 
$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID
 
Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
foreach ($customer in $customers) {
    $customerId = $customer.DefaultDomainName
    write-host "Processing $customerId"
    try {
        $token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default'
        $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
        $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
        $SccSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.compliance.protection.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true&DelegatedOrg=$($customerId)" -Credential $credential -AllowRedirection -Authentication Basic -erroraction stop -WarningAction SilentlyContinue
        $null = import-pssession $SccSession -disablenamechecking -allowclobber -CommandName "Get-ActivityAlert", "New-ActivityAlert", "Get-ProtectionAlert", "New-protectionalert", "Set-activityAlert", "Set-protectionalert"
    }
    catch {
        Write-Host "Could not get tokens or log in to session for $($customer.DefaultDomainName): $($_.Exception.Message)"
        continue
    }
    
    $ProtectionAlerts = Get-ProtectionAlert | Where-Object { $_.NotifyUser -eq "TenantAdmins" -and $_.disabled -eq $false }
    
    ForEach ($ProtectionAlert in $ProtectionAlerts) {
        $NewName = if ($ProtectionAlert.name.Length -gt 30) { "$($ProtectionAlert.name.substring(0,30)) - $($AlertingEmail)" } else { "$($ProtectionAlert.name) - $($AlertingEmail)" }
        $ExistingRule = Get-ProtectionAlert -id $NewName -ErrorAction "SilentlyContinue"
        if (!$ExistingRule) {
            $splat = @{
                name                = $NewName
                NotifyUser          = $AlertingEmail
                Operation           = $ProtectionAlert.Operation
                NotificationEnabled = $true
                Severity            = $ProtectionAlert.Severity
                Category            = $ProtectionAlert.Category
                Comment             = $ProtectionAlert.Comment
                threattype          = $ProtectionAlert.threattype
                AggregationType     = $ProtectionAlert.AggregationType
                Disabled            = $ProtectionAlert.Disabled
            }
            try {
              $null = New-ProtectionAlert @splat -ErrorAction Stop
            }
            catch {
                write-host "Could not create rule. Most likely no subscription available. Error: $($_.Exception.Message)"
            }
        }
        else {
            write-host "Rule exists, Moving on."
        }
    }

    Remove-PSSession $SccSession
 
}

And that’s it! as always, Happy PowerShelling! 🙂

6 Comments

  1. Pingback: ICYMI: PowerShell Week of 21-August-2020 | PowerShell.org

  2. Pingback: PowerShell SnippetRace 34-2020 | | PowerShell Usergroup Austria

  3. Vinson April 21, 2021 at 6:41 pm

    Having some issues with the Protection Alerts script. Out of my 60 tenants, 16 of them can successfully connect to the “ps.compliance.protection.outlook.com” Powershell session. All of the rest fail with this message – “Connecting to remote server nam04b.ps.compliance.protection.outlook.com failed with the following error message : Access is denied.”

    I find it strange that 16 connect fine and 44 fail. I think it has to be some kind of tenant setting or permission issue, being that it works for some but not others. I also get the same results when using the EXOv2 module and manually trying to connect to SCC with “Connect-IPPSSession -UserPrincipalName myself@mydomain.com -DelegatedOrganization mytenant.com”. The same tenants that fail with this script also fail with this manual SCC connection (ruling out issues with Secure App Model/tokens).

    Anyone have any insights into this? Googlefu has failed me, and I’m desperate to get these Security alerts flowing to a monitored inbox account after some recent breaches. Thank you!

    1. Tik May 13, 2021 at 4:53 pm

      I’m seeing the same issues myself. Did you ever find a resolution to this?

      1. Kelvin Tegelaar May 13, 2021 at 5:07 pm

        Contact support, it seems that this is a tenant-wide issue that appears for some partners.

        1. Tik May 13, 2021 at 6:47 pm

          Thanks Kelvin!

          I’ve opened a case. We have about a 90% fail rate across our delegated tenants. I’ll report back if there is anything we find to correct the issue without support involvement.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.