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. 🙂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
######### 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
######### 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! 🙂