Hi all,
Attached a Pipeline to generate M365 Host Objects in SFOS with Sophos Factory.
You need two Pipelines: One Child Pipeline and one Parent Pipeline:
Get-O365WebserviceUpdates Pipeline:
--- variables: - type: Credential name: firewallcreds key: firewallcreds value: FirewallCreds required: false visible: true default: false allowed_types: - username_password - type: String name: firewallhostname key: firewallhostname value: saleseng.de required: false visible: true default: false steps: - id: GetO365 name: GetO365 type: powershell_script depends: - powershell_install1 properties: content: |- <# Get-O365WebServiceUpdates.ps1 From https://aka.ms/ipurlws v1.1 8/6/2019 DESCRIPTION This script calls the REST API of the Office 365 IP and URL Web Service (Worldwide instance) and checks to see if there has been a new update since the version stored in an existing $Env:TEMP\O365_endpoints_latestversion.txt file in your user directory's temp folder (usually C:\Users\<username>\AppData\Local\Temp). If the file doesn't exist, or the latest version is newer than the current version in the file, the script returns IPs and/or URLs that have been changed, added or removed in the latest update and writes the new version and data to the output file $Env:TEMP\O365_endpoints_data.txt. USAGE Run as a scheduled task every 60 minutes. PARAMETERS n/a PREREQUISITES PS script execution policy: Bypass PowerShell 3.0 or later Does not require elevation #> #Requires -Version 3.0 # web service root URL $ws = "https://endpoints.office.com" # path where output files will be stored $versionpath = $Env:TEMP + "\O365_endpoints_latestversion.txt" $datapath = $Env:TEMP + "\O365_endpoints_data.txt" # fetch client ID and version if version file exists; otherwise create new file and client ID if (Test-Path $versionpath) { $content = Get-Content $versionpath $clientRequestId = $content[0] $lastVersion = $content[1] # Write-Output ("Version file exists! Current version: " + $lastVersion) } else { # Write-Output ("First run! Creating version file at " + $versionpath + ".") $clientRequestId = [GUID]::NewGuid().Guid $lastVersion = "0000000000" @($clientRequestId, $lastVersion) | Out-File $versionpath } # call version method to check the latest version, and pull new data if version number is different $version = Invoke-RestMethod -Uri ($ws + "/version/Worldwide?clientRequestId=" + $clientRequestId) if ($version.latest -gt $lastVersion) { # Write-Host "New version of Office 365 worldwide commercial service instance endpoints detected" # write the new version number to the version file @($clientRequestId, $version.latest) | Out-File $versionpath # invoke endpoints method to get the new data $endpointSets = Invoke-RestMethod -Uri ($ws + "/endpoints/Worldwide?clientRequestId=" + $clientRequestId) # filter results for Allow and Optimize endpoints, and transform these into custom objects with port and category # URL results $flatUrls = $endpointSets | ForEach-Object { $endpointSet = $_ $urls = $(if ($endpointSet.urls.Count -gt 0) { $endpointSet.urls } else { @() }) $urlCustomObjects = @() if ($endpointSet.category -in ("Allow", "Optimize")) { $urlCustomObjects = $urls | ForEach-Object { [PSCustomObject]@{ category = $endpointSet.category; url = $_; tcpPorts = $endpointSet.tcpPorts; udpPorts = $endpointSet.udpPorts; } } } $urlCustomObjects } # IPv4 results $flatIp4s = $endpointSets | ForEach-Object { $endpointSet = $_ $ips = $(if ($endpointSet.ips.Count -gt 0) { $endpointSet.ips } else { @() }) # IPv4 strings contain dots $ip4s = $ips | Where-Object { $_ -like '*.*' } $ip4CustomObjects = @() if ($endpointSet.category -in ("Allow", "Optimize")) { $ip4CustomObjects = $ip4s | ForEach-Object { [PSCustomObject]@{ category = $endpointSet.category; ip = $_; tcpPorts = $endpointSet.tcpPorts; udpPorts = $endpointSet.udpPorts; } } } $ip4CustomObjects } # IPv6 results $flatIp6s = $endpointSets | ForEach-Object { $endpointSet = $_ $ips = $(if ($endpointSet.ips.Count -gt 0) { $endpointSet.ips } else { @() }) # IPv6 strings contain colons $ip6s = $ips | Where-Object { $_ -like '*:*' } $ip6CustomObjects = @() if ($endpointSet.category -in ("Optimize")) { $ip6CustomObjects = $ip6s | ForEach-Object { [PSCustomObject]@{ category = $endpointSet.category; ip = $_; tcpPorts = $endpointSet.tcpPorts; udpPorts = $endpointSet.udpPorts; } } } $ip6CustomObjects } # write output to screen # Write-Output ("Client Request ID: " + $clientRequestId) # Write-Output ("Last Version: " + $lastVersion) # Write-Output ("New Version: " + $version.latest) # Write-Output "" # Write-Output "IPv4 Firewall IP Address Ranges" ($flatIp4s.ip | Sort-Object -Unique) -join "," | Out-String # Write-Output "IPv6 Firewall IP Address Ranges" # ($flatIp6s.ip | Sort-Object -Unique) -join "," | Out-String # Write-Output "URLs for Proxy Server" # ($flatUrls.url | Sort-Object -Unique) -join "," | Out-String # Write-Output ("IP and URL data written to " + $datapath) # write output to data file # Write-Output "Office 365 IP and UL Web Service data" | Out-File $datapath # Write-Output "Worldwide instance" | Out-File $datapath -Append # Write-Output "" | Out-File $datapath -Append # Write-Output ("Version: " + $version.latest) | Out-File $datapath -Append # Write-Output "" | Out-File $datapath -Append # Write-Output "IPv4 Firewall IP Address Ranges" | Out-File $datapath -Append # ($flatIp4s.ip | Sort-Object -Unique) -join "," | Out-File $datapath -Append # Write-Output "" | Out-File $datapath -Append # Write-Output "IPv6 Firewall IP Address Ranges" | Out-File $datapath -Append # ($flatIp6s.ip | Sort-Object -Unique) -join "," | Out-File $datapath -Append # Write-Output "" | Out-File $datapath -Append # Write-Output "URLs for Proxy Server" | Out-File $datapath -Append # ($flatUrls.url | Sort-Object -Unique) -join "," | Out-File $datapath -Append } else { Write-Host "Office 365 worldwide commercial service instance endpoints are up-to-date." } - id: powershell_install1 name: Install PowerShell type: powershell_install depends: [] properties: version: latest - id: ips name: Create Array type: set_variables depends: - GetO365 properties: vars: - key: ips value: '{|steps.GetO365.result.stdout | split('','')|}' - id: p1 name: O365 Builder + Upload type: pipeline depends: - ips properties: pipeline_id: 65095199c75cbe2045b641a5 pipeline_revision_id: latest variables: ips: '{|loop.item|}' index: '{|loop.index|}' firewallcreds: '{|vars.firewallcreds|}' firewallhostname: '{|vars.firewallhostname|}' subnetarray: - 0.0.0.0 - 128.0.0.0 - 192.0.0.0 - 224.0.0.0 - 240.0.0.0 - 248.0.0.0 - 252.0.0.0 - 254.0.0.0 - 255.0.0.0 - 255.128.0.0 - 255.192.0.0 - 255.224.0.0 - 255.240.0.0 - 255.248.0.0 - 255.252.0.0 - 255.254.0.0 - 255.255.0.0 - 255.255.128.0 - 255.255.192.0 - 255.255.224.0 - 255.255.240.0 - 255.255.248.0 - 255.255.252.0 - 255.255.254.0 - 255.255.255.0 - 255.255.255.128 - 255.255.255.192 - 255.255.255.224 - 255.255.255.240 - 255.255.255.248 - 255.255.255.252 - 255.255.255.254 - 255.255.255.255 each: '{|vars.ips|}' outputs: - key: ips value: '{|1|}' layout: elements: - id: GetO365 position: x: -155 'y': -75 links: - sourceId: powershell_install1 sourcePort: bottom targetPort: top vertices: [] - id: powershell_install1 position: x: -155 'y': -160 links: [] - id: ips position: x: -155 'y': 10 links: - sourceId: GetO365 sourcePort: bottom targetPort: top vertices: [] - id: p1 position: x: -155 'y': 100 links: - sourceId: ips sourcePort: bottom targetPort: top vertices: []
O365 Builder Pipeline:
--- variables: - type: String name: ips key: ips required: false visible: true default: false - type: StringArray name: subnetarray key: subnetarray value: - 0.0.0.0 - 128.0.0.0 - 192.0.0.0 - 224.0.0.0 - 240.0.0.0 - 248.0.0.0 - 252.0.0.0 - 254.0.0.0 - 255.0.0.0 - 255.128.0.0 - 255.192.0.0 - 255.224.0.0 - 255.240.0.0 - 255.248.0.0 - 255.252.0.0 - 255.254.0.0 - 255.255.0.0 - 255.255.128.0 - 255.255.192.0 - 255.255.224.0 - 255.255.240.0 - 255.255.248.0 - 255.255.252.0 - 255.255.254.0 - 255.255.255.0 - 255.255.255.128 - 255.255.255.192 - 255.255.255.224 - 255.255.255.240 - 255.255.255.248 - 255.255.255.252 - 255.255.255.254 - 255.255.255.255 required: false visible: false default: true - type: Number name: index key: index required: false visible: true default: false - type: Credential name: firewallcreds key: firewallcreds required: true visible: true default: false allowed_types: - username_password - type: String name: firewallhostname key: firewallhostname required: false visible: true default: false steps: - id: Split name: split type: set_variables depends: [] properties: vars: - key: ip value: '{|vars.ips | split(''/'') | first|}' - key: tmp value: '{|vars.ips | split(''/'') | last|}' - key: number value: '{|vars.index |}' - key: subnet value: '{|vars.subnetarray[vars.tmp]|}' - id: p1 name: Set IP Host type: pipeline depends: - Split properties: pipeline_id: 627ec8fd2038a018ef03de65 pipeline_revision_id: 650a02feeccbfcd0be7f213b variables: credential: '{|vars.firewallcreds|}' hostname: '{|vars.firewallhostname|}' port: 4444 name: '{|"M365_" + vars.number|}' ipFamily: IPv4 hostType: Network ipAddress: '{|vars.ip|}' subnet: '{|vars.subnet|}' groups: - M365 outputs: - key: ip value: '{|vars.ip|}' - key: subnet value: '{|vars.subnet|}' - key: number value: '{|vars.number|}' layout: elements: - id: Split position: x: -195 'y': -60 links: [] - id: p1 position: x: -195 'y': 20 links: - sourceId: Split sourcePort: bottom targetPort: top vertices: []
Create the Pipeline and use the Editor to copy/paste the content in your Pipeline.
Use the same names for your Pipelines like i did.
What you have to do: You have to enter your firewall IP and your Firewall Creds to Factory in the "Get-O365Webservices" Pipeline.
You can also generate a Job, which runs automatically each Day with different Creds/Hostnames, if you want.
Talking about the Pipelines in Detail:
Factory installs Powershell, uses the Microsoft Powershell Script to fetch the IP Addresses. Converts the output to an Array and Builds per IP an own API Call for the Firewall.