Combination of Azure Automation, SharePoint Online, and Exchange Online Maintains the Tenant Block List

Recently I wrote about the choice between the use of Outlook’s Junk Email Filter and tenant-wide allow/block lists. It’s a complicated topic that requires a lot of work by administrators to decide what’s best for a tenant. This article dives into the detail of what the tenant allow/block list is and how to maintain block entries automatically.

Exchange Online Protection (EOP) and Microsoft Defender for Office 365 have allow and block lists for tenants to customize the processing of inbound email. Administrators can’t add entries to the allow list. Instead, EOP adds entries for senders of blocked messages that should not have been blocked when tenants submit these messages to Microsoft.

Tenants can control the block list by adding entries for senders and domains they do not want to accept messages from. You can also add domains when you do not want to accept emails with links to these domains in the message body. Recently, Microsoft added the ability to block top-level domains (TLDs). This feature helps suppress likely spam or phishing attempts originating from certain TLDs such as the list of most-abused TLDs maintained by Spamhaus,

Microsoft 365 Defender and the Block List

The UI to maintain the block list is in the Email and Collaboration section of the Microsoft Defender 365 admin center. Go to Policies and Rules and then select Tenant Allow/Block Lists. Figure 1 shows a set of TLDs in a tenant block list.

Tenant block list includes many TLDs.
Figure 1: Tenant block list includes many TLDs

It’s easy to add blocked senders and domains or URLs for domains and TLDs. However, unless you mark a block list entry as “never expire,” the longest an entry remains on the block list is 90 days. In some respects, this is a good idea because it forces administrators to review the reasons why blocks are in place and decide whether to renew blocks or let traffic resume from a domain.

Automating Tenant Block List Updates

Automating the process is the answer. I put together a PowerShell script that can run as an Azure Automation runbook. The basic outline of the solution is:

  • Store block list entries in SharePoint Online.
  • Read the block list entries and update them in EOP.
  • Update mail flow rules to block email from TLDs and specific domains.
  • Email results to administrators.

Let’s dive into some of the technical details.

SharePoint List for Tenant Block List Entries

Block list entries that the organization wants to persist are held in a list in a SharePoint Online site. SharePoint Online lists are a good choice for storing data of this nature. The lists are easy to update, including with a mobile client. More importantly, the information is accessible using Graph requests (or Microsoft Graph PowerShell SDK cmdlets) for both interactive sessions and when an Azure Automation runbook executes on a headless server.

The SharePoint list stores both Sender and URL entries (Figure 2). Sender entries instruct EOP to block email from individual email addresses or domains. URL entries instruct EOP to block email when message bodies include URLs with the domain name. The URLs can be TLDs or individual domains.

SharePoint Online list containing block list entries.
Figure 2: SharePoint Online list containing block list entries

The script reads the block list data from the SharePoint list to create a set of entries for processing. In this code snippet, cmdlets find identifiers for the site and the list and use this information to retrieve the list items. As in many Graph requests, the retrieved data is a little funky, so the script populates a PowerShell list with the entries.

$Site = Get-MgSite -Search 'Office 365 Adoption' | Select-Object -First 1
# List in the site with the block information
$List = Get-MgSiteList -SiteId $Site.Id -Filter "displayName eq 'Domain Blocks'"

# Read items from the list and build a PowerShell list object containing blocks to apply
Write-Output ("Fetching information about tenant blocks from SharePoint Online list {0}" -f $List.displayName)
[array]$Data = Get-MgSiteListItem -ListId $List.Id -SiteId $Site.Id  -ExpandProperty Fields
[array]$Items = $Data.Fields.additionalProperties

$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($Item in $Items) {
    $ItemtoBlock    = $Item.Title
    $BlockType      = $Item.DomainType
    $Notes          = $Item.Value
    $DataLine = [PSCustomObject][Ordered]@{
        ItemToBlock = $ItemtoBlock
        BlockType   = $BlockType
        Notes       = $Notes 
    }
    $Report.Add($DataLine)
}

Updating Block List Entries

Because block list entries (in general) cannot be updated, to amend the expiration date it’s necessary to remove the existing block list entry and replace it with a new version that has an expiration date of 90 days in the future (the maximum).

The script performs different processing for Sender and URL block entries, so it separates the data fetched from SharePoint into two arrays and processes each set. This extract shows the processing for the URL set. Note the processing of the TLD blocks, which need to be in the format “\.biz*” for block list entries and the regular expression “\.biz$” to block inbound email for a TLD in a mail flow rule.

