SophosFirewall.SophosCentral.psm1

#requires -Version 5.1
#requires -Modules @{ ModuleName = 'SophosFirewall.Core'; ModuleVersion = '1.4.0' }

<#
.SYNOPSIS
    Manages Sophos Central cloud management and registration on a Sophos Firewall.
 
.DESCRIPTION
    Functions for the SYSTEM > Sophos Central area of the Sophos Firewall XML API (SFOS
    22.0): the cloud central management switches - centralized reporting, management from
    Sophos Central, and configuration backup - that sit under EnableCloudCentralManagement.
 
    Registering a firewall with a Sophos Central tenant is deliberately not part of this
    module. The API offers exactly one way to do it, and that way sends the account name and
    password of a Sophos Central administrator in the request body. A tenant secured with a
    passkey, or with any other passwordless sign-in, cannot supply those, and the one-time
    password the web admin console accepts instead has no API equivalent. Register the
    firewall through the web admin console, then use this module for the switches.
 
    Total Functions: 2 - see README.md for the full cmdlet table.
 
    Connect once with Connect-SfosFirewall, then call the cmdlets in this module without
    repeating the connection parameters.
 
.EXAMPLE
    Connect-SfosFirewall -Firewall '192.0.2.1' -Credential (Get-Credential) -SkipCertificateCheck
    Get-SfosCentralManagement
 
    Connects to the firewall and reads the current cloud central management settings.
 
.EXAMPLE
    Set-SfosCentralManagement -UseCentralReporting Disable -Confirm:$false
    Get-SfosCentralManagement
 
    Switches Sophos Central reporting off, leaving management, backup and join method
    untouched, then confirms the new state.
 
.LINK
    https://docs.sophos.com/nsg/sophos-firewall/22.0/api/
 
.LINK
    Connect-SfosFirewall
#>



#region CentralManagement

<#
.SYNOPSIS
    Retrieves the Sophos Central cloud management settings from a Sophos Firewall.
 
.DESCRIPTION
    Returns the four switches that control how this firewall works with Sophos Central:
    whether configuration backups are sent to Sophos Central, how the firewall joined
    Sophos Central, whether centralized reporting is on, and whether the firewall is managed
    from Sophos Central. There is exactly one instance of this object per firewall, held
    under the wire element EnableCloudCentralManagement - despite the name, it is a settings
    object, not a command. The cmdlet only reads; nothing on the firewall is changed. It
    needs an open connection from Connect-SfosFirewall, or the connection parameters
    supplied directly.
 
    This cmdlet does not reveal whether the firewall is actually registered with a Sophos
    Central tenant. Registration and these four switches are
    independent according to Sophos: turning a switch off does not end the registration, and
    a switch can read Enable without a registration ever having been confirmed. Check the
    Sophos Central status in the web admin console (menu Sophos Central) for the actual
    registration state.
 
.PARAMETER Firewall
    Optional. Host name or IP address of the firewall. If omitted, the value from the
    current connection is used.
 
.PARAMETER Port
    Optional. TCP port of the management API, usually 4444. If omitted, the value from
    the current connection is used.
 
.PARAMETER Username
    Optional. User name for the API login. The account needs read permission for the
    Sophos Central settings. If omitted, the value from the current connection is used.
 
.PARAMETER Password
    Optional. Password for the API login, as a SecureString. If omitted, the value from
    the current connection is used.
 
.PARAMETER SkipCertificateCheck
    Optional. Accepts the firewall certificate without validating it. Use this only for
    appliances that still present a self-signed certificate. If omitted, the certificate
    is validated.
 
.PARAMETER Session
    Optional. A session object from Connect-SfosFirewall, or the name of a session that
    was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall
    when you work with more than one at a time. Any connection parameter you pass
    explicitly still takes precedence. If omitted, the stored default connection is used.
 
.PARAMETER AsXml
    Optional. Returns the raw XML element sent by the firewall instead of a PowerShell
    object.
 
.INPUTS
    None. This cmdlet does not accept pipeline input.
 
.OUTPUTS
    System.Management.Automation.PSCustomObject. One object with the properties FWBackup,
    JoinMethod, UseCentralReporting and CMStatus. UseCentralReporting and CMStatus normally
    read Enable or Disable, but can also read the undocumented value WaitingForApproval while
    a service that was just switched on is waiting for a super admin to confirm it in the
    Sophos Central console. Returns System.Xml.XmlElement when -AsXml is used.
 
