Sophos Firewall Host Objekt Import Generator (Powershell)

Dieser Beitrag ist auch verfügbar auf: English (Englisch)

Mit Hilfe dieses Scriptes zum "Sophos Firewall Host Objekt Import Generator" lassen sich schnell beliebig viele Host Objekte in eine XML zufügen, welche sich im Nachgang schnell in die Firewall importieren lässt - ohne API. Langwieriges Zufügen viele Host Objekte gehört der Vergangenheit an.

getestet mit SFOS 19.5.x bis 20.0.x

Einleitung

Viele werden es kennen: Je nachdem wie viele Hosts man auf der Sophos Firewall anlegen muss, kann der Aufwand sehr hoch werden. Sophos hat für diesen Zweck zwei Möglichkeiten parat: Die API und einen XML Konfigurationsimport.

Die API nutze ich in einigen Fällen auch. Hier ist allerdings der Nachteil, dass viele sich mit API nicht auskennen, sich nicht dran trauen, oder die API für den Zugriff nicht konfiguriert ist. Persönlich bin ich aber ein Freund von Teilimports - die gehen immer und benötigen keine Vorbereitung.

Aus diesem Grund habe ich das unten stehenden PowerShell Script erstellt. Es liest die zu erstellenden Host Objekte aus eine CSV aus und bereitet die Daten für den Import über die GUI vor. Evtl werde ich hier in Zukunft auch eine online Version anbieten - mal sehen 😊

Solltet Ihr Fragen oder Probleme mit dem Script haben, schreibt gerne in die Kommentare.

Beschränkungen

Bei den Eingabedaten gelten die Beschränkungen der Firewall.

  • Namen können maximal 60 Zeichen haben
  • Beschreibungen können maximal 255 Zeichen haben

Zusätzlich werden von Script nur die folgenden Zeichen für den Namen akzeptiert:

  • a-zA-Z0-9_+-#!=*äüöÄÜÖß und das Leerzeichen

Achtung

Die Beschränkung der erlaubten Zeichen kann beim Scriptaufruf mit dem Parameter -skipCharCheck aufgehoben werden. Ob die zusätzlichen Zeichen dann zu Fehlern führen, müsst ihr selber testen.

KEINE FQDNs

Dieses Script erstellt nur Host Objekte und Gruppen! Für die Erstellung von FQDN Imports haben wir einen anderen Artikel mit einem anderen Tool: Sophos Firewall FQDN Objekt Import Generator

Objekt Beschreibung

Die Beschreibung von Objekten hat erst mit SFOS 20.0.1 Einzug erhalten. Egal welche Version Ihr nutzt, müssen die Felder in der CSV vorkommen. Versionen < SFOS 20.0.1 ignorieren die Daten beim Import. Sind die Felder in der CSV nicht berücksicht, wird der XMl Import nicht erstellt. Leere Beschreibung können gesetzt werden indem das Feld mit einem doppelten Semikolon ;; übersprungen und leer gelassen wird. Beispiele findet Ihr in den Demo Daten.
Werden Objekte mehrfach einer Gruppe zugewiesen, muss die erste Gruppe eine Beschreibung tragen. Folgende Beschreibungen zur gleichen Gruppe werden ignoriert.

CSV Demo Daten

CSV Demo Daten
Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List
Host1;Description for Host1;Group_IP;Description for Group_IP;IP;192.168.1.1;;;;
Host5;;Group_IP;;IP;192.168.5.1;;;;
Host9;Description for Host9;Group_IP;;IP;192.168.9.1;;;;
Host2;Description for Host2;Group_Network;Description for Group_Network;Network;192.168.2.0;24;;;
Host6;;Group_Network;;Network;192.168.6.0;255.255.255.0;;;
Host10;Description for Host10;Group_Network;;Network;192.168.10.0;24;;;
Host3;Description for Host3;Group_Range;Description for Group_Range;Range;;;192.168.3.1;192.168.3.100;
Host7;Description for Host7;Group_Range;;Range;;;192.168.7.1;192.168.7.50;a
Host4;Description for Host4;Group_List;Description for Group_List;List;;;;;192.168.4.1,192.168.4.2,192.168.4.3
Host8;Description for Host8;Group_List;;List;;;;;192.168.8.1,192.168.8.2

Download Demo Daten

Aufbau der CSV

Der Header der CSV kann in der CSV stehen, oder auch weggelassen werden. Soll der Header aber drin stehen, muss dieser immer diese Namensgebung und enthalten und muss auch in englisch bleiben:

Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List