ForEach ($BlockURL in $URLBlocks) {
    Write-Output ("Processing block for {0}" -f $BlockURL.ItemToBlock)
    If (($BlockURL.ItemToBlock.Substring(0,1)) -eq ".") {
        $URLToBlock = ("*{0}/*" -f $BlockURL.ItemToBlock)
        $TLDBlock = ("\{0}$" -f $BlockURL.ItemToBlock)
        $TLDBlocksforTransportRule += $TLDBlock
    } Else {
        $URLToBlock = $BlockURL.ItemToBlock
        $IndividualDomainBlocks += $BlockURL.ItemToBlock
    }
    # If URL is already blocked, remove the current block
    If ($URLToBlock -in $CurrentURLBlocks) {
        Write-Output ("Removing current block for {0}" -f $URLToBlock)
        $Status = Remove-TenantAllowBlockListItems -ListType URL -Entries $URLToBlock -ErrorAction SilentlyContinue
    }
    # Add the block for 90 days
    $Status = (New-TenantAllowBlockListItems -ListType URL -Entries $URLToBlock -Block `
        -ExpirationDate $ExpirationDate -ErrorAction SilentlyContinue)
    If ($Status) {
        Write-Output ("Block successfully applied for {0}" -f $URLToBlock)
        $BlockData = [PSCustomObject][Ordered]@{
            Timestamp   = Get-Date -Format s
            Block       = $URLToBlock
            BlockType   = 'URL'
        }
        $BlockReport.Add($BlockData)
    } Else {
        Write-Output "Error occurred adding block"
    }
}

The effect of the block list is to move inbound items to the quarantine. Administrators can review quarantined items to assess the kind of email captured by the block from different domains and decide if to remove or keep blocks for the domains.

A side effect to consider is that sender block list entries stop anyone in the tenant sending email to those addresses, even if the address is only one of many recipients for a message. Figure 3 shows a non-delivery notification (NDR) with status code 550.5.7.703 generated for a message sent to a blocked domain. The error saying that the recipient’s email provider rejected it is incorrect. The sending tenant applied the block.

Non-delivery notification for an email sent to a blocked domain.
Figure 3: Non-delivery notification for an email sent to a blocked domain

Blocking of inbound messages is expected. Blocking of outbound messages, especially when an address is only one of the recipients, will probably come as a surprise.

Updating Mail Flow Rules

After updating the EOP block list, the script moves on to mail flow rules. A URL block entry only blocks emails when the message body includes the URL. If you want to block all email from a domain, you can add a sender entry to the block list. I decided to use two mail flow rules.

  • Block email from TLDs.
  • Block email from specific domains.

It would be nice to use a single rule, but TLD detection depends on pattern matching defined in the FromAddressMatchesPatterns property while blocking email from domains uses a simple array defined in the SenderDomainIs property.

Here’s the code to update the mail flow rule to block email from TLDs. As you can see, if the mail flow rule doesn’t exist, the script creates it.

If ($TLDBlocksforTransportRule) {
# Now to update the transport rule to block TLDs if any TLDs are to be blocked. 
# First, check if a rule exists. If it doesn't, create it
    [array]$CheckTransportRule = Get-TransportRule -Identity $TransportRuleName -ErrorAction SilentlyContinue
    $Comments = ("Rule updated automatically on {0} to process TLDs: {1}" -f (Get-Date -format 'dd-MMM-yy HH:mm'), ($TLDBlocksforTransportRule -join ", "))
    If (!($CheckTransportRule)) {
    # Transport rule not present, so create new rule
        $NewRule = New-TransportRule -Name $TransportRuleName -Enabled $True `
            -FromAddressMatchesPatterns $TLDBlocksforTransportRule -SenderAddressLocation 'Header' `
            -Comments $Comments -Quarantine $true
        If ($NewRule) {
            Write-Output "Transport rule created to block email from specified TLDs" 
            $NewRuleCreated = $true
        }
    } Else {
    # We have a transport rule, so update it
        Write-Output "Updating transport rule for TLD blocks..."
        Set-TransportRule -Identity $TransportRuleName -FromAddressMatchesPatterns $TLDBlocksforTransportRule `
            -ErrorAction SilentlyContinue -Comments $Comments
        $BlockedTLDs = Get-TransportRule -Identity $TransportRuleName | Select-Object -ExpandProperty FromAddressMatchesPatterns
        If (!(Compare-Object -ReferenceObject $TLDBlocksForTransportRule -DifferenceObject $BlockedTLDs)) {
            Write-Output ("Transport rule updated to block email from these TLDs {0}:" -f ($TLDBlocksforTransportRule -join ", "))
        }    
    }
}

As before, email blocked by the mail flow rule ends up in the quarantine. It would be easy to amend the rule to delete the messages. Figure 4 shows the rule properties as viewed through the Exchange admin center.

Exchange mail flow rule to block messages from TLDs.
Figure 4: Exchange mail flow rule to block messages from TLDs

Using Azure Automation to Run the Script

If you run the script interactively, you’ll know when it’s done. But this is a good example of a script to execute as a scheduled Azure Automation runbook to renew blocks every 90 days. There’s nothing in the code that prevents it from running with Azure Automation (Figure 5) using a managed identity to connect to Exchange Online and the Graph SDK.

The runbook executes in Azure Automation.
Figure 5: The runbook executes in Azure Automation

The automation account used to execute the runbook must:

  • Hold the Exchange Online administrator role (the process to assign the role is described in this article).
  • Be assigned the Graph permissions necessary to read information from the SharePoint Online list (Sites.Read.All) and send email (Mail.Send). You can only assign permissions to the service principal of the automation account using PowerShell. See this article for information.
  • Have the necessary PowerShell modules loaded as resources. These modules include the Exchange Online module and the Microsoft.Graph.Authentication, Microsoft.Graph.Sites, and Microsoft.Graph.Identity.DirectoryManagement modules. Make sure that you use the latest modules (here’s a handy script to update PowerShell modules for Azure Automation accounts).

With everything in place, the runbook executes and generates an email at the end to report what it did (Figure 6).

Email sent to report tenant block list updates.
Figure 6: Email sent to report tenant block list updates

You can download the complete script from GitHub. Remember that this is code written to explore and illustrate a principle and needs better error handling and logging before it is used in production. It’s the basis for further evolution, nothing more.

Automating Tenant Operations Isn’t Very Complex

I’m sure that I’ve forgotten something, but the point is that automating Microsoft 365 management is not particularly complex once you know about the modules, cmdlets, and permissions to use. The trick is to discover what those elements are and then combine them together into a solution using available tools (which is why you read Practical365.com). Hopefully, the idea outlined here sparks some creativity in how you approach the automation of tenant management operations.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Leave a Reply