Securing Azure AD quickly and programatically

I like to think of Azure AD as the front door to a house. Whilst you can have a perfectly secure wireless network configured with filtering and all of the other fun bits, if you leave the front door open, they’ll just steal your computer!

This is why securing Azure AD should be the first thing which is completed on any cloud journey, whether that is Intune, shifting to Azure or even just Exchange Online.

So, if you are setting up a new environment for yourself, your company, or have to do these regularly for customers, wouldn’t it be nice to have a script do the work for you!

Well, here it is: Github Linky

This script will:

  • Create a conditional access policy to block specified locations (including creating the locations)
  • Create a conditional access policy to require MFA except when on-prem (including creating the on-prem location)
  • Create a conditional access policy to block legacy authentication
  • Create a conditional access policy to require MFA for admins
  • Create a conditional access policy to require MFA for guests
  • Create a Break Glass account exempt from all of the above
  • Create Azure Admins Group
  • Create Azure PIM role for global admin (only if P2 licensed)

When running the script, you will be prompted for the WAN IP range to create the on-prem MFA exception.

At script completion, the Breakglass account details will be provided to be stored in a safe place, ideally an offline physical copy.

In Azure AD we are creating a group for device admins and the breakglass with a complex 20 character random password:

#Create Azure AD Groups
#Create Admins Groups
$admingrp = New-MGGroup -DisplayName "Azure-Global-Admins" -Description "Azure Global Admins (PIM Role)" -MailNickName "azureglobaladmins" -SecurityEnabled -IsAssignableToRole

##Create Azure AD Breakglass user
$PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
$bgpassword = Get-RandomPassword -Length 20
$PasswordProfile.Password = $bgpassword
$breakglass = New-MgUser -DisplayName "Azure BreakGlass Account" -PasswordProfile $PasswordProfile -UserPrincipalName "breakglass@$suffix" -AccountEnabled -MailNickName "BreakGlass" -PasswordPolicies "DisablePasswordExpiration"

For The AD Group assignment, we need to check if PIM is enabled as this is AAD P2 only:

##Create PIM if licensed for Global Admins
$uri = "https://graph.microsoft.com/beta/organization"
$tenantdetails = (Invoke-MgGraphRequest -Uri $uri -Method Get -OutputType PSObject).value
$tenantid = $tenantdetails.id
$licensing = $tenantdetails.AssignedPlans
$islicensed = $licensing.ServicePlanId -contains "eec0eb4f-6444-4f95-aba0-50c24d67f998"

Then we configure the role:

$uri = "https://graph.microsoft.com/v1.0/directoryRoles"
$roles = (Invoke-MgGraphRequest -Uri $uri -Method Get -OutputType PSObject).value
$PIMrole = $roles | where-object DisplayName -eq "Global Administrator"

#Create the schedule without an end date
$schedule = New-Object Microsoft.Open.MSGraph.Model.AzureADMSPrivilegedSchedule
$schedule.Type = "Once"
$schedule.StartDateTime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
$schedule.endDateTime = $null
#This bombs out if group isn't fully created so lets wait 30 seconds
start-sleep -s 30
#Create PIM role
#$assign = Open-AzureADMSPrivilegedRoleAssignmentRequest -ProviderId 'aadRoles' -ResourceId $tenantid -RoleDefinitionId $PIMrole.Id -SubjectId $admingrp.id -Type 'adminAdd' -AssignmentState 'Eligible' -schedule $schedule -reason "Baseline Build"
$roleid = $PIMrole.id
$principalId = $admingrp.id
$starttime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
$params = @{
	Action = "adminAssign"
	Justification = "Grants Breakglass access to everything"
	RoleDefinitionId = $roleid
	DirectoryScopeId = "/"
	PrincipalId = $principalId
	ScheduleInfo = @{
		StartDateTime = $starttime
		Expiration = @{
			Type = "NoExpiration"
		}
	}
}