.EXAMPLE
    Get-SfosCentralManagement
 
    Returns the current Sophos Central management settings of the firewall of the current
    connection.
 
.LINK
    https://docs.sophos.com/nsg/sophos-firewall/22.0/api/
 
.LINK
    Set-SfosCentralManagement
#>

function Get-SfosCentralManagement {
    [CmdletBinding()]
    param(
        # Connection parameters (optional - use stored context if not provided)
        [string]$Firewall,
        [int]$Port,
        [string]$Username,
        [SecureString]$Password,
        [switch]$SkipCertificateCheck,
        [object]$Session,

        # Output parameters
        [switch]$AsXml
    )

    $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters

    $inner = '<Get><EnableCloudCentralManagement></EnableCloudCentralManagement></Get>'

    try {
        $response = Invoke-SfosApi -Firewall $params.Firewall `
            -Port $params.Port `
            -Username $params.Username `
            -Password $params.Password `
            -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop
    }
    catch {
        throw "Error retrieving CentralManagement: $($_.Exception.Message)"
    }

    $XmlResponse = [xml]$response.Content
    Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'EnableCloudCentralManagement' -Action 'get'

    $node = $XmlResponse.SelectSingleNode('/Response/EnableCloudCentralManagement')
    if (-not $node) {
        throw 'CentralManagement could not be retrieved from the firewall.'
    }

    if ($AsXml) {
        return $node
    }

    return [PSCustomObject]@{
        FWBackup            = [string]$node.FWBackup
        JoinMethod          = [string]$node.JoinMethod
        UseCentralReporting = [string]$node.UseCentralReporting
        CMStatus            = [string]$node.CMStatus
    }
}

<#
.SYNOPSIS
    Updates the Sophos Central cloud management settings on a Sophos Firewall.
 
.DESCRIPTION
    Updates the four EnableCloudCentralManagement switches. It needs an open connection
    from Connect-SfosFirewall, or the connection parameters supplied directly, and an
    account with permission to change Sophos Central settings. The cmdlet reads the current
    settings first and resends every field, overriding only what you explicitly pass, so
    fields you do not pass keep their current value.
 
    FWBackup depends on CMStatus, matching the web admin console, where the configuration
    backup checkbox sits nested under the "Manage from Sophos Central" checkbox. This
    cmdlet refuses to set FWBackup to BackupEnable while CMStatus (whether passed explicitly
    or read back from the firewall) resolves to Disable, and throws instead of sending a
    combination the firewall's own web admin console cannot produce.
 
    Switching UseCentralReporting or CMStatus off takes effect immediately. Switching either
    back on is accepted by the firewall and reported as a success, but has no effect until a
    super admin confirms the service in the Sophos Central console with Accept services -
    turning a service back on is not something this cmdlet, or the API behind it, can
    complete on its own. After every update the cmdlet reads the settings back: a field that
    still shows its previous value throws, naming the field and the Sophos Central console
    step it is waiting on; a field that shows the undocumented value WaitingForApproval
    produces a warning instead, because that outcome means the request was accepted and is
    pending, not that it failed silently.
 
    This cmdlet changes only EnableCloudCentralManagement. It does not register or
    unregister the firewall with Sophos Central, and switching CMStatus to Disable does not
    end an existing registration.
 
.PARAMETER FWBackup
    Optional. Whether configuration backups are sent to Sophos Central: BackupEnable or
    BackupDisable. Corresponds to "Send configuration backup to Sophos Central" in the web
    admin console. Requires CMStatus to resolve to Enable - see the description. If omitted,
    the current value is kept.
 
.PARAMETER JoinMethod
    Optional. How the firewall joined Sophos Central: Manual or ZeroTouch. This switch has
    no equivalent in the web admin console; it reflects how the join happened rather than
    being a setting an administrator toggles directly. If omitted, the current value is
    kept.
 
.PARAMETER UseCentralReporting
    Optional. Whether centralized reporting in Sophos Central is on: Enable or Disable.
    Corresponds to "Use Sophos Central reporting" in the web admin console. Switching this to
    Enable is accepted by the firewall but not completed by this cmdlet alone - see the
    description. If omitted, the current value is kept.
 
