Automatic Management of Domains whitelist for External partners in Azure AD

External Identities Management in Azure AD

Azure AD with the B2B feature allow to easily collaborate with external partners. By default, tenant configuration permit to collaborate with any external domains without restrictions, but you can select to apply Collaboration restrictions.
There are 3 modes available for Collaboration Restrictions on Azure AD:

  • Allow invitations to be sent to any domain (most inclusive)
  • Deny invitations to the specified domains
  • Allow invitations only to the specified domains (most restrictive)

That we can schematize like this with a security approach:

In this article we will focus on Whitelist mode and how to apply an automated management on it.

Whitelist impact on MS Ecosystem

There is a non-exhaustive list of Microsoft product that will be affected by the implementation of a domains whitelist on Azure AD.

ItemCommentRely on AAD Whitelist
Azure AD ApplicationsPartner user account must be part of the allowed domains list.YES
SharePointPreview – SharePoint and OneDrive integration with the Azure AD B2B one-time passcode feature is currently in preview. After preview, this feature will replace the ad-hoc external sharing experience used in OneDrive and SharePoint today for all tenants.
https://docs.microsoft.com/en-us/sharepoint/sharepoint-azureb2b-integration-preview
By default NO
B2B integration YES
TeamsAll the apps provided by MS Teams will be impacted in the way that users cannot be added to the apps if Azure settings doesn’t allow it.YES
Skype for Business (Hybrid)Skype only relies on his proper Federation settingsNO
Azure DevOpsPartner user account must be part of the allowed domains list.YES
Dynamics 365Partner user account must be part of the allowed domains list, user needs to be added first in Azure AD in order to be able to access Dynamics.YES
PowerBISettings made in Azure AD replicates in Power BI admin center.YES
Power AppsExternal users not supported yet. This feature is currently being worked on.NO
FlowFlow doesn’t support yet guest users for sharing.NO
YammerYammer doesn’t rely on Azure AD settingsNO
PlannerBuilt on Office 365 groups meaning that the user you want to add to Planner needs to already exists in Azure AD.YES
StreamBuilt on Office 365 groups meaning that the user you want to add to Stream needs to already exists in Azure AD.YES
Office 365 GroupsYou can add external users on O365 Groups if the domain is allowed on Azure AD settings.YES
Azure B2B Direct federationFederation with an External IdP based on a non whitelisted domain is not affected.
But of course invitation of a user on a non whitelisted domain is not possible.
Federation creation NO
User invitation YES
As May 2020

Portal Settings

On Azure Portal, we can mange this choice and directly add domain FQDN that we want to grant on the whitelist.

Proposed Solution

The idea here, is to provide a solution to automate this domains management and provide a single entry point where we can manage this whitelist.
By taking into account the simplicity of implementation and the cost of the solution.

Proposed Architecture

The architecture will reside on text/csv file stored on an Azure Storage, this file can be modified by business stakeholders, and this list will be automatically and with a specific schedule replicate on Azure AD settings.
In terms of assets, we just need:

  • 1 Storage Account
    • to store the text file
  • 1 Automation Account
    • with 1 PowerShell Runbook
      • to execute the targeted PowerShell code
    • with 1 schedule
      • to automate the process at a desired schedule
    • with 1 Webhook
      • if you want a simple and public trigger to launch all the process

In terms of accounts, we just need:

  • 1 AzureRunAsConnection given by the Automation Account
    • to authenticate against Azure for Storage Account steps
  • 1 dedicated user account in Azure AD with Global Administrator rights, to manage External Collaboration Restrictions settings (at the time of writing this article, the RunAs account of Automation is not compatible to authenticate against Azure AD)
Automation Architecture diagram

Automation Runbook

So there is the PowerShell code I used. This is a first version, he do the job but of course he can be improved and upgraded as you want.
The code do the following:

  1. Connect on Azure AD with Automation Credentials ($AutomationCred)
  2. Connect on Azure with Automation AzureRunAsConnection
  3. Download the content of the Storage Account file
  4. Get the default B2B Azure AD Policy name
  5. Create a custom Azure AD Policy (after a check on presence)
  6. Disable the default B2B Azure AD Policy
  7. Build a custom JSON for the policy from Storage Account content
  8. Set the target Azure AD Policy with JSON settings and put it as active
##########################################################
################### Script - Variables ###################
##########################################################
# Update these variables according to your context

# Azure AD
$AutomationCred = "aadb2baccount"
# Azure Az module
$connectionName = "AzureRunAsConnection"
# Storage Account
$containerName = "aadb2bcon"
$blobName = "AADB2B-DomainsWhiteList.csv"
$storageAccConnectionString = "Update the Storage Account Connection String according to your context"
# Azure AD Policy
$TargetAzureADPolicyName = "Here the new Azure AD Policy Name"

###########################################################
################### Connect to Azure AD ###################
###########################################################

$credObject = Get-AutomationPSCredential -Name $AutomationCred
"[INFO] Logging in to Azure AD..."
try {
    Connect-AzureAD -Credential $credObject
    "[SUCCESS] Log on Azure AD..."
}
catch {
    "[ERROR] Unable to log in Azure AD"
}

###########################################################
################### Connect to Azure Az ###################
###########################################################

