feat(hayabusa): add auto-case scoring and 6h automation

This commit is contained in:
igor04091968
2026-05-21 20:47:49 +03:00
parent 4e4898828b
commit 0c88b519ee
22 changed files with 757 additions and 32 deletions
+23
View File
@@ -342,6 +342,12 @@
AW_MONITORED_WINDOWS_HOSTNAME={{ aw_monitored_windows_hostname }} AW_MONITORED_WINDOWS_HOSTNAME={{ aw_monitored_windows_hostname }}
AW_RUS_HEALTH_STATE_DIR={{ aw_rus_health_state_dir }} AW_RUS_HEALTH_STATE_DIR={{ aw_rus_health_state_dir }}
AW_RUS_HEALTH_VALIDATION_DIR={{ aw_rus_health_validation_dir }} AW_RUS_HEALTH_VALIDATION_DIR={{ aw_rus_health_validation_dir }}
AW_HAYABUSA_AUTO_CASE_ENABLED={{ 'true' if (aw_hayabusa_auto_case_enabled | default(true) | bool) else 'false' }}
AW_HAYABUSA_AUTO_CASE_MIN_SEVERITY={{ aw_hayabusa_auto_case_min_severity | default('medium') }}
AW_HAYABUSA_TELEGRAM_ENABLED={{ 'true' if (aw_hayabusa_telegram_enabled | default(false) | bool) else 'false' }}
AW_HAYABUSA_TELEGRAM_MIN_SEVERITY={{ aw_hayabusa_telegram_min_severity | default('high') }}
AW_HAYABUSA_TELEGRAM_BOT_TOKEN={{ aw_hayabusa_telegram_bot_token | default('') }}
AW_HAYABUSA_TELEGRAM_CHAT_IDS={{ aw_hayabusa_telegram_chat_ids | default('') }}
- name: Создать каталог DLP policy engine - name: Создать каталог DLP policy engine
ansible.builtin.file: ansible.builtin.file:
@@ -1622,6 +1628,22 @@
group: root group: root
mode: "0755" mode: "0755"
- name: Установить helper case-alert для Hayabusa
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-case-alert.py"
dest: /usr/local/bin/aw-hayabusa-case-alert
owner: root
group: root
mode: "0755"
- name: Положить helper case-alert в server-side ops bundle
ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa-case-alert.py"
dest: /opt/activitywatch/aw-rus-ops/hayabusa/aw-hayabusa-case-alert.py
owner: root
group: root
mode: "0755"
- name: Установить systemd unit aw-hayabusa-drop.service - name: Установить systemd unit aw-hayabusa-drop.service
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ aw_repo_root }}/aw-server/aw-hayabusa-drop.service" src: "{{ aw_repo_root }}/aw-server/aw-hayabusa-drop.service"
@@ -1674,6 +1696,7 @@
cmd: /usr/local/bin/aw-rus-healthd.py --json cmd: /usr/local/bin/aw-rus-healthd.py --json
register: aw_post_deploy_health register: aw_post_deploy_health
changed_when: false changed_when: false
failed_when: false
- name: Показать результат aw-rus-healthd - name: Показать результат aw-rus-healthd
ansible.builtin.debug: ansible.builtin.debug:
+43 -7
View File
@@ -9,7 +9,6 @@
aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus" aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http" aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600 aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2" 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_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
@@ -29,9 +28,13 @@
aw_windows_policy_mode: "server" aw_windows_policy_mode: "server"
aw_windows_policy_refresh_seconds: 300 aw_windows_policy_refresh_seconds: 300
aw_windows_policy_engine_enabled: true aw_windows_policy_engine_enabled: true
aw_windows_policy_engine_host: "{{ aw_windows_server_host }}"
aw_windows_policy_engine_port: 5601 aw_windows_policy_engine_port: 5601
aw_windows_policy_engine_scheme: "http" aw_windows_policy_engine_scheme: "http"
aw_windows_hayabusa_auto_upload_enabled: true
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_afk_enabled_default: true aw_windows_afk_enabled_default: true
aw_windows_window_enabled_default: true aw_windows_window_enabled_default: true
aw_windows_file_ops_enabled: true aw_windows_file_ops_enabled: true
@@ -62,10 +65,38 @@
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json" aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
tasks: tasks:
- name: Вычислить inventory host AW server по умолчанию
ansible.builtin.set_fact:
aw_server_inventory_host_effective: "{{ (groups['aw_server'] | default([]) | first) | default('', true) }}"
- name: Вычислить effective host для AW server
ansible.builtin.set_fact:
aw_windows_server_host_effective: >-
{{
aw_windows_server_host
| default(
(
hostvars[aw_server_inventory_host_effective].ansible_host
| default(aw_server_inventory_host_effective, true)
)
if (aw_server_inventory_host_effective | length) > 0
else '',
true
)
}}
- name: Вычислить effective host для policy engine
ansible.builtin.set_fact:
aw_windows_policy_engine_host_effective: >-
{{
aw_windows_policy_engine_host
| default(aw_windows_server_host_effective, true)
}}
- name: Проверить обязательные переменные - name: Проверить обязательные переменные
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- aw_windows_server_host is defined - aw_windows_server_host_effective | length > 0
- aw_windows_server_port is defined - aw_windows_server_port is defined
- aw_windows_server_scheme is defined - aw_windows_server_scheme is defined
- aw_windows_domain is defined - aw_windows_domain is defined
@@ -160,7 +191,7 @@
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$params = @{ $params = @{
ServerScheme = "{{ aw_windows_server_scheme }}" ServerScheme = "{{ aw_windows_server_scheme }}"
ServerHost = "{{ aw_windows_server_host }}" ServerHost = "{{ aw_windows_server_host_effective }}"
ServerPort = {{ aw_windows_server_port }} ServerPort = {{ aw_windows_server_port }}
Version = "{{ aw_windows_package_version }}" Version = "{{ aw_windows_package_version }}"
Domain = "{{ aw_windows_domain }}" Domain = "{{ aw_windows_domain }}"
@@ -179,10 +210,15 @@
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }} LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
PolicyMode = "{{ aw_windows_policy_mode }}" PolicyMode = "{{ aw_windows_policy_mode }}"
PolicyEngineEnabled = {{ '$true' if (aw_windows_policy_engine_enabled | bool) else '$false' }} PolicyEngineEnabled = {{ '$true' if (aw_windows_policy_engine_enabled | bool) else '$false' }}
PolicyEngineHost = "{{ aw_windows_policy_engine_host }}" PolicyEngineHost = "{{ aw_windows_policy_engine_host_effective }}"
PolicyEnginePort = {{ aw_windows_policy_engine_port }} PolicyEnginePort = {{ aw_windows_policy_engine_port }}
PolicyEngineScheme = "{{ aw_windows_policy_engine_scheme }}" PolicyEngineScheme = "{{ aw_windows_policy_engine_scheme }}"
PolicyRefreshSeconds = {{ aw_windows_policy_refresh_seconds }} PolicyRefreshSeconds = {{ aw_windows_policy_refresh_seconds }}
HayabusaAutoUploadEnabled = {{ '$true' if (aw_windows_hayabusa_auto_upload_enabled | bool) else '$false' }}
HayabusaAutoUploadIntervalHours = {{ aw_windows_hayabusa_auto_upload_interval_hours | int }}
HayabusaAutoUploadHoursBack = {{ aw_windows_hayabusa_auto_upload_hours_back | int }}
HayabusaAutoUploadMode = "{{ aw_windows_hayabusa_auto_upload_mode }}"
HayabusaAutoUploadTaskName = "{{ aw_windows_hayabusa_auto_upload_task_name }}"
CustomRulesPath = "{{ aw_windows_rules_path }}" CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_path }}" CustomPolicyPath = "{{ aw_windows_policy_path }}"
} }
@@ -342,7 +378,7 @@
when: when:
- aw_windows_api_smoke_check_enabled | bool - aw_windows_api_smoke_check_enabled | bool
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}" url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET method: GET
status_code: 200 status_code: 200
return_content: true return_content: true
@@ -369,7 +405,7 @@
- aw_windows_api_smoke_check_window_enabled_effective | bool - aw_windows_api_smoke_check_window_enabled_effective | bool
- aw_windows_window_enabled_effective | bool - aw_windows_window_enabled_effective | bool
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_window_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}" url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_window_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET method: GET
status_code: 200 status_code: 200
return_content: true return_content: true
+10 -2
View File
@@ -8,7 +8,9 @@ aw_server_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-
aw_server_log_dir: "/var/log/activitywatch" aw_server_log_dir: "/var/log/activitywatch"
aw_server_user: "activitywatch" aw_server_user: "activitywatch"
aw_server_group: "activitywatch" aw_server_group: "activitywatch"
aw_worktime_report_base: "http://10.10.10.13:5610" aw_server_inventory_host: "{{ (groups['aw_server'] | default([]) | first) | default('aw-server', true) }}"
aw_server_public_host: "{{ (hostvars[aw_server_inventory_host].ansible_host | default(aw_server_inventory_host, true)) if (aw_server_inventory_host | length) > 0 else 'aw-server' }}"
aw_worktime_report_base: "http://{{ aw_server_public_host }}:5610"
aw_worktime_timezone: "Europe/Moscow" aw_worktime_timezone: "Europe/Moscow"
aw_worktime_influx_enabled: false aw_worktime_influx_enabled: false
aw_worktime_influx_url: "http://10.10.10.10:8086" aw_worktime_influx_url: "http://10.10.10.10:8086"
@@ -29,6 +31,12 @@ aw_monitored_windows_host: "192.168.100.18"
aw_monitored_windows_hostname: "SHARKON2025" aw_monitored_windows_hostname: "SHARKON2025"
aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health" aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health"
aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation" aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation"
aw_hayabusa_auto_case_enabled: true
aw_hayabusa_auto_case_min_severity: "medium"
aw_hayabusa_telegram_enabled: false
aw_hayabusa_telegram_min_severity: "high"
aw_hayabusa_telegram_bot_token: ""
aw_hayabusa_telegram_chat_ids: ""
aw_repo_root: "{{ playbook_dir | dirname }}" aw_repo_root: "{{ playbook_dir | dirname }}"
@@ -41,7 +49,7 @@ aw_apply_worktime_settings: true
aw_server_cors_origins: aw_server_cors_origins:
- "http://127.0.0.1:5600" - "http://127.0.0.1:5600"
- "http://localhost:5600" - "http://localhost:5600"
- "http://10.10.10.13:5600" - "http://{{ aw_server_public_host }}:5600"
- "http://aw-server:5600" - "http://aw-server:5600"
# Опциональные значения периода рабочего времени в Web UI. # Опциональные значения периода рабочего времени в Web UI.
+11 -3
View File
@@ -8,7 +8,9 @@ aw_server_log_dir: "/var/log/activitywatch"
aw_server_db_path: "/var/lib/activitywatch/aw-server-rust/sqlite.db" aw_server_db_path: "/var/lib/activitywatch/aw-server-rust/sqlite.db"
aw_server_user: "activitywatch" aw_server_user: "activitywatch"
aw_server_group: "activitywatch" aw_server_group: "activitywatch"
aw_worktime_report_base: "http://10.10.10.13:5610" aw_server_inventory_host: "{{ (groups['aw_server'] | default([]) | first) | default('aw-server', true) }}"
aw_server_public_host: "{{ (hostvars[aw_server_inventory_host].ansible_host | default(aw_server_inventory_host, true)) if (aw_server_inventory_host | length) > 0 else 'aw-server' }}"
aw_worktime_report_base: "http://{{ aw_server_public_host }}:5610"
aw_worktime_timezone: "Europe/Moscow" aw_worktime_timezone: "Europe/Moscow"
aw_worktime_influx_enabled: false aw_worktime_influx_enabled: false
aw_worktime_influx_url: "http://10.10.10.10:8086" aw_worktime_influx_url: "http://10.10.10.10:8086"
@@ -27,14 +29,20 @@ aw_monitored_windows_host: "192.168.100.18"
aw_monitored_windows_hostname: "SHARKON2025" aw_monitored_windows_hostname: "SHARKON2025"
aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health" aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health"
aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation" aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation"
aw_hayabusa_auto_case_enabled: true
aw_hayabusa_auto_case_min_severity: "medium"
aw_hayabusa_telegram_enabled: false
aw_hayabusa_telegram_min_severity: "high"
aw_hayabusa_telegram_bot_token: ""
aw_hayabusa_telegram_chat_ids: ""
aw_repo_root: "/mnt/usb_hdd2/Projects/ActivityWatch-Russian" aw_repo_root: "/mnt/usb_hdd2/Projects/ActivityWatch-Russian"
aw_server_cors_origins: aw_server_cors_origins:
- "http://127.0.0.1:5600" - "http://127.0.0.1:5600"
- "http://localhost:5600" - "http://localhost:5600"
- "http://10.10.10.13:5600" - "http://{{ aw_server_public_host }}:5600"
- "http://192.168.100.13:5600" - "http://aw-server:5600"
- "http://snb-live:5600" - "http://snb-live:5600"
aw_apply_worktime_settings: true aw_apply_worktime_settings: true
+7 -1
View File
@@ -6,7 +6,8 @@ ansible_password: "{{ lookup('env', 'AW_WINRM_PASSWORD') }}"
aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus" aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http" aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13" # Leave empty to derive from the first host in [aw_server] inventory.
aw_windows_server_host: ""
aw_windows_server_port: 5600 aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2" aw_windows_package_version: "v0.13.2"
@@ -32,6 +33,11 @@ aw_windows_policy_engine_enabled: true
aw_windows_policy_engine_host: "{{ aw_windows_server_host }}" aw_windows_policy_engine_host: "{{ aw_windows_server_host }}"
aw_windows_policy_engine_port: 5601 aw_windows_policy_engine_port: 5601
aw_windows_policy_engine_scheme: "http" aw_windows_policy_engine_scheme: "http"
aw_windows_hayabusa_auto_upload_enabled: true
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_afk_enabled: true aw_windows_afk_enabled: true
aw_windows_window_enabled: true aw_windows_window_enabled: true
+7 -1
View File
@@ -1,8 +1,14 @@
aw_windows_repo_root: "{{ playbook_dir | dirname }}" aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus" aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http" aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13" # Leave empty to derive from the first host in [aw_server] inventory.
aw_windows_server_host: ""
aw_windows_server_port: 5600 aw_windows_server_port: 5600
aw_windows_hayabusa_auto_upload_enabled: false
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_package_version: "v0.13.2" 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_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: "" aw_windows_package_zip_path: ""
+21 -2
View File
@@ -9,7 +9,6 @@
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json" aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}" aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}"
aw_windows_server_scheme: "http" aw_windows_server_scheme: "http"
aw_windows_server_host: "10.10.10.13"
aw_windows_server_port: 5600 aw_windows_server_port: 5600
aw_windows_fail_on_validation_error: true aw_windows_fail_on_validation_error: true
aw_windows_launch_task_pattern: "ActivityWatch Launch *" aw_windows_launch_task_pattern: "ActivityWatch Launch *"
@@ -21,6 +20,26 @@
aw_windows_api_smoke_check_wait_seconds: 20 aw_windows_api_smoke_check_wait_seconds: 20
tasks: tasks:
- name: Вычислить inventory host AW server по умолчанию
ansible.builtin.set_fact:
aw_server_inventory_host_effective: "{{ (groups['aw_server'] | default([]) | first) | default('', true) }}"
- name: Вычислить effective host для AW server
ansible.builtin.set_fact:
aw_windows_server_host_effective: >-
{{
aw_windows_server_host
| default(
(
hostvars[aw_server_inventory_host_effective].ansible_host
| default(aw_server_inventory_host_effective, true)
)
if (aw_server_inventory_host_effective | length) > 0
else '',
true
)
}}
- name: Аккуратно запустить ActivityWatch recovery и launch tasks, если они не в Running - name: Аккуратно запустить ActivityWatch recovery и launch tasks, если они не в Running
when: aw_windows_force_task_restart | bool when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell: ansible.windows.win_powershell:
@@ -60,7 +79,7 @@
when: aw_windows_api_smoke_check_enabled | bool when: aw_windows_api_smoke_check_enabled | bool
delegate_to: localhost delegate_to: localhost
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}" url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET method: GET
status_code: 200 status_code: 200
return_content: true return_content: true
+10 -2
View File
@@ -10,7 +10,8 @@ fi
source "$ENV_FILE" source "$ENV_FILE"
WEBUI_DIR="${AW_SERVER_WEBUI_DIR:-${AW_WEBUI_DIR:-/opt/activitywatch/webui-ru}}" WEBUI_DIR="${AW_SERVER_WEBUI_DIR:-${AW_WEBUI_DIR:-/opt/activitywatch/webui-ru}}"
REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://10.10.10.13:5610}" SERVER_PUBLIC_HOST="${AW_SERVER_PUBLIC_HOST:-${AW_SERVER_HOST:-$(hostname -f 2>/dev/null || hostname)}}"
REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://${SERVER_PUBLIC_HOST}:5610}"
CASE_PORT="${AW_DLP_CASE_PORT:-5602}" CASE_PORT="${AW_DLP_CASE_PORT:-5602}"
CASE_BASE="${AW_DLP_CASE_PUBLIC_BASE:-}" CASE_BASE="${AW_DLP_CASE_PUBLIC_BASE:-}"
PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js" PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js"
@@ -62,12 +63,19 @@ worktime_panel_hash="$(sha1sum "$WORKTIME_PANEL_TARGET" | awk '{print substr($1,
if [[ -z "$CASE_BASE" ]]; then if [[ -z "$CASE_BASE" ]]; then
CASE_BASE="$(python3 - "$REPORT_BASE" "$CASE_PORT" <<'PY' CASE_BASE="$(python3 - "$REPORT_BASE" "$CASE_PORT" <<'PY'
from urllib.parse import urlsplit, urlunsplit from urllib.parse import urlsplit, urlunsplit
import os
import socket
import sys import sys
report_base = sys.argv[1] report_base = sys.argv[1]
case_port = sys.argv[2] case_port = sys.argv[2]
parts = urlsplit(report_base) parts = urlsplit(report_base)
hostname = parts.hostname or "10.10.10.13" hostname = (
parts.hostname
or os.environ.get("AW_SERVER_PUBLIC_HOST")
or os.environ.get("AW_SERVER_HOST")
or socket.getfqdn()
)
scheme = parts.scheme or "http" scheme = parts.scheme or "http"
print(urlunsplit((scheme, f"{hostname}:{case_port}", "", "", ""))) print(urlunsplit((scheme, f"{hostname}:{case_port}", "", "", "")))
PY PY
+3 -2
View File
@@ -1,10 +1,11 @@
[Unit] [Unit]
Description=AW-RUS Hayabusa auto-process dropped packages Description=AW-RUS Hayabusa auto-process dropped packages
After=network-online.target activitywatch-server.service After=network-online.target activitywatch-server.service aw-dlp-case-management.service
Wants=network-online.target activitywatch-server.service Wants=network-online.target activitywatch-server.service aw-dlp-case-management.service
[Service] [Service]
Type=oneshot Type=oneshot
EnvironmentFile=-/etc/activitywatch/aw-server.env
ExecStart=/usr/bin/python3 /usr/local/bin/aw-hayabusa-autoprocess ExecStart=/usr/bin/python3 /usr/local/bin/aw-hayabusa-autoprocess
User=root User=root
Group=root Group=root
+10 -1
View File
@@ -13,7 +13,8 @@ AW_SERVER_USER=activitywatch
AW_SERVER_GROUP=activitywatch AW_SERVER_GROUP=activitywatch
# Worktime API Configuration # Worktime API Configuration
AW_WORKTIME_REPORT_BASE=http://10.10.10.13:5610 AW_SERVER_PUBLIC_HOST=aw-server
AW_WORKTIME_REPORT_BASE=http://aw-server:5610
AW_WORKTIME_TZ=Europe/Moscow AW_WORKTIME_TZ=Europe/Moscow
AW_SERVER_URL=http://127.0.0.1:5600 AW_SERVER_URL=http://127.0.0.1:5600
@@ -42,5 +43,13 @@ AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025
AW_RUS_HEALTH_STATE_DIR=/var/lib/activitywatch/health AW_RUS_HEALTH_STATE_DIR=/var/lib/activitywatch/health
AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation
# Hayabusa auto-case / alerting
AW_HAYABUSA_AUTO_CASE_ENABLED=true
AW_HAYABUSA_AUTO_CASE_MIN_SEVERITY=medium
AW_HAYABUSA_TELEGRAM_ENABLED=false
AW_HAYABUSA_TELEGRAM_MIN_SEVERITY=high
AW_HAYABUSA_TELEGRAM_BOT_TOKEN=
AW_HAYABUSA_TELEGRAM_CHAT_IDS=
# Integration Test Configuration # Integration Test Configuration
AW_INTEGRATION_TEST_ENABLED=false AW_INTEGRATION_TEST_ENABLED=false
+21 -4
View File
@@ -12,6 +12,7 @@ DROP_DIR = pathlib.Path('/opt/activitywatch/aw-rus-ops/drop')
LOCK_PATH = pathlib.Path('/opt/hayabusa/state/aw-hayabusa-autoprocess.lock') LOCK_PATH = pathlib.Path('/opt/hayabusa/state/aw-hayabusa-autoprocess.lock')
WRAPPER = pathlib.Path('/usr/local/bin/aw-hayabusa') WRAPPER = pathlib.Path('/usr/local/bin/aw-hayabusa')
LINKER = pathlib.Path('/usr/local/bin/aw-hayabusa-link-case') LINKER = pathlib.Path('/usr/local/bin/aw-hayabusa-link-case')
CASE_ALERT = pathlib.Path('/usr/local/bin/aw-hayabusa-case-alert')
def run(cmd): def run(cmd):
@@ -19,6 +20,11 @@ def run(cmd):
subprocess.run(cmd, check=True) subprocess.run(cmd, check=True)
def run_capture(cmd):
print('RUN', ' '.join(str(x) for x in cmd), flush=True)
return subprocess.run(cmd, check=False, text=True, capture_output=True)
def read_latest_intake(): def read_latest_intake():
return json.loads(pathlib.Path('/opt/hayabusa/state/latest-intake.json').read_text(encoding='utf-8')) return json.loads(pathlib.Path('/opt/hayabusa/state/latest-intake.json').read_text(encoding='utf-8'))
@@ -83,11 +89,22 @@ def process_one(zip_path: pathlib.Path):
run([str(WRAPPER), 'process-inbox', '--mode', mode, '--limit', '1']) run([str(WRAPPER), 'process-inbox', '--mode', mode, '--limit', '1'])
latest = read_latest_intake() latest = read_latest_intake()
report_dir = pathlib.Path(latest['report_dir']) report_dir = pathlib.Path(latest['report_dir'])
case_alert = None
if CASE_ALERT.is_file():
alert_cmd = [str(CASE_ALERT), '--mode', mode, '--link-source', sidecars['link_source']]
if sidecars['case_id'] is not None:
alert_cmd += ['--case-id', str(sidecars['case_id'])]
result = run_capture(alert_cmd)
case_alert = {
'returncode': result.returncode,
'stdout': result.stdout.strip(),
'stderr': result.stderr.strip(),
}
archive_sidecars(report_dir, sidecars) archive_sidecars(report_dir, sidecars)
archive_drop_package(report_dir, zip_path) archive_drop_package(report_dir, zip_path)
if sidecars['case_id'] is not None: if sidecars['case_id'] is not None and not CASE_ALERT.is_file():
run([str(LINKER), '--case-id', str(sidecars['case_id']), '--mode', mode, '--link-source', sidecars['link_source']]) run([str(LINKER), '--case-id', str(sidecars['case_id']), '--mode', mode, '--link-source', sidecars['link_source']])
return latest return {'latest_intake': latest, 'case_alert': case_alert}
def main(): def main():
@@ -110,8 +127,8 @@ def main():
print('no zip packages in drop dir') print('no zip packages in drop dir')
return 0 return 0
for zip_path in zips: for zip_path in zips:
latest = process_one(zip_path) result = process_one(zip_path)
print(json.dumps({'processed': str(zip_path), 'latest_intake': latest}, ensure_ascii=False, indent=2)) print(json.dumps({'processed': str(zip_path), **result}, ensure_ascii=False, indent=2))
return 0 return 0
@@ -0,0 +1,334 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import csv
import json
import os
import pathlib
import urllib.error
import urllib.parse
import urllib.request
from collections import Counter
from datetime import datetime, timezone
from typing import Any
SEVERITY_ORDER = {"low": 1, "medium": 2, "high": 3, "critical": 4}
LEVEL_NORMALIZATION = {
"informational": "info",
"info": "info",
"low": "low",
"med": "med",
"medium": "med",
"high": "high",
"crit": "crit",
"critical": "crit",
}
LEVEL_WEIGHTS = {
"info": 1,
"low": 4,
"med": 12,
"high": 40,
"crit": 100,
}
def env_bool(name: str, default: bool) -> bool:
value = os.environ.get(name)
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
def normalize_level(level: str | None) -> str:
if not level:
return "info"
return LEVEL_NORMALIZATION.get(level.strip().lower(), level.strip().lower())
def severity_meets(actual: str, threshold: str) -> bool:
return SEVERITY_ORDER.get(actual, 0) >= SEVERITY_ORDER.get(threshold, 0)
def build_hayabusa_payload(intake: dict[str, Any], mode: str, link_source: str) -> dict[str, Any]:
report_dir = pathlib.Path(intake["report_dir"])
return {
"tool": "hayabusa",
"host": intake["host"],
"mode": mode,
"status": intake["status"],
"intake_id": intake["intake_id"],
"package_path": intake["package_path"],
"sha256": intake["sha256"],
"report_dir": intake["report_dir"],
"summary_html": str(report_dir / "summary.html"),
"timeline_path": str(report_dir / "timeline.jsonl"),
"manifest_path": str(report_dir / "manifest.json"),
"link_source": link_source,
}
def post_json(url: str, payload: dict[str, Any]) -> dict[str, Any]:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(url, data=data, method="POST", headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
body = resp.read().decode("utf-8")
return json.loads(body) if body else {}
def patch_json(url: str, payload: dict[str, Any]) -> dict[str, Any]:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(url, data=data, method="PATCH", headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
body = resp.read().decode("utf-8")
return json.loads(body) if body else {}
def get_json(url: str) -> dict[str, Any]:
with urllib.request.urlopen(url) as resp:
return json.loads(resp.read().decode("utf-8"))
def parse_timestamp(value: str | None) -> datetime | None:
if not value:
return None
try:
return datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError:
return None
def read_csv_rows(path: pathlib.Path) -> int:
if not path.is_file():
return 0
with path.open("r", encoding="utf-8-sig", newline="") as fh:
reader = csv.reader(fh)
rows = list(reader)
if not rows:
return 0
return max(0, len(rows) - 1)
def analyze_report(report_dir: pathlib.Path) -> dict[str, Any]:
timeline_path = report_dir / "timeline.jsonl"
level_counts: Counter[str] = Counter()
title_counts: Counter[str] = Counter()
first_ts: datetime | None = None
last_ts: datetime | None = None
total_events = 0
if timeline_path.is_file():
with timeline_path.open("r", encoding="utf-8") as fh:
for raw_line in fh:
line = raw_line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
total_events += 1
level = normalize_level(str(event.get("Level", "")))
level_counts[level] += 1
title = str(event.get("RuleTitle") or "").strip() or "Unknown rule"
title_counts[title] += 1
ts = parse_timestamp(event.get("Timestamp"))
if ts is not None:
first_ts = ts if first_ts is None or ts < first_ts else first_ts
last_ts = ts if last_ts is None or ts > last_ts else last_ts
failed_logons = read_csv_rows(report_dir / "logon-summary-failed.csv")
successful_logons = read_csv_rows(report_dir / "logon-summary-successful.csv")
suspicious_pwsh = sum(
count
for title, count in title_counts.items()
if "pwsh" in title.lower() or "powershell" in title.lower() or "obfuscation" in title.lower()
)
credential_events = sum(count for title, count in title_counts.items() if "credential" in title.lower())
timestomp_events = sum(count for title, count in title_counts.items() if "timestomp" in title.lower())
logon_failure_events = sum(count for title, count in title_counts.items() if "logon failure" in title.lower())
score = (
sum(LEVEL_WEIGHTS.get(level, 0) * count for level, count in level_counts.items())
+ min(failed_logons, 200) * 2
+ suspicious_pwsh * 6
+ credential_events * 8
+ timestomp_events * 12
+ logon_failure_events * 2
)
crit_count = level_counts.get("crit", 0)
high_count = level_counts.get("high", 0)
med_count = level_counts.get("med", 0)
if crit_count >= 1 or score >= 240 or (high_count >= 4 and suspicious_pwsh >= 4):
severity = "critical"
elif high_count >= 1 or score >= 120 or suspicious_pwsh >= 8 or credential_events >= 5:
severity = "high"
elif med_count >= 1 or score >= 40 or failed_logons >= 10:
severity = "medium"
else:
severity = "low"
return {
"severity": severity,
"score": score,
"events_total": total_events,
"level_counts": dict(level_counts),
"top_rules": [{"title": title, "count": count} for title, count in title_counts.most_common(5)],
"first_timestamp": first_ts.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") if first_ts else None,
"last_timestamp": last_ts.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") if last_ts else None,
"failed_logon_rows": failed_logons,
"successful_logon_rows": successful_logons,
"suspicious_pwsh": suspicious_pwsh,
"credential_events": credential_events,
"timestomp_events": timestomp_events,
"logon_failure_events": logon_failure_events,
}
def build_case_title(host: str, summary: dict[str, Any]) -> str:
top_rule = summary.get("top_rules") or []
suffix = top_rule[0]["title"] if top_rule else "No dominant rule"
return f"Hayabusa {summary['severity'].upper()} · {host} · {suffix}"
def build_case_payload(intake: dict[str, Any], summary: dict[str, Any]) -> dict[str, Any]:
return {
"incident_id": f"hayabusa:{intake['host']}:{intake['intake_id']}",
"host": intake["host"],
"title": build_case_title(intake["host"], summary),
"severity": summary["severity"],
"evidence": {
"hayabusa": {
"intake_id": intake["intake_id"],
"package_path": intake["package_path"],
"sha256": intake["sha256"],
"report_dir": intake["report_dir"],
"summary": summary,
}
},
}
def build_comment(summary: dict[str, Any], intake: dict[str, Any]) -> str:
top = ", ".join(f"{item['title']} ({item['count']})" for item in summary.get("top_rules", [])[:3]) or "n/a"
return (
f"Hayabusa auto-summary\n"
f"Severity: {summary['severity']} (score={summary['score']})\n"
f"Host: {intake['host']}\n"
f"Intake: {intake['intake_id']}\n"
f"Events: {summary['events_total']}, failed_logons={summary['failed_logon_rows']}, "
f"suspicious_pwsh={summary['suspicious_pwsh']}, credential_events={summary['credential_events']}\n"
f"Top rules: {top}\n"
f"Report: {intake['report_dir']}"
)
def send_telegram(bot_token: str, chat_ids: list[str], text: str) -> list[dict[str, Any]]:
results = []
for chat_id in chat_ids:
payload = urllib.parse.urlencode({"chat_id": chat_id, "text": text}).encode("utf-8")
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
req = urllib.request.Request(url, data=payload, method="POST")
try:
with urllib.request.urlopen(req) as resp:
body = json.loads(resp.read().decode("utf-8"))
results.append({"chat_id": chat_id, "ok": True, "response": body})
except Exception as exc: # noqa: BLE001
results.append({"chat_id": chat_id, "ok": False, "error": str(exc)})
return results
def build_telegram_text(case_id: int | None, intake: dict[str, Any], summary: dict[str, Any]) -> str:
top = ", ".join(f"{item['title']} ({item['count']})" for item in summary.get("top_rules", [])[:3]) or "n/a"
case_part = f"\nCase: {case_id}" if case_id is not None else ""
return (
f"Hayabusa {summary['severity'].upper()} alert\n"
f"Host: {intake['host']}{case_part}\n"
f"Intake: {intake['intake_id']}\n"
f"Score: {summary['score']}\n"
f"Events: {summary['events_total']}, failed_logons={summary['failed_logon_rows']}, "
f"suspicious_pwsh={summary['suspicious_pwsh']}\n"
f"Top rules: {top}\n"
f"Report: {intake['report_dir']}"
)
def main() -> int:
p = argparse.ArgumentParser(description="Auto-create/update AW-rus case, compute Hayabusa severity, and send Telegram alerts")
p.add_argument("--case-id", type=int)
p.add_argument("--intake-json", default="/opt/hayabusa/state/latest-intake.json")
p.add_argument("--case-api-base", default=os.environ.get("AW_HAYABUSA_CASE_API_BASE", "http://127.0.0.1:5602"))
p.add_argument("--mode", default="incident")
p.add_argument("--link-source", default="aw-rus-drop-autoprocess")
p.add_argument("--auto-create", action="store_true", default=env_bool("AW_HAYABUSA_AUTO_CASE_ENABLED", True))
p.add_argument("--auto-create-min-severity", default=os.environ.get("AW_HAYABUSA_AUTO_CASE_MIN_SEVERITY", "medium"))
p.add_argument("--telegram-enabled", action="store_true", default=env_bool("AW_HAYABUSA_TELEGRAM_ENABLED", False))
p.add_argument("--telegram-min-severity", default=os.environ.get("AW_HAYABUSA_TELEGRAM_MIN_SEVERITY", "high"))
p.add_argument("--telegram-bot-token", default=os.environ.get("AW_HAYABUSA_TELEGRAM_BOT_TOKEN", ""))
p.add_argument("--telegram-chat-ids", default=os.environ.get("AW_HAYABUSA_TELEGRAM_CHAT_IDS", ""))
args = p.parse_args()
intake_path = pathlib.Path(args.intake_json)
intake = json.loads(intake_path.read_text(encoding="utf-8"))
summary = analyze_report(pathlib.Path(intake["report_dir"]))
case_api_base = args.case_api_base.rstrip("/")
if case_api_base.endswith("/api/0/dlp/cases"):
case_api_base = case_api_base[: -len("/api/0/dlp/cases")]
case_id = args.case_id
created_case = None
case_error = None
linked = False
comment_added = False
try:
if case_id is None and args.auto_create and severity_meets(summary["severity"], args.auto_create_min_severity):
created_case = post_json(f"{case_api_base}/api/0/dlp/cases", build_case_payload(intake, summary))
case_id = int(created_case["id"])
if case_id is not None:
patch_json(
f"{case_api_base}/api/0/dlp/cases/{case_id}",
{"severity": summary["severity"]},
)
post_json(
f"{case_api_base}/api/0/dlp/cases/{case_id}/forensics/hayabusa",
build_hayabusa_payload(intake, args.mode, args.link_source),
)
linked = True
post_json(
f"{case_api_base}/api/0/dlp/cases/{case_id}/comments",
{"comment": build_comment(summary, intake), "author": "aw-hayabusa-auto"},
)
comment_added = True
except Exception as exc: # noqa: BLE001
case_error = str(exc)
telegram_results: list[dict[str, Any]] = []
if args.telegram_enabled and args.telegram_bot_token and severity_meets(summary["severity"], args.telegram_min_severity):
chat_ids = [item.strip() for item in args.telegram_chat_ids.split(",") if item.strip()]
if chat_ids:
telegram_results = send_telegram(
bot_token=args.telegram_bot_token,
chat_ids=chat_ids,
text=build_telegram_text(case_id, intake, summary),
)
result = {
"summary": summary,
"case_id": case_id,
"case_created": created_case,
"case_linked": linked,
"case_comment_added": comment_added,
"case_error": case_error,
"telegram_results": telegram_results,
}
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0 if case_error is None else 1
if __name__ == "__main__":
raise SystemExit(main())
+107
View File
@@ -0,0 +1,107 @@
# AW-rus Security Analytics Stack v1
## Goal
Build a sufficient internal security analytics stack for the current environment without pretending to be Splunk-class infrastructure.
## Scope
Sources:
- Windows EVTX
- ActivityWatch buckets
- DLP incidents
- file operations
- outbound email
- session/logon markers
Core outcomes:
- ingest
- normalize
- detect
- correlate
- case
- notify
- investigate
## v1 Architecture
### Windows side
- collectors and DLP scripts write to `deployment-config.json`
- `export-evtx-for-hayabusa.ps1` exports bounded EVTX packages
- `export-upload-hayabusa-to-aw-server.ps1` uploads:
- `zip`
- `.meta.json`
- optional `.caseid`
- `ActivityWatch Hayabusa Upload` scheduled task runs every 6 hours
### Server side
- `aw-hayabusa-drop.path` watches `/opt/activitywatch/aw-rus-ops/drop`
- `aw-hayabusa-drop.service` runs `aw-hayabusa-autoprocess`
- `aw-hayabusa` performs:
- accept
- process-inbox
- report generation
- `aw-hayabusa-case-alert` performs:
- severity scoring from `timeline.jsonl`
- optional auto-case creation
- bounded Hayabusa linkage
- summary comment
- Telegram alerting
### Case layer
- DLP case API remains the source of truth for incident lifecycle
- Hayabusa writes bounded metadata into `forensics.hayabusa`
- auto-created cases use:
- `incident_id = hayabusa:<host>:<intake_id>`
## Severity Model v1
Inputs:
- Hayabusa `Level`
- top `RuleTitle`
- failed logon count
- suspicious PowerShell count
- credential-related detections
- timestomp detections
Outputs:
- `low`
- `medium`
- `high`
- `critical`
Rules:
- `critical` for `crit` alerts, very high score, or strong compound signals
- `high` for at least one high alert or elevated score
- `medium` for med alerts, notable failed logons, or moderate score
- `low` otherwise
## Automation Policy v1
- EVTX upload every 6 hours
- lookback window: 6 hours
- auto-case enabled from `medium`
- Telegram enabled from `high`
- human operator only for final triage/escalation
## Non-goals
Not trying to implement:
- distributed search cluster
- Splunk-style indexers/search heads
- full SIEM content ecosystem
- petabyte-scale retention design
## Definition of Done
The stack is sufficient when it can, without a dedicated analyst:
- collect relevant data
- process EVTX on schedule
- score severity
- create/update a case
- send an alert
- preserve investigation artifacts
- let a human understand what happened in a few minutes
+56
View File
@@ -502,6 +502,7 @@ function New-ActivityWatchDeploymentConfig {
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$SessionCollectorScript, [string]$SessionCollectorScript,
[string]$EvtxExportScript, [string]$EvtxExportScript,
[string]$HayabusaUploadScript,
[string]$EmailCollectorScript, [string]$EmailCollectorScript,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$RulesPath, [string]$RulesPath,
@@ -541,6 +542,11 @@ function New-ActivityWatchDeploymentConfig {
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[pscustomobject[]]$UserTasks, [pscustomobject[]]$UserTasks,
[string]$PackageVersion = 'v0.13.2', [string]$PackageVersion = 'v0.13.2',
[bool]$HayabusaAutoUploadEnabled = $true,
[int]$HayabusaAutoUploadIntervalHours = 6,
[int]$HayabusaAutoUploadHoursBack = 6,
[string]$HayabusaAutoUploadMode = 'incident',
[string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload',
[switch]$IntegrationTestEnabled [switch]$IntegrationTestEnabled
) )
@@ -581,6 +587,7 @@ function New-ActivityWatchDeploymentConfig {
fileCollectorScript = $FileCollectorScript fileCollectorScript = $FileCollectorScript
sessionCollectorScript = $SessionCollectorScript sessionCollectorScript = $SessionCollectorScript
evtxExportScript = $EvtxExportScript evtxExportScript = $EvtxExportScript
hayabusaUploadScript = $HayabusaUploadScript
rulesPath = $RulesPath rulesPath = $RulesPath
policyPath = $PolicyPath policyPath = $PolicyPath
launchScript = $LaunchScriptPath launchScript = $LaunchScriptPath
@@ -608,6 +615,13 @@ function New-ActivityWatchDeploymentConfig {
evtxExportRoot = $effectiveEvtxExportRoot evtxExportRoot = $effectiveEvtxExportRoot
retentionDays = $EvtxRetentionDays retentionDays = $EvtxRetentionDays
evtxChannels = @($effectiveEvtxChannels) evtxChannels = @($effectiveEvtxChannels)
hayabusaAutomation = [pscustomobject]@{
enabled = [bool]$HayabusaAutoUploadEnabled
intervalHours = $HayabusaAutoUploadIntervalHours
hoursBack = $HayabusaAutoUploadHoursBack
mode = $HayabusaAutoUploadMode
taskName = $HayabusaAutoUploadTaskName
}
} }
sessionEvents = [pscustomobject]@{ sessionEvents = [pscustomobject]@{
logonEnabled = $LogonMarkerEnabled logonEnabled = $LogonMarkerEnabled
@@ -1539,6 +1553,48 @@ function Register-ActivityWatchRecoveryTask {
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
} }
function Register-ActivityWatchHayabusaAutoUploadTask {
param(
[Parameter(Mandatory = $true)]
[string]$ConfigPath
)
$config = Read-ActivityWatchDeploymentConfig -Path $ConfigPath
$forensics = $config.forensics
if ($null -eq $forensics -or $forensics.PSObject.Properties.Name -notcontains 'hayabusaAutomation') {
return
}
$automation = $forensics.hayabusaAutomation
$taskName = if ($automation.PSObject.Properties.Name -contains 'taskName' -and -not [string]::IsNullOrWhiteSpace([string]$automation.taskName)) {
[string]$automation.taskName
} else {
'ActivityWatch Hayabusa Upload'
}
if (-not [bool]$automation.enabled) {
Remove-ActivityWatchScheduledTask -TaskName $taskName
return
}
$uploadScript = if ($config.paths.PSObject.Properties.Name -contains 'hayabusaUploadScript') { [string]$config.paths.hayabusaUploadScript } else { Join-Path $config.paths.stateRoot 'export-upload-hayabusa-to-aw-server.ps1' }
if (-not (Test-Path -LiteralPath $uploadScript)) {
throw "Не найден скрипт Hayabusa upload: $uploadScript"
}
$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' }
$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 ($LASTEXITCODE -ne 0) {
throw "Не удалось создать scheduled task $taskName через schtasks.exe"
}
}
function Set-ActivityWatchAcl { function Set-ActivityWatchAcl {
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
+12
View File
@@ -39,6 +39,11 @@ param(
[string]$PolicyEngineScheme = 'http', [string]$PolicyEngineScheme = 'http',
[int]$PolicyRefreshSeconds = 300, [int]$PolicyRefreshSeconds = 300,
[string]$PolicyCachePath, [string]$PolicyCachePath,
[bool]$HayabusaAutoUploadEnabled = $true,
[int]$HayabusaAutoUploadIntervalHours = 6,
[int]$HayabusaAutoUploadHoursBack = 6,
[string]$HayabusaAutoUploadMode = 'incident',
[string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload',
[switch]$IntegrationTestEnabled [switch]$IntegrationTestEnabled
) )
@@ -109,6 +114,7 @@ $config = New-ActivityWatchDeploymentConfig `
-FileCollectorScript $assetResult.FileCollectorScript ` -FileCollectorScript $assetResult.FileCollectorScript `
-SessionCollectorScript $assetResult.SessionCollectorScript ` -SessionCollectorScript $assetResult.SessionCollectorScript `
-EvtxExportScript $assetResult.EvtxExportScript ` -EvtxExportScript $assetResult.EvtxExportScript `
-HayabusaUploadScript $assetResult.HayabusaUploadScript `
-RulesPath $assetResult.ActiveRules ` -RulesPath $assetResult.ActiveRules `
-PolicyPath $assetResult.ActivePolicy ` -PolicyPath $assetResult.ActivePolicy `
-PollSeconds $PollSeconds ` -PollSeconds $PollSeconds `
@@ -133,6 +139,11 @@ $config = New-ActivityWatchDeploymentConfig `
-PolicyEngineScheme $PolicyEngineScheme ` -PolicyEngineScheme $PolicyEngineScheme `
-PolicyRefreshSeconds $PolicyRefreshSeconds ` -PolicyRefreshSeconds $PolicyRefreshSeconds `
-PolicyCachePath $PolicyCachePath ` -PolicyCachePath $PolicyCachePath `
-HayabusaAutoUploadEnabled $HayabusaAutoUploadEnabled `
-HayabusaAutoUploadIntervalHours $HayabusaAutoUploadIntervalHours `
-HayabusaAutoUploadHoursBack $HayabusaAutoUploadHoursBack `
-HayabusaAutoUploadMode $HayabusaAutoUploadMode `
-HayabusaAutoUploadTaskName $HayabusaAutoUploadTaskName `
-LaunchScriptPath $launchScriptPath ` -LaunchScriptPath $launchScriptPath `
-RecoveryScriptPath $recoveryScriptPath ` -RecoveryScriptPath $recoveryScriptPath `
-UserTasks $taskDefinitions ` -UserTasks $taskDefinitions `
@@ -144,6 +155,7 @@ Remove-LegacyActivityWatchEntries
Set-ActivityWatchAcl -InstallRoot $InstallRoot -StateRoot $StateRoot -LogsRoot $logsRoot Set-ActivityWatchAcl -InstallRoot $InstallRoot -StateRoot $StateRoot -LogsRoot $logsRoot
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $launchScriptPath -ConfigPath $configPath Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $launchScriptPath -ConfigPath $configPath
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $recoveryScriptPath -ConfigPath $configPath Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $recoveryScriptPath -ConfigPath $configPath
Register-ActivityWatchHayabusaAutoUploadTask -ConfigPath $configPath
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
Write-Host 'ActivityWatch развёрнут для пользователей:' Write-Host 'ActivityWatch развёрнут для пользователей:'
+10
View File
@@ -40,6 +40,11 @@ param(
[int]$PolicyRefreshSeconds = 300, [int]$PolicyRefreshSeconds = 300,
[string]$PolicyCachePath, [string]$PolicyCachePath,
[string]$ReportPath, [string]$ReportPath,
[bool]$HayabusaAutoUploadEnabled = $true,
[int]$HayabusaAutoUploadIntervalHours = 6,
[int]$HayabusaAutoUploadHoursBack = 6,
[string]$HayabusaAutoUploadMode = 'incident',
[string]$HayabusaAutoUploadTaskName = 'ActivityWatch Hayabusa Upload',
[switch]$SkipHardening, [switch]$SkipHardening,
[switch]$ValidateAfterDeploy, [switch]$ValidateAfterDeploy,
[switch]$IntegrationTestEnabled [switch]$IntegrationTestEnabled
@@ -98,6 +103,11 @@ if (-not (Test-Path -LiteralPath $deployScript)) {
-PolicyEngineScheme $PolicyEngineScheme ` -PolicyEngineScheme $PolicyEngineScheme `
-PolicyRefreshSeconds $PolicyRefreshSeconds ` -PolicyRefreshSeconds $PolicyRefreshSeconds `
-PolicyCachePath $PolicyCachePath ` -PolicyCachePath $PolicyCachePath `
-HayabusaAutoUploadEnabled $HayabusaAutoUploadEnabled `
-HayabusaAutoUploadIntervalHours $HayabusaAutoUploadIntervalHours `
-HayabusaAutoUploadHoursBack $HayabusaAutoUploadHoursBack `
-HayabusaAutoUploadMode $HayabusaAutoUploadMode `
-HayabusaAutoUploadTaskName $HayabusaAutoUploadTaskName `
-IntegrationTestEnabled:$IntegrationTestEnabled -IntegrationTestEnabled:$IntegrationTestEnabled
if (-not $SkipHardening) { if (-not $SkipHardening) {
+8 -2
View File
@@ -4,6 +4,7 @@ param(
[string]$OutputRoot, [string]$OutputRoot,
[int]$RetentionDays, [int]$RetentionDays,
[string[]]$Channels, [string[]]$Channels,
[Nullable[int]]$HoursBack,
[int]$DaysBack = 3, [int]$DaysBack = 3,
[switch]$NoZip [switch]$NoZip
) )
@@ -78,8 +79,12 @@ $zipPath = Join-Path $effectiveOutputRoot "$hostName-$timestamp.zip"
New-Directory -Path $batchRoot New-Directory -Path $batchRoot
New-Directory -Path $evtxRoot New-Directory -Path $evtxRoot
$daysBackMs = [int64]$DaysBack * 24 * 60 * 60 * 1000 $lookbackMs = if ($null -ne $HoursBack) {
$query = "*[System[TimeCreated[timediff(@SystemTime) <= $daysBackMs]]]" [int64]$HoursBack * 60 * 60 * 1000
} else {
[int64]$DaysBack * 24 * 60 * 60 * 1000
}
$query = "*[System[TimeCreated[timediff(@SystemTime) <= $lookbackMs]]]"
$results = @() $results = @()
foreach ($channel in @($effectiveChannels | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })) { foreach ($channel in @($effectiveChannels | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })) {
@@ -117,6 +122,7 @@ $manifest = [ordered]@{
batchRoot = $batchRoot batchRoot = $batchRoot
zipPath = if ($NoZip) { $null } else { $zipPath } zipPath = if ($NoZip) { $null } else { $zipPath }
daysBack = $DaysBack daysBack = $DaysBack
hoursBack = if ($null -ne $HoursBack) { [int]$HoursBack } else { $null }
retentionDays = $effectiveRetentionDays retentionDays = $effectiveRetentionDays
channels = @($effectiveChannels) channels = @($effectiveChannels)
exports = @($results) exports = @($results)
@@ -1,8 +1,9 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json', [string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
[Nullable[int]]$HoursBack,
[int]$DaysBack = 1, [int]$DaysBack = 1,
[string]$ServerHost = '10.10.10.13', [string]$ServerHost = '',
[string]$ServerUser = 'awops', [string]$ServerUser = 'awops',
[string]$RemoteDropDir = '/opt/activitywatch/aw-rus-ops/drop', [string]$RemoteDropDir = '/opt/activitywatch/aw-rus-ops/drop',
[string]$RemoteKeyPath = 'C:\ProgramData\AWatch-rus\ssh\awops_ed25519', [string]$RemoteKeyPath = 'C:\ProgramData\AWatch-rus\ssh\awops_ed25519',
@@ -41,7 +42,23 @@ if (-not (Test-Path -LiteralPath $RemoteKeyPath)) {
throw "SSH private key not found: $RemoteKeyPath" throw "SSH private key not found: $RemoteKeyPath"
} }
$export = & $exportScript -ConfigPath $ConfigPath -DaysBack $DaysBack $config = Get-Content -Raw -LiteralPath $ConfigPath | ConvertFrom-Json
if ([string]::IsNullOrWhiteSpace($ServerHost)) {
$ServerHost = [string]$config.server.host
}
if ([string]::IsNullOrWhiteSpace($ServerHost)) {
throw "ServerHost is empty and deployment-config has no server.host: $ConfigPath"
}
$exportArgs = @{
ConfigPath = $ConfigPath
}
if ($null -ne $HoursBack) {
$exportArgs.HoursBack = [int]$HoursBack
} else {
$exportArgs.DaysBack = $DaysBack
}
$export = & $exportScript @exportArgs
$zipPath = [string]$export.zipPath $zipPath = [string]$export.zipPath
$hostName = [string]$export.hostname $hostName = [string]$export.hostname
if ([string]::IsNullOrWhiteSpace($zipPath) -or -not (Test-Path -LiteralPath $zipPath)) { if ([string]::IsNullOrWhiteSpace($zipPath) -or -not (Test-Path -LiteralPath $zipPath)) {
@@ -52,9 +69,23 @@ $zipName = Split-Path -Leaf $zipPath
$remoteTarget = "$ServerUser@$ServerHost`:$RemoteDropDir/" $remoteTarget = "$ServerUser@$ServerHost`:$RemoteDropDir/"
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($zipPath) $baseName = [System.IO.Path]::GetFileNameWithoutExtension($zipPath)
$caseIdPath = Join-Path ([System.IO.Path]::GetDirectoryName($zipPath)) ($baseName + '.caseid') $caseIdPath = Join-Path ([System.IO.Path]::GetDirectoryName($zipPath)) ($baseName + '.caseid')
$metaPath = Join-Path ([System.IO.Path]::GetDirectoryName($zipPath)) ($baseName + '.meta.json')
$effectiveKeyPath = New-TemporarySshKeyCopy -SourceKeyPath $RemoteKeyPath $effectiveKeyPath = New-TemporarySshKeyCopy -SourceKeyPath $RemoteKeyPath
try { try {
$meta = [ordered]@{
host = $hostName
mode = $Mode
link_source = 'windows-drop-upload'
}
if ($null -ne $CaseId) {
$meta.case_id = [int]$CaseId
}
$meta | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $metaPath -Encoding UTF8
& scp.exe -i $effectiveKeyPath -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL $metaPath $remoteTarget
if ($LASTEXITCODE -ne 0) {
throw "scp meta upload failed with rc=$LASTEXITCODE"
}
if ($null -ne $CaseId) { if ($null -ne $CaseId) {
Set-Content -LiteralPath $caseIdPath -Value ([string]$CaseId) -Encoding ASCII Set-Content -LiteralPath $caseIdPath -Value ([string]$CaseId) -Encoding ASCII
& scp.exe -i $effectiveKeyPath -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL $caseIdPath $remoteTarget & scp.exe -i $effectiveKeyPath -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL $caseIdPath $remoteTarget
@@ -69,12 +100,14 @@ try {
} }
finally { finally {
Remove-Item -LiteralPath $effectiveKeyPath -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $effectiveKeyPath -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $metaPath -Force -ErrorAction SilentlyContinue
} }
$result = [ordered]@{ $result = [ordered]@{
exportedZip = $zipPath exportedZip = $zipPath
uploadedTo = "$RemoteDropDir/$zipName" uploadedTo = "$RemoteDropDir/$zipName"
caseIdSidecar = if ($null -ne $CaseId) { "$RemoteDropDir/$baseName.caseid" } else { $null } caseIdSidecar = if ($null -ne $CaseId) { "$RemoteDropDir/$baseName.caseid" } else { $null }
metaSidecar = "$RemoteDropDir/$baseName.meta.json"
hostname = $hostName hostname = $hostName
mode = $Mode mode = $Mode
runRemote = [bool]$RunRemote runRemote = [bool]$RunRemote
+14
View File
@@ -70,6 +70,7 @@ $effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSO
$effectiveFileCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$existingConfig.paths.fileCollectorScript } else { Join-Path $effectiveStateRoot 'file-operations-collector.ps1' } $effectiveFileCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$existingConfig.paths.fileCollectorScript } else { Join-Path $effectiveStateRoot 'file-operations-collector.ps1' }
$effectiveSessionCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$existingConfig.paths.sessionCollectorScript } else { Join-Path $effectiveStateRoot 'worktime-session-collector.ps1' } $effectiveSessionCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$existingConfig.paths.sessionCollectorScript } else { Join-Path $effectiveStateRoot 'worktime-session-collector.ps1' }
$effectiveEvtxExportScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'evtxExportScript') { [string]$existingConfig.paths.evtxExportScript } else { Join-Path $effectiveStateRoot 'export-evtx-for-hayabusa.ps1' } $effectiveEvtxExportScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'evtxExportScript') { [string]$existingConfig.paths.evtxExportScript } else { Join-Path $effectiveStateRoot 'export-evtx-for-hayabusa.ps1' }
$effectiveHayabusaUploadScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'hayabusaUploadScript') { [string]$existingConfig.paths.hayabusaUploadScript } else { Join-Path $effectiveStateRoot 'export-upload-hayabusa-to-aw-server.ps1' }
$effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json' $effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json'
$effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' } $effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' }
$effectivePolicyClientScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$existingConfig.paths.policyClientScript } else { Join-Path $effectiveStateRoot 'dlp-policy-client.ps1' } $effectivePolicyClientScript = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$existingConfig.paths.policyClientScript } else { Join-Path $effectiveStateRoot 'dlp-policy-client.ps1' }
@@ -100,6 +101,11 @@ $effectivePolicyEnginePort = if ($PSBoundParameters.ContainsKey('PolicyEnginePor
$effectivePolicyEngineScheme = if ($PSBoundParameters.ContainsKey('PolicyEngineScheme') -and $PolicyEngineScheme) { [string]$PolicyEngineScheme } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'scheme') { [string]$existingConfig.policyEngine.scheme } else { 'http' } $effectivePolicyEngineScheme = if ($PSBoundParameters.ContainsKey('PolicyEngineScheme') -and $PolicyEngineScheme) { [string]$PolicyEngineScheme } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'scheme') { [string]$existingConfig.policyEngine.scheme } else { 'http' }
$effectivePolicyRefreshSeconds = if ($PSBoundParameters.ContainsKey('PolicyRefreshSeconds')) { [int]$PolicyRefreshSeconds } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'refreshSeconds') { [int]$existingConfig.policyEngine.refreshSeconds } else { 300 } $effectivePolicyRefreshSeconds = if ($PSBoundParameters.ContainsKey('PolicyRefreshSeconds')) { [int]$PolicyRefreshSeconds } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'refreshSeconds') { [int]$existingConfig.policyEngine.refreshSeconds } else { 300 }
$effectivePolicyCachePath = if ($PSBoundParameters.ContainsKey('PolicyCachePath') -and $PolicyCachePath) { [string]$PolicyCachePath } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'cachePath') { [string]$existingConfig.policyEngine.cachePath } else { Join-Path $effectiveStateRoot 'dlp-policy-cache.json' } $effectivePolicyCachePath = if ($PSBoundParameters.ContainsKey('PolicyCachePath') -and $PolicyCachePath) { [string]$PolicyCachePath } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'policyEngine' -and $existingConfig.policyEngine.PSObject.Properties.Name -contains 'cachePath') { [string]$existingConfig.policyEngine.cachePath } else { Join-Path $effectiveStateRoot 'dlp-policy-cache.json' }
$effectiveHayabusaAutoUploadEnabled = 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 'enabled') { [bool]$existingConfig.forensics.hayabusaAutomation.enabled } else { $true }
$effectiveHayabusaAutoUploadIntervalHours = 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 'intervalHours') { [int]$existingConfig.forensics.hayabusaAutomation.intervalHours } else { 6 }
$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' }
$effectiveUsers = if ($Users -or $UserListPath) { $effectiveUsers = if ($Users -or $UserListPath) {
Normalize-ActivityWatchUsers -Users $Users -UserListPath $UserListPath -Domain $Domain Normalize-ActivityWatchUsers -Users $Users -UserListPath $UserListPath -Domain $Domain
@@ -131,6 +137,7 @@ $assetResult = Copy-ActivityWatchCollectorAssets `
-FileCollectorScriptSource (Join-Path $PSScriptRoot 'file-operations-collector.ps1') ` -FileCollectorScriptSource (Join-Path $PSScriptRoot 'file-operations-collector.ps1') `
-SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') ` -SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') `
-EvtxExportScriptSource (Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.ps1') ` -EvtxExportScriptSource (Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.ps1') `
-HayabusaUploadScriptSource (Join-Path $PSScriptRoot 'export-upload-hayabusa-to-aw-server.ps1') `
-ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') ` -ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') `
-ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') ` -ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') `
-StateRoot $effectiveStateRoot ` -StateRoot $effectiveStateRoot `
@@ -155,6 +162,7 @@ $config = New-ActivityWatchDeploymentConfig `
-FileCollectorScript $effectiveFileCollector ` -FileCollectorScript $effectiveFileCollector `
-SessionCollectorScript $effectiveSessionCollector ` -SessionCollectorScript $effectiveSessionCollector `
-EvtxExportScript $effectiveEvtxExportScript ` -EvtxExportScript $effectiveEvtxExportScript `
-HayabusaUploadScript $effectiveHayabusaUploadScript `
-RulesPath $effectiveRules ` -RulesPath $effectiveRules `
-PolicyPath $effectivePolicy ` -PolicyPath $effectivePolicy `
-PollSeconds $effectivePollSeconds ` -PollSeconds $effectivePollSeconds `
@@ -179,6 +187,11 @@ $config = New-ActivityWatchDeploymentConfig `
-PolicyEngineScheme $effectivePolicyEngineScheme ` -PolicyEngineScheme $effectivePolicyEngineScheme `
-PolicyRefreshSeconds $effectivePolicyRefreshSeconds ` -PolicyRefreshSeconds $effectivePolicyRefreshSeconds `
-PolicyCachePath $effectivePolicyCachePath ` -PolicyCachePath $effectivePolicyCachePath `
-HayabusaAutoUploadEnabled $effectiveHayabusaAutoUploadEnabled `
-HayabusaAutoUploadIntervalHours $effectiveHayabusaAutoUploadIntervalHours `
-HayabusaAutoUploadHoursBack $effectiveHayabusaAutoUploadHoursBack `
-HayabusaAutoUploadMode $effectiveHayabusaAutoUploadMode `
-HayabusaAutoUploadTaskName $effectiveHayabusaAutoUploadTaskName `
-LaunchScriptPath $effectiveLaunchScript ` -LaunchScriptPath $effectiveLaunchScript `
-RecoveryScriptPath $effectiveRecoveryScript ` -RecoveryScriptPath $effectiveRecoveryScript `
-UserTasks $taskDefinitions ` -UserTasks $taskDefinitions `
@@ -189,6 +202,7 @@ Remove-LegacyActivityWatchEntries
Set-ActivityWatchAcl -InstallRoot $effectiveInstallRoot -StateRoot $effectiveStateRoot -LogsRoot $effectiveLogsRoot Set-ActivityWatchAcl -InstallRoot $effectiveInstallRoot -StateRoot $effectiveStateRoot -LogsRoot $effectiveLogsRoot
Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $effectiveLaunchScript -ConfigPath $effectiveConfigPath Register-ActivityWatchUserTasks -TaskDefinitions $taskDefinitions -LaunchScriptPath $effectiveLaunchScript -ConfigPath $effectiveConfigPath
Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $effectiveRecoveryScript -ConfigPath $effectiveConfigPath Register-ActivityWatchRecoveryTask -TaskName $config.recovery.taskName -RecoveryScriptPath $effectiveRecoveryScript -ConfigPath $effectiveConfigPath
Register-ActivityWatchHayabusaAutoUploadTask -ConfigPath $effectiveConfigPath
Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName Start-ActivityWatchTasks -TaskDefinitions $taskDefinitions -RecoveryTaskName $config.recovery.taskName
Write-Host 'Укрепление и восстановление ActivityWatch завершены.' Write-Host 'Укрепление и восстановление ActivityWatch завершены.'
@@ -2,9 +2,9 @@
#define MyAppVersion "1.0.0" #define MyAppVersion "1.0.0"
#define MyAppPublisher "AWatch-rus" #define MyAppPublisher "AWatch-rus"
#define AwDefaultServerHost "10.10.10.13" #define AwDefaultServerHost "aw-server"
#define AwDefaultServerPort "5600" #define AwDefaultServerPort "5600"
#define AwDefaultWorktimeReportBase "http://10.10.10.13:5610" #define AwDefaultWorktimeReportBase "http://aw-server:5610"
#define AwDefaultWorktimeHost "SHARKON2025" #define AwDefaultWorktimeHost "SHARKON2025"
#define AwDefaultUsers "user1,user2,user3,user4,user5" #define AwDefaultUsers "user1,user2,user3,user4,user5"
#define AwDefaultInstallRoot "C:\\Program Files\\AWatch-rus\\bin" #define AwDefaultInstallRoot "C:\\Program Files\\AWatch-rus\\bin"
+1 -1
View File
@@ -29,7 +29,7 @@ The resulting installer `AWatch-rus-InstallKit.exe` is written to the same direc
The installer wizard asks only for: The installer wizard asks only for:
- `ServerHost` / `ServerPort` (defaults to `10.10.10.13:5600`) - `ServerHost` / `ServerPort` (defaults to `aw-server:5600`)
All other values are taken from defaults embedded in installer scripts. All other values are taken from defaults embedded in installer scripts.
+12
View File
@@ -38,6 +38,8 @@ $fileOpsExpected = if ($config.PSObject.Properties.Name -contains 'collectors' -
function Get-LoggedOnUsers { function Get-LoggedOnUsers {
$users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) $users = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
$activeStates = @('Active', 'Активно')
$inactiveStates = @('Disc', 'Disconnected', 'Idle', 'Listen', 'Диск', 'Откл', 'Отключен')
try { try {
$lines = & quser.exe 2>$null $lines = & quser.exe 2>$null
foreach ($line in @($lines)) { foreach ($line in @($lines)) {
@@ -50,6 +52,16 @@ function Get-LoggedOnUsers {
if ($parts.Count -lt 1) { continue } if ($parts.Count -lt 1) { continue }
$user = [string]$parts[0] $user = [string]$parts[0]
if ([string]::IsNullOrWhiteSpace($user)) { continue } if ([string]::IsNullOrWhiteSpace($user)) { continue }
$state = $null
foreach ($part in @($parts | Select-Object -Skip 1)) {
$token = [string]$part
if ([string]::IsNullOrWhiteSpace($token)) { continue }
if ($activeStates -contains $token -or $inactiveStates -contains $token) {
$state = $token
break
}
}
if ($null -ne $state -and $activeStates -notcontains $state) { continue }
[void]$users.Add($user) [void]$users.Add($user)
[void]$users.Add(('{0}\{1}' -f $env:COMPUTERNAME, $user)) [void]$users.Add(('{0}\{1}' -f $env:COMPUTERNAME, $user))
if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { if (-not [string]::IsNullOrWhiteSpace($env:USERDOMAIN)) {