[HowTo] Sophos Firewall - Generate M365 Host Objects via Sophos Factory

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.