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.
Item | Comment | Rely on AAD Whitelist |
---|
Azure AD Applications | Partner user account must be part of the allowed domains list. | YES |
SharePoint | Preview – 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 |
Teams | All 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 settings | NO |
Azure DevOps | Partner user account must be part of the allowed domains list. | YES |
Dynamics 365 | Partner 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 |
PowerBI | Settings made in Azure AD replicates in Power BI admin center. | YES |
Power Apps | External users not supported yet. This feature is currently being worked on. | NO |
Flow | Flow doesn’t support yet guest users for sharing. | NO |
Yammer | Yammer doesn’t rely on Azure AD settings | NO |
Planner | Built on Office 365 groups meaning that the user you want to add to Planner needs to already exists in Azure AD. | YES |
Stream | Built on Office 365 groups meaning that the user you want to add to Stream needs to already exists in Azure AD. | YES |
Office 365 Groups | You can add external users on O365 Groups if the domain is allowed on Azure AD settings. | YES |
Azure B2B Direct federation | Federation 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
- 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:
- Connect on Azure AD with Automation Credentials ($AutomationCred)
- Connect on Azure with Automation AzureRunAsConnection
- Download the content of the Storage Account file
- Get the default B2B Azure AD Policy name
- Create a custom Azure AD Policy (after a check on presence)
- Disable the default B2B Azure AD Policy
- Build a custom JSON for the policy from Storage Account content
- 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 😉