Advisory: Sophos Endpoint "Your connection isn't private" after reboot. Policy settings can be returned to normal. See: KB-000045954 for the latest updates.

Sophos Central Endpoint: Sophos Central API Tamper protection toolkit

Disclaimer: This information is provided as-is for the benefit of the Community. Please contact Sophos Professional Services if you require assistance with your specific environment.


Overview

This article describes how to create a PowerShell script to simplify and automate the management of tamper protection settings for devices in Sophos Central. This tool is particularly useful for large-scale environments, where manually toggling tamper protection for numerous devices would be time-consuming and prone to error.

 

Applies to the following Sophos products and versions.

Sophos Central Endpoint Windows
Sophos Central Endpoint Windows Server

Key Features

  • Automated Tamper Protection Management: Easily toggle tamper protection settings for Sophos Central devices.
  • Legacy OS Support: Ensures smooth upgrade processes for legacy operating systems.
  • Custom Reporting: Generates comprehensive reports of tamper activity.
  • User-Friendly Interface: Menu-driven interface for ease of use.
  • Authentication Handling: Securely manages API authentication and token refresh.
  • Error Handling: Robustly catches and reports errors during API interactions and report generation.
  • Device Selection Flexibility: Target endpoints, servers, or all Windows devices within Sophos Central.
  • Bulk Operations: Reduces time and effort for tamper protection modifications.
  • Detailed Reporting: Provides post-operation reports detailing actions taken on each device.

 

Preparing the Script

Ensure PowerShell 5.1 or higher is installed and you have access to the Sophos Central API. Gather the necessary API credentials (Client ID and Secret) for authentication.

 

Risks and Considerations

  • API Usage: Improper use of the API can lead to disruptions. Understand the implications of tamper protection modifications.
  • Execution Policy: Changing the execution policy can expose your system to security risks. Use the 'Unrestricted' policy judiciously.
  • Testing: Always test the script in a controlled environment before use in production.

  

Ideal Usage Scenario

The toolkit is suited for scenarios requiring bulk modifications of tamper protection, such as before an OS upgrade across multiple legacy systems. It ensures accurate and efficient execution of tamper activities, minimizing manual effort and error.

<#
.SYNOPSIS
SophosTamperToolkit - A script for comprehensive management of tamper protection on Sophos Endpoints.

.DESCRIPTION
The SophosTamperToolkit script is designed for the efficient management of tamper protection across a range of operating systems in Sophos Endpoint environments. Its key functionalities include:
1. Authenticating with the Sophos Central API.
2. Listing endpoints and servers, including legacy systems, and providing their tamper protection status.
3. Allowing the user to select specific endpoints or servers for modifying tamper protection.
4. Offering the choice to select individual systems or all systems within the chosen category.
5. Disabling or enabling tamper protection on the selected systems, based on user input.
6. Generating a comprehensive report detailing the actions performed, with the option to include tamper protection passwords if needed.

.NOTES
Version:        1.0
Author:         Ismail Jaweed Ahmed
Creation Date:  22/12/2023
Dependencies:   Requires PowerShell 5.1 or higher and access to the Sophos Central API.

.EXAMPLE
PS> .\SophosTamperToolkit.ps1
# This command runs the SophosTamperToolkit script.

#>



function art{

write-host "
      #####    ####    #####    ##  ##    ####     #####          
     ##       ##  ##   ##  ##   ##  ##   ##  ##   ##              
      ####    ##  ##   #####    ######   ##  ##    ####           
         ##   ##  ##   ##       ##  ##   ##  ##       ##          
     #####     ####    ##       ##  ##    ####    #####           
" -ForegroundColor white -BackgroundColor Blue
write-host "                   Cybersecurity Delivered.                       " -ForegroundColor Red -BackgroundColor Black
Write-Host " "
Write-Host "------------------------------------------------------------------" -ForegroundColor Red
Write-Host "        Sophos Central API TamperProtection toolkit               "-ForegroundColor White
Write-Host "------------------------------------------------------------------" -ForegroundColor Red
}

