diff --git a/ansible/deploy_aw_windows_phase2.yml b/ansible/deploy_aw_windows_phase2.yml index 0f4a4ee..5af2043 100644 --- a/ansible/deploy_aw_windows_phase2.yml +++ b/ansible/deploy_aw_windows_phase2.yml @@ -21,6 +21,8 @@ aw_windows_state_root: "C:\\ProgramData\\ActivityWatch-Phase2" aw_windows_afk_enabled: true aw_windows_window_enabled: true + aw_windows_local_agent_logs_enabled: false + aw_windows_logon_marker_enabled: true aw_windows_skip_hardening: false aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json" aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json" @@ -87,6 +89,8 @@ StateRoot = "{{ aw_windows_state_root }}" AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }} WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }} + LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }} + LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }} CustomRulesPath = "{{ aw_windows_rules_path }}" CustomPolicyPath = "{{ aw_windows_policy_path }}" } diff --git a/ansible/group_vars/windows.example.yml b/ansible/group_vars/windows.example.yml index 439ce5d..8b41439 100644 --- a/ansible/group_vars/windows.example.yml +++ b/ansible/group_vars/windows.example.yml @@ -19,6 +19,8 @@ aw_windows_install_root: "C:\\Program Files\\ActivityWatch-Phase2" aw_windows_state_root: "C:\\ProgramData\\ActivityWatch-Phase2" aw_windows_afk_enabled: true aw_windows_window_enabled: true +aw_windows_local_agent_logs_enabled: false +aw_windows_logon_marker_enabled: true aw_windows_skip_hardening: false aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json" diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1 index c195dc2..5ed4cb3 100755 --- a/windows/ActivityWatch.Windows.Common.psm1 +++ b/windows/ActivityWatch.Windows.Common.psm1 @@ -319,6 +319,8 @@ function New-ActivityWatchDeploymentConfig { [int]$RecoveryIntervalSeconds, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$LocalAgentLogsEnabled = $true, + [bool]$LogonMarkerEnabled = $true, [Parameter(Mandatory = $true)] [string]$LaunchScriptPath, [Parameter(Mandatory = $true)] @@ -355,6 +357,13 @@ function New-ActivityWatchDeploymentConfig { afkEnabled = $AfkEnabled windowEnabled = $WindowEnabled } + logging = [pscustomobject]@{ + localAgentLogsEnabled = $LocalAgentLogsEnabled + } + sessionEvents = [pscustomobject]@{ + logonEnabled = $LogonMarkerEnabled + bucketPrefix = 'aw-session-events' + } recovery = [pscustomobject]@{ intervalSeconds = $RecoveryIntervalSeconds taskName = 'ActivityWatch Recovery' @@ -447,6 +456,92 @@ function Test-CollectorRunning { return [bool](`$processes | Select-Object -First 1) } +function Invoke-AwJsonPost { + param( + [Parameter(Mandatory = `$true)][string]`$Uri, + [Parameter(Mandatory = `$true)][string]`$Json + ) + + `$bytes = [Text.Encoding]::UTF8.GetBytes(`$Json) + Invoke-RestMethod -Method Post -Uri `$Uri -ContentType 'application/json; charset=utf-8' -Body `$bytes | Out-Null +} + +function Ensure-Bucket { + param( + [string]`$BucketId, + [string]`$ClientName, + [string]`$BucketType + ) + + if (`$script:KnownBuckets.ContainsKey(`$BucketId)) { + return + } + + `$body = @{ + client = `$ClientName + type = `$BucketType + hostname = `$script:Hostname + } | ConvertTo-Json -Compress + + Invoke-AwJsonPost -Uri "`$(`$script:ApiBase)/buckets/`$BucketId" -Json `$body + `$script:KnownBuckets[`$BucketId] = `$true +} + +function Send-LogonMarkerIfNeeded { + param( + [pscustomobject]`$Config, + [int]`$SessionId + ) + + `$sessionEvents = if (`$Config.PSObject.Properties.Name -contains 'sessionEvents') { `$Config.sessionEvents } else { `$null } + `$logging = if (`$Config.PSObject.Properties.Name -contains 'logging') { `$Config.logging } else { `$null } + `$logonEnabled = if (`$sessionEvents -and `$sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]`$sessionEvents.logonEnabled } else { `$false } + if (-not `$logonEnabled) { + return + } + + `$bucketPrefix = if (`$sessionEvents -and `$sessionEvents.PSObject.Properties.Name -contains 'bucketPrefix' -and -not [string]::IsNullOrWhiteSpace([string]`$sessionEvents.bucketPrefix)) { + [string]`$sessionEvents.bucketPrefix + } + else { + 'aw-session-events' + } + + `$stateRoot = [string]`$Config.paths.stateRoot + if ([string]::IsNullOrWhiteSpace(`$stateRoot)) { + return + } + + `$markerDir = Join-Path `$stateRoot 'markers' + if (-not (Test-Path -LiteralPath `$markerDir)) { + New-Item -Path `$markerDir -ItemType Directory -Force | Out-Null + } + + `$markerFile = Join-Path `$markerDir ("logon-{0}-{1}.marker" -f `$env:USERNAME, `$SessionId) + if (Test-Path -LiteralPath `$markerFile) { + return + } + + `$bucketId = ('{0}_{1}' -f `$bucketPrefix, `$script:Hostname) + Ensure-Bucket -BucketId `$bucketId -ClientName 'aw-session-events' -BucketType 'aw.session.event' + + `$payload = @{ + timestamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') + duration = 0 + data = @{ + eventType = 'logon' + username = `$env:USERNAME + userId = "`$(`$env:USERDOMAIN)\`$(`$env:USERNAME)" + sessionId = `$SessionId + hostname = `$script:Hostname + source = 'launch-watchers-phase2' + } + } | ConvertTo-Json -Depth 5 -Compress + + Invoke-AwJsonPost -Uri "`$(`$script:ApiBase)/buckets/`$bucketId/heartbeat?pulsetime=1" -Json `$payload + Set-Content -LiteralPath `$markerFile -Value ((Get-Date).ToUniversalTime().ToString('o')) -Encoding UTF8 +} + function Start-CollectorScriptIfNeeded { param( [string]`$ScriptPath, @@ -475,6 +570,9 @@ function Start-CollectorScriptIfNeeded { `$config = Get-DeploymentConfig -Path `$ConfigPath `$sessionId = (Get-Process -Id `$PID).SessionId `$installRoot = [string]`$config.paths.installRoot +`$script:ApiBase = '{0}://{1}:{2}/api/0' -f [string]`$config.server.scheme, [string]`$config.server.host, [string]`$config.server.port +`$script:Hostname = `$env:COMPUTERNAME +`$script:KnownBuckets = @{} `$collectorScript = [string]`$config.paths.collectorScript `$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { '' } `$afkExe = Join-Path `$installRoot 'aw-watcher-afk\aw-watcher-afk.exe' @@ -500,6 +598,7 @@ if (`$windowEnabled -and -not (Test-ProcessInSession -Name 'aw-watcher-window' - Start-Process -FilePath `$windowExe -ArgumentList `$serverArgs -WindowStyle Hidden } +Send-LogonMarkerIfNeeded -Config `$config -SessionId `$sessionId Start-CollectorScriptIfNeeded -ScriptPath `$collectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId Start-CollectorScriptIfNeeded -ScriptPath `$endpointCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId "@ diff --git a/windows/browser-domains-native-collector.ps1 b/windows/browser-domains-native-collector.ps1 index ddff965..bd28f71 100755 --- a/windows/browser-domains-native-collector.ps1 +++ b/windows/browser-domains-native-collector.ps1 @@ -59,8 +59,9 @@ $resolvedPulseSeconds = if ($PSBoundParameters.ContainsKey('PulseSeconds')) { $P $resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\ActivityWatch\logs' } $resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("browser-domains-{0}.log" -f $env:USERNAME) } $resolvedIncidentLogPath = if ($IncidentLogPath) { $IncidentLogPath } else { Join-Path $resolvedLogsRoot ("dlp-incidents-{0}.log" -f $env:USERNAME) } +$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true } -if (-not (Test-Path -LiteralPath $resolvedLogsRoot)) { +if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) { New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null } @@ -68,6 +69,7 @@ $script:ApiBase = '{0}://{1}:{2}/api/0' -f $resolvedServerScheme, $resolvedServe $script:Hostname = $env:COMPUTERNAME $script:SessionId = (Get-Process -Id $PID).SessionId $script:KnownBuckets = @{} +$script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled $script:LogPath = $resolvedLogPath $script:IncidentLogPath = $resolvedIncidentLogPath $script:IncidentState = @{} @@ -102,6 +104,10 @@ $script:CategoryRules = @( function Write-CollectorLog { param([string]$Message) + if (-not $script:LocalAgentLogsEnabled) { + return + } + try { Add-Content -LiteralPath $script:LogPath -Value ('{0} {1}' -f (Get-Date -Format s), $Message) } @@ -112,6 +118,10 @@ function Write-CollectorLog { function Write-DlpIncidentLog { param([string]$Message) + if (-not $script:LocalAgentLogsEnabled) { + return + } + try { Add-Content -LiteralPath $script:IncidentLogPath -Value ('{0} {1}' -f (Get-Date -Format s), $Message) } diff --git a/windows/deploy-domain-users.ps1 b/windows/deploy-domain-users.ps1 index 58b46f2..da246c6 100755 --- a/windows/deploy-domain-users.ps1 +++ b/windows/deploy-domain-users.ps1 @@ -18,6 +18,8 @@ param( [int]$RecoveryIntervalSeconds = 180, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$LocalAgentLogsEnabled = $false, + [bool]$LogonMarkerEnabled = $true, [string]$CustomRulesPath, [string]$CustomPolicyPath ) @@ -78,6 +80,8 @@ $config = New-ActivityWatchDeploymentConfig ` -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` + -LogonMarkerEnabled $LogonMarkerEnabled ` -LaunchScriptPath $launchScriptPath ` -RecoveryScriptPath $recoveryScriptPath ` -UserTasks $taskDefinitions ` diff --git a/windows/deploy-ensemble.ps1 b/windows/deploy-ensemble.ps1 index 248aa34..3f54a3c 100644 --- a/windows/deploy-ensemble.ps1 +++ b/windows/deploy-ensemble.ps1 @@ -18,6 +18,8 @@ param( [int]$RecoveryIntervalSeconds = 180, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$LocalAgentLogsEnabled = $false, + [bool]$LogonMarkerEnabled = $true, [string]$CustomRulesPath, [string]$CustomPolicyPath, [string]$ReportPath, @@ -59,6 +61,8 @@ if (-not (Test-Path -LiteralPath $deployScript)) { -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` + -LogonMarkerEnabled $LogonMarkerEnabled ` -CustomRulesPath $CustomRulesPath ` -CustomPolicyPath $CustomPolicyPath @@ -76,6 +80,8 @@ if (-not $SkipHardening) { -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` + -LogonMarkerEnabled $LogonMarkerEnabled ` -CustomRulesPath $CustomRulesPath ` -CustomPolicyPath $CustomPolicyPath } diff --git a/windows/deploy-single-user.ps1 b/windows/deploy-single-user.ps1 index 165fe13..878eae4 100755 --- a/windows/deploy-single-user.ps1 +++ b/windows/deploy-single-user.ps1 @@ -17,6 +17,8 @@ param( [int]$RecoveryIntervalSeconds = 180, [bool]$AfkEnabled = $true, [bool]$WindowEnabled = $true, + [bool]$LocalAgentLogsEnabled = $false, + [bool]$LogonMarkerEnabled = $true, [string]$CustomRulesPath, [string]$CustomPolicyPath ) @@ -76,6 +78,8 @@ $config = New-ActivityWatchDeploymentConfig ` -RecoveryIntervalSeconds $RecoveryIntervalSeconds ` -AfkEnabled $AfkEnabled ` -WindowEnabled $WindowEnabled ` + -LocalAgentLogsEnabled $LocalAgentLogsEnabled ` + -LogonMarkerEnabled $LogonMarkerEnabled ` -LaunchScriptPath $launchScriptPath ` -RecoveryScriptPath $recoveryScriptPath ` -UserTasks $taskDefinitions ` diff --git a/windows/dlp-endpoint-signals-collector.ps1 b/windows/dlp-endpoint-signals-collector.ps1 index a1875c8..6c122f8 100644 --- a/windows/dlp-endpoint-signals-collector.ps1 +++ b/windows/dlp-endpoint-signals-collector.ps1 @@ -23,6 +23,9 @@ function Get-DeploymentConfig { function Write-EndpointLog { param([string]$Message) + if (-not $script:LocalAgentLogsEnabled) { + return + } try { Add-Content -LiteralPath $script:LogPath -Value ('{0} {1}' -f (Get-Date -Format s), $Message) } @@ -459,8 +462,9 @@ $resolvedPolicyPath = if ($PolicyPath) { $PolicyPath } elseif ($deploymentConfig $resolvedPollSeconds = if ($PSBoundParameters.ContainsKey('PollSeconds')) { $PollSeconds } elseif ($deploymentConfig) { [int]$deploymentConfig.collector.pollSeconds } else { 5 } $resolvedLogsRoot = if ($deploymentConfig) { [string]$deploymentConfig.paths.logsRoot } else { 'C:\ProgramData\ActivityWatch\logs' } $resolvedLogPath = if ($LogPath) { $LogPath } else { Join-Path $resolvedLogsRoot ("endpoint-signals-{0}.log" -f $env:USERNAME) } +$resolvedLocalAgentLogsEnabled = if ($deploymentConfig -and $deploymentConfig.PSObject.Properties.Name -contains 'logging' -and $deploymentConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$deploymentConfig.logging.localAgentLogsEnabled } else { $true } -if (-not (Test-Path -LiteralPath $resolvedLogsRoot)) { +if ($resolvedLocalAgentLogsEnabled -and -not (Test-Path -LiteralPath $resolvedLogsRoot)) { New-Item -Path $resolvedLogsRoot -ItemType Directory -Force | Out-Null } @@ -474,6 +478,7 @@ $script:SeenPrintJob = @{} $script:SeenPrintEvent = @{} $script:LastClipboardHash = $null $script:PulseSeconds = [Math]::Max($resolvedPollSeconds * 3, 30) +$script:LocalAgentLogsEnabled = $resolvedLocalAgentLogsEnabled $script:LogPath = $resolvedLogPath Load-DlpPolicy -Path $resolvedPolicyPath diff --git a/windows/hardening-recovery.ps1 b/windows/hardening-recovery.ps1 index 262557b..90c6fde 100755 --- a/windows/hardening-recovery.ps1 +++ b/windows/hardening-recovery.ps1 @@ -15,6 +15,8 @@ param( [int]$RecoveryIntervalSeconds, [bool]$AfkEnabled, [bool]$WindowEnabled, + [bool]$LocalAgentLogsEnabled, + [bool]$LogonMarkerEnabled, [string]$CustomRulesPath, [string]$CustomPolicyPath, [switch]$RepairPackage, @@ -59,6 +61,8 @@ $effectivePulseSeconds = if ($PSBoundParameters.ContainsKey('PulseSeconds')) { $ $effectiveRecoveryInterval = if ($PSBoundParameters.ContainsKey('RecoveryIntervalSeconds')) { $RecoveryIntervalSeconds } elseif ($existingConfig) { [int]$existingConfig.recovery.intervalSeconds } else { 180 } $effectiveAfkEnabled = if ($PSBoundParameters.ContainsKey('AfkEnabled')) { [bool]$AfkEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'collectors' -and $existingConfig.collectors.PSObject.Properties.Name -contains 'afkEnabled') { [bool]$existingConfig.collectors.afkEnabled } else { $true } $effectiveWindowEnabled = if ($PSBoundParameters.ContainsKey('WindowEnabled')) { [bool]$WindowEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'collectors' -and $existingConfig.collectors.PSObject.Properties.Name -contains 'windowEnabled') { [bool]$existingConfig.collectors.windowEnabled } else { $true } +$effectiveLocalAgentLogsEnabled = if ($PSBoundParameters.ContainsKey('LocalAgentLogsEnabled')) { [bool]$LocalAgentLogsEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'logging' -and $existingConfig.logging.PSObject.Properties.Name -contains 'localAgentLogsEnabled') { [bool]$existingConfig.logging.localAgentLogsEnabled } else { $false } +$effectiveLogonMarkerEnabled = if ($PSBoundParameters.ContainsKey('LogonMarkerEnabled')) { [bool]$LogonMarkerEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$existingConfig.sessionEvents.logonEnabled } else { $true } $effectiveVersion = if ($Version) { $Version } elseif ($existingConfig) { [string]$existingConfig.package.version } else { 'v0.13.2' } $effectiveUsers = if ($Users -or $UserListPath) { @@ -112,6 +116,8 @@ $config = New-ActivityWatchDeploymentConfig ` -RecoveryIntervalSeconds $effectiveRecoveryInterval ` -AfkEnabled $effectiveAfkEnabled ` -WindowEnabled $effectiveWindowEnabled ` + -LocalAgentLogsEnabled $effectiveLocalAgentLogsEnabled ` + -LogonMarkerEnabled $effectiveLogonMarkerEnabled ` -LaunchScriptPath $effectiveLaunchScript ` -RecoveryScriptPath $effectiveRecoveryScript ` -UserTasks $taskDefinitions `