.PARAMETER CMStatus
    Optional. Whether the firewall is managed from Sophos Central: Enable or Disable.
    Corresponds to "Use Sophos Central management" in the web admin console. Switching this
    to Enable is accepted by the firewall but not completed by this cmdlet alone - see the
    description. If omitted, the current value is kept.
 
.PARAMETER Firewall
    Optional. Host name or IP address of the firewall. If omitted, the value from the
    current connection is used.
 
.PARAMETER Port
    Optional. TCP port of the management API, usually 4444. If omitted, the value from
    the current connection is used.
 
.PARAMETER Username
    Optional. User name for the API login. The account needs permission to change Sophos
    Central settings. If omitted, the value from the current connection is used.
 
.PARAMETER Password
    Optional. Password for the API login, as a SecureString. If omitted, the value from
    the current connection is used.
 
.PARAMETER SkipCertificateCheck
    Optional. Accepts the firewall certificate without validating it. Use this only for
    appliances that still present a self-signed certificate. If omitted, the certificate
    is validated.
 
.PARAMETER Session
    Optional. A session object from Connect-SfosFirewall, or the name of a session that
    was registered with Connect-SfosFirewall -Name. Use it to address a specific firewall
    when you work with more than one at a time. Any connection parameter you pass
    explicitly still takes precedence. If omitted, the stored default connection is used.
 
.INPUTS
    None. This cmdlet does not accept pipeline input.
 
.OUTPUTS
    None. The cmdlet writes no output. It throws if the firewall rejects the update, and
    also if the firewall reports success but a requested change to UseCentralReporting or
    CMStatus was not applied - see the description.
 
.EXAMPLE
    Set-SfosCentralManagement -CMStatus Disable -WhatIf
 
    Shows what the call would change without sending it to the firewall.
 
.EXAMPLE
    Set-SfosCentralManagement -UseCentralReporting Disable -Confirm:$false
 
    Switches only centralized reporting off, leaving management, backup and join method
    untouched, without asking for confirmation. Use this form only in scripts where the
    value has already been reviewed.
 
.EXAMPLE
    Set-SfosCentralManagement -FWBackup BackupEnable -CMStatus Disable -Confirm:$false
 
    Throws before sending anything to the firewall, because FWBackup cannot be BackupEnable
    while CMStatus is Disable.
 
.LINK
    https://docs.sophos.com/nsg/sophos-firewall/22.0/api/
 
.LINK
    Get-SfosCentralManagement
#>

