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 secret
function Get-ClientSecret {
param (
[string]$secretName
)
# Implementation for retrieving secret from a secure storage like Azure Key Vault
return "your-secure-secret" # Placeholder for testing
}
# Helper functions
function 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] = $namedLocationId
return $namedLocationId
}
function Update-NamedLocationTrustStatus {
param (
[string]$NamedLocationId,
[bool]$IsTrusted,
[string]$LocationType = "#microsoft.graph.ipNamedLocation"
)
$body = @{
"@odata.type" = $LocationType
IsTrusted = $IsTrusted
}
try {
Update-MgIdentityConditionalAccessNamedLocation -NamedLocationId $NamedLocationId -BodyParameter $body
return $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.Id
return $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 $params
Write-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 = $true
ipRanges = @(
@{
"@odata.type" = "#microsoft.graph.iPv4CidrRange"
cidrAddress = "$ip/32"
}
)
}
$newLocation = New-MgIdentityConditionalAccessNamedLocation -BodyParameter $params
$namedLocationId = $newLocation.Id
$locationCache["knocknoc_$ip"] = $namedLocationId
Write-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 + $namedLocationId
Update-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-MgIdentityConditionalAccessPolicy
foreach ($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 deletion
if (-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 $NamedLocationId
Write-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" = $LocationType
IsTrusted = $IsTrusted
}
try {
Update-MgIdentityConditionalAccessNamedLocation -NamedLocationId $NamedLocationId -BodyParameter $body
return $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-MgIdentityConditionalAccessPolicy
foreach ($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.Id
if (-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 function
if ($actionMap.ContainsKey($action)) {
& $actionMap[$action]
} else {
Write-Host "Invalid action specified"
}