From 106d796d95489332e0ac8d62491a6bbf7540b2d8 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Fri, 12 Jun 2026 10:57:39 +0300 Subject: [PATCH] fix(hayabusa): restore scheduled EVTX upload pipeline --- .../hayabusa-tools/src/bin/autoprocess.rs | 2 - adk-rust/crates/hayabusa-tools/src/lib.rs | 3 +- ansible/README.md | 3 ++ ansible/deploy_aw_server.yml | 16 ++++++++ ansible/deploy_aw_windows.yml | 2 + ansible/group_vars/aw_windows.yml | 1 + ansible/group_vars/windows.example.yml | 1 + aw-server/hayabusa/README.md | 13 ++++++ aw-server/hayabusa/aw-hayabusa.sh | 41 +++++++++++++------ docs/runbook.md | 25 +++++++++++ docs/security-analytics-stack-v1.md | 5 +++ docs/wiki/Hayabusa-Security-Analytics.md | 6 +++ docs/windows-hayabusa-evtx-export.md | 10 +++++ windows/ActivityWatch.Windows.Common.psm1 | 9 +++- windows/deploy-domain-users.ps1 | 2 + windows/deploy-ensemble.ps1 | 2 + .../export-upload-hayabusa-to-aw-server.ps1 | 40 ++++++++++++++++-- windows/hardening-recovery.ps1 | 2 + 18 files changed, 164 insertions(+), 19 deletions(-) diff --git a/adk-rust/crates/hayabusa-tools/src/bin/autoprocess.rs b/adk-rust/crates/hayabusa-tools/src/bin/autoprocess.rs index 2af8641..f4502bf 100644 --- a/adk-rust/crates/hayabusa-tools/src/bin/autoprocess.rs +++ b/adk-rust/crates/hayabusa-tools/src/bin/autoprocess.rs @@ -120,8 +120,6 @@ fn process_one(zip_path: &Path) -> Result { "process-inbox".to_string(), "--mode".to_string(), mode.clone(), - "--limit".to_string(), - "1".to_string(), ], )?; let latest = read_json_file(Path::new(LATEST_INTAKE))?; diff --git a/adk-rust/crates/hayabusa-tools/src/lib.rs b/adk-rust/crates/hayabusa-tools/src/lib.rs index 8dc3618..1049bb0 100644 --- a/adk-rust/crates/hayabusa-tools/src/lib.rs +++ b/adk-rust/crates/hayabusa-tools/src/lib.rs @@ -92,7 +92,8 @@ fn decode_optional_json(body: String) -> Result { pub fn read_json_file(path: &Path) -> Result { let text = fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?; - serde_json::from_str(&text).with_context(|| format!("decode {}", path.display())) + serde_json::from_str(text.trim_start_matches('\u{feff}')) + .with_context(|| format!("decode {}", path.display())) } pub fn write_json_pretty(value: &Value) -> Result { diff --git a/ansible/README.md b/ansible/README.md index badadd0..a309023 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -157,6 +157,7 @@ Playbook: - `aw_windows_hayabusa_auto_upload_hours_back: 6` — lookback для каждого запуска; - `aw_windows_hayabusa_auto_upload_mode: "incident"` — mode для server-side processing; - `aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"` — имя scheduled task. +- `aw_windows_hayabusa_auto_upload_run_as_user: "Администратор"` — production principal для scheduled task на RDP-хосте. На `SHARKON2025` запуск `powershell.exe` из `SYSTEM` возвращал `0xC0000142`, поэтому авто-upload должен идти как interactive/highest task от локального администратора. ## Server-side Hayabusa auto-case и Telegram alerting @@ -176,6 +177,8 @@ Playbook: - пишет bounded metadata в `forensics.hayabusa`; - отправляет Telegram alert. +Для Windows direct upload пользователь `awops` на AW-server должен иметь право записи в `/opt/activitywatch/aw-rus-ops/drop`; нормальное состояние каталога: owner/group `awops:awops`, mode `0750`. Unit `aw-hayabusa-drop.service` работает от root и после обработки очищает `drop`. + Основные vars: - `aw_hayabusa_auto_case_enabled: true` diff --git a/ansible/deploy_aw_server.yml b/ansible/deploy_aw_server.yml index ab3829e..a807e93 100644 --- a/ansible/deploy_aw_server.yml +++ b/ansible/deploy_aw_server.yml @@ -2591,6 +2591,22 @@ - /opt/activitywatch/aw-rus-ops/ansible - /opt/activitywatch/aw-rus-ops/drop + - name: Проверить наличие upload user awops для Hayabusa drop-zone + ansible.builtin.command: + cmd: id -u awops + register: aw_hayabusa_drop_upload_user + changed_when: false + failed_when: false + + - name: Разрешить awops писать в Hayabusa drop-zone + ansible.builtin.file: + path: /opt/activitywatch/aw-rus-ops/drop + state: directory + owner: awops + group: awops + mode: "0750" + when: aw_hayabusa_drop_upload_user.rc == 0 + - name: Положить исходный wrapper в server-side ops bundle ansible.builtin.copy: src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa.sh" diff --git a/ansible/deploy_aw_windows.yml b/ansible/deploy_aw_windows.yml index 15e475c..20ff1e3 100644 --- a/ansible/deploy_aw_windows.yml +++ b/ansible/deploy_aw_windows.yml @@ -41,6 +41,7 @@ aw_windows_hayabusa_auto_upload_hours_back: 6 aw_windows_hayabusa_auto_upload_mode: "incident" aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload" + aw_windows_hayabusa_auto_upload_run_as_user: "" aw_windows_file_1c_auto_upload_enabled: true aw_windows_file_1c_auto_upload_interval_minutes: 15 aw_windows_file_1c_auto_upload_task_name: "ActivityWatch File1C Upload" @@ -311,6 +312,7 @@ HayabusaAutoUploadHoursBack = {{ aw_windows_hayabusa_auto_upload_hours_back | int }} HayabusaAutoUploadMode = "{{ aw_windows_hayabusa_auto_upload_mode }}" HayabusaAutoUploadTaskName = "{{ aw_windows_hayabusa_auto_upload_task_name }}" + HayabusaAutoUploadRunAsUser = "{{ aw_windows_hayabusa_auto_upload_run_as_user }}" File1CAutoUploadEnabled = {{ '$true' if (aw_windows_file_1c_auto_upload_enabled | bool) else '$false' }} File1CAutoUploadIntervalMinutes = {{ aw_windows_file_1c_auto_upload_interval_minutes | int }} File1CAutoUploadTaskName = "{{ aw_windows_file_1c_auto_upload_task_name }}" diff --git a/ansible/group_vars/aw_windows.yml b/ansible/group_vars/aw_windows.yml index c32ea75..3746d53 100644 --- a/ansible/group_vars/aw_windows.yml +++ b/ansible/group_vars/aw_windows.yml @@ -39,6 +39,7 @@ aw_windows_hayabusa_auto_upload_interval_hours: 6 aw_windows_hayabusa_auto_upload_hours_back: 6 aw_windows_hayabusa_auto_upload_mode: "incident" aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload" +aw_windows_hayabusa_auto_upload_run_as_user: "{{ aw_windows_builtin_administrator_name }}" aw_windows_afk_enabled: true aw_windows_window_enabled: true diff --git a/ansible/group_vars/windows.example.yml b/ansible/group_vars/windows.example.yml index c30c8f0..128cae6 100644 --- a/ansible/group_vars/windows.example.yml +++ b/ansible/group_vars/windows.example.yml @@ -9,6 +9,7 @@ aw_windows_hayabusa_auto_upload_interval_hours: 6 aw_windows_hayabusa_auto_upload_hours_back: 6 aw_windows_hayabusa_auto_upload_mode: "incident" aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload" +aw_windows_hayabusa_auto_upload_run_as_user: "" aw_windows_package_version: "v0.13.2" aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip" aw_windows_package_zip_path: "" diff --git a/aw-server/hayabusa/README.md b/aw-server/hayabusa/README.md index 68799c1..bf17b5d 100644 --- a/aw-server/hayabusa/README.md +++ b/aw-server/hayabusa/README.md @@ -105,4 +105,17 @@ Server-side prerequisite for user `awops`: printf '%s\n' 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILoFWQmgoUJj1P7mp1/fB5aBkI3fVgjPme9jmK8Gh9jr igor@snb-live' | sudo tee /var/lib/awops/.ssh/authorized_keys >/dev/null sudo chown awops:awops /var/lib/awops/.ssh/authorized_keys sudo chmod 600 /var/lib/awops/.ssh/authorized_keys +sudo chown awops:awops /opt/activitywatch/aw-rus-ops/drop +sudo chmod 0750 /opt/activitywatch/aw-rus-ops/drop ``` + +Production scheduled task on `SHARKON2025`: + +- task name: `ActivityWatch Hayabusa Upload` +- action: `C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -Mode incident` +- principal: `Администратор`, `LogonType=Interactive`, `RunLevel=Highest` +- normal `LastTaskResult`: `0` + +Do not switch this task back to `SYSTEM` on the current RDP host: Task Scheduler starts `powershell.exe` under `SYSTEM`, but the process exits with `0xC0000142` before the upload script starts. + +Server-side processing accepts Windows zip packages with backslash path separators and UTF-8 BOM in sidecar JSON. `aw-hayabusa-autoprocess` processes the full incoming queue after accepting a drop package, so stale incoming files from an earlier failed run are drained before the latest intake is recorded. diff --git a/aw-server/hayabusa/aw-hayabusa.sh b/aw-server/hayabusa/aw-hayabusa.sh index 0f03548..a02bdd6 100644 --- a/aw-server/hayabusa/aw-hayabusa.sh +++ b/aw-server/hayabusa/aw-hayabusa.sh @@ -113,19 +113,36 @@ detect_host_from_manifest() { extract_zip_normalized() { local package_path="$1" local dest_dir="$2" - command -v zipinfo >/dev/null 2>&1 || fail "zipinfo is required to inspect ${package_path}" - command -v unzip >/dev/null 2>&1 || fail "unzip is required to extract ${package_path}" + command -v python3 >/dev/null 2>&1 || fail "python3 is required to extract ${package_path}" mkdir -p "${dest_dir}" - local entry normalized - while IFS= read -r entry; do - normalized="${entry//\\//}" - case "${normalized}" in - ""|.|/*|*"/../"*|../*|*"..") - fail "unsafe zip entry: ${entry}" - ;; - esac - done < <(zipinfo -1 "${package_path}") - unzip -q "${package_path}" -d "${dest_dir}" + python3 - "${package_path}" "${dest_dir}" <<'PY' +import os +import shutil +import sys +import zipfile + +package_path = sys.argv[1] +dest_dir = os.path.abspath(sys.argv[2]) + +with zipfile.ZipFile(package_path) as archive: + for info in archive.infolist(): + name = info.filename.replace("\\", "/") + is_dir = info.is_dir() or name.endswith("/") + if is_dir: + name = name.rstrip("/") + parts = [part for part in name.split("/") if part] + if not parts or name.startswith("/") or any(part in (".", "..") for part in parts): + raise SystemExit(f"unsafe zip entry: {info.filename}") + target_path = os.path.abspath(os.path.join(dest_dir, *parts)) + if os.path.commonpath([dest_dir, target_path]) != dest_dir: + raise SystemExit(f"unsafe zip entry: {info.filename}") + if is_dir: + os.makedirs(target_path, exist_ok=True) + continue + os.makedirs(os.path.dirname(target_path), exist_ok=True) + with archive.open(info) as src, open(target_path, "wb") as dst: + shutil.copyfileobj(src, dst) +PY } write_package_manifest() { diff --git a/docs/runbook.md b/docs/runbook.md index d3eb1cc..2041a31 100755 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -276,6 +276,10 @@ powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-up 3. Проверить результат: ```bash +systemctl is-active aw-hayabusa-drop.path +systemctl is-failed aw-hayabusa-drop.service || true +aw-hayabusa doctor +aw-hayabusa inventory cat /opt/hayabusa/state/latest-intake.json journalctl -u aw-hayabusa-drop.service -n 80 --no-pager curl -fsS http://127.0.0.1:5602/api/0/dlp/cases/30 @@ -288,6 +292,27 @@ curl -fsS http://127.0.0.1:5602/api/0/dlp/cases/30 - в case есть `forensics.hayabusa`; - Telegram alert уже уходит в операторский чат. +Production scheduled task на `SHARKON2025`: + +- `ActivityWatch Hayabusa Upload` +- principal `Администратор`, `LogonType=Interactive`, `RunLevel=Highest` +- период `6` часов, lookback `6` часов +- нормальный `LastTaskResult=0` + +Если `LastTaskResult=3221225794` (`0xC0000142`) и в `C:\ProgramData\AWatch-rus\logs\hayabusa-upload.log` нет новой строки, скрипт не стартовал. На текущем RDP-хосте это воспроизводится даже минимальной SYSTEM-задачей с `powershell.exe`; пересоздать Hayabusa task как interactive/highest от `Администратор`, не от `SYSTEM`. + +Если upload прошёл, но сервер не обработал пакет: + +```bash +sudo systemctl reset-failed aw-hayabusa-drop.path aw-hayabusa-drop.service +sudo systemctl start aw-hayabusa-drop.path +sudo systemctl start aw-hayabusa-drop.service +find /opt/activitywatch/aw-rus-ops/drop -maxdepth 1 -type f -ls +find /opt/hayabusa/inbox/incoming -maxdepth 1 -type f -ls +``` + +`drop` и `incoming` после успешной обработки должны быть пустыми; latest intake должен указывать на последний пакет `SHARKON2025`. + ### Hayabusa: manual fallback / production validation end-to-end Цель: подтвердить один реальный путь diff --git a/docs/security-analytics-stack-v1.md b/docs/security-analytics-stack-v1.md index a8d958a..0f88796 100644 --- a/docs/security-analytics-stack-v1.md +++ b/docs/security-analytics-stack-v1.md @@ -34,15 +34,20 @@ Core outcomes: - `.meta.json` - optional `.caseid` - `ActivityWatch Hayabusa Upload` scheduled task runs every 6 hours +- on `SHARKON2025` the task runs as interactive/highest `Администратор`; `SYSTEM` PowerShell tasks fail with `0xC0000142` before the script starts +- sidecar JSON is written as UTF-8 without BOM; server-side readers also tolerate BOM for older files ### Server side - `aw-hayabusa-drop.path` watches `/opt/activitywatch/aw-rus-ops/drop` - `aw-hayabusa-drop.service` runs `aw-hayabusa-autoprocess` +- `/opt/activitywatch/aw-rus-ops/drop` is writable by `awops` and processed by root-owned systemd units - `aw-hayabusa` performs: - accept - process-inbox - report generation +- Windows zip entries with backslash separators are normalized during extraction +- autoprocess drains the incoming queue after accepting a drop package, preventing stale failed-run packages from being linked to a newer drop upload - `aw-hayabusa-case-alert` performs: - severity scoring from `timeline.jsonl` - optional auto-case creation diff --git a/docs/wiki/Hayabusa-Security-Analytics.md b/docs/wiki/Hayabusa-Security-Analytics.md index 592980a..b8d77d7 100644 --- a/docs/wiki/Hayabusa-Security-Analytics.md +++ b/docs/wiki/Hayabusa-Security-Analytics.md @@ -5,6 +5,7 @@ ## Что уже работает - Windows-хост раз в `6` часов делает `EVTX export + upload` +- production scheduled task: `ActivityWatch Hayabusa Upload`, principal `Администратор`, `LogonType=Interactive`, `RunLevel=Highest` - `AW-server` автоматически подхватывает пакет из `drop` - `aw-hayabusa` строит forensic-отчёт - `aw-hayabusa-case-alert` считает severity и score @@ -22,11 +23,16 @@ powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-up На сервере для проверки: ```bash +systemctl is-active aw-hayabusa-drop.path +systemctl is-failed aw-hayabusa-drop.service || true +aw-hayabusa inventory cat /opt/hayabusa/state/latest-intake.json journalctl -u aw-hayabusa-drop.service -n 80 --no-pager curl -fsS http://127.0.0.1:5602/api/0/dlp/cases/30 ``` +Ожидаемо: `drop` и `incoming` пустые, `latest-intake.json` имеет `status=ok`, `host=SHARKON2025`, а `LastTaskResult` Windows-задачи равен `0`. + ## Что получает оператор - `summary.html` diff --git a/docs/windows-hayabusa-evtx-export.md b/docs/windows-hayabusa-evtx-export.md index 68a9d11..f5002e1 100644 --- a/docs/windows-hayabusa-evtx-export.md +++ b/docs/windows-hayabusa-evtx-export.md @@ -83,6 +83,16 @@ This wrapper: - uploads `.meta.json`; - uploads the `zip` to the AW-server drop directory. +Scheduled production upload on `SHARKON2025`: + +- task: `ActivityWatch Hayabusa Upload` +- principal: `Администратор`, interactive, highest privileges +- interval: `6` hours +- lookback: `6` hours +- success: `LastTaskResult=0` and a new line in `C:\ProgramData\AWatch-rus\logs\hayabusa-upload.log` + +`LastTaskResult=3221225794` (`0xC0000142`) with no new upload log means Task Scheduler failed to start `powershell.exe`; keep this task on the interactive administrator principal for this host. + ## Boundaries - output stays outside standard AW buckets diff --git a/windows/ActivityWatch.Windows.Common.psm1 b/windows/ActivityWatch.Windows.Common.psm1 index 82eace1..a12ce54 100755 --- a/windows/ActivityWatch.Windows.Common.psm1 +++ b/windows/ActivityWatch.Windows.Common.psm1 @@ -942,6 +942,7 @@ function New-ActivityWatchDeploymentConfig { [int]$HayabusaAutoUploadHoursBack = 6, [string]$HayabusaAutoUploadMode = 'incident', [string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload', + [string]$HayabusaAutoUploadRunAsUser, [bool]$File1CAutoUploadEnabled = $true, [int]$File1CAutoUploadIntervalHours = 6, [int]$File1CAutoUploadIntervalMinutes = 15, @@ -1037,6 +1038,7 @@ function New-ActivityWatchDeploymentConfig { hoursBack = $HayabusaAutoUploadHoursBack mode = $HayabusaAutoUploadMode taskName = $HayabusaAutoUploadTaskName + runAsUser = $HayabusaAutoUploadRunAsUser } } analytics = [pscustomobject]@{ @@ -2531,11 +2533,16 @@ function Register-ActivityWatchHayabusaAutoUploadTask { $intervalHours = [Math]::Max(1, [int]$automation.intervalHours) $hoursBack = [Math]::Max(1, [int]$automation.hoursBack) $mode = if ($automation.PSObject.Properties.Name -contains 'mode' -and -not [string]::IsNullOrWhiteSpace([string]$automation.mode)) { [string]$automation.mode } else { 'incident' } + $runAsUser = if ($automation.PSObject.Properties.Name -contains 'runAsUser' -and -not [string]::IsNullOrWhiteSpace([string]$automation.runAsUser)) { [string]$automation.runAsUser } else { '' } $powerShellExe = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' $taskCommand = "`"$powerShellExe`" -NoProfile -ExecutionPolicy Bypass -File `"$uploadScript`" -ConfigPath `"$ConfigPath`" -HoursBack $hoursBack -Mode `"$mode`"" Remove-ActivityWatchScheduledTask -TaskName $taskName - & schtasks.exe /Create /TN $taskName /TR $taskCommand /SC HOURLY /MO $intervalHours /ST 00:00 /RU SYSTEM /RL HIGHEST /F | Out-Null + if ($runAsUser) { + & schtasks.exe /Create /TN $taskName /TR $taskCommand /SC HOURLY /MO $intervalHours /ST 00:00 /RU $runAsUser /IT /RL HIGHEST /F | Out-Null + } else { + & schtasks.exe /Create /TN $taskName /TR $taskCommand /SC HOURLY /MO $intervalHours /ST 00:00 /RU SYSTEM /RL HIGHEST /F | Out-Null + } if ($LASTEXITCODE -ne 0) { throw "Не удалось создать scheduled task $taskName через schtasks.exe" } diff --git a/windows/deploy-domain-users.ps1 b/windows/deploy-domain-users.ps1 index 76f26a9..ba9cc3c 100755 --- a/windows/deploy-domain-users.ps1 +++ b/windows/deploy-domain-users.ps1 @@ -45,6 +45,7 @@ param( [int]$HayabusaAutoUploadHoursBack = 6, [string]$HayabusaAutoUploadMode = 'incident', [string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload', + [string]$HayabusaAutoUploadRunAsUser, [bool]$File1CAutoUploadEnabled = $true, [int]$File1CAutoUploadIntervalHours = 6, [int]$File1CAutoUploadIntervalMinutes = 15, @@ -157,6 +158,7 @@ $config = New-ActivityWatchDeploymentConfig ` -HayabusaAutoUploadHoursBack $HayabusaAutoUploadHoursBack ` -HayabusaAutoUploadMode $HayabusaAutoUploadMode ` -HayabusaAutoUploadTaskName $HayabusaAutoUploadTaskName ` + -HayabusaAutoUploadRunAsUser $HayabusaAutoUploadRunAsUser ` -File1CAutoUploadEnabled $File1CAutoUploadEnabled ` -File1CAutoUploadIntervalHours $File1CAutoUploadIntervalHours ` -File1CAutoUploadIntervalMinutes $File1CAutoUploadIntervalMinutes ` diff --git a/windows/deploy-ensemble.ps1 b/windows/deploy-ensemble.ps1 index 888ecbc..979464c 100644 --- a/windows/deploy-ensemble.ps1 +++ b/windows/deploy-ensemble.ps1 @@ -46,6 +46,7 @@ param( [int]$HayabusaAutoUploadHoursBack = 6, [string]$HayabusaAutoUploadMode = 'incident', [string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload', + [string]$HayabusaAutoUploadRunAsUser, [bool]$File1CAutoUploadEnabled = $true, [int]$File1CAutoUploadIntervalHours = 6, [int]$File1CAutoUploadIntervalMinutes = 15, @@ -118,6 +119,7 @@ if (-not (Test-Path -LiteralPath $deployScript)) { -HayabusaAutoUploadHoursBack $HayabusaAutoUploadHoursBack ` -HayabusaAutoUploadMode $HayabusaAutoUploadMode ` -HayabusaAutoUploadTaskName $HayabusaAutoUploadTaskName ` + -HayabusaAutoUploadRunAsUser $HayabusaAutoUploadRunAsUser ` -File1CAutoUploadEnabled $File1CAutoUploadEnabled ` -File1CAutoUploadIntervalHours $File1CAutoUploadIntervalHours ` -File1CAutoUploadIntervalMinutes $File1CAutoUploadIntervalMinutes ` diff --git a/windows/export-upload-hayabusa-to-aw-server.ps1 b/windows/export-upload-hayabusa-to-aw-server.ps1 index 905d386..758dcdc 100644 --- a/windows/export-upload-hayabusa-to-aw-server.ps1 +++ b/windows/export-upload-hayabusa-to-aw-server.ps1 @@ -16,6 +16,25 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +$LogDir = Join-Path (Split-Path -Parent $ConfigPath) 'logs' +$LogPath = Join-Path $LogDir 'hayabusa-upload.log' +New-Item -ItemType Directory -Path $LogDir -Force | Out-Null + +function Write-RunLog { + param( + [Parameter(Mandatory = $true)] + [string]$Message + ) + + $line = '{0} {1}' -f ([DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')), $Message + Add-Content -LiteralPath $LogPath -Value $line -Encoding UTF8 +} + +trap { + Write-RunLog ("ERROR: " + ($_ | Out-String).Trim()) + exit 1 +} + function New-TemporarySshKeyCopy { param( [Parameter(Mandatory = $true)] @@ -27,9 +46,20 @@ function New-TemporarySshKeyCopy { $tempKeyPath = Join-Path $tempDir 'awops_ed25519' Copy-Item -LiteralPath $SourceKeyPath -Destination $tempKeyPath -Force + $currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $grantPrincipals = @( + ('*' + $currentIdentity.User.Value), + '*S-1-5-18', + '*S-1-5-32-544' + ) | + Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | + Select-Object -Unique + & icacls.exe $tempKeyPath /inheritance:r | Out-Null - & icacls.exe $tempKeyPath /grant:r "$($env:USERNAME):(F)" | Out-Null - & icacls.exe $tempKeyPath /remove:g 'Users' 'Authenticated Users' 'Everyone' 'BUILTIN\Users' 'BUILTIN\Administrators' 'NT AUTHORITY\SYSTEM' 2>$null | Out-Null + foreach ($principal in $grantPrincipals) { + & icacls.exe $tempKeyPath /grant:r "$principal`:(F)" | Out-Null + } + & icacls.exe $tempKeyPath /remove:g 'Users' 'Authenticated Users' 'Everyone' 'BUILTIN\Users' 2>$null | Out-Null return $tempKeyPath } @@ -42,6 +72,8 @@ if (-not (Test-Path -LiteralPath $RemoteKeyPath)) { throw "SSH private key not found: $RemoteKeyPath" } +Write-RunLog ("start hoursBack={0} daysBack={1} mode={2} serverHost={3} runRemote={4}" -f $HoursBack, $DaysBack, $Mode, $ServerHost, [bool]$RunRemote) + $config = Get-Content -Raw -LiteralPath $ConfigPath | ConvertFrom-Json if ([string]::IsNullOrWhiteSpace($ServerHost)) { $ServerHost = [string]$config.server.host @@ -81,7 +113,8 @@ try { if ($null -ne $CaseId) { $meta.case_id = [int]$CaseId } - $meta | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $metaPath -Encoding UTF8 + $metaJson = $meta | ConvertTo-Json -Depth 6 + [System.IO.File]::WriteAllText($metaPath, $metaJson, [System.Text.UTF8Encoding]::new($false)) & scp.exe -i $effectiveKeyPath -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL $metaPath $remoteTarget if ($LASTEXITCODE -ne 0) { throw "scp meta upload failed with rc=$LASTEXITCODE" @@ -97,6 +130,7 @@ try { if ($LASTEXITCODE -ne 0) { throw "scp upload failed with rc=$LASTEXITCODE" } + Write-RunLog ("upload complete zip={0} remote={1}" -f $zipPath, $remoteTarget) } finally { Remove-Item -LiteralPath $effectiveKeyPath -Force -ErrorAction SilentlyContinue diff --git a/windows/hardening-recovery.ps1 b/windows/hardening-recovery.ps1 index da89141..a3edad8 100755 --- a/windows/hardening-recovery.ps1 +++ b/windows/hardening-recovery.ps1 @@ -119,6 +119,7 @@ $effectiveHayabusaAutoUploadIntervalHours = if ($existingConfig -and $existingCo $effectiveHayabusaAutoUploadHoursBack = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'hoursBack') { [int]$existingConfig.forensics.hayabusaAutomation.hoursBack } else { 6 } $effectiveHayabusaAutoUploadMode = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'mode') { [string]$existingConfig.forensics.hayabusaAutomation.mode } else { 'incident' } $effectiveHayabusaAutoUploadTaskName = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'taskName') { [string]$existingConfig.forensics.hayabusaAutomation.taskName } else { 'ActivityWatch Hayabusa Upload' } +$effectiveHayabusaAutoUploadRunAsUser = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'hayabusaAutomation' -and $existingConfig.forensics.hayabusaAutomation.PSObject.Properties.Name -contains 'runAsUser') { [string]$existingConfig.forensics.hayabusaAutomation.runAsUser } else { '' } $effectiveFile1CAutoUploadEnabled = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.analytics.file1cAutomation.enabled } else { $true } $effectiveFile1CAutoUploadIntervalHours = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'intervalHours') { [int]$existingConfig.analytics.file1cAutomation.intervalHours } else { 6 } $effectiveFile1CAutoUploadIntervalMinutes = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'analytics' -and $existingConfig.analytics.PSObject.Properties.Name -contains 'file1cAutomation' -and $existingConfig.analytics.file1cAutomation.PSObject.Properties.Name -contains 'intervalMinutes') { [int]$existingConfig.analytics.file1cAutomation.intervalMinutes } else { [Math]::Max(1, $effectiveFile1CAutoUploadIntervalHours) * 60 } @@ -237,6 +238,7 @@ $config = New-ActivityWatchDeploymentConfig ` -HayabusaAutoUploadHoursBack $effectiveHayabusaAutoUploadHoursBack ` -HayabusaAutoUploadMode $effectiveHayabusaAutoUploadMode ` -HayabusaAutoUploadTaskName $effectiveHayabusaAutoUploadTaskName ` + -HayabusaAutoUploadRunAsUser $effectiveHayabusaAutoUploadRunAsUser ` -File1CAutoUploadEnabled $effectiveFile1CAutoUploadEnabled ` -File1CAutoUploadIntervalHours $effectiveFile1CAutoUploadIntervalHours ` -File1CAutoUploadIntervalMinutes $effectiveFile1CAutoUploadIntervalMinutes `