PoshSophos.psm1


#region Core Functions

function Get-SophosCentralCredential {

    try {

        Write-Verbose -Message 'Retrieving Sophos API Credentials'

        if (!$Global:sophosClientID) {

            $Global:sophosClientID = Read-Host 'Enter Sophos Client ID (push ctrl + c to exit)'
        }

        if (!$Global:sophosClientSecret) {

            $Global:sophosClientSecret = Read-Host 'Enter Sophos Client Secret (push ctrl + c to exit)'
        }

        @{
            'ClientID'     = $Global:sophosClientID
            'ClientSecret' = $Global:sophosClientSecret
        }

        Write-Verbose -Message 'Retrieved API Credentials'
    }
    catch {

        Write-Error -Message 'Problem getting Sophos credential variables'
    }
}

function Connect-SophosCentral {

    [CmdletBinding()]
    param (

        [Parameter(Mandatory, HelpMessage = 'The client ID from the Sophos Central API credential/service principal')]
        [String] $ClientID,

        [Parameter(HelpMessage = 'The client secret from the Sophos Central API credential/service principal')]
        [String] $ClientSecret
    )

    $currentProtocol = [Net.ServicePointManager]::SecurityProtocol

    if ($currentProtocol.ToString().Split(',').Trim() -notcontains 'Tls12') {

        [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12
    }

    if ($null -eq $ClientSecret) {

        $ClientSecret = Read-Host -AsSecureString -Prompt 'Client Secret: '
    }

    $loginUri = 'https://id.sophos.com/api/v2/oauth2/token'

    $body = @{

        grant_type    = 'client_credentials'
        client_id     = $ClientID
        client_secret = $ClientSecret
        scope         = 'token'
    }

    try {

        $response = Invoke-WebRequest -Uri $loginUri -Body $body -ContentType 'application/x-www-form-urlencoded' -Method Post -UseBasicParsing
    }
    catch {

        throw 'Error requesting access token: {0}' -f $_
    }

    if ($response.Content) {

        $authDetails = $response.Content | ConvertFrom-Json
        $expiresAt = (Get-Date).AddSeconds($authDetails.expires_in - 60)


        $authDetails | Add-Member -MemberType NoteProperty -Name expires_at -Value $expiresAt
        $authDetails.access_token = $authDetails.access_token | ConvertTo-SecureString -AsPlainText -Force
        $Script:SophosCentral = $authDetails
    }

    $Script:SophosCentral
}

function New-SophosCentralHeaders {

    $sophosCredentials = Get-SophosCentralCredential

    $null = Connect-SophosCentral -ClientID $sophosCredentials.ClientID -ClientSecret $sophosCredentials.ClientSecret

    $Script:sophosHeaders = @{

        Authorization = 'Bearer ' + (Unprotect-Secret -Secret $Script:SophosCentral.access_token)
    }

    $tenantUri = 'https://api.central.sophos.com/whoami/v1'

    $tenantInfo = Invoke-RestMethod -Uri $tenantUri -Headers $Script:sophosHeaders -Method Get -UseBasicParsing

    $Script:SophosCentral | Add-Member -MemberType NoteProperty -Name GlobalEndpoint -Value $tenantInfo.apiHosts.global
    $Script:SophosCentral | Add-Member -MemberType NoteProperty -Name RegionEndpoint -Value $tenantInfo.apiHosts.dataRegion
    $Script:SophosCentral | Add-Member -MemberType NoteProperty -Name TenantID -Value $tenantInfo.id
    $Script:SophosCentral | Add-Member -MemberType NoteProperty -Name IDType -Value $tenantInfo.idType

    $Script:sophosHeaders.Add('X-Tenant-ID', $Script:SophosCentral.TenantID)
}

function Invoke-SophosCentralWebRequest {

    [CmdletBinding()]
    param (

        [Parameter(Mandatory)]
        [System.URI] $Uri,

        [ValidateSet('Get', 'Post', 'Put', 'Delete')]
        [string] $Method = 'Get',

        [System.Collections.Hashtable] $Body
    )

    $requestParams = @{
        Uri             = $uri
        Headers         = $Script:sophosHeaders
        UseBasicParsing = $true
        Method          = $Method
    }

    if ((!$Body) -and ($Method -in ('Post', 'Put'))) {

        $bodyTemp = @{} | ConvertTo-Json

        $requestParams.Add('Body', $bodyTemp)
    }
    elseif (($Body) -and ($Method -eq 'Get')) {

        $requestParams.Add('Body', $Body)
    }
    elseif ($Body) {

        $requestParams.Add('Body', ($Body | ConvertTo-Json -Depth 5))
    }

    if ($Method -notin ('Delete', 'Get')) {

        $requestParams.Add('ContentType', 'application/json')
    }

    try {

        $response = Invoke-RestMethod @requestParams

        if ($response.items) {

            $response.items
        }
        else {

            $response
        }
    }
    catch {

        $errorDetails = ConvertFrom-Json -InputObject $_.ErrorDetails.Message

        throw $errorDetails.message
    }


    $finished = $false

    do {
        if ($response.pages.nextKey) {

            if ($uri.AbsoluteUri -like '*`?*') {

                $requestParams['Uri'] = $uri.AbsoluteUri + '&pageFromKey=' + $response.pages.nextKey
            }
            else {

                $requestParams['Uri'] = $uri.AbsoluteUri + '?pageFromKey=' + $response.pages.nextKey
            }

            $response = Invoke-RestMethod @requestParams

            $response.items
        }
        elseif ($response.has_more) {

            if ($uri.AbsoluteUri -like '*`?*') {

                $requestParams['Uri'] = $uri.AbsoluteUri + '&cursor=' + $response.next_cursor
            }
            else {

                $requestParams['Uri'] = $uri.AbsoluteUri + '?cursor=' + $response.next_cursor
            }

            $response = Invoke-RestMethod @requestParams

            $response.items
        }
        else {

            $finished = $true
        }
    } while ($finished -eq $false)
}


#endregion

#region Helper Functions


function Invoke-UriBuilder {

    [CmdletBinding()]
    param (

        [Parameter(Mandatory)]
        [System.Uri] $Uri,

        [hashtable] $OriginalPsBoundParameters,

        [array] $FilteredParameters
    )

    New-SophosCentralHeaders

    $regionEndpoint = $Script:SophosCentral.RegionEndpoint

    $Uri = $regionEndpoint + $Uri

    $uriBuilder = [System.UriBuilder]::New($Uri.AbsoluteUri)

    $blockedParams = 'Verbose', 'Force', 'Debug', 'WhatIf' + $FilteredParameters

    $keys = $OriginalPsBoundParameters.Keys | Where-Object { $blockedParams -notcontains $_ }

    foreach ($param in $keys) {

        if (($OriginalPsBoundParameters[$param]) -and ($param)) {

            $paramToLower = $param.ToString()[0].ToString().ToLower() + $param.ToString().Substring(1)

            if ($OriginalPsBoundParameters[$param] -is [array]) {

                $queryBuilder = $paramToLower + '=' + ($OriginalPsBoundParameters[$param] -join ',')
            }
            else {

                if ($OriginalPsBoundParameters[$param].GetType().Name -eq 'DateTime') {

                    $OriginalPsBoundParameters[$param] = $OriginalPsBoundParameters[$param].ToUniversalTime().ToString('u').Replace(' ', 'T')
                }

                $queryBuilder = $paramToLower + '=' + $OriginalPsBoundParameters[$param]
            }
            if (($null -eq $uriBuilder.Query) -or ($uriBuilder.Query.Length -le 1 )) {

                $uriBuilder.Query = $queryBuilder
            }
            else {

                $uriBuilder.Query = $uriBuilder.Query.Substring(1) + '&' + $queryBuilder
            }
        }
    }

    [System.Uri]::New($uriBuilder.Uri)
}

function Unprotect-Secret {

    [CmdletBinding()]
    [OutputType([System.String])]
    param (

        [Parameter(Mandatory, HelpMessage = 'The Secure String to convert to plain text')]
        [SecureString] $Secret
    )

    $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secret)
    [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
}


#endregion

#region Endpoint Functions


function Get-SophosCentralEndpoint {

    [CmdletBinding()]
    param (

        [ValidateSet('bad', 'good', 'suspicious', 'unknown')]
        [string[]] $HealthStatus,

        [ValidateSet('computer', 'server', 'securityVm')]
        [string[]] $Type,

        [System.Boolean] $TamperProtectionEnabled,

        [ValidateSet('creatingWhitelist', 'installing', 'locked', 'notInstalled', 'registering', 'starting', 'stopping', 'unavailable', 'uninstalled', 'unlocked')]
        [string[]] $LockdownStatus,

        [ValidateSet('isolated', 'notIsolated')]
        [string] $IsolationStatus,

        [ValidateSet('basic', 'summary', 'full')]
        [string] $View,

        [string] $HostnameContains,

        [string] $IpAddresses,

        [string] $MacAddresses,

        [string] $Search,

        [ValidateSet('hostname', 'groupName', 'associatedPersonName', 'ipAddress', 'osName')]
        [string] $SearchField = 'hostname',

        [datetime] $LastSeenBefore,

        [datetime] $LastSeenAfter,

        [ValidateScript({
                if ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID'
                }
                else {
                    return $true
                }
            })]
        [string[]] $ID
    )

    if ($ID.count -gt 0) {

        $PsBoundParameters.Add('ids', $PsBoundParameters['ID'])

        $null = $PsBoundParameters.Remove('ID')
    }

    $uriEndpoint = '/endpoint/v1/endpoints'

    $uri = Invoke-UriBuilder -Uri $uriEndpoint -OriginalPsBoundParameters $PsBoundParameters

    Invoke-SophosCentralWebRequest -Uri $uri
}

