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! 🙂

Recent Articles

The return of CyberDrain CTF

CyberDrain CTF returns! (and so do I!)

It’s been since september that I actually picked up a digital pen equivalent and wrote anything down. This was due to me being busy with life but also my side projects like CIPP. I’m trying to get back into the game of scripting and blogging about these scripts. There’s still so much to automate and so little time, right? ;)

Monitoring with PowerShell: Monitoring Acronis Backups

Intro

This is a monitoring script requested via Reddit, One of the reddit r/msp users wondered how they can monitor Acronis a little bit easier. I jumped on this because it happened pretty much at the same time that I was asked to speak at the Acronis CyberSummit so it kinda made sense to script this so I have something to demonstrate at my session there.

Monitoring with PowerShell: Monitoring VSS Snapshots

Intro

Wow! It’s been a while since I’ve blogged. I’ve just been so swamped with CIPP that I’ve just let the blogging go entirely. It’s a shame because I think out of all my hobbies it’s one I enjoy the most. It’s always nice helping others achieve their scripting target. I even got a couple of LinkedIn questions asking if I was done with blogging but I’m not. Writing always gives me some more piece of mind so I’ll try to catch up again. I know I’ve said that before but this time I’ll follow through. I’m sitting down right now and scheduling the release of 5 blogs in one go. No more whining and no more waiting.