Die CSV muss immer aus 10 Spalten bestehen und auch in der angegebenen Reihenfolge angeordnert sein.

  1. Host Name
    Anzeigename des Hostobjektes (Pflichtfeld)
  2. Host Beschreibung
    Die Beschreibung des Hostobjekts (Optional, wird bei SFOS < 20.0.1 ignoriert beim Import)
  3. Gruppen Name
    Der Anzeigename der Hostgruppe der der Host zugefügt werden soll (Optional)
  4. Gruppenbeschreibung
    Die Beschreibung der Hostgruppe. Kommt die Gruppe mehrfach vor, wird immer nur die Beschreibung des ersten Vorkommens verwendet! (Optional, wird nur einmal pro Hostgruppe benötigt)
  5. Host Typ
    Hier wird der Typ des Host Objektes gewählt. Groß- und Kleinschreibung ist egal. Die Namen sind selbstsprechend. (Pflicht)
    • IP
      Hier wird Spalte 6 (IP Adresse) verwendet, 7-10 werden ignoriert
    • Network
      Hier muss in Spalte 6 (IP Adresse) die Netzadresse stehen und in Spalte 7 (Subnetzmaske) die entsprechende Subnetzmaske in voller Schreibweise (z.B.: 255.255.255.0) oder in CIDR Notation (z.B.: 24). Die Spalten 8-10 werden ignoriert
    • Range
      Die Start IP muss in Spalte 8 (IP von) und die End IP muss in Spalte 9 (IP bis) stehen. Die Spalten 6-7, 10 werden ignoriert
    • List
      Die Liste muss mit Komma getrennt in Spalte 10 stehen. Die Spalten 6-9 werden ignoriert
  6. IP Adresse
    Die IP Adresse (Typ IP) oder Netzmaske (Typ Network) - (Pflicht beim Typ IP oder Network)
  7. Subnetzmaske
    Die Netzmaske - (Pflicht beim Typ Network)
  8. IP von
    Die Start IP bei einem IP Bereich (Typ Range) - (Pflicht beim Typ Range)
  9. IP bis
    Die End IP bei einem IP Bereich (Typ Range) - (Pflicht beim Typ Range)
  10. IP LIste
    Eine mit Komma getrennte Liste von IP Adressen - (Pflicht beim Typ List)

Leere Spalten werden einfach mit einem weiteren Semikolon dargestellt. Bitte schaut euch dazu die Demo CSV an, da diese den Aufbau und die Möglichkeiten gut verdeutlicht.

Powershell Script

Achtung

Wie immer gilt: Die Verwendung des Scripts und der generierten Daten erfolgt komplett auf eigene Gefahr. Wir übernehmen keine Garantie und Haftung für Fehler die hier entstehen könnten. Ein Test in einer Testumgebung wird in allen Fällen empfohlen.

Quellcode und Download

PowerShell Script zum Erstellen eines XML Imports aus einer CSV
<#
.DESCRIPTION
    Creates Sophos XML Import files for hosts objekts from a given CSV file

.PARAMETER -skipCharCheck
    Allow all characters in name and description

.PARAMETER -debug
    Print created hosts to console

.EXAMPLE
    Just start the script. Input and output file are prompted

.NOTES
    Demo CSV Data for input can be downloaded at: https://it-tech.wiki/shog

.LINK
    More information at https://it-tech.wiki/shog

.AUTHOR
    Sebastian Mies
    [email protected]

.LICENSE
    MIT License

    Copyright (c) 2024 Sebastian Mies

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
#>

param (
    [switch]$skipCharCheck,
    [switch]$debug
)


Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Function to validate IPv4 Address
function Validate-IPv4Address {
    param (
        [string]$IPAddress
    )
    return [System.Net.IPAddress]::TryParse($IPAddress, [ref]$null)
}

# Function to validate Subnet Mask
function Validate-SubnetMask {
    param (
        [string]$subnetMask
    )
    $validMasks = @(
        "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"
    )
    return $validMasks -contains $subnetMask
}

# Function to convert CIDR to full subnet mask
function Convert-CIDRToSubnetMask {
    param (
        [int]$CIDR
    )
    if ($CIDR -lt 0 -or $CIDR -gt 32) {
        throw "CIDR value must be between 0 and 32."
    }
    $binaryString = "1" * $CIDR + "0" * (32 - $CIDR)
    return ([convert]::ToInt32($binaryString.Substring(0, 8), 2)).ToString() + "." +
           ([convert]::ToInt32($binaryString.Substring(8, 8), 2)).ToString() + "." +
           ([convert]::ToInt32($binaryString.Substring(16, 8), 2)).ToString() + "." +
           ([convert]::ToInt32($binaryString.Substring(24, 8), 2)).ToString()
}