function Get-SophosCentralEndpointDetail {

    [CmdletBinding()]
    param (

        [ValidateSet('basic', 'summary', 'full')]
        [string] $View,

        [ValidateScript({
                if ($false -eq [System.Guid]::TryParse($_, $([ref][guid]::Empty))) {
                    throw 'Not a valid GUID'
                }
                else {
                    return $true
                }
            })]
        [string[]] $ID
    )

    if ($ID.count -gt 0) {

        $PsBoundParameters.Add('ids', $PsBoundParameters['ID'])

        $null = $PsBoundParameters.Remove('ID')
    }

    $uriEndpoint = ('/endpoint/v1/endpoints/{0}' -f $ID)

    $uri = Invoke-UriBuilder -Uri $uriEndpoint -OriginalPsBoundParameters $PsBoundParameters

    Invoke-SophosCentralWebRequest -Uri $uri
}

function Remove-SophosCentralEndpoint {

    [CmdletBinding()]
    param (

        [Parameter(Mandatory, ValueFromPipelineByPropertyName = $True)]
        [Alias('ID')]
        [string[]] $EndpointId
    )

    begin {

    }

    process {

        foreach ($endpoint in $EndpointId) {

            $uriEndpoint = '/endpoint/v1/endpoints/{0}' -f $endpoint

            $uri = Invoke-UriBuilder -Uri $uriEndpoint

            Invoke-SophosCentralWebRequest -Uri $uri -Method Delete
        }
    }

    end {

    }
}