try {
    $servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
    "[INFO] Logging in to Azure..."
    $connectionResult =  Connect-AzAccount -Tenant $servicePrincipalConnection.TenantID `
                             -ApplicationId $servicePrincipalConnection.ApplicationID   `
                             -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `
                             -ServicePrincipal
    "[SUCCESS] Log on Azure with $connectionName..."
}
catch {
    "[ERROR] Unable to log in Azure"
}

############################################################
################### Storage Account Step ###################
############################################################

# Storage Account context
try {
    $storageAccContext = New-AzStorageContext -ConnectionString $storageAccConnectionString
    "[SUCCESS] Storage Account Context created"
}
catch {
    "[ERROR] Unable to create Storage Account Context"
}

# Set Temp folder
$TempPath = $env:temp + "\AADB2B-DomainsWhiteList.csv"

# Download the csv file content from Storage Account and store it on Temp path
try {
    $blob = Get-AzStorageBlob -Container $containerName -Blob $blobName -Context $storageAccContext
    Get-AzStorageBlobContent -CloudBlob $blob.ICloudBlob -Destination $TempPath -Context $storageAccContext
    "[SUCCESS] Storage Account content downloaded on Temp Path"
}
catch {
    "[ERROR] Unable to download Storage Account content and store it on Temp Path"
}

############################################################
################### Azure AD Policy Step ###################
############################################################

# Clean Variables
$csv = ""
$DomainsList = ""
$FinalDomainsList = ""

# Azure AD B2B Policy Default Settings
$defaultjson = @"
{"B2BManagementPolicy":{"InvitationsAllowedAndBlockedDomainsPolicy":{"BlockedDomains":[]},"AutoRedeemPolicy":{"AdminConsentedForUsersIntoTenantIds":[],"NoAADConsentForUsersFromTenantsIds":[]}}}
"@

# Get the Default Azure AD Policy for B2B
try {
    $defaultpolicy = Get-AzureADPolicy | Where-Object {$_.Type -eq 'B2BManagementPolicy' -and $_.DisplayName -eq 'B2BManagementPolicy'}
    "[SUCCESS] Get the Default Azure AD Policy for B2B"
}
catch {
    "[ERROR] Unable to get the Default Azure AD Policy for B2B"
}

# Check Azure AD custom Policy Presence
$CustomAzureADPolicyPresence = (Get-AzureADPolicy | Where-Object {$_.DisplayName -eq $TargetAzureADPolicyName })

If ($CustomAzureADPolicyPresence -eq $null){
    "[INFO] Azure AD Custom Policy for B2B named: $TargetAzureADPolicyName is not already created"
    # Create a New Azure AD Custom Policy
    try {
            New-AzureADPolicy -DisplayName $TargetAzureADPolicyName -Definition $defaultjson -IsOrganizationDefault $false -Type B2BManagementPolicy
            "[SUCCESS] New Custom Azure AD Policy for B2B named: $TargetAzureADPolicyName is now created"
    }
    catch {
            "[ERROR] Unable to create the New Custom Azure AD Policy for B2B named: $TargetAzureADPolicyName"
    }
}
else {
    "[INFO] Azure AD Custom Policy for B2B named: $TargetAzureADPolicyName is already present on the configuration"
}

# Disable the default Policy
try {
    Set-AzureADPolicy -Definition $defaultjson -Id $defaultpolicy.Id -IsOrganizationDefault $false
    "[SUCCESS] The default Azure AD Policy for B2B is now Disable"
}
catch {
    "[ERROR] Unable to disable the default Azure AD Policy for B2B"
}

# Enable the new policy that we want to put with Domains from CSV file
## Import CSV content
$csv = Get-Content -Path $TempPath
"[INFO] Content of Domains WhiteList file is $csv "
## Add " between each entry and ,
foreach ($record in $csv) {
    $DomainsList += '"' + ($record) + '"' + ','
}
#For Debug "List with quote and virgule: $DomainsList"

## Delete the last 2 characters for domain list (delete , and ")
$FinalDomainsList = $DomainsList.Substring(0,$DomainsList.Length-2)
#For Debug "Final List: $FinalDomainsList"

## Build the JSON 
$TargetJSONcontent = @"
    {
        "B2BManagementPolicy": {
          InvitationsAllowedAndBlockedDomainsPolicy: {
            "AllowedDomains": [
              $($FinalDomainsList)"
            ],
            "BlockedDomains": [
              
            ]
          }
        }
      }
"@
"[INFO] There is the final JSON $TargetJSONcontent"

# Get custom Target Azure AD Policy settings
try {
    $targetpolicy = Get-AzureADPolicy | Where-Object {$_.Type -eq 'B2BManagementPolicy' -and $_.DisplayName -eq $TargetAzureADPolicyName}
    "[SUCCESS] Able to get settings for Azure AD Policy $TargetAzureADPolicyName"
    "[INFO] Custom Azure AD Policy ID is '$targetpolicy.ID' "
}
catch {
    "[ERROR] Unable to get settings for Azure AD Policy $TargetAzureADPolicyName"
}

# Set custom Target Azure AD Policy as Active with JSON settings on Domains WhiteList
try {
    Set-AzureADPolicy -Definition $TargetJSONcontent -Id $targetpolicy.Id -IsOrganizationDefault $true
    "[SUCCESS] The custom Target Azure AD Policy for B2B named $TargetAzureADPolicyName is now enable with Domains WhiteList from CSV file"
}
catch {
    "[ERROR] Unable to set the custom Target Azure AD Policy for B2B named $TargetAzureADPolicyName as default and active B2B policy"
}

Result

Storage Account content:

After Runbook execution look on the result.

Enjoy 😉