# Function to show open file dialog
function Show-OpenFileDialog {
    $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $OpenFileDialog.Filter = "CSV files (*.csv)|*.csv"
    $OpenFileDialog.ShowDialog() | Out-Null
    return $OpenFileDialog.FileName
}

# Function to show save file dialog
function Show-SaveFileDialog {
    $SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
    $SaveFileDialog.Filter = "XML files (*.xml)|*.xml"
    $SaveFileDialog.ShowDialog() | Out-Null
    return $SaveFileDialog.FileName
}

# Function to validate allowed characters in strings
function Validate-AllowedCharacters {
    param (
        [string]$inputString
    )
    return $inputString -match '^[a-zA-Z0-9_+\-#!=* äüöÄÜÖß]+$'
}

# Ensure console output encoding is correct
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8


# Detect system language
$language = [System.Globalization.CultureInfo]::CurrentCulture.TwoLetterISOLanguageName
$isGerman = $language -eq 'de'

# Load messages
if ($isGerman) {
    $msgMissingHostName = "Zeile {0}: Host Name fehlt. Überspringen."
    $msgInvalidColumns = "Zeile {0}: Ungültige Spaltenanzahl. Erwartet: {1}, Tatsächlich: {2}. Überspringen."
    $msgInvalidHostName = "Zeile {0}, Host '{1}': Ungültige Zeichen im Host Name. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
    $msgInvalidHostDescription = "Zeile {0}, Host '{1}': Ungültige Zeichen in der Host-Beschreibung. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
    $msgInvalidGroupName = "Zeile {0}, Host '{1}': Ungültige Zeichen im Gruppenname. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
    $msgInvalidGroupDescription = "Zeile {0}, Host '{1}': Ungültige Zeichen in der Gruppenbeschreibung. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
    $msgTruncateHostName = "Zeile {0}, Host '{1}': Host Name überschreitet 60 Zeichen. Er wird gekürzt."
    $msgTruncateHostDescription = "Zeile {0}, Host '{1}': Host-Beschreibung überschreitet 255 Zeichen. Sie wird gekürzt."
    $msgMissingIpAddress = "Zeile {0}, Host '{1}': IP-Adresse fehlt für den Host-Typ 'IP'. Überspringen."
    $msgInvalidIpAddress = "Zeile {0}, Host '{1}': Ungültige IP-Adresse '{2}'. Überspringen."
    $msgMissingNetworkInfo = "Zeile {0}, Host '{1}': IP-Adresse oder Subnetz fehlt für den Host-Typ 'Network'. Überspringen."
    $msgInvalidNetworkAddress = "Zeile {0}, Host '{1}': Ungültige Netzwerkadresse '{2}'. Überspringen."
    $msgInvalidSubnetMask = "Zeile {0}, Host '{1}': Ungültige Subnetzmaske '{2}'. Überspringen."
    $msgInvalidCidr = "Zeile {0}, Host '{1}': Ungültiger CIDR-Wert '{2}'. Überspringen."
    $msgMissingRangeInfo = "Zeile {0}, Host '{1}': IP-From oder IP-To fehlt für den Host-Typ 'Range'. Überspringen."
    $msgInvalidIpRange = "Zeile {0}, Host '{1}': Ungültiger IP-Bereich '{2} - {3}'. Überspringen."
    $msgInvalidRangeOrder = "Zeile {0}, Host '{1}': Ungültiger IP-Bereich '{2} - {3}'. 'IP To' sollte größer als 'IP From' sein. Überspringen."
    $msgMissingIpList = "Zeile {0}, Host '{1}': IP-Liste fehlt für den Host-Typ 'List'. Überspringen."
    $msgIpListTooLong = "Zeile {0}, Host '{1}': IP-Liste überschreitet 1000 Einträge. Überspringen."
    $msgInvalidIpInList = "Zeile {0}, Host '{1}': Ungültige IP-Adresse '{2}' in der IP-Liste. Überspringen."
    $msgUnsupportedHostType = "Zeile {0}, Host '{1}': Nicht unterstützter Host-Typ '{2}'. Überspringen."
    $msgFileNotSelected = "XML-Dateipfad nicht ausgewählt."
    $msgXmlSaved = "XML-Datei wurde in {0} gespeichert"
    $msgTarSaved = "XML-Datei wurde in {0} gespeichert"
    $msgErrorTar = "Fehler beim Erstellen der TAR-Datei: {0}"
    $msgNoArchivingTool = "Kein geeignetes Archivierungstool gefunden. Bitte die Datei manuell in ein TAR-Archiv ohne Komprimierung mit dem Namen Entities.xml verschieben, bevor sie in die Firewall importiert wird."
    $msgCreatedXml = "XML-Datei erstellt: {0} - {1}"
} else {
    $msgMissingHostName = "Row {0}: Missing Host Name. Skipping."
    $msgInvalidColumns = "Row {0}: Invalid number of columns. Expected: {1}, Actual: {2}. Skipping."
    $msgInvalidHostName = "Row {0}, Host '{1}': Invalid characters in Host Name. Skipping. Use -skipCharCheck to bypass this check."
    $msgInvalidHostDescription = "Row {0}, Host '{1}': Invalid characters in Host Description. Skipping. Use -skipCharCheck to bypass this check."
    $msgInvalidGroupName = "Row {0}, Host '{1}': Invalid characters in Group Name. Skipping. Use -skipCharCheck to bypass this check."
    $msgInvalidGroupDescription = "Row {0}, Host '{1}': Invalid characters in Group Description. Skipping. Use -skipCharCheck to bypass this check."
    $msgTruncateHostName = "Row {0}, Host '{1}': Host Name exceeds 60 characters. It will be truncated."
    $msgTruncateHostDescription = "Row {0}, Host '{1}': Host Description exceeds 255 characters. It will be truncated."
    $msgMissingIpAddress = "Row {0}, Host '{1}': Missing IP Address for Host Type 'IP'. Skipping."
    $msgInvalidIpAddress = "Row {0}, Host '{1}': Invalid IP Address '{2}'. Skipping."
    $msgMissingNetworkInfo = "Row {0}, Host '{1}': Missing IP Address or Subnet for Host Type 'Network'. Skipping."
    $msgInvalidNetworkAddress = "Row {0}, Host '{1}': Invalid Network Address '{2}'. Skipping."
    $msgInvalidSubnetMask = "Row {0}, Host '{1}': Invalid Subnet Mask '{2}'. Skipping."
    $msgInvalidCidr = "Row {0}, Host '{1}': Invalid CIDR value '{2}'. Skipping."
    $msgMissingRangeInfo = "Row {0}, Host '{1}': Missing IP From or IP To for Host Type 'Range'. Skipping."
    $msgInvalidIpRange = "Row {0}, Host '{1}': Invalid IP Range '{2} - {3}'. Skipping."
    $msgInvalidRangeOrder = "Row {0}, Host '{1}': Invalid IP Range '{2} - {3}'. 'IP To' should be greater than 'IP From'. Skipping."
    $msgMissingIpList = "Row {0}, Host '{1}': Missing IP List for Host Type 'List'. Skipping."
    $msgIpListTooLong = "Row {0}, Host '{1}': IP List exceeds 1000 entries. Skipping."
    $msgInvalidIpInList = "Row {0}, Host '{1}': Invalid IP Address '{2}' in IP List. Skipping."
    $msgUnsupportedHostType = "Row {0}, Host '{1}': Unsupported Host Type '{2}'. Skipping."
    $msgFileNotSelected = "XML file path not selected."
    $msgXmlSaved = "XML file saved to {0}"
    $msgTarSaved = "XML file saved to {0}"
    $msgErrorTar = "Error creating TAR file: {0}"
    $msgNoArchivingTool = "No suitable archiving tool found. Please manually move the file into a TAR archive named Entities.xml without compression before importing it into the firewall."
    $msgCreatedXml = "Created XML: {0} - {1}"
}

