Microsoft Entra
Overview
This integration uses both a bash script and PowerShell script to execute. The PowerShell script manages conditional access policies and named locations in Microsoft Azure using the Microsoft Graph API. It supports adding, removing, and flushing IP-based named locations within conditional access policies. The script is designed to optimize API calls by caching policy and location data.
Prerequisites
• Azure PowerShell Module
• Microsoft Graph PowerShell Module
• Azure AD app registration with appropriate permissions to manage conditional access policies
Parameters
• **`$action`**:action: Specifies the action to perform. Acceptable values are `add`, `remove`, and `flush`.
• **`$acl`**:acl: Name of the access control list (ACL) to modify.
• **`$ip`**:ip: IP address to add or remove from the named location.
Functions
Get-ClientSecret
powershell
functionGet-ClientSecret {
paramGet-ClientSecret(
{param ([string]string]$secretName))
returnreturn"your-secure-secret" # Placeholder for testing}Retrieves a client secret securely from a storage system like Azure Key Vault.
Get-NamedLocationId
```powershell
functionGet-NamedLocationId {
Get-NamedLocationId {param ([string]string]$displayName)
displayName)# Implementation...}```Retrieves the ID of a named location by its display name, caching the result to minimize repeated API calls.
Update-NamedLocationTrustStatus
```powershell
functionUpdate-NamedLocationTrustStatus {
paramUpdate-NamedLocationTrustStatus(
{param ([string]string]$NamedLocationId,
NamedLocationId,[bool]$IsTrusted,[string]$LocationType[bool]$IsTrusted,
=[string]$LocationType ="#microsoft.graph.ipNamedLocation"))# Implementation...}```
Updates the trust status of a named location.Get-ConditionalAccessPolicy
```powershell
functionGet-ConditionalAccessPolicy {
Get-ConditionalAccessPolicy {param ([string]string]$displayName)
displayName)# Implementation...}```
Retrieves a conditional access policy by its display name, caching the result to minimize repeated API calls.Update-ConditionalAccessPolicy
```powershell
function Update-ConditionalAccessPolicy {
param (
[string]$PolicyId,
[array]$LocationIds
)
# Implementation...
}
```Updates a conditional access policy with a list of named location IDs.
Add-NamedLocationAndModifyPolicy
```powershell
function Add-NamedLocationAndModifyPolicy {
# Implementation...
}
```Adds a named location if it does not exist and updates the specified conditional access policy to include the new named location.
Remove-LocationFromPolicies
```powershell
function Remove-LocationFromPolicies {
param ([string]$NamedLocationId)
# Implementation...
}
```Removes a named location from all conditional access policies and then deletes the named location.
Flush-NamedLocationsFromPolicy
```powershell
function Flush-NamedLocationsFromPolicy {
# Implementation...
}
```Removes all named locations matching the pattern `knocknoc_*` from all conditional access policies and then deletes these named locations.
Usage
Add a Named Location and Modify Policy
```powershell
.\Script.ps1 -action add -acl "aclName" -ip "192.168.1.1"
```Adds the specified IP address as a named location and modifies the specified access control list to include this named location.
Remove a Named Location from Policies
```powershell
.\Script.ps1 -action remove -acl "aclName" -ip "192.168.1.1"
```Removes the specified IP address from the named locations in all policies.
Flush Named Locations from Policies
```powershell
.\Script.ps1 -action flush -acl "aclName"
```Removes all named locations that match the pattern `knocknoc_*` from all policies.
Authentication
The script requires authentication with Microsoft Graph API. This is achieved by creating a PSCredential object using the client ID, tenant ID, and client secret.
Example
```powershell
param (
[ValidateSet('add', 'remove', 'flush')][string]$action,
[string]$acl,
[ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')][string]$ip
)
# Authentication
$ClientId = "client"
$TenantId = "tenant"
$ClientSecret = "secret"
try {
$ClientSecretPass = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ClientId, $ClientSecretPass
Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential -NoWelcome
} catch {
Write-Host "Failed to authenticate with Microsoft Graph. Error: $_"
exit 1
}
# Action map
$actionMap = @{
'add' = { Add-NamedLocationAndModifyPolicy }
'remove' = {
$NamedLocationId = Get-NamedLocationId "knocknoc_$ip"
if ($null -eq $NamedLocationId) {
Write-Host "Named location 'knocknoc_$ip' not found."
} else {
Remove-LocationFromPolicies -NamedLocationId $NamedLocationId
}
}
'flush' = { Flush-NamedLocationsFromPolicy }
}
# Execute action
if ($actionMap.ContainsKey($action)) {
& $actionMap[$action]
} else {
Write-Host "Invalid action specified"
}
```Script
param ([ValidateSet('add', 'remove', 'flush')][string]$action,[string]$acl,[ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')][string]$ip)
# Caching policy and named location data to reduce repeated API calls$policyCache = @{}$locationCache = @{}
# Securely retrieve client secretfunction Get-ClientSecret {param ([string]$secretName)# Implementation for retrieving secret from a secure storage like Azure Key Vaultreturn "your-secure-secret" # Placeholder for testing}
# Helper functionsfunction Get-NamedLocationId {param ([string]$displayName)if ($locationCache.ContainsKey($displayName)) {return $locationCache[$displayName]}
try {$namedLocations = Get-MgIdentityConditionalAccessNamedLocation -Filter "DisplayName eq '$displayName'"} catch {Write-Host "Error occurred while querying named locations: $_"return $null}
if ($null -eq $namedLocations -or $namedLocations.Count -eq 0) {Write-Host "Named location '$displayName' not found."return $null}
$namedLocation = $namedLocations | Select-Object -First 1$namedLocationId = $namedLocation.Id
if ([string]::IsNullOrEmpty($namedLocationId)) {Write-Host "Named location ID is null or empty."return $null}
$locationCache[$displayName] = $namedLocationIdreturn $namedLocationId}
function Update-NamedLocationTrustStatus {param ([string]$NamedLocationId,[bool]$IsTrusted,[string]$LocationType = "#microsoft.graph.ipNamedLocation")
$body = @{"@odata.type" = $LocationTypeIsTrusted = $IsTrusted}
try {Update-MgIdentityConditionalAccessNamedLocation -NamedLocationId $NamedLocationId -BodyParameter $bodyreturn $true} catch {Write-Host "Failed to update trust status for $NamedLocationId. Error: $_"return $false}}
function Get-ConditionalAccessPolicy {param ([string]$displayName)if ($policyCache.ContainsKey($displayName)) {return $policyCache[$displayName]}
$policy = Get-MgIdentityConditionalAccessPolicy -Filter "DisplayName eq '$displayName'"if ($null -eq $policy) {Write-Host "Conditional Access Policy '$displayName' not found."return $null}$policyCache[$displayName] = Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $policy.Idreturn $policyCache[$displayName]}
function Update-ConditionalAccessPolicy {param ([string]$PolicyId,[array]$LocationIds)
$validIds = $LocationIds | Where-Object { $_ -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' }if ($validIds.Count -eq 0) {Write-Host "No valid location IDs to update for policy $PolicyId."return}
try {$params = @{Conditions = @{Locations = @{IncludeLocations = $validIds}}}Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $PolicyId -BodyParameter $paramsWrite-Host "Policy $PolicyId updated successfully."} catch {Write-Host "Failed to update policy $PolicyId. Error: $_"}}
function Add-NamedLocationAndModifyPolicy {$namedLocationId = Get-NamedLocationId "knocknoc_$ip"$policy = Get-ConditionalAccessPolicy "knocknoc_$acl"
if ($null -eq $namedLocationId) {$params = @{"@odata.type" = "#microsoft.graph.ipNamedLocation"DisplayName = "knocknoc_$ip"IsTrusted = $trueipRanges = @(@{"@odata.type" = "#microsoft.graph.iPv4CidrRange"cidrAddress = "$ip/32"})}$newLocation = New-MgIdentityConditionalAccessNamedLocation -BodyParameter $params$namedLocationId = $newLocation.Id$locationCache["knocknoc_$ip"] = $namedLocationIdWrite-Host "Created new named location 'knocknoc_$ip' with ID $namedLocationId."} else {Write-Host "Named location 'knocknoc_$ip' already exists. No action required."return}
if ($null -eq $policy) {Write-Host "Policy not found"return}
$currentLocations = @($policy.Conditions.Locations.IncludeLocations)if ($namedLocationId -notin $currentLocations) {$updatedLocations = $currentLocations + $namedLocationIdUpdate-ConditionalAccessPolicy -PolicyId $policy.Id -LocationIds $updatedLocations}}
function Remove-LocationFromPolicies {param ([string]$NamedLocationId)
if ([string]::IsNullOrEmpty($NamedLocationId)) {Write-Host "Invalid named location ID."return}
$allPolicies = Get-MgIdentityConditionalAccessPolicy$policiesToUpdate = @()
foreach ($policy in $allPolicies) {if ($NamedLocationId -in $policy.Conditions.Locations.IncludeLocations) {$updatedLocations = $policy.Conditions.Locations.IncludeLocations | Where-Object { $_ -ne $NamedLocationId }$policiesToUpdate += [PSCustomObject]@{ PolicyId = $policy.Id; LocationIds = $updatedLocations }}}
foreach ($policyUpdate in $policiesToUpdate) {Update-ConditionalAccessPolicy -PolicyId $policyUpdate.PolicyId -LocationIds $policyUpdate.LocationIds}
# Validate updates$allPolicies = Get-MgIdentityConditionalAccessPolicyforeach ($policy in $allPolicies) {if ($NamedLocationId -in $policy.Conditions.Locations.IncludeLocations) {Write-Host "Location $NamedLocationId still exists in policy $($policy.Id) after update attempt."return}}
# Update trust status before deletionif (-not (Update-NamedLocationTrustStatus -NamedLocationId $NamedLocationId -IsTrusted $false)) {Write-Host "Failed to update trust status for $NamedLocationId. Cannot proceed with deletion."return}
try {Remove-MgIdentityConditionalAccessNamedLocation -NamedLocationId $NamedLocationIdWrite-Host "Named location $NamedLocationId removed successfully."} catch {Write-Host "Failed to remove named location $NamedLocationId. Error: $_"}}
function Flush-NamedLocationsFromPolicy {$namedLocations = Get-MgIdentityConditionalAccessNamedLocation | Where-Object { $_.DisplayName -like "*knocknoc_*" }
$jobs = @()$throttleLimit = 5 # Adjust the throttle limit as needed
foreach ($location in $namedLocations) {$jobs += Start-Job -ScriptBlock {param ($location,$ClientId,$TenantId,$ClientSecret)
function Update-NamedLocationTrustStatus {param ([string]$NamedLocationId,[bool]$IsTrusted,[string]$LocationType = "#microsoft.graph.ipNamedLocation")
$body = @{"@odata.type" = $LocationTypeIsTrusted = $IsTrusted}
try {Update-MgIdentityConditionalAccessNamedLocation -NamedLocationId $NamedLocationId -BodyParameter $bodyreturn $true} catch {Write-Host "Failed to update trust status for $NamedLocationId. Error: $_"return $false}}
function Remove-LocationFromPolicies {param ([string]$NamedLocationId)
$allPolicies = Get-MgIdentityConditionalAccessPolicy$policiesToUpdate = @()
foreach ($policy in $allPolicies) {if ($NamedLocationId -in $policy.Conditions.Locations.IncludeLocations) {$updatedLocations = $policy.Conditions.Locations.IncludeLocations | Where-Object { $_ -ne $NamedLocationId }$policiesToUpdate += [PSCustomObject]@{ PolicyId = $policy.Id; LocationIds = $updatedLocations }}}
foreach ($policyUpdate in $policiesToUpdate) {Update-ConditionalAccessPolicy -PolicyId $policyUpdate.PolicyId -LocationIds $policyUpdate.LocationIds}
# Validate updates$allPolicies = Get-MgIdentityConditionalAccessPolicyforeach ($policy in $allPolicies) {if ($NamedLocationId -in $policy.Conditions.Locations.IncludeLocations) {Write-Host "Location $NamedLocationId still exists in policy $($policy.Id) after update attempt."return $false}}return $true}
function Update-ConditionalAccessPolicy {param ([string]$PolicyId,[array]$LocationIds)
$validIds = $LocationIds | Where-Object { $_ -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' }if ($validIds.Count -eq 0) {Write-Host "No valid location IDs to update for policy $PolicyId."return}
try {$params = @{Conditions = @{Locations = @{IncludeLocations = $validIds}}}Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $PolicyId -BodyParameter $params} catch {Write-Host "Failed to update policy $PolicyId. Error: $_"}}
# Re-establish connection inside job$ClientSecretPass = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ClientId, $ClientSecretPass
Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential -NoWelcome
$trustUpdateSuccess = Update-NamedLocationTrustStatus -NamedLocationId $location.Id -IsTrusted $false
if ($trustUpdateSuccess) {$removeSuccess = $false$retryCount = 0$maxRetries = 5
while (-not $removeSuccess -and $retryCount -lt $maxRetries) {$removeSuccess = Remove-LocationFromPolicies -NamedLocationId $location.Idif (-not $removeSuccess) {Start-Sleep -Seconds 5 # Adjust sleep time as needed$retryCount++}}
if ($removeSuccess) {try {Remove-MgIdentityConditionalAccessNamedLocation -NamedLocationId $location.Id} catch {Write-Host "Failed to remove named location $location.Id after updates. Error: $_"}} else {Write-Host "Could not confirm removal from policies for location $location.Id."}} else {Write-Host "Skipping deletion due to failed trust status update for location $location.Id."}} -ArgumentList $location, $ClientId, $TenantId, $ClientSecret
while ($jobs.Count -ge $throttleLimit) {$jobs | ForEach-Object { Wait-Job -Job $_ }$jobs | ForEach-Object { Receive-Job -Job $_ }$jobs | ForEach-Object { Remove-Job -Job $_ }$jobs = $jobs | Where-Object { $_.State -ne 'Completed' }Start-Sleep -Seconds 1}}
# Wait for all remaining jobs to complete$jobs | ForEach-Object { Wait-Job -Job $_ }$jobs | ForEach-Object { Receive-Job -Job $_ }$jobs | ForEach-Object { Remove-Job -Job $_ }}
# Authentication and connection setup$ClientId = "client"$TenantId = "tenant"$ClientSecret = "secret"
try {$ClientSecretPass = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ClientId, $ClientSecretPass
Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential -NoWelcome} catch {Write-Host "Failed to authenticate with Microsoft Graph. Error: $_"exit 1}
# Define a hashtable with actions as keys and their corresponding functions as values$actionMap = @{'add' = { Add-NamedLocationAndModifyPolicy }'remove' = {$NamedLocationId = Get-NamedLocationId "knocknoc_$ip"if ($null -eq $NamedLocationId) {Write-Host "Named location 'knocknoc_$ip' not found."} else {Remove-LocationFromPolicies -NamedLocationId $NamedLocationId}}'flush' = { Flush-NamedLocationsFromPolicy }}
# Check if the action exists in the hashtable and invoke the corresponding functionif ($actionMap.ContainsKey($action)) {& $actionMap[$action]} else {Write-Host "Invalid action specified"}Notes
• Ensure that the Azure and Microsoft Graph PowerShell modules are installed and imported in your environment.
• Adjust the throttle limit for concurrency as needed.
• Securely store and retrieve secrets using Azure Key Vault or another secure method.