# Introduction and Information Display
function DisplayIntro {
    Clear-Host
    art
    Write-Host "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - NOTE - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" -ForegroundColor Yellow -BackgroundColor Black
    Write-Host "Welcome to the Sophos Central API TamperProtection Toolkit                         " -ForegroundColor Cyan
    Write-Host "This script interacts with Sophos Central API to manage tamper protection.         " -ForegroundColor Cyan
    Write-Host "It requires API credentials for authentication and operation.                      " -ForegroundColor Cyan
    Write-Host "It requires API credentials for authentication and operation.                      " -ForegroundColor Cyan
    Write-Host "Please ensure you have the necessary permissions and understand the risks involved." -ForegroundColor Cyan
    Write-Host "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" -ForegroundColor Yellow -BackgroundColor Black
    Write-Host ""
    Write-Host "Press CTRL+C to exit." -ForegroundColor Red -BackgroundColor Black
    pause 
    
}


function Gettime {

    $global:time = Get-Date
}

function GetClientIDandSecret {
Clear-Host
art
    Write-Host "Please provide the API Credentials of the Sophos Central"
    $global:clientid = Read-Host "Enter Client ID"
    $global:clientsecret = Read-Host "Enter Client Secret"
}

function GetAuthToken {

    $body = "grant_type=client_credentials&scope=token&client_id=$global:clientid&client_secret=$global:clientsecret"
    $headers = @{"Content-Type" = "application/x-www-form-urlencoded"}
    Sleep 1
    Write-Host "Authenticating to API Gateway.." -ForegroundColor Yellow
    try {
        $response = Invoke-RestMethod -Uri 'https://id.sophos.com/api/v2/oauth2/token' -Method 'POST' -Headers $headers -Body $body
        $global:token = $response.access_token
        $global:time = Get-Date
        Write-Host "Authentication Successful." -ForegroundColor Green
    } catch {
        Write-Host "Authentication Failed." -ForegroundColor Red
        Write-Host "Read the Error Code and Message" -ForegroundColor Yellow
        Write-Error $_
        Write-Host "`nPlease try again..." -ForegroundColor Cyan
        pause
        CheckandDoAuth
    }
}

function GetDataRegion {

    Write-Host "Collecting Data Region Details.." -ForegroundColor Yellow
    $headers = @{"Authorization" = "Bearer $global:token"}
    $gdrresponse = Invoke-RestMethod 'https://api.central.sophos.com/whoami/v1' -Method 'GET' -Headers $headers
    $global:TenantId = $gdrresponse.id
    $global:dataregion = $gdrresponse.apiHosts.dataRegion
    Write-Host "Data Region Details Collected Successfully." -ForegroundColor Green
}

function CheckandDoAuth {
Clear-Host
art
    if ($global:time -eq $null) {
        Write-Host "No authentication found"
        Write-Host "Proceeding to Authenticate"
        GetClientIDandSecret
        GetAuthToken
        GetDataRegion
    } else {
        $currtime = Get-Date
        $timedifference = ($currtime - $global:time).TotalSeconds
        if ($timedifference -gt 3600) {
            Write-Host "Authentication expired"
            GetClientIDandSecret
            GetAuthToken
            GetDataRegion
        } else {
            Write-Host "The previous authentication is still valid."
        }
    }
}

function GetEndpoints {
Clear-Host
art
    if (-not ($global:dataregion -and $global:token -and $global:TenantId)) {
        Write-Host "Required data not available. Please authenticate first." -ForegroundColor Red
        return
    }

    $url = "$($global:dataregion)/endpoint/v1/endpoints?pageSize=500&pageTotal=true&view=full"
    $headers = @{
        "Authorization" = "Bearer $($global:token)"
        "X-Tenant-ID" = $global:TenantId
        "Accept" = "application/json"
    }

    try {
        $global:epresponse = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
    } catch {
        Write-Host "Failed to retrieve endpoints: $($_.Exception.Message)" -ForegroundColor Red
    }
}

function IsLegacyOS($osName) {

    $legacyOSList = @("Windows Server 2012", "Windows Server 2008", "Windows SBS 2011", "Windows 8.1", "Windows 7", "Windows 10")
    foreach ($legacyOS in $legacyOSList) {
        if ($osName -like "$legacyOS*") {
            return $true
        }
    }
    return $false
}