#endregion

#regoin SIEM Integration Functions


function Get-SophosCentralSiemEvents {

    [CmdletBinding()]
    param (

        # The starting date from which alerts will be retrieved defined as Unix timestamp in UTC. Ignored if cursor is set. Must be within last 24 hours.
        [datetime] $FromDate,

        [string] $ExcludedTypes,

        # The maximum number of items to return (per page), default is 200, max is 1000.
        [int] $Limit
    )

    $params = @{}

    if ($FromDate) {

        $params = @{

            from_date = $FromDate
        }
    }

    if ($ExcludedTypes) {

        $params += @{

            excluded_types = $ExcludedTypes
        }
    }

    $uriEndpoint = '/siem/v1/events'

    $uri = Invoke-UriBuilder -Uri $uriEndpoint -OriginalPsBoundParameters $params

    $response = Invoke-SophosCentralWebRequest -Uri $uri

    if ($response.created_at) {

        $response
    }
    else {

        Write-Host -Object 'No events found at this time'
    }
}

function Get-SophosCentralSiemAlerts {

    [CmdletBinding()]
    param (

        # The starting date from which alerts will be retrieved defined as Unix timestamp in UTC. Ignored if cursor is set. Must be within last 24 hours.
        [datetime] $FromDate,

        # The maximum number of items to return (per page), default is 200, max is 1000.
        [int] $Limit
    )

    $params = @{}

    if ($FromDate) {

        $params = @{

            from_date = $FromDate
        }
    }

    $uriEndpoint = '/siem/v1/alerts'

    $uri = Invoke-UriBuilder -Uri $uriEndpoint -OriginalPsBoundParameters $params

    $response = Invoke-SophosCentralWebRequest -Uri $uri

    if ($response.created_at) {

        $response
    }
    else {

        Write-Host -Object 'No alerts found at this time'
    }
}

#endregion