# Load CSV file
$csvPath = Show-OpenFileDialog
if (-not $csvPath) {
    Write-Error $msgFileNotSelected
    exit
}

# Import CSV with semicolon delimiter
$csvData = Import-Csv -Path $csvPath -Delimiter ";"

# Check for correct number of columns
$requiredColumns = 10
$validData = @()
$firstLine = $true

foreach ($line in Get-Content -Path $csvPath) {
    # Ignore the first line if it contains the column headers
    if ($firstLine) {
        $firstLine = $false
        if ($line -match '^Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List$') {
            continue
        }
    }

    $columns = $line -split ";"
    if ($columns.Length -ne $requiredColumns) {
        Write-Warning ($msgInvalidColumns -f $rowIndex, $requiredColumns, $columns.Length)
    } else {
        $validData += ,($line -split ";")
    }
}

# Initialize XML Document
[xml]$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDeclaration = $xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", $null)
$null = $xmlDoc.AppendChild($xmlDeclaration)

$configurationElement = $xmlDoc.CreateElement("Configuration")
$configurationElement.SetAttribute("APIVersion", "2000.2")
$configurationElement.SetAttribute("IS_WIFI6", "0")
$configurationElement.SetAttribute("IPS_CAT_VER", "1")
$null = $xmlDoc.AppendChild($configurationElement)

$hostGroups = @{}

