SophosEndpoints.psm1
################################################################################################################## ### Global constants ################################################################################################################## $baseServiceUrl = "https://endpoints.office.com/endpoints/Worldwide/?ClientRequestId={b10c5ed1-bad1-445f-b386-b919946339a7}" $exceptionUrl = "/objects/http/exception/" $networkUrl = "/objects/network/network/" $netsGroupUrl = "/objects/network/group/" $commentIp = " autocreated on " + (Get-Date).ToString("yyyy-MM-dd") $commentException = " autocreated on " + (Get-Date).ToString("yyyy-MM-dd") $tokenBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes("token:" + "dummytoken")) $log = "" $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" ################################################################################################################## ### Functions to interact with sophos ################################################################################################################## function Add-NetToUtm { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCustomObject] $netToAdd ) $body = $netToAdd | convertto-json -compress $response = Invoke-RestMethod -Uri $networkUrl -Method Post -Headers $headers -Body $body | convertto-json return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + " - Adding Net:`r`n" + $response } function Get-ExceptionFromUtm { $excList = Invoke-RestMethod -Uri $exceptionUrl -Method Get -Headers $headers $exc = $excList | Where-Object {$_.comment -like $UtmExceptionPrefix + '*' -and $_.name -like $UtmExceptionPrefix + '*'} return $exc } function Get-NetsFromUtm { $utmNetList = Invoke-RestMethod -Uri $networkUrl -Method Get -Headers $headers $m365NetList = $utmNetList | Where-Object {$_.comment -like $UtmIpPrefix + '*' -and $_.name -like $UtmIpPrefix + '*'} return $m365NetList; } function Get-NetsGroupFromUtm { $utmNetList = Invoke-RestMethod -Uri $netsGroupUrl -Method Get -Headers $headers $netsGroup = $utmNetList | Where-Object {$_.comment -like $UtmIpPrefix + '*' -and $_.name -like $UtmIpPrefix + '*'} return $netsGroup; } function Remove-NetFromUtm { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCustomObject] $netToDelete ) $networkUrlDel = $networkUrl + $netToDelete._ref $response = Invoke-RestMethod -Uri $networkUrlDel -Method Delete -Headers $headers | convertto-json return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + " - Deleting Net:`r`n" + $response } function Set-ExceptionInUtm { param( [Parameter(Mandatory = $false)] [Object[]] $inUtm, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [Object[]] $inWeb ) if($null -eq $inUtm) { return Set-ExceptionPost -exception $inWeb } else { return Set-ExceptionPatch -inUtm $inUtm -inWeb $inWeb } } function Set-ExceptionPatch { param( [Parameter(Mandatory = $false)] [Object[]] $inUtm, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [Object[]] $inWeb ) $exceptionUrlPatch = $exceptionUrl + $inUtm._ref $body = $inWeb | convertto-json $response = Invoke-RestMethod -Uri $exceptionUrlPatch -Method Patch -Headers $headers -Body $body | convertto-json return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + " - Patching existing Exception:`r`n" + $response } function Set-ExceptionPost { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCustomObject] $exception ) $body = $exception | convertto-json -compress $response = Invoke-RestMethod -Uri $exceptionUrl -Method Post -Headers $headers -Body $body | convertto-json return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + " - Creating Exception:`r`n" + $response } function Set-NetsGroupUtm { param( [Parameter(Mandatory = $false)] [Object] $group, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [Object[]] $nets ) if($null -eq $group) { return Set-NetsGroupPost -nets $nets } else { return Set-NetsGroupPatch -group $group -nets $nets } } function Set-NetsGroupPatch { param( [Parameter(Mandatory = $false)] [Object] $group, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [Object[]] $nets ) $netsGroupUrlPatch = $netsGroupUrl + $group._ref $members = @() foreach($net in $nets) { $members = $members += $net._ref } $group = [ordered]@{comment = $commentIp; name = $UtmIpPrefix + " - All Nets Group"; members = $members} $body = $group | convertto-json -compress $response = Invoke-RestMethod -Uri $netsGroupUrlPatch -Method Patch -Headers $headers -Body $body | convertto-json return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + " - Patching existing Network Group:`r`n" + $response } function Set-NetsGroupPost { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCustomObject] $nets ) $members = @() foreach($net in $nets) { $members = $members += $net._ref } $group = [ordered]@{comment = $commentIp; name = $UtmIpPrefix + " - All Nets Group"; members = $members} $body = $group | convertto-json -compress $response = Invoke-RestMethod -Uri $netsGroupUrl -Method Post -Headers $headers -Body $body | convertto-json return (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") + " - Creating Network Group:`r`n" + $response } function Set-NetsInUtm { param( [Parameter(Mandatory = $false)] [Object[]] $inUtm, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [Object[]] $inWeb ) $response = "" if ($null -ne $inUtm) { $input = [System.Linq.Enumerable]::ToList([psobject[]]$inUtm) } $difference = [System.Linq.Enumerable]::ToList([psobject[]]$inWeb) $diffs = Compare-Object -ReferenceObject $input -DifferenceObject $difference -IncludeEqual -Property @('address', 'netmask') -PassThru $toDelete = $diffs | Where-Object {$_.SideIndicator -eq "<="} | Select-Object -Property * -ExcludeProperty SideIndicator $toAdd = $diffs | Where-Object {$_.SideIndicator -eq "=>"} | Select-Object -Property * -ExcludeProperty SideIndicator foreach($netAdd in $toAdd) { $response += Add-NetToUtm -netToAdd $netAdd Start-Sleep -Seconds 1 } foreach($netDelete in $toDelete) { $response += Remove-NetFromUtm -netToDelete $netDelete Start-Sleep -Seconds 1 } return $response } function Set-EndpointsInUtm { <# .SYNOPSIS Set networks and web protection exception in Sophos UTM for Microsoft 365 connectivity .DESCRIPTION This function will access updated information from the Office 365 IP Address and URL web service. It will create, update, or delete networks and web protection exceptions in Sophos UTM with these data to prioritize Microsoft 365 Urls for better access to the service. .PARAMETER Instance The service instance inside Microsoft 365. .PARAMETER ClientRequestId The client request id to connect to the web service to query up to date Urls. .PARAMETER UtmApiUrl The URL of the Sophos UTM Api. .PARAMETER UtmApiKey The Api Key for the Sophos UTM. .PARAMETER UtmIpPrefix The prefix for naming new networks in the Sophos UTM. .PARAMETER UtmExceptionPrefix The prefix for naming new exception in the Sophos UTM. .PARAMETER UtmExceptionDisabledChecks The checks that will be disabled in the web protection exception. .PARAMETER TenantName The tenant name to replace wildcard Urls in the webservice. .PARAMETER ServiceAreas The service areas to filter endpoints by in the webservice. .PARAMETER LogFilePath The file to print the logs to. .EXAMPLE Set-EndpointsInUtm -UtmApiUrl "https://sophos.testlab.live:4444/api" -UtmApiKey "kjAHGansdzyPdsYhmILKgOWsh" -TenantName testlab -LogFilePath "Set-EndpointsInUtm.log" .EXAMPLE Set-EndpointsInUtm -ClientRequestId b10c5ed1-bad1-445f-b386-b919946339a7 -UtmIpPrefix "O365 Network" -UtmExceptionPrefix "O365 Exception" -UtmApiUrl "https://sophos.testlab.live:4444/api" -UtmApiKey "kjAHGansdzyPdsYhmILKgOWsh" .EXAMPLE Set-EndpointsInUtm -UtmApiUrl "https://sophos.testlab.live:4444/api" -UtmApiKey "kjAHGansdzyPdsYhmILKgOWsh" -TenantName testlab -UtmExceptionDisabledChecks @('av', 'cache', 'certcheck', 'certdate', 'check_max_download', 'content_removal', 'contenttype_blacklist', 'extensions', 'log_access', 'log_blocked', 'patience', 'ssl_scanning', 'url_filter', 'user_auth') #> [CmdletBinding()] Param ( [Parameter(Mandatory = $false)] [ValidateSet('Worldwide', 'Germany', 'China', 'USGovDoD', 'USGovGCCHigh')] [String] $Instance = "Worldwide", [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [guid] $ClientRequestId = [guid]::NewGuid(), [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String] $UtmApiUrl = "https://sophos:4444/api", [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $UtmApiKey, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String] $UtmIpPrefix = 'Microsoft365 Net', [Parameter(Mandatory = $false)] [String] $UtmExceptionPrefix = 'Microsoft365 Exception', [Parameter(Mandatory = $false)] [ValidateSet('av', 'cache', 'certcheck', 'certdate', 'check_max_download', 'content_removal', 'contenttype_blacklist', 'extensions', 'log_access', 'log_blocked', 'patience', 'ssl_scanning', 'url_filter', 'user_auth')] [string[]] $UtmExceptionDisabledChecks = @('ssl_scanning', 'user_auth'), [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $TenantName, [Parameter(Mandatory = $false)] [ValidateSet('Exchange', 'Skype', 'SharePoint', 'Common')] [string[]] $ServiceAreas, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $LogFilePath ) # Update constants $script:baseServiceUrl = "https://endpoints.office.com/endpoints/$Instance/?ClientRequestId={$ClientRequestId}" $script:exceptionUrl = $UtmApiUrl + "/objects/http/exception/" $script:networkUrl = $UtmApiUrl + "/objects/network/network/" $script:netsGroupUrl = $UtmApiUrl + "/objects/network/group/" $script:commentIp = $UtmIpPrefix + " autocreated on " + (Get-Date).ToString("yyyy-MM-dd") $script:commentException = $UtmExceptionPrefix + " autocreated on " + (Get-Date).ToString("yyyy-MM-dd") $script:tokenBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes("token:" + $UtmApiKey)) $script:log = "" if($script:headers.'Accept') { $script:headers.Remove('Accept') >$null } $script:headers.add("Accept", "application/json") if($script:headers.'Content-Type') { $script:headers.Remove('Content-Type') >$null } $script:headers.add("Content-Type", "application/json") if($script:headers.'X-Restd-Err-Ack') { $script:headers.Remove('X-Restd-Err-Ack') >$null } $script:headers.add("X-Restd-Err-Ack", "all") if($script:headers.'Authorization') { $script:headers.Remove('Authorization') >$null } $script:headers.add("Authorization","Basic " + $tokenBase64) # Retrieve the list of nets from sophos $netsSophos = Get-NetsFromUtm # Retrieve the list of nets from web $endpoints = Get-Endpoints $ips = Get-Ips $endpoints $netsWeb = Get-NetsFromWeb $ips | convertto-json | convertfrom-json # Update nets in sophos if ($null -eq $netsSophos) { $log += Set-NetsInUtm -inWeb $netsWeb } else { $log += Set-NetsInUtm -inUtm $netsSophos -inWeb $netsWeb } # Retrieve the list of nets from sophos $netsSophos = Get-NetsFromUtm # Retrieve nets group from sophos $netsGroupSophos = Get-NetsGroupFromUtm # Update nets group in sophos if ($null -eq $netsGroupSophos) { $log += Set-NetsGroupUtm -nets $netsSophos } else { $log += Set-NetsGroupUtm -group $netsGroupSophos -nets $netsSophos } # Retrieve web protection exception from sophos $excSophos = Get-ExceptionFromUtm # Retrieve host list from web $excWeb = Get-ExceptionFromWeb $endpoints # Update web protection exception in sophos if ($null -eq $excSophos) { $log += Set-ExceptionInUtm -inWeb $excWeb } else { $log += Set-ExceptionInUtm -inUtm $excSophos -inWeb $excWeb } # Write Logfile if ($LogFilePath) { $log | Out-File -FilePath $LogFilePath -Append -Encoding ascii } } ################################################################################################################## ### Functions to get and filter endpoints ################################################################################################################## function Get-Endpoints { Param ( [Parameter(Mandatory = $false)] [ValidateSet('Worldwide', 'Germany', 'China', 'USGovDoD', 'USGovGCCHigh')] [String] $Instance = "Worldwide", [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [guid] $ClientRequestId = [guid]::NewGuid() ) $baseServiceUrl = "https://endpoints.office.com/endpoints/$Instance/?ClientRequestId={$ClientRequestId}" $url = $baseServiceUrl if ($TenantName) { $url += "&TenantName=$TenantName" } if ($ServiceAreas) { $url += "&ServiceAreas=" + ($ServiceAreas -Join ",") } return Invoke-RestMethod -Uri $url } function Get-ExceptionFromWeb { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [psobject[]] $endpoints ) $urls = Get-Urls $endpoints $domains = @() foreach($url in $urls) { $domains = $domains += Get-Regex $url } $exception = [ordered]@{aaa = @(); comment = $commentException; domains = $domains; endpoints_groups = @(); name = $UtmExceptionPrefix + " - All Hosts"; networks = @(); operator = "AND"; skiplist = $UtmExceptionDisabledChecks; sp_categories = @(); status = $true; tags = @(); user_agents = @()} return $exception } function Get-Ips { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [psobject[]] $endpoints ) return $endpoints | Where-Object { $_.category -in @("Optimize", "Allow")} | Where-Object { $_.ips } | ForEach-Object { $_.ips } | Sort-Object -Unique } function Get-NetsFromWeb { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [psobject[]] $ips ) $nets = @() foreach($ip in $ips) { if ($ip -match "\.") { $address = $ip.split('/')[0] $netmask = $ip.split('/')[1] $nets = $nets += [ordered]@{address = $address; address6 = ""; comment = $commentIp; interface = ""; name = $UtmIpPrefix + " - " + $address; netmask = $netmask; netmask6 = 0; resolved = $true; resolved6 = $false} } } return $nets } function Get-Regex { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $Fqdn ) return "^https?://" + $Fqdn.Replace(".", "\.").Replace("*", "[A-Za-z0-9.-]*") } function Get-Urls { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [psobject[]] $endpoints ) return $endpoints | Where-Object { $_.category -in @("Optimize", "Allow")} | Where-Object { $_.urls } | ForEach-Object { $_.urls } | Sort-Object -Unique } ################################################################################################################## ### Initialize connection ################################################################################################################## [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Might be needed to resolve 'could not establish trust relationship for the SSL/TLS secure channel' errors # when attempting to authenticate to the UTM if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) { $certCallback = @" using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; public class ServerCertificateValidationCallback { public static void Ignore() { if(ServicePointManager.ServerCertificateValidationCallback ==null) { ServicePointManager.ServerCertificateValidationCallback += delegate ( Object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors ) { return true; }; } } } "@ Add-Type $certCallback } [ServerCertificateValidationCallback]::Ignore() # Sets the TLS level to match sophos $AllProtocols = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12' [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols |