260 lines
12 KiB
YAML
260 lines
12 KiB
YAML
---
|
||
- name: Развернуть Windows/RDP collector'ы AWatch-rus
|
||
hosts: aw_windows
|
||
gather_facts: false
|
||
|
||
vars:
|
||
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
|
||
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
|
||
aw_windows_server_scheme: "http"
|
||
aw_windows_server_host: "10.10.10.13"
|
||
aw_windows_server_port: 5600
|
||
aw_windows_package_version: "v0.13.2"
|
||
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
|
||
aw_windows_package_zip_path: ""
|
||
aw_windows_domain: "SHARKON2025"
|
||
aw_windows_users:
|
||
- user1
|
||
- user2
|
||
- user3
|
||
- user4
|
||
- user5
|
||
aw_windows_extra_users: []
|
||
aw_windows_users_effective: "{{ (aw_windows_users + aw_windows_extra_users) | unique }}"
|
||
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
|
||
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
|
||
aw_windows_afk_enabled: true
|
||
aw_windows_window_enabled: true
|
||
aw_windows_file_ops_enabled: true
|
||
aw_windows_local_agent_logs_enabled: false
|
||
aw_windows_incident_capture_enabled: true
|
||
aw_windows_incident_screenshot_enabled: true
|
||
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
|
||
aw_windows_logon_marker_enabled: true
|
||
aw_windows_skip_hardening: false
|
||
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
|
||
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
|
||
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
|
||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation"
|
||
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
|
||
aw_windows_recovery_task_name: "ActivityWatch Recovery"
|
||
aw_windows_force_task_restart: true
|
||
aw_windows_api_smoke_check_enabled: true
|
||
aw_windows_api_smoke_check_bucket: ""
|
||
aw_windows_api_smoke_check_limit: 10
|
||
aw_windows_fail_on_validation_error: true
|
||
aw_windows_migration_enabled: true
|
||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
|
||
|
||
tasks:
|
||
- name: Проверить обязательные переменные
|
||
ansible.builtin.assert:
|
||
that:
|
||
- aw_windows_server_host is defined
|
||
- aw_windows_server_port is defined
|
||
- aw_windows_server_scheme is defined
|
||
- aw_windows_domain is defined
|
||
- aw_windows_users_effective | length > 0
|
||
- aw_windows_install_root is defined
|
||
- aw_windows_state_root is defined
|
||
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
|
||
|
||
- name: Создать каталоги развёртывания
|
||
ansible.windows.win_file:
|
||
path: "{{ item }}"
|
||
state: directory
|
||
loop:
|
||
- "{{ aw_windows_deploy_root }}"
|
||
- "{{ aw_windows_deploy_root }}\\windows"
|
||
|
||
- name: Загрузить Windows toolkit развёртывания
|
||
ansible.windows.win_copy:
|
||
src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
|
||
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
|
||
loop:
|
||
- ActivityWatch.Windows.Common.psd1
|
||
- ActivityWatch.Windows.Common.psm1
|
||
- browser-domains-native-collector.ps1
|
||
- dlp-endpoint-signals-collector.ps1
|
||
- email-outbound-collector.ps1
|
||
- file-operations-collector.ps1
|
||
- worktime-session-collector.ps1
|
||
- migrate-awatch-rus-paths.ps1
|
||
- deploy-domain-users.ps1
|
||
- deploy-ensemble.ps1
|
||
- hardening-recovery.ps1
|
||
- validate-deployment.ps1
|
||
- web-category-rules.example.json
|
||
- dlp-policy.example.json
|
||
|
||
- name: Нормализовать кодировку PowerShell файлов (UTF-8 BOM для Windows PowerShell)
|
||
ansible.windows.win_powershell:
|
||
script: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$toolkitDir = "{{ aw_windows_deploy_root }}\windows"
|
||
$encIn = New-Object System.Text.UTF8Encoding($false)
|
||
$encOut = New-Object System.Text.UTF8Encoding($true)
|
||
Get-ChildItem -LiteralPath $toolkitDir -File -Include *.ps1,*.psm1,*.psd1 | ForEach-Object {
|
||
$text = [System.IO.File]::ReadAllText($_.FullName, $encIn)
|
||
[System.IO.File]::WriteAllText($_.FullName, $text, $encOut)
|
||
}
|
||
|
||
- name: Загрузить список пользователей для доменного развёртывания
|
||
ansible.windows.win_copy:
|
||
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
|
||
content: |
|
||
{% for user in aw_windows_users_effective -%}
|
||
{{ user }}
|
||
{% endfor -%}
|
||
|
||
- name: Проверить нужен ли migration с legacy ActivityWatch путей
|
||
when: aw_windows_migration_enabled | bool
|
||
ansible.windows.win_stat:
|
||
path: "{{ aw_windows_legacy_state_root }}\\deployment-config.json"
|
||
register: aw_windows_legacy_config
|
||
|
||
- name: Выполнить безопасную migration legacy prod в AWatch-rus
|
||
when:
|
||
- aw_windows_migration_enabled | bool
|
||
- aw_windows_legacy_config.stat.exists | default(false)
|
||
ansible.windows.win_powershell:
|
||
script: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$result = & "{{ aw_windows_deploy_root }}\windows\migrate-awatch-rus-paths.ps1" `
|
||
-OldInstallRoot "{{ aw_windows_legacy_install_root }}" `
|
||
-OldStateRoot "{{ aw_windows_legacy_state_root }}" `
|
||
-NewInstallRoot "{{ aw_windows_install_root }}" `
|
||
-NewStateRoot "{{ aw_windows_state_root }}" `
|
||
-ToolkitRoot "{{ aw_windows_deploy_root }}\windows"
|
||
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_migration_report_remote_path }}" -Encoding utf8
|
||
|
||
- name: Запустить Windows/RDP ensemble развёртывание
|
||
ansible.windows.win_powershell:
|
||
script: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$params = @{
|
||
ServerScheme = "{{ aw_windows_server_scheme }}"
|
||
ServerHost = "{{ aw_windows_server_host }}"
|
||
ServerPort = {{ aw_windows_server_port }}
|
||
Version = "{{ aw_windows_package_version }}"
|
||
Domain = "{{ aw_windows_domain }}"
|
||
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
|
||
InstallRoot = "{{ aw_windows_install_root }}"
|
||
StateRoot = "{{ aw_windows_state_root }}"
|
||
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled | bool) else '$false' }}
|
||
WindowEnabled = {{ '$true' if (aw_windows_window_enabled | bool) else '$false' }}
|
||
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
|
||
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_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' }}
|
||
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
|
||
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
|
||
CustomRulesPath = "{{ aw_windows_rules_path }}"
|
||
CustomPolicyPath = "{{ aw_windows_policy_path }}"
|
||
}
|
||
{% if (aw_windows_package_url | default('') | string | length) > 0 %}
|
||
$params.PackageUrl = "{{ aw_windows_package_url }}"
|
||
{% endif %}
|
||
{% if (aw_windows_package_zip_path | default('') | string | length) > 0 %}
|
||
$params.PackageZipPath = "{{ aw_windows_package_zip_path }}"
|
||
{% endif %}
|
||
{% if aw_windows_skip_hardening | bool %}
|
||
$params.SkipHardening = $true
|
||
{% endif %}
|
||
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
|
||
|
||
- name: Удалить лишние ActivityWatch Launch tasks вне текущего deployment-config
|
||
ansible.windows.win_powershell:
|
||
script: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json
|
||
$desired = @($config.userTasks | ForEach-Object { [string]$_.LaunchTaskName })
|
||
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch *' })) {
|
||
if ($desired -notcontains [string]$task.TaskName) {
|
||
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||
& cmd.exe /c "schtasks /Delete /TN `"$($task.TaskName)`" /F >nul 2>&1" | Out-Null
|
||
}
|
||
}
|
||
|
||
- name: Принудительно запустить ActivityWatch recovery и launch tasks
|
||
when: aw_windows_force_task_restart | bool
|
||
ansible.windows.win_powershell:
|
||
script: |
|
||
$ErrorActionPreference = 'Stop'
|
||
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
|
||
Get-ScheduledTask |
|
||
Where-Object TaskName -like "{{ aw_windows_launch_task_pattern }}" |
|
||
ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
|
||
|
||
- name: Получить Windows hostname для AW smoke-check bucket
|
||
when:
|
||
- aw_windows_api_smoke_check_enabled | bool
|
||
- aw_windows_afk_enabled | bool
|
||
ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME"
|
||
register: aw_windows_hostname_result
|
||
changed_when: false
|
||
|
||
- name: Вычислить AW AFK smoke-check bucket
|
||
when:
|
||
- aw_windows_api_smoke_check_enabled | bool
|
||
- aw_windows_afk_enabled | bool
|
||
- aw_windows_hostname_result.stdout is defined
|
||
ansible.builtin.set_fact:
|
||
aw_windows_api_smoke_check_bucket_effective: >-
|
||
{{
|
||
aw_windows_api_smoke_check_bucket
|
||
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
|
||
else 'aw-watcher-afk_' ~ (aw_windows_hostname_result.stdout | trim)
|
||
}}
|
||
|
||
- name: Выполнить AW API smoke-check (проверка наличия свежих событий в AFK бакете)
|
||
when:
|
||
- aw_windows_api_smoke_check_enabled | bool
|
||
- aw_windows_afk_enabled | bool
|
||
ansible.builtin.uri:
|
||
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
|
||
method: GET
|
||
status_code: 200
|
||
register: aw_windows_api_smoke_result
|
||
until: aw_windows_api_smoke_result.json | length > 0
|
||
retries: 5
|
||
delay: 5
|
||
ignore_errors: true
|
||
|
||
- name: Валидировать развёртывание на эндпоинте
|
||
ansible.windows.win_powershell:
|
||
script: |
|
||
$ErrorActionPreference = 'Stop'
|
||
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
|
||
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
|
||
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
|
||
return $result
|
||
|
||
- name: Создать локальную директорию для отчётов валидации
|
||
ansible.builtin.file:
|
||
path: "{{ aw_windows_validation_local_dir }}"
|
||
state: directory
|
||
mode: "0755"
|
||
delegate_to: localhost
|
||
|
||
- name: Стянуть отчёт валидации с эндпоинта
|
||
ansible.builtin.fetch:
|
||
src: "{{ aw_windows_validation_remote_path }}"
|
||
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
|
||
flat: true
|
||
|
||
- name: Проверить статус валидации
|
||
ansible.builtin.shell: |
|
||
python3 - <<'PY'
|
||
import json, sys
|
||
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r') as f:
|
||
data = json.load(f)
|
||
if not data.get('overallOk', False):
|
||
print(f"Validation failed for {{ inventory_hostname }}: {data.get('summary', 'Unknown error')}")
|
||
sys.exit(1)
|
||
PY
|
||
delegate_to: localhost
|
||
when: aw_windows_fail_on_validation_error | bool
|