# Process each row in CSV
$rowIndex = 0
foreach ($row in $validData) {
    $rowIndex++
    $hostName = $row[0].Trim()
    if (-not $hostName) {
        Write-Warning ($msgMissingHostName -f $rowIndex)
        continue
    }

    $hostDescription = $row[1].Trim()
    $groupName = $row[2].Trim()
    $groupDescription = $row[3].Trim()
    $hostType = $row[4].Trim()
    $ipAddress = $row[5].Trim()
    $ipSubnet = $row[6].Trim()
    $ipFrom = $row[7].Trim()
    $ipTo = $row[8].Trim()
    $ipList = $row[9].Trim()

    # Validate allowed characters in Host Name and Description
    if (-not $skipCharCheck) {
        if (-not (Validate-AllowedCharacters -inputString $hostName)) {
            Write-Warning ($msgInvalidHostName -f $rowIndex, $hostName)
            continue
        }
        if ($hostDescription -and -not (Validate-AllowedCharacters -inputString $hostDescription)) {
            Write-Warning ($msgInvalidHostDescription -f $rowIndex, $hostName)
            continue
        }
        if ($groupName -and -not (Validate-AllowedCharacters -inputString $groupName)) {
            Write-Warning ($msgInvalidGroupName -f $rowIndex, $hostName)
            continue
        }
        if ($groupDescription -and -not (Validate-AllowedCharacters -inputString $groupDescription)) {
            Write-Warning ($msgInvalidGroupDescription -f $rowIndex, $hostName)
            continue
        }
    }

    # Validate and truncate Host Name
    if ($hostName.Length -gt 60) {
        Write-Warning ($msgTruncateHostName -f $rowIndex, $hostName)
        $hostName = $hostName.Substring($hostName.Length - 60)
    }

    # Validate and truncate Host Description
    if ($hostDescription.Length -gt 255) {
        Write-Warning ($msgTruncateHostDescription -f $rowIndex, $hostName)
        $hostDescription = $hostDescription.Substring($hostDescription.Length - 255)
    }

    # Validate Host Type and required fields
    $isValid = $true
    switch -Regex ($hostType.ToLower()) {
        '^ip$' {
            if (-not $ipAddress) {
                Write-Warning ($msgMissingIpAddress -f $rowIndex, $hostName)
                $isValid = $false
            }
            elseif (-not (Validate-IPv4Address -IPAddress $ipAddress)) {
                Write-Warning ($msgInvalidIpAddress -f $rowIndex, $hostName, $ipAddress)
                $isValid = $false
            }
            else {
                $hostType = "IP"
            }
        }
        '^network$' {
            if (-not $ipAddress -or -not $ipSubnet) {
                Write-Warning ($msgMissingNetworkInfo -f $rowIndex, $hostName)
                $isValid = $false
            }
            elseif (-not (Validate-IPv4Address -IPAddress $ipAddress)) {
                Write-Warning ($msgInvalidNetworkAddress -f $rowIndex, $hostName, $ipAddress)
                $isValid = $false
            }
            else {
                try {
                    if ($ipSubnet -match '^\d+$') {
                        $ipSubnetInt = [int]$ipSubnet
                        $ipSubnet = Convert-CIDRToSubnetMask -CIDR $ipSubnetInt
                    } 
                    elseif (-not (Validate-SubnetMask -subnetMask $ipSubnet)) {
                        Write-Warning ($msgInvalidSubnetMask -f $rowIndex, $hostName, $ipSubnet)
                        $isValid = $false
                    }
                } catch {
                    Write-Warning ($msgInvalidCidr -f $rowIndex, $hostName, $ipSubnet)
                    $isValid = $false
                }
                $hostType = "Network"
            }
        }
        '^range$' {
            if (-not $ipFrom -or -not $ipTo) {
                Write-Warning ($msgMissingRangeInfo -f $rowIndex, $hostName)
                $isValid = $false
            }
            elseif (-not (Validate-IPv4Address -IPAddress $ipFrom) -or -not (Validate-IPv4Address -IPAddress $ipTo)) {
                Write-Warning ($msgInvalidIpRange -f $rowIndex, $hostName, $ipFrom, $ipTo)
                $isValid = $false
            }
            else {
                $startIP = [System.Net.IPAddress]::Parse($ipFrom).GetAddressBytes()
                $endIP = [System.Net.IPAddress]::Parse($ipTo).GetAddressBytes()
                if ([BitConverter]::ToUInt32($startIP, 0) -ge [BitConverter]::ToUInt32($endIP, 0)) {
                    Write-Warning ($msgInvalidRangeOrder -f $rowIndex, $hostName, $ipFrom, $ipTo)
                    $isValid = $false
                }
                $hostType = "IPRange"
            }
        }
        '^list$' {
            if (-not $ipList) {
                Write-Warning ($msgMissingIpList -f $rowIndex, $hostName)
                $isValid = $false
            }
            else {
                $ipListArray = $ipList -split ',\s*'
                if ($ipListArray.Length -gt 1000) {
                    Write-Warning ($msgIpListTooLong -f $rowIndex, $hostName)
                    $isValid = $false
                }
                else {
                    $validIPs = $ipListArray | ForEach-Object {
                        if (-not (Validate-IPv4Address -IPAddress $_)) {
                            Write-Warning ($msgInvalidIpInList -f $rowIndex, $hostName, $_)
                            $false
                        } else {
                            $true
                        }
                    }
                    if ($validIPs -contains $false) {
                        $isValid = $false
                    }
                }
                $hostType = "IPList"
            }
        }
        default {
            Write-Warning ($msgUnsupportedHostType -f $rowIndex, $hostName, $hostType)
            $isValid = $false
        }
    }

    # If valid, create IPHost element
    if ($isValid) {
        $ipHostElement = $xmlDoc.CreateElement("IPHost")
        $ipHostElement.SetAttribute("transactionid", "")

        $nameElement = $xmlDoc.CreateElement("Name")
        $nameElement.InnerText = $hostName
        $null = $ipHostElement.AppendChild($nameElement)

        $descriptionElement = $xmlDoc.CreateElement("Description")
        if ($hostDescription) {
            $descriptionElement.InnerText = $hostDescription
        } else {
            $descriptionElement.IsEmpty = $true
        }
        $null = $ipHostElement.AppendChild($descriptionElement)

        $ipFamilyElement = $xmlDoc.CreateElement("IPFamily")
        $ipFamilyElement.InnerText = "IPv4"
        $null = $ipHostElement.AppendChild($ipFamilyElement)

        $hostTypeElement = $xmlDoc.CreateElement("HostType")
        $hostTypeElement.InnerText = $hostType
        $null = $ipHostElement.AppendChild($hostTypeElement)

        switch ($hostType) {
            "IP" {
                $ipAddressElement = $xmlDoc.CreateElement("IPAddress")
                $ipAddressElement.InnerText = $ipAddress
                $null = $ipHostElement.AppendChild($ipAddressElement)
            }
            "Network" {
                $ipAddressElement = $xmlDoc.CreateElement("IPAddress")
                $ipAddressElement.InnerText = $ipAddress
                $null = $ipHostElement.AppendChild($ipAddressElement)

                $subnetElement = $xmlDoc.CreateElement("Subnet")
                $subnetElement.InnerText = $ipSubnet
                $null = $ipHostElement.AppendChild($subnetElement)
            }
            "IPRange" {
                $startIPAddressElement = $xmlDoc.CreateElement("StartIPAddress")
                $startIPAddressElement.InnerText = $ipFrom
                $null = $ipHostElement.AppendChild($startIPAddressElement)

                $endIPAddressElement = $xmlDoc.CreateElement("EndIPAddress")
                $endIPAddressElement.InnerText = $ipTo
                $null = $ipHostElement.AppendChild($endIPAddressElement)
            }
            "IPList" {
                $listOfIPAddressesElement = $xmlDoc.CreateElement("ListOfIPAddresses")
                $listOfIPAddressesElement.InnerText = ($ipListArray -join ",")
                $null = $ipHostElement.AppendChild($listOfIPAddressesElement)
            }
        }

        $null = $configurationElement.AppendChild($ipHostElement)

        if ($debug) {
            Write-Host ($msgCreatedXml -f $hostName, $hostType) -ForegroundColor Blue
        }
    }

    # Process Group Name and Description
    if ($groupName) {
        if ($groupName.Length -gt 60) {
            Write-Warning ($msgTruncateHostName -f $rowIndex, $groupName)
            $groupName = $groupName.Substring($groupName.Length - 60)
        }

        if ($groupDescription.Length -gt 255) {
            Write-Warning ($msgTruncateHostDescription -f $rowIndex, $groupName)
            $groupDescription = $groupDescription.Substring($groupDescription.Length - 255)
        }

        if (-not $hostGroups.ContainsKey($groupName)) {
            $hostGroups[$groupName] = @{
                Description = $groupDescription
                Hosts = @()
            }
        }

        $hostGroups[$groupName].Hosts += $hostName
    }
}