function HandleLegacyOSMachines([string]$machineType) {

    if (-not $machineType) {
        Write-Host "Machine type not specified in HandleLegacyOSMachines."
        return
    }

    $legacyMachines = $global:epresponse.items | Where-Object {
        $_.type -eq $machineType -and (& IsLegacyOS $_.os.name)
    }

    if ($legacyMachines.Count -eq 0) {
        Write-Host "No legacy OS $machineType found." -ForegroundColor Red
        pause
        clear-host
        return ShowMenu
        
    }

    HandleMachineTamperProtectionLogic -machines $legacyMachines -machineType $machineType
}


# Function to handle machine tamper protection (endpoints/servers)
function HandleMachineTamperProtection([string]$machineType) {

    if (-not $machineType) {
        Write-Host "Machine type not specified in HandleMachineTamperProtection."
        return
    }

    $machines = $global:epresponse.items | Where-Object { $_.type -eq $machineType }

    if ($machines.Count -eq 0) {
        Write-Host "No $machineType found." -ForegroundColor Red
        pause
        return ShowMenu
        clear-host
        
    }

    HandleMachineTamperProtectionLogic -machines $machines -machineType $machineType
}


function HandleMachineTamperProtectionLogic([object[]]$machines, [string]$machineType) {
    if (-not $machineType) {
        Write-Host "Machine type not specified in HandleMachineTamperProtectionLogic."
        return
    }

    $selectionChoice = Read-Host "Do you want to select specific $machineType or apply to all? Enter 'specific' or 'all'"

    if ($selectionChoice.ToLower() -eq 'specific') {
        $selectedMachines = SelectSpecificMachines $machines
    } elseif ($selectionChoice.ToLower() -eq 'all') {
        $selectedMachines = $machines
    } else {
        Write-Host "Invalid choice. Please enter 'specific' or 'all'."
        return
    }

    if ($selectedMachines) {
        $actionChoice = Read-Host "Do you want to 'turn on' or 'turn off' tamper protection for the selected $machineType? (Enter 'on' or 'off')"
        
$actions = @()
foreach ($machine in $selectedMachines) {
    $actionResult = ChangeTamperProtection $machine.id ($actionChoice -eq "on")
    $object = New-Object PSObject -Property @{
        ID = $machine.id
        Action = $actionResult
    }
    $actions += $object
}
        GenerateAndDisplayReport $selectedMachines $actions
    } else {
        Write-Host "No $machineType(s) selected for tamper protection."
    }
}


# Function to select specific machines
function SelectSpecificMachines($machines) {

    $index = 1
    $machineMap = @{}
    foreach ($machine in $machines) {
        Write-Host "$index. Hostname: $($machine.hostname), OS: $($machine.os.name), ID: $($machine.id)"
        $machineMap[$index.ToString()] = $machine
        $index++
    }

    $selectedIndexes = Read-Host "Enter the numbers of the machines to select (comma-separated)"
    $selectedMachines = @()

    foreach ($indexString in $selectedIndexes -split ',') {
        $trimmedIndex = $indexString.Trim()
        if ($machineMap.ContainsKey($trimmedIndex)) {
            $selectedMachines += $machineMap[$trimmedIndex]
        } else {
            Write-Host "Index $trimmedIndex not found"
        }
    }

    Write-Host "Selected machines count: $($selectedMachines.Count)"
    return $selectedMachines
}


# Function to generate and display report
function GenerateAndDisplayReport($machines, $actions) {
    $includePassword = Read-Host "Would you like to include the tamper password in the report? (yes/no)"
    $reportData = @()

    foreach ($machine in $machines) {
        $action = ($actions | Where-Object { $_.ID -eq $machine.id }).Action
        $reportEntry = [PSCustomObject]@{
            Hostname = $machine.hostname
            OS = $machine.os.name
            ID = $machine.id
            ActionTaken = $action
        }

        if ($includePassword -eq 'yes') {
            $password = RetrieveTamperPassword $machine.id
            $reportEntry | Add-Member -NotePropertyName "TamperPassword" -NotePropertyValue $password
        }

        $reportData += $reportEntry
    }

    $reportPath = "$(Get-Location)\TamperProtectionReport.csv"
    $reportData | Export-Csv -Path $reportPath -NoTypeInformation -Force
    Write-Host "Report generated at: $reportPath"
    pause
    return ShowMenu
}