function Set-SfosCentralManagement {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param(
        [ValidateSet('BackupEnable', 'BackupDisable')]
        [string]$FWBackup,

        [ValidateSet('Manual', 'ZeroTouch')]
        [string]$JoinMethod,

        [ValidateSet('Enable', 'Disable')]
        [string]$UseCentralReporting,

        [ValidateSet('Enable', 'Disable')]
        [string]$CMStatus,

        # Connection parameters (optional - use stored context if not provided)
        [string]$Firewall,
        [int]$Port,
        [string]$Username,
        [SecureString]$Password,
        [switch]$SkipCertificateCheck,

        [object]$Session
    )

    $params = Resolve-SfosParameters -BoundParameters $PSBoundParameters
    $bp = $PSBoundParameters

    # Cheap short-circuit: if the caller passed both FWBackup and CMStatus explicitly, the
    # conflict is already fully known and is reported before the firewall is contacted at
    # all - not just before anything is written.
    if ($bp.ContainsKey('FWBackup') -and $bp.ContainsKey('CMStatus') -and
        $FWBackup -eq 'BackupEnable' -and $CMStatus -eq 'Disable') {
        throw "Cannot update CentralManagement: FWBackup cannot be 'BackupEnable' together with CMStatus 'Disable' - configuration backup to Sophos Central depends on Sophos Central management being on, matching the web admin console. Set CMStatus to 'Enable' as well, or leave FWBackup at 'BackupDisable'."
    }

    $existing = Get-SfosCentralManagement -Firewall $params.Firewall `
        -Port $params.Port `
        -Username $params.Username `
        -Password $params.Password `
        -SkipCertificateCheck:$params.SkipCertificateCheck

    $targetFWBackup = if ($bp.ContainsKey('FWBackup')) { $FWBackup } else { $existing.FWBackup }
    $targetJoinMethod = if ($bp.ContainsKey('JoinMethod')) { $JoinMethod } else { $existing.JoinMethod }
    $targetUseCentralReporting = if ($bp.ContainsKey('UseCentralReporting')) { $UseCentralReporting } else { $existing.UseCentralReporting }
    $targetCMStatus = if ($bp.ContainsKey('CMStatus')) { $CMStatus } else { $existing.CMStatus }

    if ($targetFWBackup -eq 'BackupEnable' -and $targetCMStatus -eq 'Disable') {
        throw "Cannot update CentralManagement: FWBackup cannot be 'BackupEnable' while CMStatus resolves to 'Disable' (the current value on the firewall, since it was not passed) - configuration backup to Sophos Central depends on Sophos Central management being on, matching the web admin console. Set CMStatus to 'Enable' as well, or leave FWBackup at 'BackupDisable'."
    }

    if (-not $PSCmdlet.ShouldProcess("CentralManagement on $($params.Firewall)", 'Update')) {
        return
    }

    $inner = @"
<Set operation="update">
  <EnableCloudCentralManagement>
    <FWBackup>$(ConvertTo-SfosXmlEscaped -Text $targetFWBackup)</FWBackup>
    <JoinMethod>$(ConvertTo-SfosXmlEscaped -Text $targetJoinMethod)</JoinMethod>
    <UseCentralReporting>$(ConvertTo-SfosXmlEscaped -Text $targetUseCentralReporting)</UseCentralReporting>
    <CMStatus>$(ConvertTo-SfosXmlEscaped -Text $targetCMStatus)</CMStatus>
  </EnableCloudCentralManagement>
</Set>
"@


    try {
        $response = Invoke-SfosApi -Firewall $params.Firewall `
            -Port $params.Port `
            -Username $params.Username `
            -Password $params.Password `
            -InnerXml $inner -SkipCertificateCheck:$params.SkipCertificateCheck -ErrorAction Stop
    }
    catch {
        throw "Error updating CentralManagement: $($_.Exception.Message)"
    }

    $XmlResponse = [xml]$response.Content
    Assert-SfosApiReturnSuccess -Xml $XmlResponse -ObjectName 'EnableCloudCentralManagement' -Action 'update'

    # The firewall answers 200 for a write it does not apply. Turning a Sophos Central
    # service off works; turning one back on is accepted, reported successful and silently
    # ignored, because a service has to be accepted in the Sophos Central console before it
    # runs. A cmdlet whose name promises the change therefore reads the object back and
    # throws instead of leaving the caller with a success that never happened.
    $applied = Get-SfosCentralManagement -Firewall $params.Firewall `
        -Port $params.Port `
        -Username $params.Username `
        -Password $params.Password `
        -SkipCertificateCheck:$params.SkipCertificateCheck

    $mismatch = @(
        @{ Field = 'FWBackup'; Wanted = $targetFWBackup; Actual = [string]$applied.FWBackup }
        @{ Field = 'JoinMethod'; Wanted = $targetJoinMethod; Actual = [string]$applied.JoinMethod }
        @{ Field = 'UseCentralReporting'; Wanted = $targetUseCentralReporting; Actual = [string]$applied.UseCentralReporting }
        @{ Field = 'CMStatus'; Wanted = $targetCMStatus; Actual = [string]$applied.CMStatus }
    ) | Where-Object { $_.Wanted -ne $_.Actual }

    # A service switched on lands on the undocumented value 'WaitingForApproval' first: the
    # request was accepted and now waits for a super admin to click 'Accept services' in the
    # Sophos Central console. That is a pending request, not a failed write, so it warns.
    # Anything else that did not take effect is the silent-no-op case and throws.
    $pending = @($mismatch | Where-Object { $_.Actual -eq 'WaitingForApproval' })
    $failed = @($mismatch | Where-Object { $_.Actual -ne 'WaitingForApproval' })

    foreach ($field in $pending) {
        Write-Warning "$($field.Field) was requested as '$($field.Wanted)' and now reads 'WaitingForApproval'. The service starts once a super admin confirms it in the Sophos Central console with 'Accept services'."
    }

    if ($failed) {
        $detail = ($failed | ForEach-Object { "$($_.Field): requested '$($_.Wanted)', firewall reports '$($_.Actual)'" }) -join '; '
        throw "The firewall reported success for the CentralManagement update but did not apply it - $detail. Switching a Sophos Central service on cannot be completed through the API alone; confirm it in the Sophos Central console instead."
    }
}

#endregion