# Create IPHostGroup elements
foreach ($groupName in $hostGroups.Keys) {
    $group = $hostGroups[$groupName]

    $ipHostGroupElement = $xmlDoc.CreateElement("IPHostGroup")
    $ipHostGroupElement.SetAttribute("transactionid", "")

    $groupNameElement = $xmlDoc.CreateElement("Name")
    $groupNameElement.InnerText = $groupName
    $null = $ipHostGroupElement.AppendChild($groupNameElement)

    $groupDescriptionElement = $xmlDoc.CreateElement("Description")
    if ($group.Description) {
        $groupDescriptionElement.InnerText = $group.Description
    } else {
        $groupDescriptionElement.IsEmpty = $true
    }
    $null = $ipHostGroupElement.AppendChild($groupDescriptionElement)

    $hostListElement = $xmlDoc.CreateElement("HostList")
    foreach ($hostName in $group.Hosts) {
        $hostElement = $xmlDoc.CreateElement("Host")
        $hostElement.InnerText = $hostName
        $null = $hostListElement.AppendChild($hostElement)
    }
    $null = $ipHostGroupElement.AppendChild($hostListElement)

    $ipFamilyElement = $xmlDoc.CreateElement("IPFamily")
    $ipFamilyElement.InnerText = "IPv4"
    $null = $ipHostGroupElement.AppendChild($ipFamilyElement)

    $null = $configurationElement.AppendChild($ipHostGroupElement)

    if ($debug) {
        Write-Host ($msgCreatedXml -f $groupName, "Group") -ForegroundColor Blue
    }
}