$assign = New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params

This next section you may want to modify according to your organisation needs, this is a list of fully blocked locations, you can be extra strict in here if staff rarely roam, or for a global organisation, it may just be KP listed:

$params = @{
    "@odata.type" = "#microsoft.graph.countryNamedLocation"
    DisplayName = "Blocked Locations"
    CountriesAndRegions = @(
        "CN"
        "RU"
        "KP"
        "IN"
    )
    IncludeUnknownCountriesAndRegions = $false
    }
    
New-MgIdentityConditionalAccessNamedLocation -BodyParameter $params

We then need to grab the WAN IP range in a popup box and create the trusted location

##Prompt for WAN IP
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')

$title = 'IP Range'
$msg   = 'Enter your WAN IP Range:'

$ipRanges2 = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)

##Created Trusted Location
$ipRanges = New-Object -TypeName Microsoft.Open.MSGraph.Model.IpRange
$ipRanges.cidrAddress = $ipRanges2
$params = @{
    "@odata.type" = "#microsoft.graph.ipNamedLocation"
    DisplayName = "Trusted IP named location"
    IsTrusted = $true
    IpRanges = @(
        @{
            "@odata.type" = "#microsoft.graph.iPv4CidrRange"
            CidrAddress = $ipRanges
        }
    )
    }
    
    New-MgIdentityConditionalAccessNamedLocation -BodyParameter $params

Now we build the policies, I’ll just add the code for the blocked countries as they all are fairly similar and you can just grab the script for the others. It’s setting the policies as disabled initially, the last thing we want is to drop this script in and immediately lock ourselves out for some reason

#Get Location ID
$location = get-AzureADMSNamedLocationPolicy | where-object DisplayName -eq "Blocked-Locations"
$locationid = $location.id
## Create Conditional Access Policy
$conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet

## All Cloud Apps
$conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationCondition
$conditions.Applications.IncludeApplications = "All"
 
##All users except the Azure AD admins role and group
$conditions.Users = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessUserCondition
$conditions.Users.IncludeUsers = "All"
$conditions.Users.ExcludeUsers = $breakglass.ObjectID
$conditions.Locations = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessLocationCondition
$conditions.Locations.IncludeLocations = $locationid
 
##All devices
$conditions.ClientAppTypes = "All" 

$controls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls

##Block
$controls._Operator = "OR"
##Require device compliance
$controls.BuiltInControls = "block"

$name = "Conditional Access - Block Specific Locations"

##Disable initially just in case
$state = "Disabled"
 
New-MgIdentityConditionalAccessPolicy `
    -DisplayName $name `
    -State $state `
    -Conditions $conditions `
    -GrantControls $controls

For the MFA for Admins policy, I have included the GUID for admin roles within Azure, but again, amend as appropriate

$conditions.Users.IncludeRoles = @('62e90394-69f5-4237-9190-012177145e10', 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c', '29232cdf-9323-42fd-ade2-1d097af3e4de', 'b1be1c3e-b65d-4f19-8427-f6fa0d97feb9', '194ae4cb-b126-40b2-bd5b-6091b380977d', '729827e3-9c14-49f7-bb1b-9608f156bbb8', '966707d0-3269-4727-9be2-8c3a10f19b9d', 'b0f54661-2d74-4c50-afa3-1ec803f12efe', 'fe930be7-5e62-47db-91af-98c3a49a38b1')

After that’s all done, you get a popup with the break glass credentials:

### POPUP BG Details
Add-Type -AssemblyName PresentationCore,PresentationFramework
$username = $breakglass.UserPrincipalName
$msgBody = "Breakglass Details

Username: $username
Password: $bgpassword"
[System.Windows.MessageBox]::Show($msgBody)

Just remember after running this to do a What-If on the conditional access policies. Once you’re happy they are configured ok, turn them on. Worst case, the break-glass is excluded from all of them.

Happy coding!!!

Leave a Comment