feat(dfir): add hayabusa forensic workflow integration
This commit is contained in:
@@ -1314,6 +1314,99 @@
|
|||||||
msg: "{{ aw_hayabusa_ioc_refresh_result.stdout }}"
|
msg: "{{ aw_hayabusa_ioc_refresh_result.stdout }}"
|
||||||
when: aw_hayabusa_ioc_refresh_result.stdout is defined
|
when: aw_hayabusa_ioc_refresh_result.stdout is defined
|
||||||
|
|
||||||
|
- name: Установить server-side Hayabusa runner
|
||||||
|
when: aw_hayabusa_runner_enabled | default(false) | bool
|
||||||
|
block:
|
||||||
|
- name: Создать каталоги Hayabusa
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
loop:
|
||||||
|
- "{{ aw_hayabusa_root }}"
|
||||||
|
- "{{ aw_hayabusa_root }}/releases"
|
||||||
|
- "{{ aw_hayabusa_release_dir }}"
|
||||||
|
- "{{ aw_hayabusa_reports_dir }}"
|
||||||
|
- "{{ aw_hayabusa_state_dir }}"
|
||||||
|
- "{{ aw_hayabusa_inbox_dir }}"
|
||||||
|
- "{{ aw_hayabusa_archive_dir }}"
|
||||||
|
- "{{ aw_hayabusa_incoming_dir }}"
|
||||||
|
- "{{ aw_hayabusa_staging_dir }}"
|
||||||
|
- "{{ aw_hayabusa_archive_packages_dir }}"
|
||||||
|
- "{{ aw_hayabusa_archive_extracted_dir }}"
|
||||||
|
- "{{ aw_hayabusa_logs_dir }}"
|
||||||
|
|
||||||
|
- name: Скачать архив Hayabusa
|
||||||
|
ansible.builtin.get_url:
|
||||||
|
url: "{{ aw_hayabusa_download_url }}"
|
||||||
|
dest: "{{ aw_hayabusa_archive_path }}"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Распаковать pinned Hayabusa release
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "{{ aw_hayabusa_archive_path }}"
|
||||||
|
dest: "{{ aw_hayabusa_release_dir }}"
|
||||||
|
remote_src: true
|
||||||
|
creates: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}"
|
||||||
|
|
||||||
|
- name: Нормализовать права release Hayabusa
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ aw_hayabusa_release_dir }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
recurse: true
|
||||||
|
|
||||||
|
- name: Сделать бинарь Hayabusa исполняемым
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ aw_hayabusa_release_dir }}/{{ aw_hayabusa_binary_name }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
state: file
|
||||||
|
|
||||||
|
- name: Создать canonical symlink на текущий релиз Hayabusa
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: "{{ aw_hayabusa_release_dir }}"
|
||||||
|
dest: "{{ aw_hayabusa_current_link }}"
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Создать canonical symlink на бинарь Hayabusa
|
||||||
|
ansible.builtin.file:
|
||||||
|
src: "{{ aw_hayabusa_binary_name }}"
|
||||||
|
dest: "{{ aw_hayabusa_release_dir }}/hayabusa"
|
||||||
|
state: link
|
||||||
|
force: true
|
||||||
|
|
||||||
|
- name: Установить wrapper aw-hayabusa
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ aw_repo_root }}/aw-server/hayabusa/aw-hayabusa.sh"
|
||||||
|
dest: /usr/local/bin/aw-hayabusa
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Проверить server-side runner через doctor
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: /usr/local/bin/aw-hayabusa doctor
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Проверить загрузку profiles Hayabusa
|
||||||
|
ansible.builtin.shell: /usr/local/bin/aw-hayabusa profiles > /tmp/aw-hayabusa-profiles.txt
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Проверить наличие standard profile у Hayabusa
|
||||||
|
ansible.builtin.shell: "grep -q 'standard:' /tmp/aw-hayabusa-profiles.txt"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Post-deploy health gate (aw-health-check)
|
- name: Post-deploy health gate (aw-health-check)
|
||||||
when:
|
when:
|
||||||
- not ansible_check_mode
|
- not ansible_check_mode
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
aw_windows_incident_capture_enabled: true
|
aw_windows_incident_capture_enabled: true
|
||||||
aw_windows_incident_screenshot_enabled: true
|
aw_windows_incident_screenshot_enabled: true
|
||||||
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
||||||
|
aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports"
|
||||||
aw_windows_logon_marker_enabled: true
|
aw_windows_logon_marker_enabled: true
|
||||||
aw_windows_skip_hardening: false
|
aw_windows_skip_hardening: false
|
||||||
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
|
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
|
||||||
@@ -169,6 +170,8 @@
|
|||||||
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
|
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
|
||||||
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
|
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
|
||||||
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
|
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
|
||||||
|
EvtxExportRoot = "{{ aw_windows_forensics_root }}"
|
||||||
|
EvtxRetentionDays = {{ aw_windows_evtx_retention_days | int }}
|
||||||
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' }}
|
||||||
@@ -185,6 +188,13 @@
|
|||||||
{% if (aw_windows_package_zip_path | default('') | string | length) > 0 %}
|
{% if (aw_windows_package_zip_path | default('') | string | length) > 0 %}
|
||||||
$params.PackageZipPath = "{{ aw_windows_package_zip_path }}"
|
$params.PackageZipPath = "{{ aw_windows_package_zip_path }}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if (aw_windows_evtx_channels | default([]) | length) > 0 %}
|
||||||
|
$params.EvtxChannels = @(
|
||||||
|
{% for channel in aw_windows_evtx_channels %}
|
||||||
|
"{{ channel }}"{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
)
|
||||||
|
{% endif %}
|
||||||
{% if (aw_windows_hostname_override | default('') | string | length) > 0 %}
|
{% if (aw_windows_hostname_override | default('') | string | length) > 0 %}
|
||||||
$params.AwHostname = "{{ aw_windows_hostname_override }}"
|
$params.AwHostname = "{{ aw_windows_hostname_override }}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -18,14 +18,24 @@
|
|||||||
tsj_bot_default_chat_id: "{{ telegram_default_chat_id | default(telegram_allowed_chat_ids.split(',')[0]) }}"
|
tsj_bot_default_chat_id: "{{ telegram_default_chat_id | default(telegram_allowed_chat_ids.split(',')[0]) }}"
|
||||||
|
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
|
- name: Проверить наличие существующего .env бота на хосте
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ tsj_bot_env_path }}"
|
||||||
|
register: tsj_bot_existing_env
|
||||||
|
|
||||||
- name: Проверить обязательные переменные
|
- name: Проверить обязательные переменные
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- telegram_bot_token is defined
|
- >
|
||||||
- telegram_bot_token | length > 20
|
(
|
||||||
- telegram_allowed_chat_ids is defined
|
telegram_bot_token is defined and
|
||||||
- telegram_allowed_chat_ids | length > 0
|
(telegram_bot_token | string | length) > 20 and
|
||||||
fail_msg: "Задайте telegram_bot_token и telegram_allowed_chat_ids (см. group_vars/proxmox-bot.example.yml)."
|
telegram_allowed_chat_ids is defined and
|
||||||
|
(telegram_allowed_chat_ids | string | length) > 0
|
||||||
|
)
|
||||||
|
or
|
||||||
|
(tsj_bot_existing_env.stat.exists | default(false))
|
||||||
|
fail_msg: "Задайте telegram_bot_token и telegram_allowed_chat_ids или оставьте на хосте существующий {{ tsj_bot_env_path }}."
|
||||||
|
|
||||||
- name: Проверить наличие исходного файла бота на контроллере
|
- name: Проверить наличие исходного файла бота на контроллере
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
@@ -70,7 +80,12 @@
|
|||||||
mode: "0750"
|
mode: "0750"
|
||||||
notify: Restart tsj bot
|
notify: Restart tsj bot
|
||||||
|
|
||||||
- name: Сгенерировать .env бота
|
- name: Сгенерировать полный .env бота
|
||||||
|
when:
|
||||||
|
- telegram_bot_token is defined
|
||||||
|
- (telegram_bot_token | string | length) > 20
|
||||||
|
- telegram_allowed_chat_ids is defined
|
||||||
|
- (telegram_allowed_chat_ids | string | length) > 0
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: "{{ tsj_bot_env_path }}"
|
dest: "{{ tsj_bot_env_path }}"
|
||||||
owner: "{{ tsj_bot_user }}"
|
owner: "{{ tsj_bot_user }}"
|
||||||
@@ -133,11 +148,44 @@
|
|||||||
AW_RUS_WORKTIME_BASE={{ tsj_bot_aw_rus_worktime_base | default('http://10.10.10.13:5610') }}
|
AW_RUS_WORKTIME_BASE={{ tsj_bot_aw_rus_worktime_base | default('http://10.10.10.13:5610') }}
|
||||||
AW_RUS_WORKTIME_HEAL_CMD={{ tsj_bot_aw_rus_worktime_heal_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl start aw-worktime-ui-bridge.service'") }}
|
AW_RUS_WORKTIME_HEAL_CMD={{ tsj_bot_aw_rus_worktime_heal_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl start aw-worktime-ui-bridge.service'") }}
|
||||||
AW_RUS_DLP_HEAL_CMD={{ tsj_bot_aw_rus_dlp_heal_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'") }}
|
AW_RUS_DLP_HEAL_CMD={{ tsj_bot_aw_rus_dlp_heal_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'") }}
|
||||||
|
AW_RUS_CASE_API_BASE={{ tsj_bot_aw_rus_case_api_base | default('http://10.10.10.13:5602') }}
|
||||||
|
AW_RUS_HAYABUSA_ENABLED={{ tsj_bot_aw_rus_hayabusa_enabled | default('true') }}
|
||||||
|
AW_RUS_HAYABUSA_SSH_CMD={{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13") }}
|
||||||
AW_RUS_HOST={{ tsj_bot_aw_rus_host | default('SHARKON2025') }}
|
AW_RUS_HOST={{ tsj_bot_aw_rus_host | default('SHARKON2025') }}
|
||||||
AW_RUS_PRIMARY_USER={{ tsj_bot_aw_rus_primary_user | default('USER1') }}
|
AW_RUS_PRIMARY_USER={{ tsj_bot_aw_rus_primary_user | default('USER1') }}
|
||||||
AW_RUS_STALE_SEC={{ tsj_bot_aw_rus_stale_sec | default(900) }}
|
AW_RUS_STALE_SEC={{ tsj_bot_aw_rus_stale_sec | default(900) }}
|
||||||
notify: Restart tsj bot
|
notify: Restart tsj bot
|
||||||
|
|
||||||
|
- name: Обновить только AW-Rus/Hayabusa env ключи в существующем .env
|
||||||
|
when:
|
||||||
|
- not (
|
||||||
|
telegram_bot_token is defined and
|
||||||
|
(telegram_bot_token | string | length) > 20 and
|
||||||
|
telegram_allowed_chat_ids is defined and
|
||||||
|
(telegram_allowed_chat_ids | string | length) > 0
|
||||||
|
)
|
||||||
|
- tsj_bot_existing_env.stat.exists | default(false)
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: "{{ tsj_bot_env_path }}"
|
||||||
|
regexp: "^{{ item.key }}="
|
||||||
|
line: "{{ item.key }}={{ item.value }}"
|
||||||
|
create: false
|
||||||
|
owner: "{{ tsj_bot_user }}"
|
||||||
|
group: "{{ tsj_bot_group }}"
|
||||||
|
mode: "0640"
|
||||||
|
loop:
|
||||||
|
- { key: "AW_RUS_API_BASE", value: "{{ tsj_bot_aw_rus_api_base | default('http://10.10.10.13:5600/api/0') }}" }
|
||||||
|
- { key: "AW_RUS_WORKTIME_BASE", value: "{{ tsj_bot_aw_rus_worktime_base | default('http://10.10.10.13:5610') }}" }
|
||||||
|
- { key: "AW_RUS_WORKTIME_HEAL_CMD", value: "{{ tsj_bot_aw_rus_worktime_heal_cmd | default(\"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl start aw-worktime-ui-bridge.service'\") }}" }
|
||||||
|
- { key: "AW_RUS_DLP_HEAL_CMD", value: "{{ tsj_bot_aw_rus_dlp_heal_cmd | default(\"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'\") }}" }
|
||||||
|
- { key: "AW_RUS_CASE_API_BASE", value: "{{ tsj_bot_aw_rus_case_api_base | default('http://10.10.10.13:5602') }}" }
|
||||||
|
- { key: "AW_RUS_HAYABUSA_ENABLED", value: "{{ tsj_bot_aw_rus_hayabusa_enabled | default('true') }}" }
|
||||||
|
- { key: "AW_RUS_HAYABUSA_SSH_CMD", value: "{{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default(\"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13\") }}" }
|
||||||
|
- { key: "AW_RUS_HOST", value: "{{ tsj_bot_aw_rus_host | default('SHARKON2025') }}" }
|
||||||
|
- { key: "AW_RUS_PRIMARY_USER", value: "{{ tsj_bot_aw_rus_primary_user | default('USER1') }}" }
|
||||||
|
- { key: "AW_RUS_STALE_SEC", value: "{{ tsj_bot_aw_rus_stale_sec | default(900) }}" }
|
||||||
|
notify: Restart tsj bot
|
||||||
|
|
||||||
- name: Установить systemd unit бота
|
- name: Установить systemd unit бота
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: "/etc/systemd/system/{{ tsj_bot_service_name }}"
|
dest: "/etc/systemd/system/{{ tsj_bot_service_name }}"
|
||||||
|
|||||||
@@ -12,6 +12,24 @@ ansible_become_password: "{{ lookup('env', 'AW_SUDO_PASSWORD') | default(lookup(
|
|||||||
aw_hayabusa_ioc_refresh_enabled: false
|
aw_hayabusa_ioc_refresh_enabled: false
|
||||||
aw_hayabusa_rules_root: "/mnt/usb_hdd1/Projects/hayabusa/rules"
|
aw_hayabusa_rules_root: "/mnt/usb_hdd1/Projects/hayabusa/rules"
|
||||||
aw_hayabusa_ioc_output_dir: "{{ aw_server_data_dir }}/dlp-ioc"
|
aw_hayabusa_ioc_output_dir: "{{ aw_server_data_dir }}/dlp-ioc"
|
||||||
|
aw_hayabusa_runner_enabled: true
|
||||||
|
aw_hayabusa_version: "v3.9.0"
|
||||||
|
aw_hayabusa_asset_name: "hayabusa-3.9.0-lin-x64-gnu.zip"
|
||||||
|
aw_hayabusa_binary_name: "hayabusa-3.9.0-lin-x64-gnu"
|
||||||
|
aw_hayabusa_download_url: "https://github.com/Yamato-Security/hayabusa/releases/download/v3.9.0/hayabusa-3.9.0-lin-x64-gnu.zip"
|
||||||
|
aw_hayabusa_root: "/opt/hayabusa"
|
||||||
|
aw_hayabusa_release_dir: "{{ aw_hayabusa_root }}/releases/{{ aw_hayabusa_version }}"
|
||||||
|
aw_hayabusa_current_link: "{{ aw_hayabusa_root }}/current"
|
||||||
|
aw_hayabusa_archive_path: "/tmp/{{ aw_hayabusa_asset_name }}"
|
||||||
|
aw_hayabusa_reports_dir: "{{ aw_hayabusa_root }}/reports"
|
||||||
|
aw_hayabusa_state_dir: "{{ aw_hayabusa_root }}/state"
|
||||||
|
aw_hayabusa_inbox_dir: "{{ aw_hayabusa_root }}/inbox"
|
||||||
|
aw_hayabusa_archive_dir: "{{ aw_hayabusa_root }}/archive"
|
||||||
|
aw_hayabusa_incoming_dir: "{{ aw_hayabusa_inbox_dir }}/incoming"
|
||||||
|
aw_hayabusa_staging_dir: "{{ aw_hayabusa_inbox_dir }}/staging"
|
||||||
|
aw_hayabusa_archive_packages_dir: "{{ aw_hayabusa_archive_dir }}/packages"
|
||||||
|
aw_hayabusa_archive_extracted_dir: "{{ aw_hayabusa_archive_dir }}/extracted"
|
||||||
|
aw_hayabusa_logs_dir: "{{ aw_hayabusa_state_dir }}/logs"
|
||||||
aw_dlp_policy_engine_enabled: true
|
aw_dlp_policy_engine_enabled: true
|
||||||
aw_dlp_policy_engine_bind_host: "0.0.0.0"
|
aw_dlp_policy_engine_bind_host: "0.0.0.0"
|
||||||
aw_dlp_policy_engine_port: 5601
|
aw_dlp_policy_engine_port: 5601
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ aw_windows_local_agent_logs_enabled: false
|
|||||||
aw_windows_incident_capture_enabled: true
|
aw_windows_incident_capture_enabled: true
|
||||||
aw_windows_incident_screenshot_enabled: true
|
aw_windows_incident_screenshot_enabled: true
|
||||||
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
||||||
|
aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports"
|
||||||
|
aw_windows_evtx_retention_days: 14
|
||||||
|
aw_windows_evtx_channels:
|
||||||
|
- Security
|
||||||
|
- System
|
||||||
|
- Application
|
||||||
|
- Microsoft-Windows-PowerShell/Operational
|
||||||
|
- Microsoft-Windows-TerminalServices-LocalSessionManager/Operational
|
||||||
|
- Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational
|
||||||
aw_windows_logon_marker_enabled: true
|
aw_windows_logon_marker_enabled: true
|
||||||
aw_windows_skip_hardening: false
|
aw_windows_skip_hardening: false
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ tsj_bot_aw_rus_api_base: "http://10.10.10.13:5600/api/0"
|
|||||||
tsj_bot_aw_rus_worktime_base: "http://10.10.10.13:5610"
|
tsj_bot_aw_rus_worktime_base: "http://10.10.10.13:5610"
|
||||||
tsj_bot_aw_rus_worktime_heal_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl start aw-worktime-ui-bridge.service'"
|
tsj_bot_aw_rus_worktime_heal_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl start aw-worktime-ui-bridge.service'"
|
||||||
tsj_bot_aw_rus_dlp_heal_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'"
|
tsj_bot_aw_rus_dlp_heal_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'"
|
||||||
|
tsj_bot_aw_rus_case_api_base: "http://10.10.10.13:5602"
|
||||||
|
tsj_bot_aw_rus_hayabusa_enabled: "true"
|
||||||
|
tsj_bot_aw_rus_hayabusa_ssh_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13"
|
||||||
tsj_bot_aw_rus_host: "SHARKON2025"
|
tsj_bot_aw_rus_host: "SHARKON2025"
|
||||||
tsj_bot_aw_rus_primary_user: "USER1"
|
tsj_bot_aw_rus_primary_user: "USER1"
|
||||||
tsj_bot_aw_rus_stale_sec: 900
|
tsj_bot_aw_rus_stale_sec: 900
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ aw_windows_local_agent_logs_enabled: false
|
|||||||
aw_windows_incident_capture_enabled: true
|
aw_windows_incident_capture_enabled: true
|
||||||
aw_windows_incident_screenshot_enabled: true
|
aw_windows_incident_screenshot_enabled: true
|
||||||
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
||||||
|
aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports"
|
||||||
|
aw_windows_evtx_retention_days: 14
|
||||||
|
aw_windows_evtx_channels:
|
||||||
|
- Security
|
||||||
|
- System
|
||||||
|
- Application
|
||||||
|
- Microsoft-Windows-PowerShell/Operational
|
||||||
|
- Microsoft-Windows-TerminalServices-LocalSessionManager/Operational
|
||||||
|
- Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational
|
||||||
aw_windows_logon_marker_enabled: true
|
aw_windows_logon_marker_enabled: true
|
||||||
aw_windows_skip_hardening: false
|
aw_windows_skip_hardening: false
|
||||||
|
|
||||||
|
|||||||
@@ -1149,6 +1149,15 @@
|
|||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
try {
|
try {
|
||||||
const cases = await caseApi("/api/0/dlp/cases?host=" + encodeURIComponent(host) + "&limit=100", { method: "GET" });
|
const cases = await caseApi("/api/0/dlp/cases?host=" + encodeURIComponent(host) + "&limit=100", { method: "GET" });
|
||||||
|
function renderCaseDfir(c) {
|
||||||
|
const hayabusa = c && c.forensics && c.forensics.hayabusa;
|
||||||
|
if (!hayabusa) return "";
|
||||||
|
const status = String(hayabusa.status || "");
|
||||||
|
const mode = String(hayabusa.mode || "");
|
||||||
|
const reportDir = String(hayabusa.report_dir || "");
|
||||||
|
const title = reportDir ? ' title="' + escapeHtml(reportDir) + '"' : "";
|
||||||
|
return '<span' + title + '>Hayabusa ' + escapeHtml(status) + (mode ? " · " + escapeHtml(mode) : "") + '</span>';
|
||||||
|
}
|
||||||
const rows = (cases || []).map(function (c) {
|
const rows = (cases || []).map(function (c) {
|
||||||
return (
|
return (
|
||||||
"<tr>" +
|
"<tr>" +
|
||||||
@@ -1158,15 +1167,16 @@
|
|||||||
"<td>" + escapeHtml(String(c.title || "")) + "</td>" +
|
"<td>" + escapeHtml(String(c.title || "")) + "</td>" +
|
||||||
"<td>" + escapeHtml(String(c.assignee || "")) + "</td>" +
|
"<td>" + escapeHtml(String(c.assignee || "")) + "</td>" +
|
||||||
"<td>" + escapeHtml(String(c.incident_id || "")) + "</td>" +
|
"<td>" + escapeHtml(String(c.incident_id || "")) + "</td>" +
|
||||||
|
"<td>" + renderCaseDfir(c) + "</td>" +
|
||||||
"<td>" + escapeHtml(String(c.updated_at || c.created_at || "")) + "</td>" +
|
"<td>" + escapeHtml(String(c.updated_at || c.created_at || "")) + "</td>" +
|
||||||
"</tr>"
|
"</tr>"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
tbody.innerHTML = rows.length ? rows.join("") : '<tr><td colspan="7">Кейсов нет.</td></tr>';
|
tbody.innerHTML = rows.length ? rows.join("") : '<tr><td colspan="8">Кейсов нет.</td></tr>';
|
||||||
const status = center.querySelector("[data-aw-ru-dlp-cases-status]");
|
const status = center.querySelector("[data-aw-ru-dlp-cases-status]");
|
||||||
if (status) status.textContent = "Кейсов: " + (cases || []).length;
|
if (status) status.textContent = "Кейсов: " + (cases || []).length;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
tbody.innerHTML = '<tr><td colspan="7">Ошибка загрузки кейсов: ' + escapeHtml(error.message) + '</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="8">Ошибка загрузки кейсов: ' + escapeHtml(error.message) + '</td></tr>';
|
||||||
const status = center.querySelector("[data-aw-ru-dlp-cases-status]");
|
const status = center.querySelector("[data-aw-ru-dlp-cases-status]");
|
||||||
if (status) status.textContent = "Кейсы недоступны";
|
if (status) status.textContent = "Кейсы недоступны";
|
||||||
}
|
}
|
||||||
@@ -1388,8 +1398,8 @@
|
|||||||
'<div class="aw-ru-dlp-status" data-aw-ru-dlp-cases-status>Кейсов: 0</div>' +
|
'<div class="aw-ru-dlp-status" data-aw-ru-dlp-cases-status>Кейсов: 0</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<table class="aw-ru-dlp-table">' +
|
'<table class="aw-ru-dlp-table">' +
|
||||||
'<thead><tr><th>ID</th><th>Статус</th><th>Severity</th><th>Заголовок</th><th>Исполнитель</th><th>Incident ID</th><th>Обновлено</th></tr></thead>' +
|
'<thead><tr><th>ID</th><th>Статус</th><th>Severity</th><th>Заголовок</th><th>Исполнитель</th><th>Incident ID</th><th>DFIR</th><th>Обновлено</th></tr></thead>' +
|
||||||
'<tbody data-aw-ru-dlp-cases><tr><td colspan="7">Загрузка...</td></tr></tbody>' +
|
'<tbody data-aw-ru-dlp-cases><tr><td colspan="8">Загрузка...</td></tr></tbody>' +
|
||||||
'</table>' +
|
'</table>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="aw-ru-dlp-message" data-aw-ru-dlp-message></div>';
|
'<div class="aw-ru-dlp-message" data-aw-ru-dlp-message></div>';
|
||||||
|
|||||||
@@ -9,6 +9,22 @@ from pydantic import BaseModel, Field
|
|||||||
CaseStatus = Literal["open", "investigating", "resolved", "closed"]
|
CaseStatus = Literal["open", "investigating", "resolved", "closed"]
|
||||||
|
|
||||||
|
|
||||||
|
class CaseHayabusaLink(BaseModel):
|
||||||
|
tool: Literal["hayabusa"] = "hayabusa"
|
||||||
|
host: str = Field(min_length=1, max_length=128)
|
||||||
|
mode: str = Field(min_length=1, max_length=32)
|
||||||
|
status: str = Field(min_length=1, max_length=64)
|
||||||
|
intake_id: str | None = Field(default=None, max_length=256)
|
||||||
|
package_path: str | None = Field(default=None, max_length=1024)
|
||||||
|
sha256: str | None = Field(default=None, max_length=128)
|
||||||
|
report_dir: str | None = Field(default=None, max_length=1024)
|
||||||
|
summary_html: str | None = Field(default=None, max_length=1024)
|
||||||
|
timeline_path: str | None = Field(default=None, max_length=1024)
|
||||||
|
manifest_path: str | None = Field(default=None, max_length=1024)
|
||||||
|
linked_at: str | None = Field(default=None, max_length=64)
|
||||||
|
link_source: str | None = Field(default=None, max_length=64)
|
||||||
|
|
||||||
|
|
||||||
class CaseCreate(BaseModel):
|
class CaseCreate(BaseModel):
|
||||||
incident_id: str = Field(min_length=1, max_length=256)
|
incident_id: str = Field(min_length=1, max_length=256)
|
||||||
host: str | None = Field(default=None, max_length=128)
|
host: str | None = Field(default=None, max_length=128)
|
||||||
@@ -51,6 +67,6 @@ class CaseRecord(BaseModel):
|
|||||||
source_bucket: str | None
|
source_bucket: str | None
|
||||||
source_event_ts: str | None
|
source_event_ts: str | None
|
||||||
evidence: dict | None
|
evidence: dict | None
|
||||||
|
forensics: dict | None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import Any
|
|||||||
from fastapi import FastAPI, HTTPException, Query
|
from fastapi import FastAPI, HTTPException, Query
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from case_schema import CaseCommentCreate, CaseCreate, CaseUpdate
|
from case_schema import CaseCommentCreate, CaseCreate, CaseHayabusaLink, CaseUpdate
|
||||||
from case_storage import CaseStorage
|
from case_storage import CaseStorage
|
||||||
|
|
||||||
DB = Path(os.environ.get("AW_DLP_CASE_DB_PATH", "/opt/activitywatch/dlp-case-management/cases.db"))
|
DB = Path(os.environ.get("AW_DLP_CASE_DB_PATH", "/opt/activitywatch/dlp-case-management/cases.db"))
|
||||||
@@ -76,3 +76,11 @@ def add_comment(case_id: int, payload: CaseCommentCreate) -> dict[str, Any]:
|
|||||||
@APP.get("/api/0/dlp/cases/{case_id}/comments")
|
@APP.get("/api/0/dlp/cases/{case_id}/comments")
|
||||||
def list_comments(case_id: int, limit: int = Query(default=200, ge=1, le=2000)) -> list[dict[str, Any]]:
|
def list_comments(case_id: int, limit: int = Query(default=200, ge=1, le=2000)) -> list[dict[str, Any]]:
|
||||||
return STORE.list_comments(case_id=case_id, limit=limit)
|
return STORE.list_comments(case_id=case_id, limit=limit)
|
||||||
|
|
||||||
|
|
||||||
|
@APP.post("/api/0/dlp/cases/{case_id}/forensics/hayabusa")
|
||||||
|
def link_hayabusa(case_id: int, payload: CaseHayabusaLink) -> dict[str, Any]:
|
||||||
|
try:
|
||||||
|
return STORE.link_hayabusa(case_id=case_id, payload=payload.model_dump(exclude_none=True), actor="api")
|
||||||
|
except KeyError:
|
||||||
|
raise HTTPException(status_code=404, detail="case not found")
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class CaseStorage:
|
|||||||
source_bucket TEXT,
|
source_bucket TEXT,
|
||||||
source_event_ts TEXT,
|
source_event_ts TEXT,
|
||||||
evidence_json TEXT,
|
evidence_json TEXT,
|
||||||
|
forensics_json TEXT,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
updated_at TEXT NOT NULL
|
updated_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
@@ -69,20 +70,35 @@ class CaseStorage:
|
|||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
self._ensure_column(c, "cases", "forensics_json", "TEXT")
|
||||||
c.commit()
|
c.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensure_column(c: sqlite3.Connection, table: str, column: str, definition: str) -> None:
|
||||||
|
columns = {
|
||||||
|
str(row["name"])
|
||||||
|
for row in c.execute(f"PRAGMA table_info({table})").fetchall()
|
||||||
|
}
|
||||||
|
if column not in columns:
|
||||||
|
c.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _now() -> str:
|
def _now() -> str:
|
||||||
return datetime.now(timezone.utc).isoformat()
|
return datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_case_dict(row: sqlite3.Row) -> dict[str, Any]:
|
def _load_json_field(raw: Any) -> dict[str, Any] | None:
|
||||||
evidence = None
|
if not raw:
|
||||||
if row["evidence_json"]:
|
return None
|
||||||
try:
|
try:
|
||||||
evidence = json.loads(row["evidence_json"])
|
return json.loads(raw)
|
||||||
except Exception:
|
except Exception:
|
||||||
evidence = None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _to_case_dict(cls, row: sqlite3.Row) -> dict[str, Any]:
|
||||||
|
evidence = cls._load_json_field(row["evidence_json"])
|
||||||
|
forensics = cls._load_json_field(row["forensics_json"])
|
||||||
return {
|
return {
|
||||||
"id": int(row["id"]),
|
"id": int(row["id"]),
|
||||||
"incident_id": row["incident_id"],
|
"incident_id": row["incident_id"],
|
||||||
@@ -94,6 +110,7 @@ class CaseStorage:
|
|||||||
"source_bucket": row["source_bucket"],
|
"source_bucket": row["source_bucket"],
|
||||||
"source_event_ts": row["source_event_ts"],
|
"source_event_ts": row["source_event_ts"],
|
||||||
"evidence": evidence,
|
"evidence": evidence,
|
||||||
|
"forensics": forensics,
|
||||||
"created_at": row["created_at"],
|
"created_at": row["created_at"],
|
||||||
"updated_at": row["updated_at"],
|
"updated_at": row["updated_at"],
|
||||||
}
|
}
|
||||||
@@ -114,8 +131,8 @@ class CaseStorage:
|
|||||||
"""
|
"""
|
||||||
INSERT INTO cases (
|
INSERT INTO cases (
|
||||||
incident_id, host, title, severity, assignee, status,
|
incident_id, host, title, severity, assignee, status,
|
||||||
source_bucket, source_event_ts, evidence_json, created_at, updated_at
|
source_bucket, source_event_ts, evidence_json, forensics_json, created_at, updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
payload["incident_id"],
|
payload["incident_id"],
|
||||||
@@ -126,6 +143,7 @@ class CaseStorage:
|
|||||||
payload.get("source_bucket"),
|
payload.get("source_bucket"),
|
||||||
payload.get("source_event_ts"),
|
payload.get("source_event_ts"),
|
||||||
json.dumps(normalized_evidence, ensure_ascii=False) if normalized_evidence is not None else None,
|
json.dumps(normalized_evidence, ensure_ascii=False) if normalized_evidence is not None else None,
|
||||||
|
None,
|
||||||
now,
|
now,
|
||||||
now,
|
now,
|
||||||
),
|
),
|
||||||
@@ -195,6 +213,46 @@ class CaseStorage:
|
|||||||
c.commit()
|
c.commit()
|
||||||
return self.get_case(case_id, c)
|
return self.get_case(case_id, c)
|
||||||
|
|
||||||
|
def link_hayabusa(self, case_id: int, payload: dict[str, Any], actor: str | None = None) -> dict[str, Any]:
|
||||||
|
now = self._now()
|
||||||
|
with self.conn() as c:
|
||||||
|
existing = self.get_case(case_id, c)
|
||||||
|
forensics = existing.get("forensics") or {}
|
||||||
|
forensics["hayabusa"] = {
|
||||||
|
"tool": "hayabusa",
|
||||||
|
"host": payload["host"],
|
||||||
|
"mode": payload["mode"],
|
||||||
|
"status": payload["status"],
|
||||||
|
"intake_id": payload.get("intake_id"),
|
||||||
|
"package_path": payload.get("package_path"),
|
||||||
|
"sha256": payload.get("sha256"),
|
||||||
|
"report_dir": payload.get("report_dir"),
|
||||||
|
"summary_html": payload.get("summary_html"),
|
||||||
|
"timeline_path": payload.get("timeline_path"),
|
||||||
|
"manifest_path": payload.get("manifest_path"),
|
||||||
|
"linked_at": payload.get("linked_at") or now,
|
||||||
|
"link_source": payload.get("link_source") or "api",
|
||||||
|
}
|
||||||
|
c.execute(
|
||||||
|
"UPDATE cases SET forensics_json = ?, updated_at = ? WHERE id = ?",
|
||||||
|
(json.dumps(forensics, ensure_ascii=False), now, int(case_id)),
|
||||||
|
)
|
||||||
|
self._insert_audit(
|
||||||
|
c,
|
||||||
|
case_id=case_id,
|
||||||
|
action="link_hayabusa",
|
||||||
|
actor=actor,
|
||||||
|
details={
|
||||||
|
"host": payload["host"],
|
||||||
|
"mode": payload["mode"],
|
||||||
|
"status": payload["status"],
|
||||||
|
"intake_id": payload.get("intake_id"),
|
||||||
|
"report_dir": payload.get("report_dir"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
c.commit()
|
||||||
|
return self.get_case(case_id, c)
|
||||||
|
|
||||||
def add_comment(self, case_id: int, comment: str, author: str | None = None) -> dict[str, Any]:
|
def add_comment(self, case_id: int, comment: str, author: str | None = None) -> dict[str, Any]:
|
||||||
now = self._now()
|
now = self._now()
|
||||||
with self.conn() as c:
|
with self.conn() as c:
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from case_storage import CaseStorage
|
||||||
|
|
||||||
|
|
||||||
|
class CaseStorageHayabusaLinkTest(unittest.TestCase):
|
||||||
|
def test_link_hayabusa_metadata(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
db_path = Path(tmpdir) / "cases.db"
|
||||||
|
storage = CaseStorage(db_path)
|
||||||
|
created = storage.create_case(
|
||||||
|
{
|
||||||
|
"incident_id": "inc-1",
|
||||||
|
"host": "SHARKON2025",
|
||||||
|
"title": "DLP print incident",
|
||||||
|
"severity": "high",
|
||||||
|
},
|
||||||
|
actor="test",
|
||||||
|
)
|
||||||
|
linked = storage.link_hayabusa(
|
||||||
|
case_id=int(created["id"]),
|
||||||
|
payload={
|
||||||
|
"host": "SHARKON2025",
|
||||||
|
"mode": "incident",
|
||||||
|
"status": "ok",
|
||||||
|
"intake_id": "pkg-1",
|
||||||
|
"report_dir": "/opt/hayabusa/reports/SHARKON2025/run-1",
|
||||||
|
"package_path": "/opt/hayabusa/archive/packages/SHARKON2025/pkg-1.zip",
|
||||||
|
"sha256": "abc123",
|
||||||
|
"link_source": "unit-test",
|
||||||
|
},
|
||||||
|
actor="test",
|
||||||
|
)
|
||||||
|
hayabusa = (linked.get("forensics") or {}).get("hayabusa") or {}
|
||||||
|
self.assertEqual(hayabusa.get("tool"), "hayabusa")
|
||||||
|
self.assertEqual(hayabusa.get("host"), "SHARKON2025")
|
||||||
|
self.assertEqual(hayabusa.get("mode"), "incident")
|
||||||
|
self.assertEqual(hayabusa.get("status"), "ok")
|
||||||
|
self.assertEqual(hayabusa.get("intake_id"), "pkg-1")
|
||||||
|
self.assertEqual(hayabusa.get("link_source"), "unit-test")
|
||||||
|
audit = storage.list_audit(int(created["id"]))
|
||||||
|
self.assertTrue(any(row.get("action") == "link_hayabusa" for row in audit))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,522 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HAYA_ROOT="${AW_HAYABUSA_ROOT:-/opt/hayabusa}"
|
||||||
|
HAYA_CURRENT="${HAYA_ROOT}/current"
|
||||||
|
HAYA_BIN="${HAYA_CURRENT}/hayabusa"
|
||||||
|
HAYA_RULES="${HAYA_CURRENT}/rules"
|
||||||
|
HAYA_CONFIG="${HAYA_CURRENT}/config"
|
||||||
|
HAYA_REPORTS_ROOT="${AW_HAYABUSA_REPORTS_ROOT:-${HAYA_ROOT}/reports}"
|
||||||
|
HAYA_STATE_ROOT="${AW_HAYABUSA_STATE_ROOT:-${HAYA_ROOT}/state}"
|
||||||
|
HAYA_INCOMING_DIR="${AW_HAYABUSA_INCOMING_DIR:-${HAYA_ROOT}/inbox/incoming}"
|
||||||
|
HAYA_STAGING_DIR="${AW_HAYABUSA_STAGING_DIR:-${HAYA_ROOT}/inbox/staging}"
|
||||||
|
HAYA_ARCHIVE_PACKAGES_DIR="${AW_HAYABUSA_ARCHIVE_PACKAGES_DIR:-${HAYA_ROOT}/archive/packages}"
|
||||||
|
HAYA_ARCHIVE_EXTRACTED_DIR="${AW_HAYABUSA_ARCHIVE_EXTRACTED_DIR:-${HAYA_ROOT}/archive/extracted}"
|
||||||
|
HAYA_LOGS_DIR="${AW_HAYABUSA_LOGS_DIR:-${HAYA_ROOT}/state/logs}"
|
||||||
|
LAST_REPORT_DIR=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
aw-hayabusa doctor
|
||||||
|
aw-hayabusa inventory
|
||||||
|
aw-hayabusa accept --package <zip> [--host HOST]
|
||||||
|
aw-hayabusa process-inbox [--mode <quick|incident|full>] [--limit N]
|
||||||
|
aw-hayabusa profiles
|
||||||
|
aw-hayabusa version
|
||||||
|
aw-hayabusa <quick|incident|full> --input <file-or-dir> [--host HOST] [--label LABEL] [--output-root DIR] [--threads N]
|
||||||
|
|
||||||
|
Modes:
|
||||||
|
quick Fast CSV triage with HTML summary and logon summary
|
||||||
|
incident Rich JSONL timeline for incident review with HTML summary and logon summary
|
||||||
|
full Broad JSONL timeline with all rule families enabled, HTML summary and logon summary
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "ERROR: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitize() {
|
||||||
|
printf '%s' "$1" | tr ' /:@' '_' | tr -cd 'A-Za-z0-9._-'
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_layout() {
|
||||||
|
[ -x "${HAYA_BIN}" ] || fail "Hayabusa binary not found at ${HAYA_BIN}"
|
||||||
|
[ -d "${HAYA_RULES}" ] || fail "Hayabusa rules directory not found at ${HAYA_RULES}"
|
||||||
|
[ -d "${HAYA_CONFIG}" ] || fail "Hayabusa config directory not found at ${HAYA_CONFIG}"
|
||||||
|
mkdir -p \
|
||||||
|
"${HAYA_REPORTS_ROOT}" \
|
||||||
|
"${HAYA_STATE_ROOT}" \
|
||||||
|
"${HAYA_LOGS_DIR}" \
|
||||||
|
"${HAYA_ROOT}/inbox" \
|
||||||
|
"${HAYA_ROOT}/archive" \
|
||||||
|
"${HAYA_INCOMING_DIR}" \
|
||||||
|
"${HAYA_STAGING_DIR}" \
|
||||||
|
"${HAYA_ARCHIVE_PACKAGES_DIR}" \
|
||||||
|
"${HAYA_ARCHIVE_EXTRACTED_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_logged() {
|
||||||
|
local log_file="$1"
|
||||||
|
shift
|
||||||
|
{
|
||||||
|
printf '[%s] CMD:' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
printf ' %q' "$@"
|
||||||
|
printf '\n'
|
||||||
|
} | tee -a "${log_file}"
|
||||||
|
"$@" 2>&1 | tee -a "${log_file}"
|
||||||
|
return "${PIPESTATUS[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_manifest() {
|
||||||
|
local manifest_path="$1"
|
||||||
|
local mode="$2"
|
||||||
|
local host="$3"
|
||||||
|
local input_path="$4"
|
||||||
|
local report_dir="$5"
|
||||||
|
local status="$6"
|
||||||
|
local output_format="$7"
|
||||||
|
cat >"${manifest_path}" <<EOF
|
||||||
|
{
|
||||||
|
"mode": "${mode}",
|
||||||
|
"host": "${host}",
|
||||||
|
"input": "${input_path}",
|
||||||
|
"report_dir": "${report_dir}",
|
||||||
|
"status": "${status}",
|
||||||
|
"output_format": "${output_format}",
|
||||||
|
"generated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
json_field() {
|
||||||
|
local json_path="$1"
|
||||||
|
local field_name="$2"
|
||||||
|
python3 - "$json_path" "$field_name" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
path, field = sys.argv[1], sys.argv[2]
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding='utf-8') as fh:
|
||||||
|
obj = json.load(fh)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(0)
|
||||||
|
value = obj.get(field)
|
||||||
|
if value is None:
|
||||||
|
sys.exit(0)
|
||||||
|
print(str(value))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_host_from_manifest() {
|
||||||
|
local manifest_path="$1"
|
||||||
|
local host=""
|
||||||
|
host="$(json_field "${manifest_path}" host || true)"
|
||||||
|
if [ -z "${host}" ]; then
|
||||||
|
host="$(json_field "${manifest_path}" hostname || true)"
|
||||||
|
fi
|
||||||
|
printf '%s' "${host}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_package_manifest() {
|
||||||
|
local manifest_path="$1"
|
||||||
|
local package_path="$2"
|
||||||
|
local host="$3"
|
||||||
|
local intake_id="$4"
|
||||||
|
local sha256="$5"
|
||||||
|
local status="$6"
|
||||||
|
local stage_dir="$7"
|
||||||
|
local report_dir="$8"
|
||||||
|
cat >"${manifest_path}" <<EOF
|
||||||
|
{
|
||||||
|
"package_path": "${package_path}",
|
||||||
|
"host": "${host}",
|
||||||
|
"intake_id": "${intake_id}",
|
||||||
|
"sha256": "${sha256}",
|
||||||
|
"status": "${status}",
|
||||||
|
"stage_dir": "${stage_dir}",
|
||||||
|
"report_dir": "${report_dir}",
|
||||||
|
"processed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_state_json() {
|
||||||
|
local state_path="$1"
|
||||||
|
local body="$2"
|
||||||
|
printf '%s\n' "${body}" > "${state_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mode() {
|
||||||
|
local mode="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local input_path=""
|
||||||
|
local host=""
|
||||||
|
local label=""
|
||||||
|
local output_root="${HAYA_REPORTS_ROOT}"
|
||||||
|
local threads=""
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--input|-i)
|
||||||
|
[ "$#" -ge 2 ] || fail "--input requires a value"
|
||||||
|
input_path="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--host)
|
||||||
|
[ "$#" -ge 2 ] || fail "--host requires a value"
|
||||||
|
host="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--label)
|
||||||
|
[ "$#" -ge 2 ] || fail "--label requires a value"
|
||||||
|
label="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--output-root)
|
||||||
|
[ "$#" -ge 2 ] || fail "--output-root requires a value"
|
||||||
|
output_root="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--threads)
|
||||||
|
[ "$#" -ge 2 ] || fail "--threads requires a value"
|
||||||
|
threads="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unknown argument: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "${input_path}" ] || fail "--input is required"
|
||||||
|
[ -e "${input_path}" ] || fail "Input path does not exist: ${input_path}"
|
||||||
|
|
||||||
|
ensure_layout
|
||||||
|
mkdir -p "${output_root}"
|
||||||
|
|
||||||
|
if [ -z "${host}" ]; then
|
||||||
|
host="$(basename "${input_path}")"
|
||||||
|
if [ "${host}" = "." ] || [ "${host}" = "/" ]; then
|
||||||
|
host="unknown"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
host="$(sanitize "${host}")"
|
||||||
|
[ -n "${host}" ] || host="unknown"
|
||||||
|
|
||||||
|
local label_suffix=""
|
||||||
|
if [ -n "${label}" ]; then
|
||||||
|
label_suffix="_$(sanitize "${label}")"
|
||||||
|
fi
|
||||||
|
local run_ts
|
||||||
|
run_ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
local report_dir="${output_root}/${host}/${run_ts}_${mode}${label_suffix}"
|
||||||
|
local log_file="${report_dir}/run.log"
|
||||||
|
local manifest_file="${report_dir}/manifest.json"
|
||||||
|
local html_file="${report_dir}/summary.html"
|
||||||
|
local timeline_file=""
|
||||||
|
local output_format=""
|
||||||
|
local -a input_args=()
|
||||||
|
local -a common_args=("-w" "-q" "-C" "-r" "${HAYA_RULES}" "-c" "${HAYA_CONFIG}" "-O")
|
||||||
|
local -a mode_args=()
|
||||||
|
local -a command=()
|
||||||
|
local -a logon_command=()
|
||||||
|
|
||||||
|
mkdir -p "${report_dir}"
|
||||||
|
|
||||||
|
if [ -d "${input_path}" ]; then
|
||||||
|
input_args=("-d" "${input_path}")
|
||||||
|
else
|
||||||
|
input_args=("-f" "${input_path}")
|
||||||
|
fi
|
||||||
|
if [ -n "${threads}" ]; then
|
||||||
|
common_args+=("-t" "${threads}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${mode}" in
|
||||||
|
quick)
|
||||||
|
timeline_file="${report_dir}/timeline.csv"
|
||||||
|
output_format="csv"
|
||||||
|
mode_args=("-E" "-P" "-m" "medium" "-o" "${timeline_file}" "-H" "${html_file}")
|
||||||
|
command=("${HAYA_BIN}" "csv-timeline" "${input_args[@]}" "${common_args[@]}" "${mode_args[@]}")
|
||||||
|
;;
|
||||||
|
incident)
|
||||||
|
timeline_file="${report_dir}/timeline.jsonl"
|
||||||
|
output_format="jsonl"
|
||||||
|
mode_args=("-L" "-m" "low" "-o" "${timeline_file}" "-H" "${html_file}")
|
||||||
|
command=("${HAYA_BIN}" "json-timeline" "${input_args[@]}" "${common_args[@]}" "${mode_args[@]}")
|
||||||
|
;;
|
||||||
|
full)
|
||||||
|
timeline_file="${report_dir}/timeline.jsonl"
|
||||||
|
output_format="jsonl"
|
||||||
|
mode_args=("-L" "-A" "-D" "-n" "-u" "-m" "informational" "-o" "${timeline_file}" "-H" "${html_file}")
|
||||||
|
command=("${HAYA_BIN}" "json-timeline" "${input_args[@]}" "${common_args[@]}" "${mode_args[@]}")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unsupported mode: ${mode}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
logon_command=("${HAYA_BIN}" "logon-summary" "${input_args[@]}" "-q" "-C" "-c" "${HAYA_CONFIG}" "-O" "-o" "${report_dir}/logon-summary")
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "mode=${mode}"
|
||||||
|
echo "host=${host}"
|
||||||
|
echo "input=${input_path}"
|
||||||
|
echo "report_dir=${report_dir}"
|
||||||
|
echo "output_format=${output_format}"
|
||||||
|
} | tee -a "${log_file}" >/dev/null
|
||||||
|
|
||||||
|
local status="ok"
|
||||||
|
if ! run_logged "${log_file}" "${command[@]}"; then
|
||||||
|
status="failed"
|
||||||
|
fi
|
||||||
|
if ! run_logged "${log_file}" "${logon_command[@]}"; then
|
||||||
|
status="failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_manifest "${manifest_file}" "${mode}" "${host}" "${input_path}" "${report_dir}" "${status}" "${output_format}"
|
||||||
|
ln -sfn "${report_dir}" "${HAYA_STATE_ROOT}/latest-run"
|
||||||
|
ln -sfn "${report_dir}" "${HAYA_STATE_ROOT}/latest-${host}"
|
||||||
|
LAST_REPORT_DIR="${report_dir}"
|
||||||
|
|
||||||
|
echo "Report directory: ${report_dir}"
|
||||||
|
if [ "${status}" != "ok" ]; then
|
||||||
|
echo "ERROR: Hayabusa run failed; see ${log_file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory() {
|
||||||
|
ensure_layout
|
||||||
|
local incoming_count staged_count archived_pkg_count archived_extract_count
|
||||||
|
incoming_count=$(find "${HAYA_INCOMING_DIR}" -maxdepth 1 -type f -name '*.zip' | wc -l)
|
||||||
|
staged_count=$(find "${HAYA_STAGING_DIR}" -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||||
|
archived_pkg_count=$(find "${HAYA_ARCHIVE_PACKAGES_DIR}" -type f -name '*.zip' | wc -l)
|
||||||
|
archived_extract_count=$(find "${HAYA_ARCHIVE_EXTRACTED_DIR}" -mindepth 2 -maxdepth 2 -type d | wc -l)
|
||||||
|
echo "aw-hayabusa inventory"
|
||||||
|
echo "incoming_zip=${incoming_count}"
|
||||||
|
echo "staged_dirs=${staged_count}"
|
||||||
|
echo "archived_packages=${archived_pkg_count}"
|
||||||
|
echo "archived_payloads=${archived_extract_count}"
|
||||||
|
if [ -L "${HAYA_STATE_ROOT}/latest-run" ]; then
|
||||||
|
echo "latest_run=$(readlink -f "${HAYA_STATE_ROOT}/latest-run")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
accept_package() {
|
||||||
|
local package_path=""
|
||||||
|
local host=""
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--package)
|
||||||
|
[ "$#" -ge 2 ] || fail "--package requires a value"
|
||||||
|
package_path="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--host)
|
||||||
|
[ "$#" -ge 2 ] || fail "--host requires a value"
|
||||||
|
host="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unknown argument: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -n "${package_path}" ] || fail "--package is required"
|
||||||
|
[ -f "${package_path}" ] || fail "Package not found: ${package_path}"
|
||||||
|
ensure_layout
|
||||||
|
|
||||||
|
local ts base_name safe_base dest_path sha256
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
base_name="$(basename "${package_path}")"
|
||||||
|
safe_base="$(sanitize "${base_name}")"
|
||||||
|
[ -n "${safe_base}" ] || safe_base="incoming.zip"
|
||||||
|
dest_path="${HAYA_INCOMING_DIR}/${ts}_${safe_base}"
|
||||||
|
cp -f "${package_path}" "${dest_path}"
|
||||||
|
sha256="$(sha256sum "${dest_path}" | awk '{print $1}')"
|
||||||
|
printf '%s %s\n' "${sha256}" "$(basename "${dest_path}")" > "${dest_path}.sha256"
|
||||||
|
if [ -n "${host}" ]; then
|
||||||
|
write_state_json "${dest_path}.host" "${host}"
|
||||||
|
fi
|
||||||
|
echo "Accepted package: ${dest_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_manifest_path() {
|
||||||
|
local stage_dir="$1"
|
||||||
|
find "${stage_dir}" -type f -name 'manifest.json' | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
find_evtx_root() {
|
||||||
|
local stage_dir="$1"
|
||||||
|
if [ -d "${stage_dir}/evtx" ]; then
|
||||||
|
printf '%s' "${stage_dir}/evtx"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
find "${stage_dir}" -type d -name evtx | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
process_one_package() {
|
||||||
|
local package_path="$1"
|
||||||
|
local mode="$2"
|
||||||
|
local forced_host="${3:-}"
|
||||||
|
|
||||||
|
ensure_layout
|
||||||
|
|
||||||
|
local package_name package_base intake_id stage_dir package_sha256
|
||||||
|
package_name="$(basename "${package_path}")"
|
||||||
|
package_base="${package_name%.zip}"
|
||||||
|
intake_id="$(sanitize "${package_base}")"
|
||||||
|
stage_dir="${HAYA_STAGING_DIR}/${intake_id}"
|
||||||
|
mkdir -p "${stage_dir}"
|
||||||
|
|
||||||
|
package_sha256="$(sha256sum "${package_path}" | awk '{print $1}')"
|
||||||
|
unzip -q -o "${package_path}" -d "${stage_dir}"
|
||||||
|
|
||||||
|
local manifest_path host evtx_root archive_pkg_dir archive_pkg_path archive_extract_dir status report_dir
|
||||||
|
manifest_path="$(find_manifest_path "${stage_dir}")"
|
||||||
|
host="${forced_host}"
|
||||||
|
if [ -z "${host}" ] && [ -f "${package_path}.host" ]; then
|
||||||
|
host="$(cat "${package_path}.host" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "${host}" ] && [ -n "${manifest_path}" ]; then
|
||||||
|
host="$(detect_host_from_manifest "${manifest_path}")"
|
||||||
|
fi
|
||||||
|
if [ -z "${host}" ]; then
|
||||||
|
host="${package_base%%-*}"
|
||||||
|
fi
|
||||||
|
host="$(sanitize "${host}")"
|
||||||
|
[ -n "${host}" ] || host="unknown"
|
||||||
|
|
||||||
|
archive_pkg_dir="${HAYA_ARCHIVE_PACKAGES_DIR}/${host}"
|
||||||
|
archive_extract_dir="${HAYA_ARCHIVE_EXTRACTED_DIR}/${host}/${intake_id}"
|
||||||
|
mkdir -p "${archive_pkg_dir}" "${archive_extract_dir}"
|
||||||
|
|
||||||
|
evtx_root="$(find_evtx_root "${stage_dir}")"
|
||||||
|
status="ok"
|
||||||
|
report_dir=""
|
||||||
|
if [ -z "${evtx_root}" ] || ! find "${evtx_root}" -type f \( -iname '*.evtx' -o -iname '*.json' -o -iname '*.jsonl' \) | grep -q .; then
|
||||||
|
status="failed-no-evtx"
|
||||||
|
else
|
||||||
|
if run_mode "${mode}" --input "${evtx_root}" --host "${host}" --label "${package_base}"; then
|
||||||
|
report_dir="${LAST_REPORT_DIR}"
|
||||||
|
status="ok"
|
||||||
|
else
|
||||||
|
report_dir="${LAST_REPORT_DIR}"
|
||||||
|
status="failed-analysis"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "${package_path}" "${archive_pkg_dir}/${intake_id}.zip"
|
||||||
|
[ -f "${package_path}.sha256" ] && mv "${package_path}.sha256" "${archive_pkg_dir}/${intake_id}.zip.sha256"
|
||||||
|
[ -f "${package_path}.host" ] && mv "${package_path}.host" "${archive_pkg_dir}/${intake_id}.host"
|
||||||
|
mv "${stage_dir}" "${archive_extract_dir}/payload"
|
||||||
|
write_package_manifest "${archive_extract_dir}/intake.json" "${archive_pkg_dir}/${intake_id}.zip" "${host}" "${intake_id}" "${package_sha256}" "${status}" "${archive_extract_dir}/payload" "${report_dir}"
|
||||||
|
write_state_json "${HAYA_STATE_ROOT}/latest-intake.json" "$(cat "${archive_extract_dir}/intake.json")"
|
||||||
|
echo "Processed package: ${archive_pkg_dir}/${intake_id}.zip"
|
||||||
|
echo "Archive payload: ${archive_extract_dir}/payload"
|
||||||
|
if [ -n "${report_dir}" ]; then
|
||||||
|
echo "Report directory: ${report_dir}"
|
||||||
|
fi
|
||||||
|
[ "${status}" = "ok" ] || fail "Package workflow ended with status=${status}; archived for inspection"
|
||||||
|
}
|
||||||
|
|
||||||
|
process_inbox() {
|
||||||
|
local mode="incident"
|
||||||
|
local limit="0"
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--mode)
|
||||||
|
[ "$#" -ge 2 ] || fail "--mode requires a value"
|
||||||
|
mode="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--limit)
|
||||||
|
[ "$#" -ge 2 ] || fail "--limit requires a value"
|
||||||
|
limit="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unknown argument: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
case "${mode}" in
|
||||||
|
quick|incident|full) ;;
|
||||||
|
*) fail "Unsupported mode for process-inbox: ${mode}" ;;
|
||||||
|
esac
|
||||||
|
ensure_layout
|
||||||
|
|
||||||
|
local count=0 pkg
|
||||||
|
while IFS= read -r pkg; do
|
||||||
|
process_one_package "${pkg}" "${mode}"
|
||||||
|
count=$((count + 1))
|
||||||
|
if [ "${limit}" -gt 0 ] && [ "${count}" -ge "${limit}" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < <(find "${HAYA_INCOMING_DIR}" -maxdepth 1 -type f -name '*.zip' | sort)
|
||||||
|
[ "${count}" -gt 0 ] || echo "No packages in ${HAYA_INCOMING_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local subcommand="${1:-}"
|
||||||
|
case "${subcommand}" in
|
||||||
|
doctor)
|
||||||
|
ensure_layout
|
||||||
|
echo "aw-hayabusa doctor: OK"
|
||||||
|
echo "root=${HAYA_ROOT}"
|
||||||
|
echo "current=${HAYA_CURRENT}"
|
||||||
|
echo "binary=${HAYA_BIN}"
|
||||||
|
echo "rules=${HAYA_RULES}"
|
||||||
|
echo "config=${HAYA_CONFIG}"
|
||||||
|
echo "reports=${HAYA_REPORTS_ROOT}"
|
||||||
|
echo "state=${HAYA_STATE_ROOT}"
|
||||||
|
echo "incoming=${HAYA_INCOMING_DIR}"
|
||||||
|
echo "staging=${HAYA_STAGING_DIR}"
|
||||||
|
echo "archive_packages=${HAYA_ARCHIVE_PACKAGES_DIR}"
|
||||||
|
echo "archive_extracted=${HAYA_ARCHIVE_EXTRACTED_DIR}"
|
||||||
|
echo "logs=${HAYA_LOGS_DIR}"
|
||||||
|
;;
|
||||||
|
inventory)
|
||||||
|
inventory
|
||||||
|
;;
|
||||||
|
accept)
|
||||||
|
shift
|
||||||
|
accept_package "$@"
|
||||||
|
;;
|
||||||
|
process-inbox)
|
||||||
|
shift
|
||||||
|
process_inbox "$@"
|
||||||
|
;;
|
||||||
|
profiles)
|
||||||
|
ensure_layout
|
||||||
|
cd "${HAYA_CURRENT}"
|
||||||
|
exec "${HAYA_BIN}" list-profiles
|
||||||
|
;;
|
||||||
|
version)
|
||||||
|
ensure_layout
|
||||||
|
cd "${HAYA_CURRENT}"
|
||||||
|
exec "${HAYA_BIN}" help
|
||||||
|
;;
|
||||||
|
quick|incident|full)
|
||||||
|
shift
|
||||||
|
cd "${HAYA_CURRENT}"
|
||||||
|
run_mode "${subcommand}" "$@"
|
||||||
|
;;
|
||||||
|
""|-h|--help|help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unknown subcommand: ${subcommand}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Hayabusa Artifact Workflow 2026-05-14
|
||||||
|
|
||||||
|
This document records the server-side EVTX intake and archive workflow on `10.10.10.13`.
|
||||||
|
|
||||||
|
## Directories
|
||||||
|
|
||||||
|
- incoming packages:
|
||||||
|
- `/opt/hayabusa/inbox/incoming`
|
||||||
|
- transient staging:
|
||||||
|
- `/opt/hayabusa/inbox/staging`
|
||||||
|
- generated reports:
|
||||||
|
- `/opt/hayabusa/reports`
|
||||||
|
- archived raw packages:
|
||||||
|
- `/opt/hayabusa/archive/packages/<HOST>/`
|
||||||
|
- archived extracted payloads:
|
||||||
|
- `/opt/hayabusa/archive/extracted/<HOST>/<INTAKE_ID>/payload/`
|
||||||
|
- state:
|
||||||
|
- `/opt/hayabusa/state/latest-intake.json`
|
||||||
|
- `/opt/hayabusa/state/latest-run`
|
||||||
|
- `/opt/hayabusa/state/latest-<HOST>`
|
||||||
|
- `/opt/hayabusa/state/logs`
|
||||||
|
|
||||||
|
## Operator flow
|
||||||
|
|
||||||
|
1. Accept a package into server inbox:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aw-hayabusa accept --package /path/to/HOST-YYYYMMDD-HHMMSS.zip --host HOST
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Inspect queue:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aw-hayabusa inventory
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Process queued packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aw-hayabusa process-inbox --mode incident
|
||||||
|
```
|
||||||
|
|
||||||
|
## Processing behavior
|
||||||
|
|
||||||
|
- the package is extracted into staging;
|
||||||
|
- host is resolved from explicit `--host`, sidecar `.host`, embedded `manifest.json`, or package name fallback;
|
||||||
|
- if EVTX payload exists, Hayabusa analysis is launched through the existing runner modes;
|
||||||
|
- regardless of success, the package and extracted payload are moved into archive;
|
||||||
|
- `intake.json` records:
|
||||||
|
- package path
|
||||||
|
- host
|
||||||
|
- intake id
|
||||||
|
- sha256
|
||||||
|
- status
|
||||||
|
- extracted payload path
|
||||||
|
- report directory
|
||||||
|
- processed timestamp
|
||||||
|
|
||||||
|
## Failure semantics
|
||||||
|
|
||||||
|
- malformed or empty packages are not lost;
|
||||||
|
- the workflow archives them with `status=failed-*`;
|
||||||
|
- the operator can inspect archived payloads without touching AW runtime storage.
|
||||||
|
|
||||||
|
## Validation evidence
|
||||||
|
|
||||||
|
- `aw-hayabusa inventory` shows queue and archive counts
|
||||||
|
- a synthetic package was accepted, archived, and recorded with:
|
||||||
|
- `status=failed-no-evtx`
|
||||||
|
- synthetic artifacts were removed after validation so production storage stayed clean
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
- this phase does not yet move packages from Windows automatically
|
||||||
|
- this phase does not yet attach reports to AW-rus incidents or cases
|
||||||
|
- successful report generation from real EVTX remains a later validation phase
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Hayabusa AW-rus Integration 2026-05-14
|
||||||
|
|
||||||
|
This document defines the bounded integration between Hayabusa DFIR and the normal AW-rus operator path.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Hayabusa is used as DFIR enrichment after incidents, not as a new real-time detector.
|
||||||
|
|
||||||
|
## When to use Hayabusa follow-up
|
||||||
|
|
||||||
|
Recommended triggers:
|
||||||
|
|
||||||
|
- high-severity DLP incidents that justify host-side forensic review;
|
||||||
|
- repeated incidents on the same host or user;
|
||||||
|
- suspicious print, USB, email, or document-export activity that needs Windows event corroboration;
|
||||||
|
- operator-driven escalation where case review needs EVTX-based timeline evidence.
|
||||||
|
|
||||||
|
Not recommended:
|
||||||
|
|
||||||
|
- routine low-signal incidents;
|
||||||
|
- replacing normal AW-rus health/runtime checks;
|
||||||
|
- pushing raw Sigma detections into AW buckets.
|
||||||
|
|
||||||
|
## Operator path
|
||||||
|
|
||||||
|
1. Export EVTX package on Windows with `export-evtx-for-hayabusa.ps1`.
|
||||||
|
2. Transfer the resulting zip package to `10.10.10.13`.
|
||||||
|
3. Run one of:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aw-hayabusa accept --package /path/to/HOST-YYYYMMDD-HHMMSS.zip --host HOST
|
||||||
|
aw-hayabusa process-inbox --mode incident
|
||||||
|
```
|
||||||
|
|
||||||
|
or from Telegram bot:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/aw_dfir /path/to/HOST-YYYYMMDD-HHMMSS.zip HOST [CASE_ID] [MODE]
|
||||||
|
```
|
||||||
|
|
||||||
|
Default mode is `incident`.
|
||||||
|
|
||||||
|
## What gets linked to a case
|
||||||
|
|
||||||
|
Case management stores only bounded metadata:
|
||||||
|
|
||||||
|
- `tool=hayabusa`
|
||||||
|
- `host`
|
||||||
|
- `mode`
|
||||||
|
- `status`
|
||||||
|
- `intake_id`
|
||||||
|
- `package_path`
|
||||||
|
- `sha256`
|
||||||
|
- `report_dir`
|
||||||
|
- `summary_html`
|
||||||
|
- `timeline_path`
|
||||||
|
- `manifest_path`
|
||||||
|
- `linked_at`
|
||||||
|
- `link_source`
|
||||||
|
|
||||||
|
The raw Sigma output, full timelines, and extracted payloads stay under `/opt/hayabusa`, not inside AW buckets or case comments.
|
||||||
|
|
||||||
|
## UI behavior
|
||||||
|
|
||||||
|
Case Management shows a short `DFIR` field:
|
||||||
|
|
||||||
|
- `Hayabusa ok · incident`
|
||||||
|
- `Hayabusa failed-* · incident`
|
||||||
|
|
||||||
|
This is intentionally short; report paths remain operator-facing metadata, not primary UI content.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
- No raw forensic output is copied into normal AW runtime buckets.
|
||||||
|
- No automatic case creation from Hayabusa findings.
|
||||||
|
- Hayabusa remains an enrichment layer around incidents and investigations.
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# Hayabusa Operator and IB Guide 2026-05-14
|
||||||
|
|
||||||
|
This document explains the role of Hayabusa inside `AW-rus` for operators and IB.
|
||||||
|
|
||||||
|
## What Hayabusa adds
|
||||||
|
|
||||||
|
Hayabusa adds a bounded DFIR layer for Windows Event Log analysis:
|
||||||
|
|
||||||
|
- EVTX-based timeline review
|
||||||
|
- Sigma-based detection enrichment
|
||||||
|
- logon and activity context around an already interesting host or incident
|
||||||
|
- forensic artifacts that can be attached to case review
|
||||||
|
|
||||||
|
It is useful when `AW-rus` or DLP already surfaced something worth investigating further.
|
||||||
|
|
||||||
|
## What Hayabusa does not replace
|
||||||
|
|
||||||
|
Hayabusa is not:
|
||||||
|
|
||||||
|
- a replacement for normal `AW-rus` runtime monitoring
|
||||||
|
- a replacement for DLP policy enforcement
|
||||||
|
- a real-time SIEM
|
||||||
|
- a reason to copy raw Sigma output into AW buckets or case comments
|
||||||
|
|
||||||
|
The normal operational path remains:
|
||||||
|
|
||||||
|
- `AW-rus` for activity/runtime visibility
|
||||||
|
- DLP collectors and policy engine for signal generation
|
||||||
|
- case management for operator workflow
|
||||||
|
- Hayabusa for bounded forensic enrichment
|
||||||
|
|
||||||
|
## When operators should run it
|
||||||
|
|
||||||
|
Recommended cases:
|
||||||
|
|
||||||
|
- high-severity DLP incidents
|
||||||
|
- repeated suspicious incidents on one host or user
|
||||||
|
- print, USB, file export, or email activity that needs Windows event corroboration
|
||||||
|
- investigation requests from IB after an incident is already known
|
||||||
|
|
||||||
|
Do not run it for every minor signal. It is meant for escalation and investigation, not daily noise.
|
||||||
|
|
||||||
|
## Operator workflow
|
||||||
|
|
||||||
|
1. Export EVTX package on Windows:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Transfer the resulting zip package to `10.10.10.13`.
|
||||||
|
|
||||||
|
3. Run server-side processing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aw-hayabusa accept --package /path/to/HOST-YYYYMMDD-HHMMSS.zip --host HOST
|
||||||
|
aw-hayabusa process-inbox --mode incident
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the Telegram operator path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/aw_dfir /path/to/HOST-YYYYMMDD-HHMMSS.zip HOST [CASE_ID] [MODE]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. If a case already exists, link only bounded metadata to the case.
|
||||||
|
|
||||||
|
## Where artifacts live
|
||||||
|
|
||||||
|
Windows export staging:
|
||||||
|
|
||||||
|
- `C:\ProgramData\AWatch-rus\forensics\evtx-exports`
|
||||||
|
|
||||||
|
Server-side intake and reports:
|
||||||
|
|
||||||
|
- incoming packages:
|
||||||
|
- `/opt/hayabusa/inbox/incoming`
|
||||||
|
- transient staging:
|
||||||
|
- `/opt/hayabusa/inbox/staging`
|
||||||
|
- archived raw packages:
|
||||||
|
- `/opt/hayabusa/archive/packages/<HOST>/`
|
||||||
|
- archived extracted payloads:
|
||||||
|
- `/opt/hayabusa/archive/extracted/<HOST>/<INTAKE_ID>/payload/`
|
||||||
|
- reports:
|
||||||
|
- `/opt/hayabusa/reports/<HOST>/<UTC_TIMESTAMP>_<MODE>[_LABEL]/`
|
||||||
|
- run state and logs:
|
||||||
|
- `/opt/hayabusa/state`
|
||||||
|
|
||||||
|
## What is stored in AW-rus
|
||||||
|
|
||||||
|
Only bounded metadata is attached to a case:
|
||||||
|
|
||||||
|
- tool
|
||||||
|
- host
|
||||||
|
- mode
|
||||||
|
- status
|
||||||
|
- intake id
|
||||||
|
- package path
|
||||||
|
- sha256
|
||||||
|
- report directory
|
||||||
|
- summary path
|
||||||
|
- timeline path
|
||||||
|
- manifest path
|
||||||
|
- linked timestamp
|
||||||
|
- link source
|
||||||
|
|
||||||
|
Raw forensic output stays under `/opt/hayabusa`.
|
||||||
|
|
||||||
|
## Retention and storage notes
|
||||||
|
|
||||||
|
Windows-side export retention:
|
||||||
|
|
||||||
|
- controlled by `aw_windows_evtx_retention_days`
|
||||||
|
- default: `14` days
|
||||||
|
|
||||||
|
Windows-side export channels:
|
||||||
|
|
||||||
|
- controlled by `aw_windows_evtx_channels`
|
||||||
|
- default set:
|
||||||
|
- `Security`
|
||||||
|
- `System`
|
||||||
|
- `Application`
|
||||||
|
- `Microsoft-Windows-PowerShell/Operational`
|
||||||
|
- `Microsoft-Windows-TerminalServices-LocalSessionManager/Operational`
|
||||||
|
- `Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational`
|
||||||
|
|
||||||
|
Server-side storage:
|
||||||
|
|
||||||
|
- kept outside standard AW buckets
|
||||||
|
- kept outside normal DLP screenshot artifacts
|
||||||
|
- intended for forensic review, not for routine dashboarding
|
||||||
|
|
||||||
|
## IB view
|
||||||
|
|
||||||
|
From an IB perspective, Hayabusa in this project is:
|
||||||
|
|
||||||
|
- a post-incident enrichment layer
|
||||||
|
- useful for Windows event corroboration and timeline reconstruction
|
||||||
|
- intentionally separated from the main activity-monitoring data plane
|
||||||
|
|
||||||
|
This design keeps the main operator UI readable while preserving forensic detail when needed.
|
||||||
|
|
||||||
|
## Limits and false expectations to avoid
|
||||||
|
|
||||||
|
- Sigma detections depend on the quality and completeness of Windows logging.
|
||||||
|
- Missing or weak audit policy reduces value immediately.
|
||||||
|
- No EVTX means no meaningful Hayabusa result.
|
||||||
|
- A successful Hayabusa run does not prove malicious activity by itself.
|
||||||
|
- A clean Hayabusa run does not prove the absence of suspicious behavior.
|
||||||
|
- This contour is deliberately not an always-on detector and not a SIEM replacement.
|
||||||
|
|
||||||
|
## Canonical companion docs
|
||||||
|
|
||||||
|
- source and packaging:
|
||||||
|
- `docs/hayabusa-source-packaging-2026-05-14.md`
|
||||||
|
- server runner:
|
||||||
|
- `docs/hayabusa-server-runner-2026-05-14.md`
|
||||||
|
- artifact workflow:
|
||||||
|
- `docs/hayabusa-artifact-workflow-2026-05-14.md`
|
||||||
|
- AW-rus integration:
|
||||||
|
- `docs/hayabusa-aw-rus-integration-2026-05-14.md`
|
||||||
|
- Windows EVTX export:
|
||||||
|
- `docs/windows-hayabusa-evtx-export.md`
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Hayabusa Server-Side Runner 2026-05-14
|
||||||
|
|
||||||
|
This document records the production runner model for Hayabusa on `10.10.10.13`.
|
||||||
|
|
||||||
|
## Install layout
|
||||||
|
|
||||||
|
- root: `/opt/hayabusa`
|
||||||
|
- pinned release: `/opt/hayabusa/releases/v3.9.0`
|
||||||
|
- active symlink: `/opt/hayabusa/current`
|
||||||
|
- operator entrypoint: `/usr/local/bin/aw-hayabusa`
|
||||||
|
|
||||||
|
## Runtime directories
|
||||||
|
|
||||||
|
- inbox: `/opt/hayabusa/inbox`
|
||||||
|
- archive: `/opt/hayabusa/archive`
|
||||||
|
- reports: `/opt/hayabusa/reports`
|
||||||
|
- state: `/opt/hayabusa/state`
|
||||||
|
|
||||||
|
## Operator entrypoint
|
||||||
|
|
||||||
|
Supported helper subcommands:
|
||||||
|
|
||||||
|
- `aw-hayabusa doctor`
|
||||||
|
- `aw-hayabusa inventory`
|
||||||
|
- `aw-hayabusa accept --package <zip> [--host HOST]`
|
||||||
|
- `aw-hayabusa process-inbox [--mode incident] [--limit N]`
|
||||||
|
- `aw-hayabusa profiles`
|
||||||
|
- `aw-hayabusa version`
|
||||||
|
|
||||||
|
Supported analysis modes:
|
||||||
|
|
||||||
|
- `aw-hayabusa quick --input <file-or-dir> [--host HOST]`
|
||||||
|
- `aw-hayabusa incident --input <file-or-dir> [--host HOST]`
|
||||||
|
- `aw-hayabusa full --input <file-or-dir> [--host HOST]`
|
||||||
|
|
||||||
|
## Mode intent
|
||||||
|
|
||||||
|
- `quick`
|
||||||
|
- fast CSV timeline
|
||||||
|
- HTML summary
|
||||||
|
- logon summary
|
||||||
|
- intended for first-pass triage
|
||||||
|
|
||||||
|
- `incident`
|
||||||
|
- JSONL timeline
|
||||||
|
- HTML summary
|
||||||
|
- logon summary
|
||||||
|
- intended for normal incident review
|
||||||
|
|
||||||
|
- `full`
|
||||||
|
- JSONL timeline
|
||||||
|
- deprecated/noisy/unsupported rules enabled
|
||||||
|
- HTML summary
|
||||||
|
- logon summary
|
||||||
|
- intended for deeper DFIR review
|
||||||
|
|
||||||
|
## Output naming
|
||||||
|
|
||||||
|
Reports are stored under:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/reports/<HOST>/<UTC_TIMESTAMP>_<MODE>[_LABEL]/`
|
||||||
|
|
||||||
|
Typical contents:
|
||||||
|
|
||||||
|
- `timeline.csv` or `timeline.jsonl`
|
||||||
|
- `summary.html`
|
||||||
|
- `logon-summary-*.csv`
|
||||||
|
- `run.log`
|
||||||
|
- `manifest.json`
|
||||||
|
|
||||||
|
Latest-run symlinks:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/state/latest-run`
|
||||||
|
- `/opt/hayabusa/state/latest-<HOST>`
|
||||||
|
|
||||||
|
## Intake and archive workflow
|
||||||
|
|
||||||
|
Incoming packages:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/inbox/incoming/*.zip`
|
||||||
|
|
||||||
|
Transient staging:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/inbox/staging/<INTAKE_ID>/`
|
||||||
|
|
||||||
|
Archived raw packages:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/archive/packages/<HOST>/<INTAKE_ID>.zip`
|
||||||
|
|
||||||
|
Archived extracted payloads:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/archive/extracted/<HOST>/<INTAKE_ID>/payload/`
|
||||||
|
- intake metadata:
|
||||||
|
- `/opt/hayabusa/archive/extracted/<HOST>/<INTAKE_ID>/intake.json`
|
||||||
|
|
||||||
|
State/log helpers:
|
||||||
|
|
||||||
|
- `/opt/hayabusa/state/latest-intake.json`
|
||||||
|
- `/opt/hayabusa/state/logs/`
|
||||||
|
|
||||||
|
## Minimal operator flow
|
||||||
|
|
||||||
|
1. Drop or copy an export package:
|
||||||
|
- `aw-hayabusa accept --package /path/to/HOST-YYYYMMDD-HHMMSS.zip`
|
||||||
|
2. Check queue:
|
||||||
|
- `aw-hayabusa inventory`
|
||||||
|
3. Process packages:
|
||||||
|
- `aw-hayabusa process-inbox --mode incident`
|
||||||
|
|
||||||
|
## Validation baseline
|
||||||
|
|
||||||
|
Minimum server-side validation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aw-hayabusa doctor
|
||||||
|
aw-hayabusa profiles
|
||||||
|
aw-hayabusa inventory
|
||||||
|
```
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
- Hayabusa is not deployed as a daemon.
|
||||||
|
- No AW bucket ingestion happens in this phase.
|
||||||
|
- EVTX intake orchestration remains a later phase.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Hayabusa Source and Packaging Decision 2026-05-14
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- upstream source of truth: `Yamato-Security/hayabusa`
|
||||||
|
- fork policy: do not use a fork unless a concrete required patch exists and is documented
|
||||||
|
- runtime role in this project: `DFIR enrichment`
|
||||||
|
|
||||||
|
## Pinned release
|
||||||
|
|
||||||
|
- release tag: `v3.9.0`
|
||||||
|
- server target platform: `x86_64`, `debian 13`
|
||||||
|
- selected asset:
|
||||||
|
- `hayabusa-3.9.0-lin-x64-gnu.zip`
|
||||||
|
- selected asset URL:
|
||||||
|
- `https://github.com/Yamato-Security/hayabusa/releases/download/v3.9.0/hayabusa-3.9.0-lin-x64-gnu.zip`
|
||||||
|
|
||||||
|
## Packaging model
|
||||||
|
|
||||||
|
- analysis host: `10.10.10.13`
|
||||||
|
- install root: `/opt/hayabusa`
|
||||||
|
- versioned release root: `/opt/hayabusa/releases/v3.9.0`
|
||||||
|
- active symlink target:
|
||||||
|
- `/opt/hayabusa/current`
|
||||||
|
- suggested executable path:
|
||||||
|
- `/opt/hayabusa/current/hayabusa`
|
||||||
|
- suggested wrapper path:
|
||||||
|
- `/usr/local/bin/aw-hayabusa`
|
||||||
|
|
||||||
|
## Artifact boundaries
|
||||||
|
|
||||||
|
- raw incoming EVTX:
|
||||||
|
- `/opt/hayabusa/inbox`
|
||||||
|
- processed EVTX archive:
|
||||||
|
- `/opt/hayabusa/archive`
|
||||||
|
- generated reports:
|
||||||
|
- `/opt/hayabusa/reports`
|
||||||
|
- run logs / metadata:
|
||||||
|
- `/opt/hayabusa/state`
|
||||||
|
|
||||||
|
These paths are intentionally outside normal ActivityWatch buckets and outside ordinary DLP artifact roots.
|
||||||
|
|
||||||
|
## Integrity note
|
||||||
|
|
||||||
|
The official release currently does not publish a separate checksum asset in the GitHub release asset list.
|
||||||
|
|
||||||
|
Therefore the deployment model should:
|
||||||
|
|
||||||
|
1. download the pinned asset URL;
|
||||||
|
2. calculate `sha256` locally during automation;
|
||||||
|
3. store the computed value in deployment logs or a local manifest;
|
||||||
|
4. fail deployment if the downloaded asset name or pinned tag does not match expectations.
|
||||||
|
|
||||||
|
## Why this model
|
||||||
|
|
||||||
|
- no dependency on an unreviewed fork;
|
||||||
|
- reproducible server-side installation;
|
||||||
|
- no attempt to run Hayabusa as a real-time daemon;
|
||||||
|
- clean separation between `AW-rus` runtime data and forensic artifacts.
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
- [ИБ-профиль DLP](../dlp-security-functional-spec-ru.md) - подробное описание реализованного DLP/monitoring-контура для службы ИБ
|
- [ИБ-профиль DLP](../dlp-security-functional-spec-ru.md) - подробное описание реализованного DLP/monitoring-контура для службы ИБ
|
||||||
- [Runtime status: DLP chain](../dlp-runtime-chain-status-2026-05-13.md) - фактический live-статус policy/cases/integrations/compliance
|
- [Runtime status: DLP chain](../dlp-runtime-chain-status-2026-05-13.md) - фактический live-статус policy/cases/integrations/compliance
|
||||||
- [Runtime status: Content analysis](../dlp-content-analysis-runtime-status-2026-05-13.md) - фактический live-статус dictionary/regex/OCR/IOC
|
- [Runtime status: Content analysis](../dlp-content-analysis-runtime-status-2026-05-13.md) - фактический live-статус dictionary/regex/OCR/IOC
|
||||||
|
- [Hayabusa AW-rus integration](../hayabusa-aw-rus-integration-2026-05-14.md) - bounded DFIR enrichment path для incidents/cases/operator flow
|
||||||
|
- [Hayabusa operator and IB guide](../hayabusa-operator-ib-guide-2026-05-14.md) - когда запускать forensic path, где лежат артефакты и какие у него границы
|
||||||
|
|
||||||
### Компоненты
|
### Компоненты
|
||||||
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - мониторинг clipboard, печати, USB
|
- [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - мониторинг clipboard, печати, USB
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Windows EVTX Export for Hayabusa
|
||||||
|
|
||||||
|
This document defines the Windows-side export path for Hayabusa DFIR enrichment.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Windows hosts do not analyze EVTX locally for this contour.
|
||||||
|
|
||||||
|
They export selected event logs into a bounded forensic staging area, and the server-side Hayabusa workflow on `10.10.10.13` analyzes those artifacts later.
|
||||||
|
|
||||||
|
## Export script
|
||||||
|
|
||||||
|
- script: `windows/export-evtx-for-hayabusa.ps1`
|
||||||
|
- deployed path on Windows host:
|
||||||
|
- `<StateRoot>\export-evtx-for-hayabusa.ps1`
|
||||||
|
|
||||||
|
Default config path:
|
||||||
|
|
||||||
|
- `C:\ProgramData\AWatch-rus\deployment-config.json`
|
||||||
|
|
||||||
|
## Default export root
|
||||||
|
|
||||||
|
- `<StateRoot>\forensics\evtx-exports`
|
||||||
|
- Ansible override variable: `aw_windows_forensics_root`
|
||||||
|
- retention override variable: `aw_windows_evtx_retention_days`
|
||||||
|
- channel override variable: `aw_windows_evtx_channels`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
- `C:\ProgramData\AWatch-rus\forensics\evtx-exports`
|
||||||
|
|
||||||
|
Each run creates:
|
||||||
|
|
||||||
|
- `<forensics-root>\<HOST>-<YYYYMMDD-HHMMSS>\evtx\*.evtx`
|
||||||
|
- `<forensics-root>\<HOST>-<YYYYMMDD-HHMMSS>\manifest.json`
|
||||||
|
- optional zip:
|
||||||
|
- `<forensics-root>\<HOST>-<YYYYMMDD-HHMMSS>.zip`
|
||||||
|
|
||||||
|
## Default channel set
|
||||||
|
|
||||||
|
- `Security`
|
||||||
|
- `System`
|
||||||
|
- `Application`
|
||||||
|
- `Microsoft-Windows-PowerShell/Operational`
|
||||||
|
- `Microsoft-Windows-TerminalServices-LocalSessionManager/Operational`
|
||||||
|
- `Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `Sysmon` is intentionally not assumed by default.
|
||||||
|
- If `Sysmon` exists in the environment, it should be added later as an explicit extension.
|
||||||
|
- the channel list is now carried through deployment config and validation, not left as an implicit script default.
|
||||||
|
|
||||||
|
## Retention
|
||||||
|
|
||||||
|
- default retention: `14` days
|
||||||
|
- cleanup is local to the forensic export root
|
||||||
|
- old export directories and zip packages are removed after the retention cutoff
|
||||||
|
- retention is now exposed as `aw_windows_evtx_retention_days` in Ansible vars
|
||||||
|
|
||||||
|
## Example run
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Example with custom window:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1 -DaysBack 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
- output stays outside standard AW buckets
|
||||||
|
- output stays outside normal DLP screenshot artifacts
|
||||||
|
- this phase only defines and validates Windows export
|
||||||
|
- transfer to `10.10.10.13` and Hayabusa execution belong to later phases
|
||||||
+231
-3
@@ -319,6 +319,7 @@ class TSJGuardianBot:
|
|||||||
BTN_PFSENSE_CONFIRM = "Подтвердить pfSense: шаг 1"
|
BTN_PFSENSE_CONFIRM = "Подтвердить pfSense: шаг 1"
|
||||||
BTN_PFSENSE_CANCEL = "Отменить pfSense изменение"
|
BTN_PFSENSE_CANCEL = "Отменить pfSense изменение"
|
||||||
BTN_AW_DLP_CHECK = "Проверка AW-Rus + DLP"
|
BTN_AW_DLP_CHECK = "Проверка AW-Rus + DLP"
|
||||||
|
BTN_AW_DFIR = "Hayabusa DFIR"
|
||||||
BTN_AI_CHAT_ALIASES = ("AI чат", "Чат с поддержкой", "Техподдержка", "Тех поддержка")
|
BTN_AI_CHAT_ALIASES = ("AI чат", "Чат с поддержкой", "Техподдержка", "Тех поддержка")
|
||||||
BTN_OVPN_CERTS_ALIASES = ("OpenVPN certs", "OpenVPN cert", "OpenVPN серты", "OpenVPN сертификат")
|
BTN_OVPN_CERTS_ALIASES = ("OpenVPN certs", "OpenVPN cert", "OpenVPN серты", "OpenVPN сертификат")
|
||||||
PFSENSE_ENV_PATH = "/home/codex/infra-admin/vendor/pfsense-mcp-server/.env.readonly"
|
PFSENSE_ENV_PATH = "/home/codex/infra-admin/vendor/pfsense-mcp-server/.env.readonly"
|
||||||
@@ -361,6 +362,12 @@ class TSJGuardianBot:
|
|||||||
"AW_RUS_DLP_HEAL_CMD",
|
"AW_RUS_DLP_HEAL_CMD",
|
||||||
"",
|
"",
|
||||||
).strip()
|
).strip()
|
||||||
|
self.aw_rus_case_api_base = os.getenv("AW_RUS_CASE_API_BASE", "http://10.10.10.13:5602").strip()
|
||||||
|
self.aw_rus_hayabusa_enabled = env_bool("AW_RUS_HAYABUSA_ENABLED", True)
|
||||||
|
self.aw_rus_hayabusa_ssh_cmd = os.getenv(
|
||||||
|
"AW_RUS_HAYABUSA_SSH_CMD",
|
||||||
|
"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13",
|
||||||
|
).strip()
|
||||||
self.aw_rus_host = os.getenv("AW_RUS_HOST", "SHARKON2025").strip()
|
self.aw_rus_host = os.getenv("AW_RUS_HOST", "SHARKON2025").strip()
|
||||||
self.aw_rus_primary_user = os.getenv("AW_RUS_PRIMARY_USER", "USER1").strip()
|
self.aw_rus_primary_user = os.getenv("AW_RUS_PRIMARY_USER", "USER1").strip()
|
||||||
self.aw_rus_stale_sec = max(60, env_int("AW_RUS_STALE_SEC", 900))
|
self.aw_rus_stale_sec = max(60, env_int("AW_RUS_STALE_SEC", 900))
|
||||||
@@ -491,6 +498,7 @@ class TSJGuardianBot:
|
|||||||
"keyboard": [
|
"keyboard": [
|
||||||
[self.BTN_STATUS, self.BTN_CHECK, self.BTN_HEAL],
|
[self.BTN_STATUS, self.BTN_CHECK, self.BTN_HEAL],
|
||||||
[self.BTN_AW_DLP_CHECK],
|
[self.BTN_AW_DLP_CHECK],
|
||||||
|
[self.BTN_AW_DFIR],
|
||||||
[self.BTN_ACK, self.BTN_RESOLVE],
|
[self.BTN_ACK, self.BTN_RESOLVE],
|
||||||
[self.BTN_AI, self.BTN_FALLBACK],
|
[self.BTN_AI, self.BTN_FALLBACK],
|
||||||
[self.BTN_AI_CHAT],
|
[self.BTN_AI_CHAT],
|
||||||
@@ -1988,6 +1996,11 @@ class TSJGuardianBot:
|
|||||||
"- Проверяет AW-Rus и DLP по свежести bucket-данных и сегодняшнему worktime.\n"
|
"- Проверяет AW-Rus и DLP по свежести bucket-данных и сегодняшнему worktime.\n"
|
||||||
"- Формирует операторский итог OK/DEGRADED прямо в чате.\n"
|
"- Формирует операторский итог OK/DEGRADED прямо в чате.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
f"{self.BTN_AW_DFIR}\n"
|
||||||
|
"- Показывает, как запустить bounded DFIR-путь через Hayabusa.\n"
|
||||||
|
"- Рабочий формат: `/aw_dfir /path/to/package.zip HOST [CASE_ID] [MODE]`.\n"
|
||||||
|
"- В кейс пишется только metadata/linkage, без raw Sigma output.\n"
|
||||||
|
"\n"
|
||||||
f"{self.BTN_HEAL}\n"
|
f"{self.BTN_HEAL}\n"
|
||||||
"- Пробует автоматическое лечение проблем (рестарт нужных сервисов).\n"
|
"- Пробует автоматическое лечение проблем (рестарт нужных сервисов).\n"
|
||||||
"- Для критичного заполнения ФС авто-очистка не выполняется, нужен разбор причины.\n"
|
"- Для критичного заполнения ФС авто-очистка не выполняется, нужен разбор причины.\n"
|
||||||
@@ -2088,7 +2101,7 @@ class TSJGuardianBot:
|
|||||||
"1) Нажмите кнопку создания/восстановления или используйте `/proxmox_snapshot TARGET`.\n"
|
"1) Нажмите кнопку создания/восстановления или используйте `/proxmox_snapshot TARGET`.\n"
|
||||||
"2) Для восстановления после выбора узла отправьте `/proxmox_restore_apply CODE`.\n"
|
"2) Для восстановления после выбора узла отправьте `/proxmox_restore_apply CODE`.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Резервные slash-команды: /status /check /aw_dlp_check /heal /ack /resolve /run ... /openvpn_certs [filter] /openvpn_expiring /openvpn_config USER /openvpn_config_confirm /openvpn_config_cancel /openvpn_config_apply CODE /pfsense_confirm /pfsense_cancel /pfsense_apply CODE /proxmox_snapshot TARGET /proxmox_restore TARGET /proxmox_restore_apply CODE /proxmox_restore_cancel /proxmox_selection_cancel"
|
"Резервные slash-команды: /status /check /aw_dlp_check /aw_dfir PACKAGE HOST [CASE_ID] [MODE] /heal /ack /resolve /run ... /openvpn_certs [filter] /openvpn_expiring /openvpn_config USER /openvpn_config_confirm /openvpn_config_cancel /openvpn_config_apply CODE /pfsense_confirm /pfsense_cancel /pfsense_apply CODE /proxmox_snapshot TARGET /proxmox_restore TARGET /proxmox_restore_apply CODE /proxmox_restore_cancel /proxmox_selection_cancel"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _cmd_status(self) -> str:
|
def _cmd_status(self) -> str:
|
||||||
@@ -2205,12 +2218,59 @@ class TSJGuardianBot:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return None, f"error:{exc}"
|
return None, f"error:{exc}"
|
||||||
|
|
||||||
|
def load_worktime_activity() -> Tuple[Optional[Dict[str, Dict[str, Optional[int]]]], Optional[str]]:
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{base}/buckets", timeout=20)
|
||||||
|
r.raise_for_status()
|
||||||
|
buckets = r.json()
|
||||||
|
except Exception as exc:
|
||||||
|
return None, f"error:{exc}"
|
||||||
|
|
||||||
|
prefix = "aw-worktime-sessions_"
|
||||||
|
activity: Dict[str, Dict[str, Optional[int]]] = {}
|
||||||
|
for bucket_id in sorted(key for key in buckets if key.startswith(prefix)):
|
||||||
|
bucket_host = bucket_id[len(prefix):]
|
||||||
|
latest_ts = None
|
||||||
|
latest_active = False
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{base}/buckets/{bucket_id}/events?limit=20", timeout=20)
|
||||||
|
r.raise_for_status()
|
||||||
|
events = r.json()
|
||||||
|
except Exception as exc:
|
||||||
|
activity[bucket_host] = {
|
||||||
|
"active": False,
|
||||||
|
"age_seconds": None,
|
||||||
|
"error": str(exc),
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(events, list):
|
||||||
|
for event in events:
|
||||||
|
raw_ts = event.get("timestamp")
|
||||||
|
if not raw_ts:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
event_ts = datetime.fromisoformat(raw_ts.replace("Z", "+00:00")).astimezone(timezone.utc)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if latest_ts is None or event_ts > latest_ts:
|
||||||
|
latest_ts = event_ts
|
||||||
|
latest_active = bool((event.get("data") or {}).get("active"))
|
||||||
|
|
||||||
|
age_seconds = None
|
||||||
|
if latest_ts is not None:
|
||||||
|
age_seconds = max(0, int((now - latest_ts).total_seconds()))
|
||||||
|
activity[bucket_host] = {
|
||||||
|
"active": bool(latest_ts and latest_active and (age_seconds or 0) <= self.aw_rus_stale_sec),
|
||||||
|
"age_seconds": age_seconds,
|
||||||
|
}
|
||||||
|
|
||||||
|
return activity, None
|
||||||
|
|
||||||
checks = [
|
checks = [
|
||||||
(f"aw-watcher-window_{host}", "watcher-window"),
|
(f"aw-watcher-window_{host}", "watcher-window"),
|
||||||
(f"aw-watcher-afk_{host}", "watcher-afk"),
|
(f"aw-watcher-afk_{host}", "watcher-afk"),
|
||||||
(f"aw-dlp-endpoint-signals_{host}", "dlp-endpoint"),
|
(f"aw-dlp-endpoint-signals_{host}", "dlp-endpoint"),
|
||||||
(f"aw-file-operations_{host}", "dlp-fileops-host"),
|
|
||||||
("aw-file-operations_10.10.10.13", "dlp-fileops-server"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
lines = ["Проверка AW-Rus + DLP:"]
|
lines = ["Проверка AW-Rus + DLP:"]
|
||||||
@@ -2227,6 +2287,46 @@ class TSJGuardianBot:
|
|||||||
else:
|
else:
|
||||||
lines.append(f"- {label}: OK age={age}s end={tail}")
|
lines.append(f"- {label}: OK age={age}s end={tail}")
|
||||||
|
|
||||||
|
worktime_activity, worktime_error = load_worktime_activity()
|
||||||
|
fileops_checks = [
|
||||||
|
(f"aw-file-operations_{host}", "dlp-fileops-host", host),
|
||||||
|
("aw-file-operations_10.10.10.13", "dlp-fileops-server", "10.10.10.13"),
|
||||||
|
]
|
||||||
|
for bucket_id, label, bucket_host in fileops_checks:
|
||||||
|
if worktime_activity is None:
|
||||||
|
age, tail = bucket_age(bucket_id)
|
||||||
|
if age is None:
|
||||||
|
lines.append(f"- {label}: FAIL (worktime-map {worktime_error}; bucket {tail})")
|
||||||
|
failures.append(label)
|
||||||
|
continue
|
||||||
|
if age > self.aw_rus_stale_sec:
|
||||||
|
lines.append(f"- {label}: STALE age={age}s end={tail} (worktime-map unavailable)")
|
||||||
|
failures.append(label)
|
||||||
|
else:
|
||||||
|
lines.append(f"- {label}: OK age={age}s end={tail} (worktime-map unavailable)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
host_meta = worktime_activity.get(bucket_host)
|
||||||
|
if host_meta is None:
|
||||||
|
lines.append(f"- {label}: OK unmanaged host={bucket_host}")
|
||||||
|
continue
|
||||||
|
if not host_meta.get("active"):
|
||||||
|
age_seconds = host_meta.get("age_seconds")
|
||||||
|
age_tail = f" age={age_seconds}s" if age_seconds is not None else ""
|
||||||
|
lines.append(f"- {label}: OK inactive host={bucket_host}{age_tail}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
age, tail = bucket_age(bucket_id)
|
||||||
|
if age is None:
|
||||||
|
lines.append(f"- {label}: FAIL (active host bucket missing or unreadable: {tail})")
|
||||||
|
failures.append(label)
|
||||||
|
continue
|
||||||
|
if age > self.aw_rus_stale_sec:
|
||||||
|
lines.append(f"- {label}: STALE age={age}s end={tail}")
|
||||||
|
failures.append(label)
|
||||||
|
else:
|
||||||
|
lines.append(f"- {label}: OK age={age}s end={tail}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.get(f"{worktime_base}/reports/worktime/today?format=csv", timeout=20)
|
r = requests.get(f"{worktime_base}/reports/worktime/today?format=csv", timeout=20)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -2411,6 +2511,111 @@ class TSJGuardianBot:
|
|||||||
out.extend(after_lines)
|
out.extend(after_lines)
|
||||||
return "\n".join(out)
|
return "\n".join(out)
|
||||||
|
|
||||||
|
def _aw_rus_hayabusa_usage_text(self) -> str:
|
||||||
|
return (
|
||||||
|
"Hayabusa DFIR:\n"
|
||||||
|
"- bounded forensic path для EVTX package -> Hayabusa -> case linkage.\n"
|
||||||
|
"- рабочий запуск: /aw_dfir /path/to/package.zip HOST [CASE_ID] [MODE]\n"
|
||||||
|
"- MODE по умолчанию: incident\n"
|
||||||
|
"- в кейс пишутся только metadata и ссылки на артефакты, без raw Sigma output."
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_marked_block(text: str, begin_marker: str, end_marker: str) -> str:
|
||||||
|
start = text.find(begin_marker)
|
||||||
|
end = text.find(end_marker)
|
||||||
|
if start < 0 or end < 0 or end <= start:
|
||||||
|
return ""
|
||||||
|
return text[start + len(begin_marker):end].strip()
|
||||||
|
|
||||||
|
def _aw_rus_case_link_hayabusa(self, case_id: int, payload: Dict) -> None:
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.aw_rus_case_api_base.rstrip('/')}/api/0/dlp/cases/{int(case_id)}/forensics/hayabusa",
|
||||||
|
json=payload,
|
||||||
|
timeout=20,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
def _aw_rus_hayabusa_run(
|
||||||
|
self,
|
||||||
|
package_path: str,
|
||||||
|
host: str | None = None,
|
||||||
|
case_id: int | None = None,
|
||||||
|
mode: str = "incident",
|
||||||
|
) -> str:
|
||||||
|
if not self.aw_rus_hayabusa_enabled:
|
||||||
|
return "Hayabusa DFIR trigger отключён."
|
||||||
|
package_path = (package_path or "").strip()
|
||||||
|
host = (host or "").strip() or None
|
||||||
|
mode = (mode or "incident").strip().lower() or "incident"
|
||||||
|
if not package_path:
|
||||||
|
return self._aw_rus_hayabusa_usage_text()
|
||||||
|
if mode not in {"quick", "incident", "full"}:
|
||||||
|
return f"Неверный mode: {mode}. Допустимо: quick, incident, full."
|
||||||
|
|
||||||
|
accept_cmd = f"sudo /usr/local/bin/aw-hayabusa accept --package {shlex.quote(package_path)}"
|
||||||
|
if host:
|
||||||
|
accept_cmd += f" --host {shlex.quote(host)}"
|
||||||
|
remote_script = (
|
||||||
|
"set -eu\n"
|
||||||
|
f"{accept_cmd}\n"
|
||||||
|
"process_rc=0\n"
|
||||||
|
f"sudo /usr/local/bin/aw-hayabusa process-inbox --mode {shlex.quote(mode)} --limit 1 || process_rc=$?\n"
|
||||||
|
"echo '__AW_HAYA_INTAKE_JSON_BEGIN__'\n"
|
||||||
|
"sudo cat /opt/hayabusa/state/latest-intake.json\n"
|
||||||
|
"echo '__AW_HAYA_INTAKE_JSON_END__'\n"
|
||||||
|
"exit \"$process_rc\"\n"
|
||||||
|
)
|
||||||
|
cmd = f"{self.aw_rus_hayabusa_ssh_cmd} bash -lc {shlex.quote(remote_script)}"
|
||||||
|
rc, out = self._run_shell(cmd, timeout_sec=900)
|
||||||
|
json_block = self._extract_marked_block(out, "__AW_HAYA_INTAKE_JSON_BEGIN__", "__AW_HAYA_INTAKE_JSON_END__")
|
||||||
|
if not json_block:
|
||||||
|
tail = "\n".join((out or "").splitlines()[-20:])
|
||||||
|
return f"Hayabusa DFIR: не удалось получить intake metadata.\nrc={rc}\n{tail}"
|
||||||
|
try:
|
||||||
|
intake = json.loads(json_block)
|
||||||
|
except Exception as exc:
|
||||||
|
tail = "\n".join((out or "").splitlines()[-20:])
|
||||||
|
return f"Hayabusa DFIR: intake metadata повреждены ({exc}).\nrc={rc}\n{tail}"
|
||||||
|
|
||||||
|
report_dir = intake.get("report_dir") or ""
|
||||||
|
report_dir = str(report_dir)
|
||||||
|
status = str(intake.get("status") or ("ok" if rc == 0 else f"rc-{rc}"))
|
||||||
|
link_payload = {
|
||||||
|
"host": str(intake.get("host") or host or self.aw_rus_host),
|
||||||
|
"mode": mode,
|
||||||
|
"status": status,
|
||||||
|
"intake_id": intake.get("intake_id"),
|
||||||
|
"package_path": intake.get("package_path"),
|
||||||
|
"sha256": intake.get("sha256"),
|
||||||
|
"report_dir": report_dir or None,
|
||||||
|
"summary_html": f"{report_dir}/summary.html" if report_dir else None,
|
||||||
|
"timeline_path": f"{report_dir}/timeline.jsonl" if report_dir else None,
|
||||||
|
"manifest_path": f"{report_dir}/manifest.json" if report_dir else None,
|
||||||
|
"link_source": "telegram-bot",
|
||||||
|
}
|
||||||
|
linked_line = "- case linkage: skipped"
|
||||||
|
if case_id is not None:
|
||||||
|
self._aw_rus_case_link_hayabusa(case_id, link_payload)
|
||||||
|
linked_line = f"- case linkage: OK case_id={case_id}"
|
||||||
|
|
||||||
|
verdict = "OK" if rc == 0 else f"DEGRADED rc={rc}"
|
||||||
|
lines = [
|
||||||
|
f"Hayabusa DFIR: {verdict}",
|
||||||
|
f"- host: {link_payload['host']}",
|
||||||
|
f"- mode: {mode}",
|
||||||
|
f"- status: {status}",
|
||||||
|
f"- intake_id: {intake.get('intake_id') or '-'}",
|
||||||
|
f"- package: {intake.get('package_path') or package_path}",
|
||||||
|
f"- report_dir: {report_dir or '-'}",
|
||||||
|
linked_line,
|
||||||
|
]
|
||||||
|
if rc != 0:
|
||||||
|
tail = "\n".join((out or "").splitlines()[-20:])
|
||||||
|
lines.append("- runner tail:")
|
||||||
|
lines.append(tail)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _pfsense_security_status_lines(self) -> str:
|
def _pfsense_security_status_lines(self) -> str:
|
||||||
cmd = "/usr/bin/python3 /home/codex/infra-admin/scripts/pfsense_security_status.py"
|
cmd = "/usr/bin/python3 /home/codex/infra-admin/scripts/pfsense_security_status.py"
|
||||||
try:
|
try:
|
||||||
@@ -2519,6 +2724,29 @@ class TSJGuardianBot:
|
|||||||
if text.startswith("/aw_dlp_check") or text == self.BTN_AW_DLP_CHECK:
|
if text.startswith("/aw_dlp_check") or text == self.BTN_AW_DLP_CHECK:
|
||||||
self._send_text(chat_id, self._run_operator_action("aw-dlp-check"))
|
self._send_text(chat_id, self._run_operator_action("aw-dlp-check"))
|
||||||
return
|
return
|
||||||
|
if text == self.BTN_AW_DFIR:
|
||||||
|
self._send_text(chat_id, self._aw_rus_hayabusa_usage_text())
|
||||||
|
return
|
||||||
|
if text.strip() == "/aw_dfir":
|
||||||
|
self._send_text(chat_id, self._aw_rus_hayabusa_usage_text())
|
||||||
|
return
|
||||||
|
if text.startswith("/aw_dfir "):
|
||||||
|
parts = text.split()
|
||||||
|
if len(parts) < 3:
|
||||||
|
self._send_text(chat_id, self._aw_rus_hayabusa_usage_text())
|
||||||
|
return
|
||||||
|
package_path = parts[1]
|
||||||
|
host = parts[2]
|
||||||
|
case_id = None
|
||||||
|
mode = "incident"
|
||||||
|
if len(parts) >= 4 and parts[3].isdigit():
|
||||||
|
case_id = int(parts[3])
|
||||||
|
if len(parts) >= 5:
|
||||||
|
mode = parts[4]
|
||||||
|
elif len(parts) >= 4:
|
||||||
|
mode = parts[3]
|
||||||
|
self._send_text(chat_id, self._aw_rus_hayabusa_run(package_path=package_path, host=host, case_id=case_id, mode=mode))
|
||||||
|
return
|
||||||
if text.startswith("/heal") or text == self.BTN_HEAL:
|
if text.startswith("/heal") or text == self.BTN_HEAL:
|
||||||
self._send_text(chat_id, self._run_operator_action("heal"))
|
self._send_text(chat_id, self._run_operator_action("heal"))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
[string]$FileCollectorScriptSource,
|
[string]$FileCollectorScriptSource,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$SessionCollectorScriptSource,
|
[string]$SessionCollectorScriptSource,
|
||||||
|
[string]$EvtxExportScriptSource,
|
||||||
[string]$EmailCollectorScriptSource,
|
[string]$EmailCollectorScriptSource,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$ExampleRulesSource,
|
[string]$ExampleRulesSource,
|
||||||
@@ -417,6 +418,7 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
$policyClientTarget = Join-Path $StateRoot 'dlp-policy-client.ps1'
|
$policyClientTarget = Join-Path $StateRoot 'dlp-policy-client.ps1'
|
||||||
$fileCollectorTarget = Join-Path $StateRoot 'file-operations-collector.ps1'
|
$fileCollectorTarget = Join-Path $StateRoot 'file-operations-collector.ps1'
|
||||||
$sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1'
|
$sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1'
|
||||||
|
$evtxExportTarget = Join-Path $StateRoot 'export-evtx-for-hayabusa.ps1'
|
||||||
$emailCollectorTarget = Join-Path $StateRoot 'email-outbound-collector.ps1'
|
$emailCollectorTarget = Join-Path $StateRoot 'email-outbound-collector.ps1'
|
||||||
$exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json'
|
$exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json'
|
||||||
$rulesTarget = Join-Path $StateRoot 'web-category-rules.json'
|
$rulesTarget = Join-Path $StateRoot 'web-category-rules.json'
|
||||||
@@ -430,6 +432,9 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
}
|
}
|
||||||
Copy-Item -LiteralPath $FileCollectorScriptSource -Destination $fileCollectorTarget -Force
|
Copy-Item -LiteralPath $FileCollectorScriptSource -Destination $fileCollectorTarget -Force
|
||||||
Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force
|
Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force
|
||||||
|
if ($EvtxExportScriptSource -and (Test-Path -LiteralPath $EvtxExportScriptSource)) {
|
||||||
|
Copy-Item -LiteralPath $EvtxExportScriptSource -Destination $evtxExportTarget -Force
|
||||||
|
}
|
||||||
if ($EmailCollectorScriptSource -and (Test-Path -LiteralPath $EmailCollectorScriptSource)) {
|
if ($EmailCollectorScriptSource -and (Test-Path -LiteralPath $EmailCollectorScriptSource)) {
|
||||||
Copy-Item -LiteralPath $EmailCollectorScriptSource -Destination $emailCollectorTarget -Force
|
Copy-Item -LiteralPath $EmailCollectorScriptSource -Destination $emailCollectorTarget -Force
|
||||||
}
|
}
|
||||||
@@ -458,6 +463,7 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
PolicyClientScript = $policyClientTarget
|
PolicyClientScript = $policyClientTarget
|
||||||
FileCollectorScript = $fileCollectorTarget
|
FileCollectorScript = $fileCollectorTarget
|
||||||
SessionCollectorScript = $sessionCollectorTarget
|
SessionCollectorScript = $sessionCollectorTarget
|
||||||
|
EvtxExportScript = $evtxExportTarget
|
||||||
EmailCollectorScript = $emailCollectorTarget
|
EmailCollectorScript = $emailCollectorTarget
|
||||||
ExampleRules = $exampleRulesTarget
|
ExampleRules = $exampleRulesTarget
|
||||||
ActiveRules = $rulesTarget
|
ActiveRules = $rulesTarget
|
||||||
@@ -489,6 +495,7 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
[string]$FileCollectorScript,
|
[string]$FileCollectorScript,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$SessionCollectorScript,
|
[string]$SessionCollectorScript,
|
||||||
|
[string]$EvtxExportScript,
|
||||||
[string]$EmailCollectorScript,
|
[string]$EmailCollectorScript,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$RulesPath,
|
[string]$RulesPath,
|
||||||
@@ -507,6 +514,9 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
[bool]$IncidentCaptureEnabled = $true,
|
[bool]$IncidentCaptureEnabled = $true,
|
||||||
[bool]$IncidentScreenshotEnabled = $true,
|
[bool]$IncidentScreenshotEnabled = $true,
|
||||||
[string]$IncidentArtifactsRoot,
|
[string]$IncidentArtifactsRoot,
|
||||||
|
[string]$EvtxExportRoot,
|
||||||
|
[int]$EvtxRetentionDays = 14,
|
||||||
|
[string[]]$EvtxChannels = @(),
|
||||||
[bool]$LogonMarkerEnabled = $true,
|
[bool]$LogonMarkerEnabled = $true,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$LaunchScriptPath,
|
[string]$LaunchScriptPath,
|
||||||
@@ -529,6 +539,19 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
)
|
)
|
||||||
|
|
||||||
$effectiveIncidentArtifactsRoot = if ($IncidentArtifactsRoot) { $IncidentArtifactsRoot } else { Join-Path $StateRoot 'incident-artifacts' }
|
$effectiveIncidentArtifactsRoot = if ($IncidentArtifactsRoot) { $IncidentArtifactsRoot } else { Join-Path $StateRoot 'incident-artifacts' }
|
||||||
|
$effectiveEvtxExportRoot = if ($EvtxExportRoot) { $EvtxExportRoot } else { Join-Path $StateRoot 'forensics\evtx-exports' }
|
||||||
|
$effectiveEvtxChannels = if ($EvtxChannels -and $EvtxChannels.Count -gt 0) {
|
||||||
|
@($EvtxChannels)
|
||||||
|
} else {
|
||||||
|
@(
|
||||||
|
'Security',
|
||||||
|
'System',
|
||||||
|
'Application',
|
||||||
|
'Microsoft-Windows-PowerShell/Operational',
|
||||||
|
'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational',
|
||||||
|
'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'
|
||||||
|
)
|
||||||
|
}
|
||||||
$effectivePolicyEngineHost = if ([string]::IsNullOrWhiteSpace($PolicyEngineHost)) { $ServerHost } else { $PolicyEngineHost }
|
$effectivePolicyEngineHost = if ([string]::IsNullOrWhiteSpace($PolicyEngineHost)) { $ServerHost } else { $PolicyEngineHost }
|
||||||
$effectivePolicyCachePath = if ([string]::IsNullOrWhiteSpace($PolicyCachePath)) { Join-Path $StateRoot 'dlp-policy-cache.json' } else { $PolicyCachePath }
|
$effectivePolicyCachePath = if ([string]::IsNullOrWhiteSpace($PolicyCachePath)) { Join-Path $StateRoot 'dlp-policy-cache.json' } else { $PolicyCachePath }
|
||||||
|
|
||||||
@@ -551,6 +574,7 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
emailCollectorScript = $EmailCollectorScript
|
emailCollectorScript = $EmailCollectorScript
|
||||||
fileCollectorScript = $FileCollectorScript
|
fileCollectorScript = $FileCollectorScript
|
||||||
sessionCollectorScript = $SessionCollectorScript
|
sessionCollectorScript = $SessionCollectorScript
|
||||||
|
evtxExportScript = $EvtxExportScript
|
||||||
rulesPath = $RulesPath
|
rulesPath = $RulesPath
|
||||||
policyPath = $PolicyPath
|
policyPath = $PolicyPath
|
||||||
launchScript = $LaunchScriptPath
|
launchScript = $LaunchScriptPath
|
||||||
@@ -574,6 +598,11 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
screenshotEnabled = $IncidentScreenshotEnabled
|
screenshotEnabled = $IncidentScreenshotEnabled
|
||||||
artifactsRoot = $effectiveIncidentArtifactsRoot
|
artifactsRoot = $effectiveIncidentArtifactsRoot
|
||||||
}
|
}
|
||||||
|
forensics = [pscustomobject]@{
|
||||||
|
evtxExportRoot = $effectiveEvtxExportRoot
|
||||||
|
retentionDays = $EvtxRetentionDays
|
||||||
|
evtxChannels = @($effectiveEvtxChannels)
|
||||||
|
}
|
||||||
sessionEvents = [pscustomobject]@{
|
sessionEvents = [pscustomobject]@{
|
||||||
logonEnabled = $LogonMarkerEnabled
|
logonEnabled = $LogonMarkerEnabled
|
||||||
bucketPrefix = 'aw-session-events'
|
bucketPrefix = 'aw-session-events'
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ param(
|
|||||||
[bool]$IncidentCaptureEnabled = $true,
|
[bool]$IncidentCaptureEnabled = $true,
|
||||||
[bool]$IncidentScreenshotEnabled = $true,
|
[bool]$IncidentScreenshotEnabled = $true,
|
||||||
[string]$IncidentArtifactsRoot,
|
[string]$IncidentArtifactsRoot,
|
||||||
|
[string]$EvtxExportRoot,
|
||||||
|
[int]$EvtxRetentionDays = 14,
|
||||||
|
[string[]]$EvtxChannels = @(),
|
||||||
[bool]$LogonMarkerEnabled = $true,
|
[bool]$LogonMarkerEnabled = $true,
|
||||||
[string]$AwHostname,
|
[string]$AwHostname,
|
||||||
[string]$CustomRulesPath,
|
[string]$CustomRulesPath,
|
||||||
@@ -60,6 +63,7 @@ $policyClientSource = Join-Path $PSScriptRoot 'dlp-policy-client.ps1'
|
|||||||
$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1'
|
$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1'
|
||||||
$fileCollectorSource = Join-Path $PSScriptRoot 'file-operations-collector.ps1'
|
$fileCollectorSource = Join-Path $PSScriptRoot 'file-operations-collector.ps1'
|
||||||
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
||||||
|
$evtxExportScriptSource = Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.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'
|
||||||
|
|
||||||
@@ -78,6 +82,7 @@ $assetResult = Copy-ActivityWatchCollectorAssets `
|
|||||||
-EmailCollectorScriptSource $emailCollectorSource `
|
-EmailCollectorScriptSource $emailCollectorSource `
|
||||||
-FileCollectorScriptSource $fileCollectorSource `
|
-FileCollectorScriptSource $fileCollectorSource `
|
||||||
-SessionCollectorScriptSource $sessionCollectorSource `
|
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||||
|
-EvtxExportScriptSource $evtxExportScriptSource `
|
||||||
-ExampleRulesSource $exampleRulesSource `
|
-ExampleRulesSource $exampleRulesSource `
|
||||||
-ExamplePolicySource $examplePolicySource `
|
-ExamplePolicySource $examplePolicySource `
|
||||||
-StateRoot $StateRoot `
|
-StateRoot $StateRoot `
|
||||||
@@ -101,6 +106,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-EmailCollectorScript $assetResult.EmailCollectorScript `
|
-EmailCollectorScript $assetResult.EmailCollectorScript `
|
||||||
-FileCollectorScript $assetResult.FileCollectorScript `
|
-FileCollectorScript $assetResult.FileCollectorScript `
|
||||||
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||||
|
-EvtxExportScript $assetResult.EvtxExportScript `
|
||||||
-RulesPath $assetResult.ActiveRules `
|
-RulesPath $assetResult.ActiveRules `
|
||||||
-PolicyPath $assetResult.ActivePolicy `
|
-PolicyPath $assetResult.ActivePolicy `
|
||||||
-PollSeconds $PollSeconds `
|
-PollSeconds $PollSeconds `
|
||||||
@@ -113,6 +119,9 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
||||||
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
||||||
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
||||||
|
-EvtxExportRoot $EvtxExportRoot `
|
||||||
|
-EvtxRetentionDays $EvtxRetentionDays `
|
||||||
|
-EvtxChannels $EvtxChannels `
|
||||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||||
-AwHostname $AwHostname `
|
-AwHostname $AwHostname `
|
||||||
-PolicyMode $PolicyMode `
|
-PolicyMode $PolicyMode `
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ param(
|
|||||||
[bool]$IncidentCaptureEnabled = $true,
|
[bool]$IncidentCaptureEnabled = $true,
|
||||||
[bool]$IncidentScreenshotEnabled = $true,
|
[bool]$IncidentScreenshotEnabled = $true,
|
||||||
[string]$IncidentArtifactsRoot,
|
[string]$IncidentArtifactsRoot,
|
||||||
|
[string]$EvtxExportRoot,
|
||||||
|
[int]$EvtxRetentionDays = 14,
|
||||||
|
[string[]]$EvtxChannels = @(),
|
||||||
[bool]$LogonMarkerEnabled = $true,
|
[bool]$LogonMarkerEnabled = $true,
|
||||||
[string]$AwHostname,
|
[string]$AwHostname,
|
||||||
[string]$CustomRulesPath,
|
[string]$CustomRulesPath,
|
||||||
@@ -81,6 +84,9 @@ if (-not (Test-Path -LiteralPath $deployScript)) {
|
|||||||
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
||||||
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
||||||
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
||||||
|
-EvtxExportRoot $EvtxExportRoot `
|
||||||
|
-EvtxRetentionDays $EvtxRetentionDays `
|
||||||
|
-EvtxChannels $EvtxChannels `
|
||||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||||
-AwHostname $AwHostname `
|
-AwHostname $AwHostname `
|
||||||
-CustomRulesPath $CustomRulesPath `
|
-CustomRulesPath $CustomRulesPath `
|
||||||
@@ -113,6 +119,9 @@ if (-not $SkipHardening) {
|
|||||||
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
||||||
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
||||||
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
||||||
|
-EvtxExportRoot $EvtxExportRoot `
|
||||||
|
-EvtxRetentionDays $EvtxRetentionDays `
|
||||||
|
-EvtxChannels $EvtxChannels `
|
||||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||||
-AwHostname $AwHostname `
|
-AwHostname $AwHostname `
|
||||||
-CustomRulesPath $CustomRulesPath `
|
-CustomRulesPath $CustomRulesPath `
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ param(
|
|||||||
[bool]$IncidentCaptureEnabled = $true,
|
[bool]$IncidentCaptureEnabled = $true,
|
||||||
[bool]$IncidentScreenshotEnabled = $true,
|
[bool]$IncidentScreenshotEnabled = $true,
|
||||||
[string]$IncidentArtifactsRoot,
|
[string]$IncidentArtifactsRoot,
|
||||||
|
[string]$EvtxExportRoot,
|
||||||
|
[int]$EvtxRetentionDays = 14,
|
||||||
|
[string[]]$EvtxChannels = @(),
|
||||||
[bool]$LogonMarkerEnabled = $true,
|
[bool]$LogonMarkerEnabled = $true,
|
||||||
[string]$AwHostname,
|
[string]$AwHostname,
|
||||||
[string]$CustomRulesPath,
|
[string]$CustomRulesPath,
|
||||||
@@ -45,6 +48,7 @@ $collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1
|
|||||||
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
|
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
|
||||||
$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1'
|
$emailCollectorSource = Join-Path $PSScriptRoot 'email-outbound-collector.ps1'
|
||||||
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
||||||
|
$evtxExportScriptSource = Join-Path $PSScriptRoot 'export-evtx-for-hayabusa.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'
|
||||||
|
|
||||||
@@ -60,6 +64,7 @@ $assetResult = Copy-ActivityWatchCollectorAssets `
|
|||||||
-EndpointCollectorScriptSource $endpointCollectorSource `
|
-EndpointCollectorScriptSource $endpointCollectorSource `
|
||||||
-EmailCollectorScriptSource $emailCollectorSource `
|
-EmailCollectorScriptSource $emailCollectorSource `
|
||||||
-SessionCollectorScriptSource $sessionCollectorSource `
|
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||||
|
-EvtxExportScriptSource $evtxExportScriptSource `
|
||||||
-ExampleRulesSource $exampleRulesSource `
|
-ExampleRulesSource $exampleRulesSource `
|
||||||
-ExamplePolicySource $examplePolicySource `
|
-ExamplePolicySource $examplePolicySource `
|
||||||
-StateRoot $StateRoot `
|
-StateRoot $StateRoot `
|
||||||
@@ -81,6 +86,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
||||||
-EmailCollectorScript $assetResult.EmailCollectorScript `
|
-EmailCollectorScript $assetResult.EmailCollectorScript `
|
||||||
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||||
|
-EvtxExportScript $assetResult.EvtxExportScript `
|
||||||
-RulesPath $assetResult.ActiveRules `
|
-RulesPath $assetResult.ActiveRules `
|
||||||
-PolicyPath $assetResult.ActivePolicy `
|
-PolicyPath $assetResult.ActivePolicy `
|
||||||
-PollSeconds $PollSeconds `
|
-PollSeconds $PollSeconds `
|
||||||
@@ -92,6 +98,9 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
-IncidentCaptureEnabled $IncidentCaptureEnabled `
|
||||||
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
-IncidentScreenshotEnabled $IncidentScreenshotEnabled `
|
||||||
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
-IncidentArtifactsRoot $IncidentArtifactsRoot `
|
||||||
|
-EvtxExportRoot $EvtxExportRoot `
|
||||||
|
-EvtxRetentionDays $EvtxRetentionDays `
|
||||||
|
-EvtxChannels $EvtxChannels `
|
||||||
-LogonMarkerEnabled $LogonMarkerEnabled `
|
-LogonMarkerEnabled $LogonMarkerEnabled `
|
||||||
-AwHostname $AwHostname `
|
-AwHostname $AwHostname `
|
||||||
-LaunchScriptPath $launchScriptPath `
|
-LaunchScriptPath $launchScriptPath `
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ConfigPath = 'C:\ProgramData\AWatch-rus\deployment-config.json',
|
||||||
|
[string]$OutputRoot,
|
||||||
|
[int]$RetentionDays,
|
||||||
|
[string[]]$Channels,
|
||||||
|
[int]$DaysBack = 3,
|
||||||
|
[switch]$NoZip
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function New-Directory {
|
||||||
|
param([string]$Path)
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) {
|
||||||
|
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ConfigValue {
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$Section,
|
||||||
|
[string]$Name,
|
||||||
|
$DefaultValue
|
||||||
|
)
|
||||||
|
if ($null -eq $Config) { return $DefaultValue }
|
||||||
|
if ($Config.PSObject.Properties.Name -notcontains $Section) { return $DefaultValue }
|
||||||
|
$sectionValue = $Config.$Section
|
||||||
|
if ($null -eq $sectionValue) { return $DefaultValue }
|
||||||
|
if ($sectionValue.PSObject.Properties.Name -notcontains $Name) { return $DefaultValue }
|
||||||
|
return $sectionValue.$Name
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = $null
|
||||||
|
if (Test-Path -LiteralPath $ConfigPath) {
|
||||||
|
$config = Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
|
||||||
|
$effectiveOutputRoot = if ($OutputRoot) {
|
||||||
|
$OutputRoot
|
||||||
|
} else {
|
||||||
|
[string](Get-ConfigValue -Config $config -Section 'forensics' -Name 'evtxExportRoot' -DefaultValue 'C:\ProgramData\AWatch-rus\forensics\evtx-exports')
|
||||||
|
}
|
||||||
|
$effectiveRetentionDays = if ($PSBoundParameters.ContainsKey('RetentionDays')) {
|
||||||
|
$RetentionDays
|
||||||
|
} else {
|
||||||
|
[int](Get-ConfigValue -Config $config -Section 'forensics' -Name 'retentionDays' -DefaultValue 14)
|
||||||
|
}
|
||||||
|
$effectiveChannels = if ($Channels -and $Channels.Count -gt 0) {
|
||||||
|
@($Channels)
|
||||||
|
} else {
|
||||||
|
@(Get-ConfigValue -Config $config -Section 'forensics' -Name 'evtxChannels' -DefaultValue @(
|
||||||
|
'Security',
|
||||||
|
'System',
|
||||||
|
'Application',
|
||||||
|
'Microsoft-Windows-PowerShell/Operational',
|
||||||
|
'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational',
|
||||||
|
'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Directory -Path $effectiveOutputRoot
|
||||||
|
|
||||||
|
$hostName = if ($config -and $config.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$config.awHostname)) {
|
||||||
|
[string]$config.awHostname
|
||||||
|
} else {
|
||||||
|
[string]$env:COMPUTERNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||||
|
$batchRoot = Join-Path $effectiveOutputRoot "$hostName-$timestamp"
|
||||||
|
$evtxRoot = Join-Path $batchRoot 'evtx'
|
||||||
|
$metaPath = Join-Path $batchRoot 'manifest.json'
|
||||||
|
$zipPath = Join-Path $effectiveOutputRoot "$hostName-$timestamp.zip"
|
||||||
|
|
||||||
|
New-Directory -Path $batchRoot
|
||||||
|
New-Directory -Path $evtxRoot
|
||||||
|
|
||||||
|
$daysBackMs = [int64]$DaysBack * 24 * 60 * 60 * 1000
|
||||||
|
$query = "*[System[TimeCreated[timediff(@SystemTime) <= $daysBackMs]]]"
|
||||||
|
$results = @()
|
||||||
|
|
||||||
|
foreach ($channel in @($effectiveChannels | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })) {
|
||||||
|
$safeName = (($channel -replace '[\\/:*?""<>| ]', '_').Trim('_'))
|
||||||
|
$targetPath = Join-Path $evtxRoot ($safeName + '.evtx')
|
||||||
|
try {
|
||||||
|
& wevtutil.exe epl $channel $targetPath /ow:true /q:$query | Out-Null
|
||||||
|
$exists = Test-Path -LiteralPath $targetPath
|
||||||
|
$size = if ($exists) { (Get-Item -LiteralPath $targetPath).Length } else { 0 }
|
||||||
|
$results += [pscustomobject]@{
|
||||||
|
channel = $channel
|
||||||
|
path = $targetPath
|
||||||
|
exported = $exists
|
||||||
|
size = $size
|
||||||
|
status = if ($exists) { 'ok' } else { 'empty' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$results += [pscustomobject]@{
|
||||||
|
channel = $channel
|
||||||
|
path = $targetPath
|
||||||
|
exported = $false
|
||||||
|
size = 0
|
||||||
|
status = 'error'
|
||||||
|
error = $_.Exception.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$manifest = [ordered]@{
|
||||||
|
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
||||||
|
hostname = $hostName
|
||||||
|
configPath = $ConfigPath
|
||||||
|
outputRoot = $effectiveOutputRoot
|
||||||
|
batchRoot = $batchRoot
|
||||||
|
zipPath = if ($NoZip) { $null } else { $zipPath }
|
||||||
|
daysBack = $DaysBack
|
||||||
|
retentionDays = $effectiveRetentionDays
|
||||||
|
channels = @($effectiveChannels)
|
||||||
|
exports = @($results)
|
||||||
|
}
|
||||||
|
$manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $metaPath -Encoding UTF8
|
||||||
|
|
||||||
|
if (-not $NoZip) {
|
||||||
|
if (Test-Path -LiteralPath $zipPath) {
|
||||||
|
Remove-Item -LiteralPath $zipPath -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Compress-Archive -Path (Join-Path $batchRoot '*') -DestinationPath $zipPath -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$cutoff = (Get-Date).AddDays(-1 * [Math]::Max(1, $effectiveRetentionDays))
|
||||||
|
Get-ChildItem -LiteralPath $effectiveOutputRoot -Directory -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.LastWriteTime -lt $cutoff } |
|
||||||
|
ForEach-Object { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue }
|
||||||
|
Get-ChildItem -LiteralPath $effectiveOutputRoot -File -Filter '*.zip' -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.LastWriteTime -lt $cutoff } |
|
||||||
|
ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
$manifest
|
||||||
@@ -20,6 +20,7 @@ param(
|
|||||||
[bool]$IncidentCaptureEnabled,
|
[bool]$IncidentCaptureEnabled,
|
||||||
[bool]$IncidentScreenshotEnabled,
|
[bool]$IncidentScreenshotEnabled,
|
||||||
[string]$IncidentArtifactsRoot,
|
[string]$IncidentArtifactsRoot,
|
||||||
|
[string]$EvtxExportRoot,
|
||||||
[bool]$LogonMarkerEnabled,
|
[bool]$LogonMarkerEnabled,
|
||||||
[string]$AwHostname,
|
[string]$AwHostname,
|
||||||
[string]$CustomRulesPath,
|
[string]$CustomRulesPath,
|
||||||
@@ -66,6 +67,7 @@ $effectiveCollector = Join-Path $effectiveStateRoot 'browser-domains-native-coll
|
|||||||
$effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-collector.ps1' }
|
$effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-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' }
|
$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' }
|
||||||
$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' }
|
||||||
@@ -83,6 +85,9 @@ $effectiveLocalAgentLogsEnabled = if ($PSBoundParameters.ContainsKey('LocalAgent
|
|||||||
$effectiveIncidentCaptureEnabled = if ($PSBoundParameters.ContainsKey('IncidentCaptureEnabled')) { [bool]$IncidentCaptureEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.incidentCapture.enabled } else { $true }
|
$effectiveIncidentCaptureEnabled = if ($PSBoundParameters.ContainsKey('IncidentCaptureEnabled')) { [bool]$IncidentCaptureEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'enabled') { [bool]$existingConfig.incidentCapture.enabled } else { $true }
|
||||||
$effectiveIncidentScreenshotEnabled = if ($PSBoundParameters.ContainsKey('IncidentScreenshotEnabled')) { [bool]$IncidentScreenshotEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$existingConfig.incidentCapture.screenshotEnabled } else { $true }
|
$effectiveIncidentScreenshotEnabled = if ($PSBoundParameters.ContainsKey('IncidentScreenshotEnabled')) { [bool]$IncidentScreenshotEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'screenshotEnabled') { [bool]$existingConfig.incidentCapture.screenshotEnabled } else { $true }
|
||||||
$effectiveIncidentArtifactsRoot = if ($PSBoundParameters.ContainsKey('IncidentArtifactsRoot') -and $IncidentArtifactsRoot) { $IncidentArtifactsRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$existingConfig.incidentCapture.artifactsRoot } else { Join-Path $effectiveStateRoot 'incident-artifacts' }
|
$effectiveIncidentArtifactsRoot = if ($PSBoundParameters.ContainsKey('IncidentArtifactsRoot') -and $IncidentArtifactsRoot) { $IncidentArtifactsRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'incidentCapture' -and $existingConfig.incidentCapture.PSObject.Properties.Name -contains 'artifactsRoot') { [string]$existingConfig.incidentCapture.artifactsRoot } else { Join-Path $effectiveStateRoot 'incident-artifacts' }
|
||||||
|
$effectiveEvtxExportRoot = if ($PSBoundParameters.ContainsKey('EvtxExportRoot') -and $EvtxExportRoot) { $EvtxExportRoot } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'evtxExportRoot') { [string]$existingConfig.forensics.evtxExportRoot } else { Join-Path $effectiveStateRoot 'forensics\evtx-exports' }
|
||||||
|
$effectiveEvtxRetentionDays = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'retentionDays') { [int]$existingConfig.forensics.retentionDays } else { 14 }
|
||||||
|
$effectiveEvtxChannels = if ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'forensics' -and $existingConfig.forensics.PSObject.Properties.Name -contains 'evtxChannels') { @($existingConfig.forensics.evtxChannels) } else { @() }
|
||||||
$effectiveLogonMarkerEnabled = if ($PSBoundParameters.ContainsKey('LogonMarkerEnabled')) { [bool]$LogonMarkerEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$existingConfig.sessionEvents.logonEnabled } else { $true }
|
$effectiveLogonMarkerEnabled = if ($PSBoundParameters.ContainsKey('LogonMarkerEnabled')) { [bool]$LogonMarkerEnabled } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'sessionEvents' -and $existingConfig.sessionEvents.PSObject.Properties.Name -contains 'logonEnabled') { [bool]$existingConfig.sessionEvents.logonEnabled } else { $true }
|
||||||
$effectiveAwHostname = if ($PSBoundParameters.ContainsKey('AwHostname') -and -not [string]::IsNullOrWhiteSpace($AwHostname)) { [string]$AwHostname } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$existingConfig.awHostname)) { [string]$existingConfig.awHostname } else { [string]$env:COMPUTERNAME }
|
$effectiveAwHostname = if ($PSBoundParameters.ContainsKey('AwHostname') -and -not [string]::IsNullOrWhiteSpace($AwHostname)) { [string]$AwHostname } elseif ($existingConfig -and $existingConfig.PSObject.Properties.Name -contains 'awHostname' -and -not [string]::IsNullOrWhiteSpace([string]$existingConfig.awHostname)) { [string]$existingConfig.awHostname } else { [string]$env:COMPUTERNAME }
|
||||||
$effectiveVersion = if ($Version) { $Version } elseif ($existingConfig) { [string]$existingConfig.package.version } else { 'v0.13.2' }
|
$effectiveVersion = if ($Version) { $Version } elseif ($existingConfig) { [string]$existingConfig.package.version } else { 'v0.13.2' }
|
||||||
@@ -123,6 +128,7 @@ $assetResult = Copy-ActivityWatchCollectorAssets `
|
|||||||
-EmailCollectorScriptSource (Join-Path $PSScriptRoot 'email-outbound-collector.ps1') `
|
-EmailCollectorScriptSource (Join-Path $PSScriptRoot 'email-outbound-collector.ps1') `
|
||||||
-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') `
|
||||||
-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 `
|
||||||
@@ -146,6 +152,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-EmailCollectorScript $assetResult.EmailCollectorScript `
|
-EmailCollectorScript $assetResult.EmailCollectorScript `
|
||||||
-FileCollectorScript $effectiveFileCollector `
|
-FileCollectorScript $effectiveFileCollector `
|
||||||
-SessionCollectorScript $effectiveSessionCollector `
|
-SessionCollectorScript $effectiveSessionCollector `
|
||||||
|
-EvtxExportScript $effectiveEvtxExportScript `
|
||||||
-RulesPath $effectiveRules `
|
-RulesPath $effectiveRules `
|
||||||
-PolicyPath $effectivePolicy `
|
-PolicyPath $effectivePolicy `
|
||||||
-PollSeconds $effectivePollSeconds `
|
-PollSeconds $effectivePollSeconds `
|
||||||
@@ -158,6 +165,9 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-IncidentCaptureEnabled $effectiveIncidentCaptureEnabled `
|
-IncidentCaptureEnabled $effectiveIncidentCaptureEnabled `
|
||||||
-IncidentScreenshotEnabled $effectiveIncidentScreenshotEnabled `
|
-IncidentScreenshotEnabled $effectiveIncidentScreenshotEnabled `
|
||||||
-IncidentArtifactsRoot $effectiveIncidentArtifactsRoot `
|
-IncidentArtifactsRoot $effectiveIncidentArtifactsRoot `
|
||||||
|
-EvtxExportRoot $effectiveEvtxExportRoot `
|
||||||
|
-EvtxRetentionDays $effectiveEvtxRetentionDays `
|
||||||
|
-EvtxChannels $effectiveEvtxChannels `
|
||||||
-LogonMarkerEnabled $effectiveLogonMarkerEnabled `
|
-LogonMarkerEnabled $effectiveLogonMarkerEnabled `
|
||||||
-AwHostname $effectiveAwHostname `
|
-AwHostname $effectiveAwHostname `
|
||||||
-PolicyMode $effectivePolicyMode `
|
-PolicyMode $effectivePolicyMode `
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ $collectorScript = [string]$config.paths.collectorScript
|
|||||||
$endpointCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$config.paths.endpointCollectorScript } else { Join-Path $stateRoot 'dlp-endpoint-signals-collector.ps1' }
|
$endpointCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$config.paths.endpointCollectorScript } else { Join-Path $stateRoot 'dlp-endpoint-signals-collector.ps1' }
|
||||||
$fileCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$config.paths.fileCollectorScript } else { Join-Path $stateRoot 'file-operations-collector.ps1' }
|
$fileCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'fileCollectorScript') { [string]$config.paths.fileCollectorScript } else { Join-Path $stateRoot 'file-operations-collector.ps1' }
|
||||||
$sessionCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$config.paths.sessionCollectorScript } else { Join-Path $stateRoot 'worktime-session-collector.ps1' }
|
$sessionCollectorScript = if ($config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$config.paths.sessionCollectorScript } else { Join-Path $stateRoot 'worktime-session-collector.ps1' }
|
||||||
|
$evtxExportScript = if ($config.paths.PSObject.Properties.Name -contains 'evtxExportScript') { [string]$config.paths.evtxExportScript } else { Join-Path $stateRoot 'export-evtx-for-hayabusa.ps1' }
|
||||||
$rulesPath = [string]$config.paths.rulesPath
|
$rulesPath = [string]$config.paths.rulesPath
|
||||||
$policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$config.paths.policyPath } else { Join-Path $stateRoot 'dlp-policy.json' }
|
$policyPath = if ($config.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$config.paths.policyPath } else { Join-Path $stateRoot 'dlp-policy.json' }
|
||||||
$policyClientScript = if ($config.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$config.paths.policyClientScript } else { Join-Path $stateRoot 'dlp-policy-client.ps1' }
|
$policyClientScript = if ($config.paths.PSObject.Properties.Name -contains 'policyClientScript') { [string]$config.paths.policyClientScript } else { Join-Path $stateRoot 'dlp-policy-client.ps1' }
|
||||||
@@ -43,6 +44,7 @@ $requiredFiles = @(
|
|||||||
$collectorScript,
|
$collectorScript,
|
||||||
$endpointCollectorScript,
|
$endpointCollectorScript,
|
||||||
$sessionCollectorScript,
|
$sessionCollectorScript,
|
||||||
|
$evtxExportScript,
|
||||||
$rulesPath,
|
$rulesPath,
|
||||||
$policyPath,
|
$policyPath,
|
||||||
$policyClientScript,
|
$policyClientScript,
|
||||||
@@ -202,8 +204,20 @@ $result = [ordered]@{
|
|||||||
jobTitlePolicyEnabled = $printJobTitlePolicyEnabled
|
jobTitlePolicyEnabled = $printJobTitlePolicyEnabled
|
||||||
ok = [bool]($printServiceOperationalEnabled -and $printJobTitlePolicyEnabled)
|
ok = [bool]($printServiceOperationalEnabled -and $printJobTitlePolicyEnabled)
|
||||||
}
|
}
|
||||||
|
forensics = [ordered]@{
|
||||||
|
evtxExportRoot = if ($config.PSObject.Properties.Name -contains 'forensics' -and $config.forensics.PSObject.Properties.Name -contains 'evtxExportRoot') { [string]$config.forensics.evtxExportRoot } else { $null }
|
||||||
|
retentionDays = if ($config.PSObject.Properties.Name -contains 'forensics' -and $config.forensics.PSObject.Properties.Name -contains 'retentionDays') { [int]$config.forensics.retentionDays } else { $null }
|
||||||
|
evtxChannels = if ($config.PSObject.Properties.Name -contains 'forensics' -and $config.forensics.PSObject.Properties.Name -contains 'evtxChannels') { @($config.forensics.evtxChannels) } else { @() }
|
||||||
|
ok = [bool](
|
||||||
|
($config.PSObject.Properties.Name -contains 'forensics') -and
|
||||||
|
($config.forensics.PSObject.Properties.Name -contains 'evtxExportRoot') -and
|
||||||
|
($config.forensics.PSObject.Properties.Name -contains 'retentionDays') -and
|
||||||
|
($config.forensics.PSObject.Properties.Name -contains 'evtxChannels') -and
|
||||||
|
(@($config.forensics.evtxChannels).Count -gt 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok -and $result.printTelemetry.ok)
|
$result.overallOk = [bool]($result.files.ok -and $result.tasks.ok -and $result.processes.ok -and $result.printTelemetry.ok -and $result.forensics.ok)
|
||||||
|
|
||||||
$result
|
$result
|
||||||
|
|||||||
Reference in New Issue
Block a user