# Save XML to file
$xmlPath = Show-SaveFileDialog
if (-not $xmlPath) {
    Write-Error $msgFileNotSelected
    exit
}
$entitiesXmlPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($xmlPath), "Entities.xml")
$xmlDoc.Save($entitiesXmlPath)
Write-Host ($msgXmlSaved -f $entitiesXmlPath) -ForegroundColor Green


# Create TAR archive
$tarPath = [System.IO.Path]::ChangeExtension($xmlPath, ".tar")

# Check for tar.exe
$tarExePath = "$env:SystemRoot\System32\tar.exe"
$tarCreated = $false
if (Test-Path $tarExePath) {
    $startInfo = New-Object System.Diagnostics.ProcessStartInfo
    $startInfo.FileName = $tarExePath
    $startInfo.Arguments = "-cf `"$tarPath`" -C `"$([System.IO.Path]::GetDirectoryName($entitiesXmlPath))`" Entities.xml"
    $startInfo.RedirectStandardOutput = $true
    $startInfo.RedirectStandardError = $true
    $startInfo.UseShellExecute = $false
    $startInfo.CreateNoWindow = $true

    $process = [System.Diagnostics.Process]::Start($startInfo)
    $stdout = $process.StandardOutput.ReadToEnd()
    $stderr = $process.StandardError.ReadToEnd()
    $process.WaitForExit()

    if ($process.ExitCode -eq 0 -and (Test-Path $tarPath)) {
        $tarCreated = $true
        Write-Host ($msgTarSaved -f $tarPath) -ForegroundColor Green
    } else {
        Write-Error ($msgErrorTar -f $stderr)
    }
}

# Check for 7-Zip in standard and other paths
if (-not $tarCreated) {
    $sevenZipPaths = @(
        "$env:ProgramFiles\7-Zip\7z.exe",
        "$env:ProgramFiles(x86)\7-Zip\7z.exe"
    )
    $sevenZipPath = $sevenZipPaths | Where-Object { Test-Path $_ }
    if (-not $sevenZipPath) {
        $sevenZipPath = Get-Command 7z -ErrorAction SilentlyContinue
    }
    if ($sevenZipPath) {
        $startInfo = New-Object System.Diagnostics.ProcessStartInfo
        $startInfo.FileName = $sevenZipPath
        $startInfo.Arguments = "a -ttar `"$tarPath`" `"$entitiesXmlPath`""
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true
        $startInfo.UseShellExecute = $false
        $startInfo.CreateNoWindow = $true

        $process = [System.Diagnostics.Process]::Start($startInfo)
        $stdout = $process.StandardOutput.ReadToEnd()
        $stderr = $process.StandardError.ReadToEnd()
        $process.WaitForExit()

        if ($process.ExitCode -eq 0 -and (Test-Path $tarPath)) {
            $tarCreated = $true
            Write-Host ($msgTarSaved -f $tarPath) -ForegroundColor Green
        } else {
            Write-Error ($msgErrorTar -f $stderr)
        }
    }
}

# If no suitable archive tool is found
if (-not $tarCreated) {
    Write-Warning $msgNoArchivingTool
}

# Delete the additional Entities.xml file if it is not the original XML
if ($entitiesXmlPath -ne $xmlPath) {
    Remove-Item -Path $entitiesXmlPath -Force
}
Erweitern

Download