# Function to retrieve tamper password (Placeholder)
function RetrieveTamperPassword($endpointId) {

    # Ensure required global variables are available
    if (-not ($global:dataregion -and $global:token -and $global:TenantId)) {
        Write-Host "Required data is not available (data region, token, tenant ID). Please authenticate first." -ForegroundColor Red
        return
    }

    # Construct the endpoint URL with the endpoint ID
    $url = "$($global:dataregion)/endpoint/v1/endpoints/$endpointId/tamper-protection"

    # Prepare the headers
    $headers = @{
        "Authorization" = "Bearer $($global:token)"
        "X-Tenant-ID" = $global:TenantId
        "Accept" = "application/json"
    }

    # Make the GET request
    try {
        $response = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
        return $response.password
    }
    catch {
        Write-Host "Failed to retrieve tamper protection password for endpoint ID: $endpointId. Error: $($_.Exception.Message)" -ForegroundColor Red
    }
}


# Function to turn on/off tamper protection (API Call)
function ChangeTamperProtection($endpointId, $enable) {
    $url = "$($global:dataregion)/endpoint/v1/endpoints/$endpointId/tamper-protection"
    $headers = @{
        "Authorization" = "Bearer $($global:token)"
        "X-Tenant-ID" = $global:TenantId
        "Accept" = "application/json"
    }
    $body = @{ "enabled" = $enable } | ConvertTo-Json

    try {
        $response = Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType "application/json"
        if ($enable) {
            Write-Host "Tamper protection turned on for endpoint ID: $endpointId"
            return "Turned On"
        } else {
            Write-Host "Tamper protection turned off for endpoint ID: $endpointId"
            return "Turned Off"
        }
    }
    catch {
        Write-Host "Failed to change tamper protection for endpoint ID: $endpointId. Error: $($_.Exception.Message)" -ForegroundColor Red
        return "Failed"
    }
}

# Choose between endpoints and servers, and handle tamper protection
function ShowMenu {
Clear-Host
art
    while ($true) {
        Write-Host "========== Main Menu ============" -ForegroundColor Green -BackgroundColor Black
        Write-host " "
        Write-Host "Select an option:"
        Write-Host "1. Work with endpoints" -ForegroundColor White -BackgroundColor Black
        Write-Host "2. Work with servers" -ForegroundColor White -BackgroundColor Black
        Write-Host "3. Work with legacy OS endpoints" -ForegroundColor White -BackgroundColor Black
        Write-Host "4. Work with legacy OS servers" -ForegroundColor White -BackgroundColor Black
        Write-Host "5. Exit" -ForegroundColor Red -BackgroundColor Black
        Write-Host "================================" -ForegroundColor Green -BackgroundColor Black
        $choice = Read-Host "Enter your choice (1-5)"
        switch ($choice) {
            "1" { HandleMachineTamperProtection "computer" }
            "2" { HandleMachineTamperProtection "server" }
            "3" { HandleLegacyOSMachines "computer" }
            "4" { HandleLegacyOSMachines "server" }
            "5" { Write-Host "Exiting script."; return }
            default { Write-Host "Invalid choice. Please enter a number between 1 and 5." }
        }
    }
}





# Main Script Execution
DisplayIntro
CheckandDoAuth  # Authenticate and set up
GetEndpoints    # Fetch Endpoint Data
ShowMenu       # Display the menu and handle user choices

 

Instruction to prepare the script. 

  1. Login to your current Sophos Central
  2. Follow the document below to create an API Credential with “Service Principal Super Admin” role.
    URL: API Credential Management
  3. Make a note of the Client ID and Secret.
  4. Create a new text document anywhere.
  5. Copy the code above and paste it.
  6. Save the file as with an extension “.ps1”.

 

Instructions to run the script. 

  1. Login to any windows endpoint or server.
  2. Open command prompt as Administrator.
  3. Type command PowerShell and press enter.
  4. Navigate to a location where the script is saved.
  5. Change Execution Policy (if necessary):
    1. The default PowerShell execution policy might prevent the script from running. To temporarily allow the execution of the script, open PowerShell as an administrator and run:
      Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine
  6. Run the script with it is name as seen in the screenshot below
  7. Follow the prompts to authenticate, select devices, and modify tamper protection settings.

 

Current Limitations

  • The script does not support custom imports of device lists. Selection is based on device type or ID from Sophos Central API.


Disclaimer
[edited by: Qoosh at 4:56 PM (GMT -8) on 5 Jan 2024]