EventMonitor/EventProcessors/SSHEvents.ps1

# ── SSH Events Processor ──────────────────────────────────────────────────────
# Monitors OpenSSH server connection and disconnection events.
# Uses OpenSSH/Operational log, filtered by message content.

<#
.SYNOPSIS
    Collects SSH connect and disconnect events for a user within the time window.
#>

function Get-SSHEvents {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    Get-SSHConnectEvents    -sessionId $sessionId -StartTime $StartTime -User $User
    Get-SSHDisconnectEvents -sessionId $sessionId -StartTime $StartTime -User $User
    Get-SSHFailedAuthEvents -sessionId $sessionId -StartTime $StartTime
}

# ── SSH Connect (Accepted publickey) ──────────────────────────────────────────

function Get-SSHConnectEvents {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEventsByLog -LogName 'OpenSSH/Operational' -StartTime $StartTime

        foreach ($evt in $events) {
            if ("$($evt.Properties[1].Value)" -notlike 'Accepted publickey*') { continue }

            $message = $evt.Message

            # OpenSSH connect message format:
            # "Accepted publickey for <user> from <ip> port <port> ssh2: ..."
            $sshUser = if ($message -match 'for (\S+) from') { $Matches[1] } else { '' }
            if ($sshUser -ne $User) { continue }

            $sshIP   = if ($message -match 'from (\d+\.\d+\.\d+\.\d+)') { $Matches[1] } else { '' }
            $sshPort = if ($message -match 'port (\d+)') { $Matches[1] } else { '' }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Connect' -Severity 'Info'
            $props['UserName']  = $sshUser
            $props['IPAddress'] = $sshIP
            $props['IPPort']    = $sshPort
            $props['UserSID']   = "$($evt.UserId)"

            Send-LogAnalyticsConnectEvents `
                -eventName 'SSH Connect' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-SSHConnectEvents: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-SSHConnectEvents' -User $User)
    }
}

# ── SSH Disconnect ────────────────────────────────────────────────────────────

function Get-SSHDisconnectEvents {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEventsByLog -LogName 'OpenSSH/Operational' -StartTime $StartTime

        foreach ($evt in $events) {
            if ("$($evt.Properties[1].Value)" -notlike 'Disconnected*') { continue }

            # Extract connection details from the message
            $message = $evt.Message

            # OpenSSH disconnect message formats:
            # "Disconnected from user <user> <ip> port <port>"
            # "Disconnected from <ip> port <port>"
            # Extract user from "from user <name>" pattern
            $sshUser = ''
            if ($message -match 'from user (\S+)') {
                $sshUser = $Matches[1]
            }

            # Only track disconnects for the user we're monitoring
            if ($sshUser -and $sshUser -ne $User) { continue }

            # Extract IP and port via regex for reliability
            $sshIP   = if ($message -match '(\d+\.\d+\.\d+\.\d+)') { $Matches[1] } else { '' }
            $sshPort = if ($message -match 'port (\d+)') { $Matches[1] } else { '' }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Disconnect' -Severity 'Info'
            $props['UserName'] = if ($sshUser) { $sshUser } else { $User }
            $props['IP']       = $sshIP
            $props['Port']     = $sshPort
            $props['UserSID']  = "$($evt.UserId)"

            Send-LogAnalyticsConnectEvents `
                -eventName 'SSH Disconnect' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-SSHDisconnectEvents: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-SSHDisconnectEvents' -User $User)
    }
}

# ── SSH Failed Authentication ─────────────────────────────────────────────────
# Detects brute force SSH attacks by looking for "Failed password" and
# "Invalid user" messages in the OpenSSH Operational log.
# NOT filtered by user — all failed SSH auth attempts are security-relevant.

function Get-SSHFailedAuthEvents {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime
    )

    try {
        $events = Read-WindowsEventsByLog -LogName 'OpenSSH/Operational' -StartTime $StartTime

        foreach ($evt in $events) {
            $propValue = "$($evt.Properties[1].Value)"

            # Match failed auth patterns
            if ($propValue -notlike 'Failed password*' -and
                $propValue -notlike 'Invalid user*' -and
                $propValue -notlike 'Connection closed by*authenticating user*') { continue }

            $message = $evt.Message

            $failUser = ''
            if ($message -match 'for (?:invalid user )?(\S+) from') { $failUser = $Matches[1] }
            $failIP   = if ($message -match 'from (\d+\.\d+\.\d+\.\d+)') { $Matches[1] } else { '' }
            $failPort = if ($message -match 'port (\d+)') { $Matches[1] } else { '' }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Alert' -Severity 'High'
            $props['TargetUserName']   = $failUser
            $props['SourceIP']         = $failIP
            $props['SourcePort']       = $failPort
            $props['FailureDetail']    = $propValue
            $props['EventDescription'] = 'SSH Authentication Failed'
            $props['UserSID']          = "$($evt.UserId)"

            Send-LogAnalyticsConnectEvents `
                -eventName 'SSH Authentication Failed' -Properties $props -sendEvent $evt
        }
    }
    catch {
        if ($_.Exception.Message -notlike '*No events were found*') {
            Write-EMLog -Message "Get-SSHFailedAuthEvents: $($_.Exception.Message)" -Level Error
            TrackException -ErrorRecord $_ `
                -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-SSHFailedAuthEvents')
        }
    }
}