Sophos Host Objekt Generator (Version: 1.1)
24.67 KB

Nutzung des Script

Das Script wird einfach in der Powershell aufgerufen. Die Eingabe Datei (CSV) und die Ausgabedatei (XML) wird interaktiv abgefragt über einen Dialog.
Sofern auf dem System die tar.exe im System32 Verzeichnis existiert, oder 7zip installiert ist, wird auch die nötige TAR Datei für den Import in die Firewall erstellt.

Keine TAR Datei erhalten?

Ist weder tar.exe noch 7zip vorhanden auf dem System, müsst Ihr die ausgegebene Datei selber als Entities.xml in ein unkomprimiertes TAR Archiv packen. Heißt die Datei im Archiv anders, wird der Import ignoriert!

Fehler: ExecutionPolicy / nicht digital signiert

Erhaltet Ihr einen Fehler der auf eine Reglementierung durch die ExecutionPolicy hinweist, müsst Ihr die ExecutionPolicy mit Set-ExecutionPolicy anpassen. Das Script von uns ist nicht signiert. Daher ist der Fehler auf einem Standard System zu erwarten. Die Anpassung sollte immer durch erfahrenes Personal erfolgen und nach Möglichkeit auch im Nachgang wieder Rückgängig gemacht werden. Näher Informationen zur Anpassung und Empfehlungen werden wir an dieser Stelle aber bewusst nicht ausgeben. Also entweder Google nutzen, oder erfahrenes Personal zu dem Thema ansprechen. Wir bitten um Verstandnis!

Parameter zum Aufruf

  • -skipCharCheck
    Führt keine Prüfung der erlaubten Zeichen durch.
  • -debug
    Gibt in der Kommandozeile aus welche Hosts oder Gruppen eingelesen wurden

Import der XML in die Firewall

Über das Webinterface lässt sich die Datei in die Firewall importieren. Anschließend sind die Hosts angelegt.

Hinweis

  1. Es muss immer die TAR Datei hochgeladen werden. Andere Formate werden ignoriert. Kein TAR Archiv erhalten, dann schaut unter "Keine TAR Datei erhalten"
  2. Sind in der importierten Datei seht viele Hosts, kann der Import auch mal etwas länger dauern. Generell ist die Geschwindigkeit aber auch von der vrwendeten Hardware abhängig! Bringt also ggf. etwas Geduld mit.
Import der XML Datei

FAQ

Was ist mit FQDN Objekten?

FQDNs werden von diesem Script nicht erstellt. Dazu haben wir allerdings ein weiteres Tool im Angebot. Schau einfach hier vorbei: Sophos Firewall FQDN Objekt Import Generator

Fehler mit der ExecutionPolicy / keine digitale Signatur

Mehr erfahrt ihr weiter oben im Artikel.

Ich habe einen Fehler gefunden

Prima, du hast das Easteregg gefunden 🥳
Nein, Spaß beiseite... Fehler sind nicht auszuschließen aufgrund der Menge an möglichen Testszenarios. Hast du einen Fehler gefunden, schreibe uns gerne über das Kontaktformular, oder in den Kommentaren, welche Daten zu dem Fehler führen und wir schauen, dass wir den Bug zeitnah beheben.

Fehlt was?

Fehlt Euch was im Generator, schreibt uns gerne über das Kontaktformular, oder über die Kommentare. Mal sehen ob Dein Featurewunsch es rein schafft 😊

Wo und wie wird die XML importiert?

Die erstellte Datei (.tar) bitte so wie sie ist unter "Sicherung & Firmware -> Import/Export" hochladen und importieren. Nach der Meldung, dass der Vorgang erfolgreich war, sind die gewünschten Objekte angelegt.

FQDN Objekt Import Generator Import XML -

Wie kann sichergestellt werden, dass der Import keine Backdoor oder anderes einbaut?

Entpackt einfach die TAR-Datei und öffnet die enthaltene XML Datei im Editor. Du wirst hier nur die von Dir eingesendeten Daten finden.

Weitere interessante Artikel

3 Kommentare

Schreibe einen Kommentar

Kommentare werden nicht direkt angezeigt, da sie moderiert freigegeben werden.


  1. Danke für das Script!!!
    Ich habe schon den FQDN Generator mit Freude benutzt. Jetzt eine Möglichkeit schnell die Hosts anzulegen. Einfach Top! Bin schon gespannt was hier noch so kommt. 😀

    • Danke für Dein Feedback, und prima, dass es auch außerhalb meiner eigenen Tests funtioniert! 😊
      Ein paar Ideen habe ich noch. Mal sehen wann ich weiter Zeit finde…

WordPress Cookie Plugin von Real Cookie Banner