Block direct delivery to @onmicrosoft.com addresses in a hybrid environment

We're all familiar with how Office 365 tenants work--when you spin up a new Office 365 tenant, you get a managed domain (tenant.onmicrosoft.com).  Then, maybe you configure a hybrid environment, and now your tenant has your domain, as well as your original tenant.onmicrosoft.com domain, and a new tenant.mail.onmicrosoft.com.  The two managed domains--tenant.onmicrosoft.com and tenant.mail.onmicrosoft.com both have internet-routable MX records:

Now, let's say you're in a hybrid mail scenario and you have begun synchronizing users and have migrated some mailboxes.  When you synchronize mail-enabled user accounts and migrate mailboxes, your users will get stamped with a tenant address.  Since we already know that the managed domains have their own MX hosts configured, that means that you can deliver mail destined a migrated user (who might have a primary SMTP address of user@customdomain.com) using their onmicrosoft.com address (user@tenant.onmicrosoft.com).

If you have certain mail flow or business requirements (such as DLP, encryption, or other content filtering that has not yet been configured in Office 365) that force your inbound email to traverse a certain path, you may find that unacceptable.  To prevent email delivery directly to the onmicrosoft.com namespace (or any accepted domain in Office 365) directly, you can try a number of different methods.

Transport Rules

TR Option 1

I've had some luck with this one in the past, though it seems to be hit or miss and is not as effective as other methods.  You can use a transport rule in your Exchange Online Tenant to attempt to block direct delivery using the following script:

 [array]$TenantDomains = (Get-AcceptedDomain | ? { $_.DomainName -like "*onmicrosoft.com" }).Name
New-TransportRule -FromScope NotInOrganization -RecipientDomainIs $TenantDomains -Name "Reject messages to onmicrosoft.com domains" -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText "You are not allowed to relay to this user's managed domain name. Please resubmit your message using the recipient's public email address."  -StopRuleProcessing $True

The result when you attempt to send mail to an onmicrosoft.com address in that tenant from a mailbox outside the Exchange organization (either on-premises or in-cloud):

You can see that the status code says 5.7.1 (our status code) and has _ETR appended (meaning it was generated by an Exchange Transport Rule).

However, I've recently experienced this one not working so well, so there are some other methods we can employ, each with their own pros and cons.  On to better methods!

TR Option 2

Another method is based on inspecting headers.  Specifically, looking for the Auth-As "Internal" header.  The Auth-As header is stamped in a hybrid configuration so you'll know if the message is from your "trusted" internal organization.

 New-TransportRule -SentToScope InOrganization -Name "Reject messages not routed through MX" -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText "You are not allowed to relay directly to this system. Please resubmit your message using the recipient's public email address and/or mail exchanger gateway." -ExceptIfHeaderContainsMessageHeader 'X-MS-Exchange-Organization-AuthAs' -ExceptIfHeaderContainsWords 'Internal' -StopRuleProcessing $True

This method has been more reliable, as it is relying on the destination (SentToScope) in combination with the header that has been stamped by your on-premises mail infrastructure.

TR Option 3

I like to call this the "belt and suspenders" transport rule option.  In addition to looking for the just the Auth-As header, you can configure your on-premises gateway (Exchange or whatever else you're using) to stamp an additional header on that you can check for.  If you stamp it on your gateway, you'll still want to stamp it (or verify that it has been stamped) on your Exchange hybrid endpoints relaying to Office 365 to ensure that your internal traffic does not get rejected.  In this example, we're going to stamp an X-header called "X-PassedThroughOnPremises" with a value of "Yes."

 New-TransportRule -SentToScope InOrganization -Name "Reject messages not routed through MX" -RejectMessageEnhancedStatusCode 5.7.1 -RejectMessageReasonText "You are not allowed to relay directly to this system. Please resubmit your message using the recipient's public email address and/or mail exchanger gateway." -ExceptIfHeaderContainsMessageHeader 'X-MS-Exchange-Organization-AuthAs' -ExceptIfHeaderContainsWords 'Internal' -ExceptIfHeaderMatchesMessageHeader 'X-PassedThroughOnPremises' -ExceptIfHeaderMatchesPatterns 'Yes' -StopRuleProcessing $True

Options 2 and 3 provide the best coverage, but also can cause unsightly NDRs to senders if, for some reason, their outbound MX gateway had been configured to route directly to Office 365.  And we all love support tickets.

On to a slightly more user-friendly method.

Transport Rule-Scoped Connector

I personally think this is one of the best methods to prevent senders from bypassing your MX.  This method employs the transport rule specified in either options 2 or 3 (with a slight modification) in addition to a connector to ensure traffic is rerouted to the appropriate MX host.

In this example, you own contoso.com, and your on-premises mail gateway is gateway.contoso.com.  You're going to use your on-premises system to stamp the header "X-PassedThroughOnPremises" with a value of "Yes," check for the presence of that header, and resubmit the message to the specified gateway if it's not present. And, to make sure it processes everything, we're going to assign it Priority 0 (moving it to the top of the list).

Run this in the Exchange Online PowerShell.

 [array]$TenantDomains = (Get-AcceptedDomain).Name
New-OutboundConnector -Name "Reroute to MX Connector" -IsTransportRuleScoped $true -UseMXRecord $false -SmartHosts 'gateway.contoso.com'
New-TransportRule -Name "Reroute to MX" -RecipientDomainIs $TenantDomains -RouteMessageOutboundConnector 'Reroute to MX Connector' -ExceptIfHeaderContainsMessageHeader 'X-MS-Exchange-Organization-AuthAs' -ExceptIfHeaderContainsWords 'Internal' -ExceptIfHeaderMatchesMessageHeader 'X-RoutedThroughOnPremises' -ExceptIfHeaderMatchesPatterns 'Yes' -StopRuleProcessing $true -Priority 0

Run this in the Exchange Management Shell on-premises.

 New-TransportRule -SetHeaderName 'X-PassedThroughOnPremises' -SetHeaderValue 'Yes' -Name 'Passed Through On-Premises' -StopRuleProcessing:$false -Mode 'Enforce' -Comments 'Set X-PassedThroughOnPremises header to Yes on all messages'
' -RuleErrorAction 'Ignore' -SenderAddressLocation 'Header' -Priority 0

Don't be a reject.  Be a resubmit.