diff --git a/README.md b/README.md index 04bbe3a..06fd1f1 100755 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ - `docs/codebase-onboarding.md` — обзор структуры репозитория и маршрут изучения для новичка. - `docs/deployment.md` — пошаговый деплой LXC и ActivityWatch Server. - `docs/runbook.md` — быстрый runbook для оператора. +- `docs/security-analytics-stack-v1.md` — текущий security analytics контур: Hayabusa, auto-case, scoring и Telegram alerts. - `docs/operations.md` — регламент сопровождения, бэкапов, обновлений и rollback. - `docs/GRAFANA_DASHBOARDS_RU.md` — импорт и сопровождение Grafana dashboard'ов через Ansible API playbook. - `docs/PRESENTATION_RU.md` — презентационные экраны Grafana и AW-rus со скриншотами. +- `docs/1C_FILE_ANALYTICS_STACK_RU.md` — новый ClickHouse/Grafana/AI Investigator контур для файловой 1С. - `docs/windows/ensemble.md` — orchestration-пакет для Windows-деплоя и проверки. - `docs/linux-client.md` — user-space rollout Linux-клиента ActivityWatch на удалённый `AW server`. - `docs/linux-remote-worker.md` — полный Linux remote-worker stack: GUI, SSH/console и browser admin UI вроде Proxmox `:8006`. @@ -23,6 +25,7 @@ - `aw-server/` — установочные скрипты, env-шаблон, systemd unit и RU patch для Web UI. - `ansible/` — Ansible-ensemble для автоматизированного сервера (Debian/CT). - `grafana/` — version-controlled Grafana dashboard JSON для RDP/worktime, DLP/ИБ и overview-экранов. +- `clickhouse-1c/` — отдельный analytics stack для **файловой 1С**: ETL, ClickHouse schema, detections, Grafana catalog и AI Investigator contract. - `pfsense/` — внешний poller для pfSense API и systemd unit под Debian/Ubuntu utility VM. - `windows/` — PowerShell toolkit: single-user, domain-users, ensemble orchestration, hardening/recovery, validation, Windows/RDP DLP telemetry (`aw-dlp-incidents_*`, `aw-dlp-endpoint-signals_*`) и session-level presence для удалённых Windows/RDP пользователей (`aw-worktime-sessions_*`). - `scripts/quality-gate.sh` — локальный preflight-пайплайн проверок. @@ -44,6 +47,30 @@ 8. Развернуть Windows-клиентов через `windows/deploy-ensemble.ps1`. 9. Проверить итог через `windows/validate-deployment.ps1`. +## Текущий security analytics контур + +Сейчас в `AW-rus` уже есть замкнутый forensic-контур: + +- Windows-хост раз в `6` часов делает `EVTX export + upload`; +- `aw-hayabusa-drop.path` автоматически подхватывает новый пакет; +- `aw-hayabusa` строит forensic-отчёт; +- `aw-hayabusa-case-alert` считает severity и score; +- при уровне от `medium` создаётся или обновляется case; +- при уровне от `high` уходит Telegram alert; +- в case пишется только bounded metadata, без сырых EVTX и полного timeline body. + +Практический операторский вход: + +```powershell +powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -CaseId 30 +``` + +Подробности: + +- `docs/security-analytics-stack-v1.md` +- `docs/runbook.md` +- `docs/hayabusa-operator-ib-guide-2026-05-14.md` + Для полного Ansible-сценария “с нуля” в Proxmox используйте: - `ansible/provision_proxmox_ct_and_deploy_aw.yml` @@ -61,6 +88,7 @@ - `ansible/deploy_grafana_dashboards.yml` - `docs/GRAFANA_DASHBOARDS_RU.md` +- `docs/1C_FILE_ANALYTICS_STACK_RU.md` Для Linux desktop/admin host, который должен слать watcher'ы на удалённый AW server: diff --git a/ansible/README.md b/ansible/README.md index b8fbe87..fee2558 100644 --- a/ansible/README.md +++ b/ansible/README.md @@ -122,6 +122,7 @@ Playbook: - выполняет API smoke-check bucket `aw-watcher-window_` и ожидает свежие события (по умолчанию включено); - запускает `validate-deployment.ps1`; - забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation-` по умолчанию). +- настраивает scheduled task `ActivityWatch Hayabusa Upload` с периодом и lookback по vars. Дополнительные флаги: @@ -144,6 +145,38 @@ Playbook: - `aw_windows_api_smoke_check_min_events: 1` — минимум событий, ожидаемых в smoke-check; - `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`; - `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта. +- `aw_windows_hayabusa_auto_upload_enabled: true` — включить авто-upload EVTX на AW-server; +- `aw_windows_hayabusa_auto_upload_interval_hours: 6` — период scheduled task; +- `aw_windows_hayabusa_auto_upload_hours_back: 6` — lookback для каждого запуска; +- `aw_windows_hayabusa_auto_upload_mode: "incident"` — mode для server-side processing; +- `aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"` — имя scheduled task. + +## Server-side Hayabusa auto-case и Telegram alerting + +На стороне `deploy_aw_server.yml` теперь есть server-side контур: + +- `aw-hayabusa-drop.path` +- `aw-hayabusa-drop.service` +- `aw-hayabusa-autoprocess` +- `aw-hayabusa-case-alert` + +Что делает контур: + +- автоматически подхватывает `zip` из `/opt/activitywatch/aw-rus-ops/drop`; +- запускает `aw-hayabusa`; +- считает severity/score по `timeline.jsonl`; +- создаёт или обновляет case; +- пишет bounded metadata в `forensics.hayabusa`; +- отправляет Telegram alert. + +Основные vars: + +- `aw_hayabusa_auto_case_enabled: true` +- `aw_hayabusa_auto_case_min_severity: "medium"` +- `aw_hayabusa_telegram_enabled: true` +- `aw_hayabusa_telegram_min_severity: "high"` +- `aw_hayabusa_telegram_bot_token` +- `aw_hayabusa_telegram_chat_ids` ## Развёртывание pfSense poller diff --git a/clickhouse-1c/.env.example b/clickhouse-1c/.env.example new file mode 100644 index 0000000..e2a7a2a --- /dev/null +++ b/clickhouse-1c/.env.example @@ -0,0 +1,9 @@ +CLICKHOUSE_DB=analytics_1c +CLICKHOUSE_USER=default +CLICKHOUSE_PASSWORD=change-me +CLICKHOUSE_PORT=8123 +CLICKHOUSE_NATIVE_PORT=9000 +GRAFANA_ADMIN_USER=admin +GRAFANA_ADMIN_PASSWORD=change-me +GRAFANA_PORT=3300 +CLICKHOUSE_HOST=clickhouse diff --git a/clickhouse-1c/README.md b/clickhouse-1c/README.md new file mode 100644 index 0000000..908913e --- /dev/null +++ b/clickhouse-1c/README.md @@ -0,0 +1,125 @@ +# file-1C analytics stack for AW-rus + +Этот каталог — отдельный production scaffold для **файловой 1С**. + +Он не заменяет старый `grafana-1c/` контур и не ломает его. Старый контур +остаётся для случаев, когда 1С даёт SQL/read-only views и удобна схема +`sql-exporter -> Prometheus -> Grafana`. + +Этот новый контур нужен именно тогда, когда: + +- 1С работает как **файловая база** на Windows/RDP host; +- на сам RDP host нежелательно ставить тяжёлые сервисы; +- нужны нормальные расследования, timeline, detections и cases; +- Grafana должна строиться не только по KPI, а по аудиту и аномалиям. + +## Целевая схема + +```text +File 1C + reglog + host telemetry + ↓ + raw landing + ↓ + ETL / normalize / enrich + ↓ + ClickHouse + ├─ raw_* + ├─ documents + ├─ postings + ├─ reglog_events + ├─ audit_events + ├─ host_events + ├─ entity_timeline + ├─ detections + └─ cases + ↓ + Grafana + Alerting + AI Investigator +``` + +## Что внутри + +- `docker-compose.yml` — локальный scaffold ClickHouse + Grafana. +- `.env.example` — переменные окружения. +- `clickhouse/init/*.sql` — схема БД. +- `etl/load_1c_exports.py` — loader CSV/JSON выгрузок в raw/core таблицы. +- `etl/config.example.yml` — пример ETL-конфига. +- `detections/rules.yml` — каталог правил detections. +- `detections/insert_detections.sql` — SQL-шаблоны rule-based detections. +- `grafana/dashboard-catalog.md` — целевая структура дашбордов. +- `grafana/query-pack.sql` — базовые SQL-запросы для панелей. +- `grafana/provisioning/datasources/clickhouse.yml` — provisioned datasource для Grafana. +- `detections/build_entity_timeline.sql` — сборка единого timeline слоя. +- `detections/open_cases_from_detections.sql` — шаблон открытия cases из detections. +- `ops/etl-cron.example` — пример расписания каждые 6 часов. +- `ops/retention-policy.md` — минимальная retention policy. +- `ai/INVESTIGATOR_API.md` — контракт AI Investigator поверх ClickHouse/cases. + +## Когда использовать именно этот контур + +Используй `clickhouse-1c/`, если: + +- 1С файловая; +- нужен контур `аудит -> detections -> cases -> timeline`; +- нужен drill-down в расследование, а не только KPI панели; +- данные можно выгружать из 1С в `CSV/JSON`, а не читать напрямую SQL exporter'ом. + +Используй `grafana-1c/`, если: + +- 1С даёт стабильный SQL/read-only доступ; +- достаточно KPI/Prometheus/Grafana; +- не нужен полноценный case-oriented audit stack. + +## Быстрый старт + +1. Скопировать env: + +```bash +cd clickhouse-1c +cp .env.example .env +``` + +2. Поднять ClickHouse + Grafana: + +```bash +docker compose up -d +``` + +3. Инициализировать landing-каталоги и ETL config: + +```bash +mkdir -p landing/{documents,postings,reglog,audit,host} +cp etl/config.example.yml etl/config.yml +``` + +4. Запустить ETL: + +```bash +python3 -m venv .venv +. .venv/bin/activate +pip install -r etl/requirements.txt +python etl/load_1c_exports.py --config etl/config.yml +``` + +5. Применить detections: + +```bash +clickhouse-client --queries-file detections/insert_detections.sql +``` + +6. В Grafana строить dashboards из `grafana/dashboard-catalog.md` и +`grafana/query-pack.sql`. + +## Ожидаемые источники данных + +- выгрузки 1С по документам; +- выгрузки движений/проводок; +- журнал регистрации 1С; +- audit/export критичных изменений; +- host telemetry с Windows/RDP host. + +## Границы + +- AI Investigator не пишет в 1С; +- LLM не ходит прямо в production 1С; +- в ClickHouse кладутся нормализованные выгрузки и enrichment; +- case/timeline слой считается вне 1С. diff --git a/clickhouse-1c/ai/INVESTIGATOR_API.md b/clickhouse-1c/ai/INVESTIGATOR_API.md new file mode 100644 index 0000000..56f821d --- /dev/null +++ b/clickhouse-1c/ai/INVESTIGATOR_API.md @@ -0,0 +1,80 @@ +# AI Investigator API contract + +AI Investigator не должен ходить напрямую в файловую 1С. + +Его правильный слой: + +- ClickHouse (`analytics_1c.*`) +- DLP/forensics case API +- bounded search/timeline endpoints + +## Базовые use-cases + +- почему выросли ошибки по базе; +- какие пользователи дали риск за сутки; +- что произошло по case `X`; +- собрать summary по entity timeline; +- предложить next steps без write-действий. + +## Рекомендуемые API endpoints + +### `GET /api/1/analytics-1c/summary` + +Параметры: + +- `infobase` +- `from` +- `to` + +Возвращает: + +- sales +- returns +- overdue_receivables +- detections_by_severity +- open_cases + +### `GET /api/1/analytics-1c/detections` + +Фильтры: + +- `infobase` +- `severity` +- `rule_id` +- `entity_type` +- `entity_id` + +### `GET /api/1/analytics-1c/timeline` + +Фильтры: + +- `entity_type` +- `entity_id` +- `from` +- `to` + +### `GET /api/1/analytics-1c/cases/{case_id}` + +Возвращает: + +- case card +- related detections +- related timeline rows + +## Guardrails + +- read-only SQL; +- whitelist queries; +- no direct write-back into 1С; +- no direct execution of arbitrary SQL from prompt; +- all investigator requests are logged. + +## Output style + +AI Investigator должен выдавать: + +1. краткую суть; +2. что именно найдено; +3. почему это важно; +4. что проверить дальше; +5. ссылки на case/timeline. diff --git a/clickhouse-1c/clickhouse/init/00_database.sql b/clickhouse-1c/clickhouse/init/00_database.sql new file mode 100644 index 0000000..1506c02 --- /dev/null +++ b/clickhouse-1c/clickhouse/init/00_database.sql @@ -0,0 +1 @@ +CREATE DATABASE IF NOT EXISTS analytics_1c; diff --git a/clickhouse-1c/clickhouse/init/01_raw_tables.sql b/clickhouse-1c/clickhouse/init/01_raw_tables.sql new file mode 100644 index 0000000..1093fe2 --- /dev/null +++ b/clickhouse-1c/clickhouse/init/01_raw_tables.sql @@ -0,0 +1,44 @@ +CREATE TABLE IF NOT EXISTS analytics_1c.raw_1c_documents +( + ingested_at DateTime DEFAULT now(), + source_file String, + payload String +) +ENGINE = MergeTree +ORDER BY (ingested_at, source_file); + +CREATE TABLE IF NOT EXISTS analytics_1c.raw_1c_postings +( + ingested_at DateTime DEFAULT now(), + source_file String, + payload String +) +ENGINE = MergeTree +ORDER BY (ingested_at, source_file); + +CREATE TABLE IF NOT EXISTS analytics_1c.raw_reglog +( + ingested_at DateTime DEFAULT now(), + source_file String, + payload String +) +ENGINE = MergeTree +ORDER BY (ingested_at, source_file); + +CREATE TABLE IF NOT EXISTS analytics_1c.raw_audit +( + ingested_at DateTime DEFAULT now(), + source_file String, + payload String +) +ENGINE = MergeTree +ORDER BY (ingested_at, source_file); + +CREATE TABLE IF NOT EXISTS analytics_1c.raw_host_metrics +( + ingested_at DateTime DEFAULT now(), + source_file String, + payload String +) +ENGINE = MergeTree +ORDER BY (ingested_at, source_file); diff --git a/clickhouse-1c/clickhouse/init/02_core_tables.sql b/clickhouse-1c/clickhouse/init/02_core_tables.sql new file mode 100644 index 0000000..7666259 --- /dev/null +++ b/clickhouse-1c/clickhouse/init/02_core_tables.sql @@ -0,0 +1,132 @@ +CREATE TABLE IF NOT EXISTS analytics_1c.documents +( + ts DateTime, + infobase LowCardinality(String), + organization String, + department String, + doc_type LowCardinality(String), + doc_id String, + doc_number String, + author String, + counterparty String, + operation_type String, + amount Decimal(18, 2), + status LowCardinality(String), + posted UInt8, + source_file String +) +ENGINE = MergeTree +ORDER BY (infobase, ts, doc_type, doc_id); + +CREATE TABLE IF NOT EXISTS analytics_1c.postings +( + ts DateTime, + infobase LowCardinality(String), + registrar String, + operation_type String, + account_dt String, + account_ct String, + amount Decimal(18, 2), + source_file String +) +ENGINE = MergeTree +ORDER BY (infobase, ts, registrar); + +CREATE TABLE IF NOT EXISTS analytics_1c.reglog_events +( + ts DateTime, + infobase LowCardinality(String), + user String, + host String, + app String, + event_name String, + level LowCardinality(String), + duration_ms UInt32, + message String, + source_file String +) +ENGINE = MergeTree +ORDER BY (infobase, ts, user, event_name); + +CREATE TABLE IF NOT EXISTS analytics_1c.audit_events +( + ts DateTime, + infobase LowCardinality(String), + user String, + object_type String, + object_id String, + action String, + before_hash String, + after_hash String, + risk_tag String, + source_file String +) +ENGINE = MergeTree +ORDER BY (infobase, ts, object_type, object_id); + +CREATE TABLE IF NOT EXISTS analytics_1c.host_events +( + ts DateTime, + host String, + cpu_pct Float32, + ram_pct Float32, + disk_free_gb Float32, + disk_latency_ms Float32, + smb_errors UInt32, + rdp_sessions UInt32, + backup_ok UInt8, + source_file String +) +ENGINE = MergeTree +ORDER BY (host, ts); + +CREATE TABLE IF NOT EXISTS analytics_1c.entity_timeline +( + ts DateTime, + entity_type LowCardinality(String), + entity_id String, + infobase LowCardinality(String), + actor String, + source LowCardinality(String), + event_type String, + severity LowCardinality(String), + score UInt32, + ref_id String, + summary String +) +ENGINE = MergeTree +ORDER BY (entity_type, entity_id, ts); + +CREATE TABLE IF NOT EXISTS analytics_1c.detections +( + ts DateTime, + detection_id String, + infobase LowCardinality(String), + rule_id String, + rule_title String, + entity_type LowCardinality(String), + entity_id String, + severity LowCardinality(String), + score UInt32, + summary String, + status LowCardinality(String) DEFAULT 'open' +) +ENGINE = MergeTree +ORDER BY (severity, ts, rule_id, entity_type, entity_id); + +CREATE TABLE IF NOT EXISTS analytics_1c.cases +( + opened_at DateTime, + case_id String, + infobase LowCardinality(String), + title String, + severity LowCardinality(String), + status LowCardinality(String), + assignee String, + detection_id String, + entity_type String, + entity_id String, + summary String +) +ENGINE = MergeTree +ORDER BY (opened_at, case_id); diff --git a/clickhouse-1c/clickhouse/init/03_views.sql b/clickhouse-1c/clickhouse/init/03_views.sql new file mode 100644 index 0000000..61fc03d --- /dev/null +++ b/clickhouse-1c/clickhouse/init/03_views.sql @@ -0,0 +1,26 @@ +CREATE VIEW IF NOT EXISTS analytics_1c.v_documents_daily AS +SELECT + toDate(ts) AS d, + infobase, + organization, + doc_type, + count() AS docs_total, + sum(amount) AS amount_total, + sumIf(amount, posted = 0) AS unposted_amount +FROM analytics_1c.documents +GROUP BY d, infobase, organization, doc_type; + +CREATE VIEW IF NOT EXISTS analytics_1c.v_detections_daily AS +SELECT + toDate(ts) AS d, + infobase, + severity, + count() AS detections_total, + sum(score) AS score_total +FROM analytics_1c.detections +GROUP BY d, infobase, severity; + +CREATE VIEW IF NOT EXISTS analytics_1c.v_open_cases AS +SELECT * +FROM analytics_1c.cases +WHERE status != 'closed'; diff --git a/clickhouse-1c/detections/build_entity_timeline.sql b/clickhouse-1c/detections/build_entity_timeline.sql new file mode 100644 index 0000000..d93f9ff --- /dev/null +++ b/clickhouse-1c/detections/build_entity_timeline.sql @@ -0,0 +1,44 @@ +INSERT INTO analytics_1c.entity_timeline +SELECT + ts, + 'document' AS entity_type, + doc_id AS entity_id, + infobase, + author AS actor, + 'documents' AS source, + concat('document:', doc_type) AS event_type, + if(posted = 1, 'low', 'medium') AS severity, + if(posted = 1, 5, 20) AS score, + doc_id AS ref_id, + concat('Документ ', doc_type, ' №', doc_number, ' статус=', status) AS summary +FROM analytics_1c.documents; + +INSERT INTO analytics_1c.entity_timeline +SELECT + ts, + 'user' AS entity_type, + user AS entity_id, + infobase, + user AS actor, + 'reglog' AS source, + event_name AS event_type, + if(level IN ('error', 'warn'), 'medium', 'low') AS severity, + if(level IN ('error', 'warn'), 25, 5) AS score, + concat(user, ':', toString(toUnixTimestamp(ts))) AS ref_id, + message AS summary +FROM analytics_1c.reglog_events; + +INSERT INTO analytics_1c.entity_timeline +SELECT + ts, + object_type AS entity_type, + object_id AS entity_id, + infobase, + user AS actor, + 'audit' AS source, + action AS event_type, + if(risk_tag != '', 'high', 'medium') AS severity, + if(risk_tag != '', 60, 30) AS score, + concat(object_type, ':', object_id, ':', toString(toUnixTimestamp(ts))) AS ref_id, + concat('Audit action ', action, ' risk=', risk_tag) AS summary +FROM analytics_1c.audit_events; diff --git a/clickhouse-1c/detections/insert_detections.sql b/clickhouse-1c/detections/insert_detections.sql new file mode 100644 index 0000000..cbd2d82 --- /dev/null +++ b/clickhouse-1c/detections/insert_detections.sql @@ -0,0 +1,52 @@ +INSERT INTO analytics_1c.detections +SELECT + ts, + concat('after_hours_login:', infobase, ':', user, ':', toString(toUnixTimestamp(ts))) AS detection_id, + infobase, + 'after_hours_login' AS rule_id, + 'Вход вне рабочего времени' AS rule_title, + 'user' AS entity_type, + user AS entity_id, + 'medium' AS severity, + 35 AS score, + concat('Пользователь ', user, ' выполнил вход вне рабочего времени') AS summary, + 'open' AS status +FROM analytics_1c.reglog_events +WHERE event_name ILIKE '%login%' + AND toHour(ts) NOT BETWEEN 8 AND 20; + +INSERT INTO analytics_1c.detections +SELECT + max(ts) AS ts, + concat('failed_login_burst:', infobase, ':', user, ':', toString(toUnixTimestamp(max(ts)))) AS detection_id, + infobase, + 'failed_login_burst' AS rule_id, + 'Всплеск ошибок входа' AS rule_title, + 'user' AS entity_type, + user AS entity_id, + 'high' AS severity, + 65 AS score, + concat('У пользователя ', user, ' более 5 ошибок входа за 15 минут') AS summary, + 'open' AS status +FROM analytics_1c.reglog_events +WHERE level IN ('error', 'warn') + AND (event_name ILIKE '%login%' OR message ILIKE '%парол%' OR message ILIKE '%auth%') +GROUP BY infobase, user, toStartOfFifteenMinutes(ts) +HAVING count() >= 5; + +INSERT INTO analytics_1c.detections +SELECT + max(ts) AS ts, + concat('disk_latency_high:', host, ':', toString(toUnixTimestamp(max(ts)))) AS detection_id, + '' AS infobase, + 'disk_latency_high' AS rule_id, + 'Высокая задержка диска' AS rule_title, + 'host' AS entity_type, + host AS entity_id, + 'high' AS severity, + 65 AS score, + concat('На хосте ', host, ' задержка диска превышает 50 мс') AS summary, + 'open' AS status +FROM analytics_1c.host_events +GROUP BY host, toStartOfHour(ts) +HAVING avg(disk_latency_ms) > 50; diff --git a/clickhouse-1c/detections/open_cases_from_detections.sql b/clickhouse-1c/detections/open_cases_from_detections.sql new file mode 100644 index 0000000..2f436f1 --- /dev/null +++ b/clickhouse-1c/detections/open_cases_from_detections.sql @@ -0,0 +1,16 @@ +INSERT INTO analytics_1c.cases +SELECT + ts AS opened_at, + detection_id AS case_id, + infobase, + concat('1C ', upper(severity), ' · ', rule_title, ' · ', entity_id) AS title, + severity, + 'open' AS status, + '' AS assignee, + detection_id, + entity_type, + entity_id, + summary +FROM analytics_1c.detections +WHERE severity IN ('high', 'critical') + AND detection_id NOT IN (SELECT case_id FROM analytics_1c.cases); diff --git a/clickhouse-1c/detections/rules.yml b/clickhouse-1c/detections/rules.yml new file mode 100644 index 0000000..5e83bee --- /dev/null +++ b/clickhouse-1c/detections/rules.yml @@ -0,0 +1,61 @@ +rules: + - id: after_hours_login + severity: medium + score: 35 + source: reglog_events + summary: "Вход пользователя вне рабочего времени" + - id: failed_login_burst + severity: high + score: 65 + source: reglog_events + summary: "Всплеск ошибок входа за короткое окно" + - id: mass_reposting + severity: high + score: 70 + source: audit_events + summary: "Массовое перепроведение документов" + - id: critical_object_change + severity: high + score: 75 + source: audit_events + summary: "Изменение критичного объекта 1С" + - id: manual_adjustment_spike + severity: medium + score: 45 + source: documents + summary: "Аномальный рост ручных корректировок" + - id: returns_spike + severity: medium + score: 40 + source: documents + summary: "Аномальный рост возвратов" + - id: overdue_receivables_growth + severity: high + score: 55 + source: documents + summary: "Резкий рост просроченной дебиторки" + - id: long_running_operation + severity: medium + score: 30 + source: reglog_events + summary: "Длительная операция в журнале регистрации" + - id: exchange_failure_burst + severity: high + score: 60 + source: reglog_events + summary: "Всплеск ошибок обмена" + - id: disk_latency_high + severity: high + score: 65 + source: host_events + summary: "Высокая задержка диска на файловом сервере 1С" + - id: backup_stale + severity: critical + score: 90 + source: host_events + summary: "Бэкап файловой 1С устарел или не подтвержден" + - id: unusual_account_postings + severity: medium + score: 35 + source: postings + summary: "Нетипичные проводки по счетам" diff --git a/clickhouse-1c/docker-compose.yml b/clickhouse-1c/docker-compose.yml new file mode 100644 index 0000000..4bafdf9 --- /dev/null +++ b/clickhouse-1c/docker-compose.yml @@ -0,0 +1,35 @@ +services: + clickhouse: + image: clickhouse/clickhouse-server:24.8 + container_name: aw-rus-1c-clickhouse + restart: unless-stopped + environment: + CLICKHOUSE_DB: ${CLICKHOUSE_DB} + CLICKHOUSE_USER: ${CLICKHOUSE_USER} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} + ports: + - "${CLICKHOUSE_PORT}:8123" + - "${CLICKHOUSE_NATIVE_PORT}:9000" + volumes: + - ./clickhouse/init:/docker-entrypoint-initdb.d:ro + - clickhouse_1c_data:/var/lib/clickhouse + + grafana: + image: grafana/grafana:11.2.2 + container_name: aw-rus-1c-grafana + restart: unless-stopped + environment: + GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER} + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} + GF_INSTALL_PLUGINS: grafana-clickhouse-datasource + ports: + - "${GRAFANA_PORT}:3000" + volumes: + - grafana_1c_data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning:ro + depends_on: + - clickhouse + +volumes: + clickhouse_1c_data: + grafana_1c_data: diff --git a/clickhouse-1c/etl/config.example.yml b/clickhouse-1c/etl/config.example.yml new file mode 100644 index 0000000..861e451 --- /dev/null +++ b/clickhouse-1c/etl/config.example.yml @@ -0,0 +1,24 @@ +clickhouse: + host: localhost + port: 8123 + username: default + password: change-me + database: analytics_1c + +landing: + documents: ./landing/documents + postings: ./landing/postings + reglog: ./landing/reglog + audit: ./landing/audit + host: ./landing/host + +formats: + default: jsonl + documents: jsonl + postings: jsonl + reglog: jsonl + audit: jsonl + host: jsonl + +archive_dir: ./archive +delete_after_load: false diff --git a/clickhouse-1c/etl/load_1c_exports.py b/clickhouse-1c/etl/load_1c_exports.py new file mode 100644 index 0000000..4af29ef --- /dev/null +++ b/clickhouse-1c/etl/load_1c_exports.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import csv +import json +import shutil +from dataclasses import dataclass +from datetime import datetime +from pathlib import Path +from typing import Any + +import clickhouse_connect +import yaml +from dateutil import parser as date_parser + +RAW_TABLES = { + "documents": "raw_1c_documents", + "postings": "raw_1c_postings", + "reglog": "raw_reglog", + "audit": "raw_audit", + "host": "raw_host_metrics", +} + +CORE_TABLES = { + "documents": "documents", + "postings": "postings", + "reglog": "reglog_events", + "audit": "audit_events", + "host": "host_events", +} + + +@dataclass +class Config: + clickhouse: dict[str, Any] + landing: dict[str, str] + formats: dict[str, str] + archive_dir: str | None + delete_after_load: bool + + +def parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser(description="Load file-based 1C exports into ClickHouse") + p.add_argument("--config", required=True, help="Path to YAML config") + p.add_argument("--dataset", choices=["documents", "postings", "reglog", "audit", "host"], help="Load only one dataset") + return p.parse_args() + + +def load_config(path: str) -> Config: + raw = yaml.safe_load(Path(path).read_text(encoding="utf-8")) + return Config( + clickhouse=raw["clickhouse"], + landing=raw["landing"], + formats=raw.get("formats", {"default": "jsonl"}), + archive_dir=raw.get("archive_dir"), + delete_after_load=bool(raw.get("delete_after_load", False)), + ) + + +def ch_client(conf: Config): + return clickhouse_connect.get_client( + host=conf.clickhouse["host"], + port=conf.clickhouse.get("port", 8123), + username=conf.clickhouse.get("username", "default"), + password=conf.clickhouse.get("password", ""), + database=conf.clickhouse.get("database", "analytics_1c"), + ) + + +def normalize_ts(value: Any) -> datetime: + if isinstance(value, datetime): + return value + if value is None or value == "": + return datetime.utcnow() + return date_parser.parse(str(value)) + + +def iter_rows(path: Path, fmt: str) -> list[dict[str, Any]]: + if fmt == "jsonl": + return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] + if fmt == "json": + payload = json.loads(path.read_text(encoding="utf-8")) + return payload if isinstance(payload, list) else [payload] + if fmt == "csv": + with path.open("r", encoding="utf-8-sig", newline="") as fh: + return list(csv.DictReader(fh)) + raise ValueError(f"unsupported format: {fmt}") + + +def insert_raw(client, dataset: str, source_file: str, rows: list[dict[str, Any]]) -> None: + client.insert( + RAW_TABLES[dataset], + [[source_file, json.dumps(row, ensure_ascii=False)] for row in rows], + column_names=["source_file", "payload"], + ) + + +def map_core_row(dataset: str, source_file: str, row: dict[str, Any]) -> list[Any]: + if dataset == "documents": + return [ + normalize_ts(row.get("ts") or row.get("posted_at") or row.get("created_at")), + row.get("infobase", ""), + row.get("organization", ""), + row.get("department", ""), + row.get("doc_type", ""), + row.get("doc_id", ""), + row.get("doc_number", ""), + row.get("author", ""), + row.get("counterparty", ""), + row.get("operation_type", ""), + float(row.get("amount", 0) or 0), + row.get("status", ""), + int(row.get("posted", 0) or 0), + source_file, + ] + if dataset == "postings": + return [ + normalize_ts(row.get("ts")), + row.get("infobase", ""), + row.get("registrar", ""), + row.get("operation_type", ""), + row.get("account_dt", ""), + row.get("account_ct", ""), + float(row.get("amount", 0) or 0), + source_file, + ] + if dataset == "reglog": + return [ + normalize_ts(row.get("ts")), + row.get("infobase", ""), + row.get("user", ""), + row.get("host", ""), + row.get("app", ""), + row.get("event_name", ""), + row.get("level", "info"), + int(row.get("duration_ms", 0) or 0), + row.get("message", ""), + source_file, + ] + if dataset == "audit": + return [ + normalize_ts(row.get("ts")), + row.get("infobase", ""), + row.get("user", ""), + row.get("object_type", ""), + row.get("object_id", ""), + row.get("action", ""), + row.get("before_hash", ""), + row.get("after_hash", ""), + row.get("risk_tag", ""), + source_file, + ] + if dataset == "host": + return [ + normalize_ts(row.get("ts")), + row.get("host", ""), + float(row.get("cpu_pct", 0) or 0), + float(row.get("ram_pct", 0) or 0), + float(row.get("disk_free_gb", 0) or 0), + float(row.get("disk_latency_ms", 0) or 0), + int(row.get("smb_errors", 0) or 0), + int(row.get("rdp_sessions", 0) or 0), + int(row.get("backup_ok", 0) or 0), + source_file, + ] + raise ValueError(dataset) + + +def core_columns(dataset: str) -> list[str]: + if dataset == "documents": + return ["ts", "infobase", "organization", "department", "doc_type", "doc_id", "doc_number", "author", "counterparty", "operation_type", "amount", "status", "posted", "source_file"] + if dataset == "postings": + return ["ts", "infobase", "registrar", "operation_type", "account_dt", "account_ct", "amount", "source_file"] + if dataset == "reglog": + return ["ts", "infobase", "user", "host", "app", "event_name", "level", "duration_ms", "message", "source_file"] + if dataset == "audit": + return ["ts", "infobase", "user", "object_type", "object_id", "action", "before_hash", "after_hash", "risk_tag", "source_file"] + return ["ts", "host", "cpu_pct", "ram_pct", "disk_free_gb", "disk_latency_ms", "smb_errors", "rdp_sessions", "backup_ok", "source_file"] + + +def archive_or_delete(conf: Config, dataset: str, path: Path) -> None: + if conf.archive_dir: + archive_root = Path(conf.archive_dir) / dataset + archive_root.mkdir(parents=True, exist_ok=True) + shutil.move(str(path), archive_root / path.name) + return + if conf.delete_after_load: + path.unlink(missing_ok=True) + + +def main() -> int: + args = parse_args() + conf = load_config(args.config) + client = ch_client(conf) + datasets = [args.dataset] if args.dataset else list(RAW_TABLES) + + for dataset in datasets: + landing = Path(conf.landing[dataset]) + fmt = conf.formats.get(dataset, conf.formats.get("default", "jsonl")) + if not landing.exists(): + continue + for path in sorted(p for p in landing.iterdir() if p.is_file()): + rows = iter_rows(path, fmt) + if not rows: + archive_or_delete(conf, dataset, path) + continue + insert_raw(client, dataset, path.name, rows) + client.insert( + CORE_TABLES[dataset], + [map_core_row(dataset, path.name, row) for row in rows], + column_names=core_columns(dataset), + ) + archive_or_delete(conf, dataset, path) + print(f"loaded {dataset}: {path.name} rows={len(rows)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/clickhouse-1c/etl/requirements.txt b/clickhouse-1c/etl/requirements.txt new file mode 100644 index 0000000..f824425 --- /dev/null +++ b/clickhouse-1c/etl/requirements.txt @@ -0,0 +1,3 @@ +clickhouse-connect>=0.7.16 +PyYAML>=6.0.2 +python-dateutil>=2.9.0 diff --git a/clickhouse-1c/grafana/dashboard-catalog.md b/clickhouse-1c/grafana/dashboard-catalog.md new file mode 100644 index 0000000..4e5af22 --- /dev/null +++ b/clickhouse-1c/grafana/dashboard-catalog.md @@ -0,0 +1,135 @@ +# Grafana dashboard catalog for file-based 1C + +Ниже — целевой набор дашбордов. Это не “один giant dashboard”, а иерархия под +разные роли. + +## 1. 1C Executive Summary + +Для руководства и владельца процесса. + +Панели: + +- Выручка сегодня / 7 дней / 30 дней +- Просроченная дебиторка +- Возвраты и корректировки +- Открытые cases +- High/Critical detections +- Топ проблемных баз +- Топ риск-пользователей +- Тренд аномалий + +Основные таблицы: + +- `documents` +- `detections` +- `cases` + +## 2. 1C Operations Health + +Для сопровождения и инфраструктуры. + +Панели: + +- CPU / RAM / Disk +- Disk latency +- Свободное место +- RDP sessions +- Ошибки SMB / файлового доступа +- Ошибки журнала регистрации +- Длительные операции +- Backup freshness + +Основные таблицы: + +- `host_events` +- `reglog_events` + +## 3. 1C Audit Overview + +Для контроля, ИБ и внутреннего аудита. + +Панели: + +- Входы по пользователям +- Входы вне рабочего времени +- Изменения критичных объектов +- Массовые перепроведения +- Нетипичные корректировки +- Severity split +- Топ пользователей по risk score + +Основные таблицы: + +- `reglog_events` +- `audit_events` +- `detections` + +## 4. 1C Detections + +Для triage и контроля rules. + +Панели: + +- Detections by severity +- Top rules +- Cases opened by day +- Open detections +- Critical timeline +- Top entities + +Основные таблицы: + +- `detections` +- `cases` + +## 5. 1C Investigation Timeline + +Для ручного расследования. + +Панели: + +- Entity timeline +- Case drilldown +- User trace +- Document trace +- Related detections +- Raw event details + +Основные таблицы: + +- `entity_timeline` +- `detections` +- `cases` +- `reglog_events` +- `audit_events` +- `documents` + +## 6. 1C Data Quality + +Для контроля самого пайплайна. + +Панели: + +- Последняя выгрузка по каждой базе +- ETL lag +- Битые/пустые файлы +- Rows loaded per batch +- Ошибки ETL + +Источники: + +- ETL run log +- архив landing/ETL статусов + +## Единые variables + +Во все dashboards: + +- `infobase` +- `organization` +- `user` +- `host` +- `document_type` +- `severity` +- `case_status` +- `time range` diff --git a/clickhouse-1c/grafana/provisioning/datasources/clickhouse.yml b/clickhouse-1c/grafana/provisioning/datasources/clickhouse.yml new file mode 100644 index 0000000..0ccec5e --- /dev/null +++ b/clickhouse-1c/grafana/provisioning/datasources/clickhouse.yml @@ -0,0 +1,16 @@ +apiVersion: 1 + +datasources: + - name: clickhouse-1c + uid: clickhouse-1c + type: grafana-clickhouse-datasource + access: proxy + editable: true + jsonData: + defaultDatabase: analytics_1c + port: 8123 + server: clickhouse + protocol: http + username: ${CLICKHOUSE_USER} + secureJsonData: + password: ${CLICKHOUSE_PASSWORD} diff --git a/clickhouse-1c/grafana/query-pack.sql b/clickhouse-1c/grafana/query-pack.sql new file mode 100644 index 0000000..52ecdcb --- /dev/null +++ b/clickhouse-1c/grafana/query-pack.sql @@ -0,0 +1,66 @@ +-- Executive: sales today +SELECT + toDate(ts) AS d, + sum(amount) AS sales_amount +FROM analytics_1c.documents +WHERE doc_type = 'Реализация' +GROUP BY d +ORDER BY d; + +-- Executive: overdue receivables +SELECT + organization, + sum(amount) AS overdue_receivables +FROM analytics_1c.documents +WHERE status = 'overdue' +GROUP BY organization +ORDER BY overdue_receivables DESC; + +-- Operations: host health +SELECT + ts, + host, + cpu_pct, + ram_pct, + disk_latency_ms, + disk_free_gb +FROM analytics_1c.host_events +ORDER BY ts DESC; + +-- Audit: after-hours activity +SELECT + ts, + infobase, + user, + event_name, + message +FROM analytics_1c.reglog_events +WHERE event_name ILIKE '%login%' + AND toHour(ts) NOT BETWEEN 8 AND 20 +ORDER BY ts DESC; + +-- Detections: top rules +SELECT + rule_title, + severity, + count() AS detections_total, + sum(score) AS score_total +FROM analytics_1c.detections +GROUP BY rule_title, severity +ORDER BY detections_total DESC; + +-- Investigation: entity timeline +SELECT + ts, + entity_type, + entity_id, + actor, + source, + event_type, + severity, + score, + summary +FROM analytics_1c.entity_timeline +WHERE entity_type = {entity_type:String} + AND entity_id = {entity_id:String} +ORDER BY ts; diff --git a/clickhouse-1c/ops/etl-cron.example b/clickhouse-1c/ops/etl-cron.example new file mode 100644 index 0000000..e379fa7 --- /dev/null +++ b/clickhouse-1c/ops/etl-cron.example @@ -0,0 +1,5 @@ +# every 6 hours: load exports, rebuild timeline, refresh cases +0 */6 * * * cd /opt/aw-rus/clickhouse-1c && . .venv/bin/activate && python etl/load_1c_exports.py --config etl/config.yml >> /var/log/aw-rus-1c-etl.log 2>&1 +10 */6 * * * clickhouse-client --queries-file /opt/aw-rus/clickhouse-1c/detections/build_entity_timeline.sql >> /var/log/aw-rus-1c-timeline.log 2>&1 +15 */6 * * * clickhouse-client --queries-file /opt/aw-rus/clickhouse-1c/detections/insert_detections.sql >> /var/log/aw-rus-1c-detections.log 2>&1 +20 */6 * * * clickhouse-client --queries-file /opt/aw-rus/clickhouse-1c/detections/open_cases_from_detections.sql >> /var/log/aw-rus-1c-cases.log 2>&1 diff --git a/clickhouse-1c/ops/retention-policy.md b/clickhouse-1c/ops/retention-policy.md new file mode 100644 index 0000000..dfc1c3a --- /dev/null +++ b/clickhouse-1c/ops/retention-policy.md @@ -0,0 +1,11 @@ +# Retention policy + +Минимально разумный retention для файловой 1С analytics stack: + +- landing/raw exports: `30` дней +- archived raw files: `90` дней +- raw_* в ClickHouse: `30` дней +- core tables: `365` дней +- detections/cases/timeline: `365` дней или по регламенту ИБ + +Если регуляторика требует больше, меняется отдельно от Grafana UI. diff --git a/docs/1C_FILE_ANALYTICS_STACK_RU.md b/docs/1C_FILE_ANALYTICS_STACK_RU.md new file mode 100644 index 0000000..d4a27d3 --- /dev/null +++ b/docs/1C_FILE_ANALYTICS_STACK_RU.md @@ -0,0 +1,127 @@ +# Файловая 1С: ClickHouse + Grafana + AI Investigator + +Этот документ описывает целевой и уже подготовленный scaffold для **файловой 1С**. + +Он нужен для случаев, когда: + +- 1С работает как файловая база на Windows/RDP host; +- на сам RDP host не хочется ставить лишние тяжёлые сервисы; +- нужен не только KPI-обзор, но и audit/detection/investigation контур. + +## Почему не `prometheus_1C_exporter` + +`prometheus_1C_exporter` полезен для **серверной 1С** с `rac`. + +Для файловой 1С он не даёт нужного контекста: + +- нет кластера `rac`; +- нет нормального session/license/runtime слоя как у серверной 1С; +- остаются только host-level и export-level данные. + +Поэтому для файловой 1С правильный путь другой: + +```text +1С exports + reglog + host telemetry + ↓ + ETL / normalize + ↓ + ClickHouse + ↓ + Grafana + detections + ↓ + AI Investigator +``` + +## Что входит в scaffold + +- `clickhouse-1c/` — новый каталог стека; +- ClickHouse schema для raw/core/timeline/cases; +- ETL loader CSV/JSON выгрузок; +- detection catalog; +- Grafana dashboard catalog; +- AI Investigator API contract. + +## Основные таблицы + +- `raw_1c_documents` +- `raw_1c_postings` +- `raw_reglog` +- `raw_audit` +- `raw_host_metrics` +- `documents` +- `postings` +- `reglog_events` +- `audit_events` +- `host_events` +- `entity_timeline` +- `detections` +- `cases` + +## Что визуализировать в Grafana + +Роли: + +1. `1C Executive Summary` +2. `1C Operations Health` +3. `1C Audit Overview` +4. `1C Detections` +5. `1C Investigation Timeline` +6. `1C Data Quality` + +Полный каталог панелей: + +- `clickhouse-1c/grafana/dashboard-catalog.md` +- `clickhouse-1c/grafana/query-pack.sql` + +## Detections + +Первый production-набор правил: + +- вход вне рабочего времени; +- всплеск failed logins; +- массовое перепроведение; +- изменение критичных объектов; +- аномальный рост ручных корректировок; +- аномальный рост возвратов; +- рост просроченной дебиторки; +- длительные операции; +- всплеск ошибок обмена; +- высокая задержка диска; +- stale backup; +- нетипичные проводки по счетам. + +См.: + +- `clickhouse-1c/detections/rules.yml` +- `clickhouse-1c/detections/insert_detections.sql` +- `clickhouse-1c/detections/build_entity_timeline.sql` +- `clickhouse-1c/detections/open_cases_from_detections.sql` +- `clickhouse-1c/ops/etl-cron.example` + +## Операционный порядок + +1. На файловом/RDP host: + - выгрузить данные 1С в `CSV/JSON`; + - выгрузить журнал регистрации; + - снять host telemetry. +2. На utility VM: + - положить файлы в `clickhouse-1c/landing/*`; + - прогнать `etl/load_1c_exports.py`; + - прогнать `insert_detections.sql`; + - открыть Grafana dashboards; + - при необходимости построить AI summary поверх cases/timeline. + +## Где граница AI + +AI не должен: + +- писать обратно в 1С; +- выполнять произвольный SQL; +- менять case state без явного правила. + +AI должен: + +- объяснять detections; +- строить summary по case; +- связывать audit/reglog/documents в timeline; +- предлагать next steps. diff --git a/docs/1C_GRAFANA_DEPLOYMENT_RU.md b/docs/1C_GRAFANA_DEPLOYMENT_RU.md index 441f853..bf5e9db 100755 --- a/docs/1C_GRAFANA_DEPLOYMENT_RU.md +++ b/docs/1C_GRAFANA_DEPLOYMENT_RU.md @@ -2,6 +2,12 @@ Документ описывает внедрение контура мониторинга 1С в Grafana через Prometheus и SQL Exporter. +> Важно: этот документ относится к сценарию, где 1С удобно читается через SQL/read-only views. +> Для **файловой 1С** теперь есть отдельный контур: +> +> - `docs/1C_FILE_ANALYTICS_STACK_RU.md` +> - `clickhouse-1c/README.md` + ## 1. Целевая схема 1. База 1С (PostgreSQL или MS SQL) содержит KPI views. diff --git a/docs/codebase-onboarding.md b/docs/codebase-onboarding.md index 301c3be..34cb3cb 100644 --- a/docs/codebase-onboarding.md +++ b/docs/codebase-onboarding.md @@ -26,7 +26,7 @@ - `runbook.md` — быстрые операционные действия и проверки. - `operations.md` — сопровождение, бэкапы, rollback. - `windows/*` — отдельная ветка документации по Windows-оркестрации. -- `1C_GRAFANA_DEPLOYMENT_RU.md`, `pfsense.md`, `linux-client.md` — специализированные подсистемы. +- `1C_GRAFANA_DEPLOYMENT_RU.md`, `1C_FILE_ANALYTICS_STACK_RU.md`, `pfsense.md`, `linux-client.md` — специализированные подсистемы. ### `proxmox/` Скрипты ранней инфраструктурной фазы: @@ -65,6 +65,15 @@ PowerShell toolkit для клиентской стороны: ### `grafana-1c/` Набор для SQL exporter + Prometheus + Grafana дашбордов по 1C метрикам. +### `clickhouse-1c/` +Новый stack именно для **файловой 1С**: + +- ETL выгрузок `CSV/JSON`, +- ClickHouse schema, +- rule-based detections, +- Grafana dashboard catalog, +- AI Investigator contract. + ### `scripts/` Утилиты и quality gates: @@ -84,7 +93,7 @@ PowerShell toolkit для клиентской стороны: 4. Включение автозапуска и проверка (`systemd` + `docs/runbook.md`). 5. Rollout клиентов (обычно `windows/`, иногда `scripts/install_aw_linux_client.sh`). 6. Эксплуатация и изменения через `docs/operations.md`. -7. При необходимости — расширение мониторинга (`grafana-1c/`, `pfsense/`). +7. При необходимости — расширение мониторинга (`grafana-1c/`, `clickhouse-1c/`, `pfsense/`). ## 4) Что важно понять в первую очередь @@ -120,7 +129,7 @@ PowerShell toolkit для клиентской стороны: - Если интересует эксплуатация и инциденты: `docs/runbook.md`, `docs/operations.md`, `docs/windows/troubleshooting.md`. - Если интересует автодеплой: `ansible/provision_proxmox_ct_and_deploy_aw.yml` и `ansible/tasks/`. -- Если интересует наблюдаемость: `grafana-1c/` + `pfsense/` + `prometheus`/`alerts` конфиги. +- Если интересует наблюдаемость: `grafana-1c/`, `clickhouse-1c/` + `pfsense/` + `prometheus`/`alerts` конфиги. - Если интересует hardening и DLP: `windows/hardening-recovery.ps1`, `docs/dlp-gap-analysis.md`. --- diff --git a/docs/runbook.md b/docs/runbook.md index 8efee5f..572b76b 100755 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -82,7 +82,42 @@ Playbook вычисляет `durationDefault` автоматически (вкл ## Типовые инциденты -### Hayabusa: production validation end-to-end +### Hayabusa: операторский сценарий по умолчанию + +Текущий production-сценарий уже не требует ручного `accept/process-inbox`. + +Нормальный путь для оператора: + +1. На Windows-хосте запустить: + +```powershell +powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -CaseId 30 +``` + +2. Сервер `10.10.10.13` сам: + +- примет `zip` и `.meta.json` в `/opt/activitywatch/aw-rus-ops/drop`; +- запустит `aw-hayabusa`; +- посчитает severity/score; +- создаст case при уровне от `medium`; +- отправит Telegram alert при уровне от `high`. + +3. Проверить результат: + +```bash +cat /opt/hayabusa/state/latest-intake.json +journalctl -u aw-hayabusa-drop.service -n 80 --no-pager +curl -fsS http://127.0.0.1:5602/api/0/dlp/cases/30 +``` + +Ожидаемо: + +- `latest-intake.json` имеет `status=ok`; +- `drop` после обработки пустой; +- в case есть `forensics.hayabusa`; +- Telegram alert уже уходит в операторский чат. + +### Hayabusa: manual fallback / production validation end-to-end Цель: подтвердить один реальный путь @@ -94,6 +129,12 @@ Playbook вычисляет `durationDefault` автоматически (вкл Минимальный production-proven сценарий: +Этот путь нужен только если: + +- надо руками прогнать старый пакет; +- надо повторно разобрать archived zip; +- надо отладить сам `aw-hayabusa` без drop-автоматики. + 1. На Windows-хосте сделать экспорт: ```powershell diff --git a/docs/wiki/File-1C-Analytics.md b/docs/wiki/File-1C-Analytics.md new file mode 100644 index 0000000..3503d73 --- /dev/null +++ b/docs/wiki/File-1C-Analytics.md @@ -0,0 +1,50 @@ +# File 1C Analytics + +Эта страница описывает новый контур для **файловой 1С**. + +## Когда он нужен + +Используй этот контур, если: + +- 1С файловая; +- на RDP host нельзя или нежелательно ставить тяжёлые агенты; +- нужен audit/detection/investigation стек; +- Grafana должна быть не только для KPI, но и для расследования. + +## Схема + +```text +1С exports + reglog + host telemetry + ↓ + ETL / normalize + ↓ + ClickHouse + ↓ + Grafana + detections + ↓ + AI Investigator +``` + +## Основные компоненты + +- `clickhouse-1c/README.md` +- `clickhouse-1c/clickhouse/init/*.sql` +- `clickhouse-1c/etl/load_1c_exports.py` +- `clickhouse-1c/detections/rules.yml` +- `clickhouse-1c/grafana/dashboard-catalog.md` +- `clickhouse-1c/ai/INVESTIGATOR_API.md` + +## Основные dashboard-ы + +- `1C Executive Summary` +- `1C Operations Health` +- `1C Audit Overview` +- `1C Detections` +- `1C Investigation Timeline` +- `1C Data Quality` + +## Связанные документы + +- [File 1C analytics stack](../1C_FILE_ANALYTICS_STACK_RU.md) +- [1C Grafana deployment](../1C_GRAFANA_DEPLOYMENT_RU.md) +- [Runbook](../runbook.md) diff --git a/docs/wiki/Hayabusa-Security-Analytics.md b/docs/wiki/Hayabusa-Security-Analytics.md new file mode 100644 index 0000000..592980a --- /dev/null +++ b/docs/wiki/Hayabusa-Security-Analytics.md @@ -0,0 +1,103 @@ +# Hayabusa Security Analytics + +Эта страница описывает текущий production-контур Hayabusa внутри `AW-rus`. + +## Что уже работает + +- Windows-хост раз в `6` часов делает `EVTX export + upload` +- `AW-server` автоматически подхватывает пакет из `drop` +- `aw-hayabusa` строит forensic-отчёт +- `aw-hayabusa-case-alert` считает severity и score +- при уровне от `medium` создаётся или обновляется case +- при уровне от `high` уходит Telegram alert + +## Операторский сценарий + +На Windows-хосте: + +```powershell +powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -CaseId 30 +``` + +На сервере для проверки: + +```bash +cat /opt/hayabusa/state/latest-intake.json +journalctl -u aw-hayabusa-drop.service -n 80 --no-pager +curl -fsS http://127.0.0.1:5602/api/0/dlp/cases/30 +``` + +## Что получает оператор + +- `summary.html` +- `manifest.json` +- `run.log` +- `timeline.jsonl` +- `logon-summary-successful.csv` +- `logon-summary-failed.csv` +- case в DLP case API +- Telegram alert в операторский чат + +## Как оценивается severity + +Используются: + +- Hayabusa `Level` +- top `RuleTitle` +- failed logons +- suspicious PowerShell +- credential-related detections +- timestomp detections + +Выход: + +- `low` +- `medium` +- `high` +- `critical` + +## Границы + +В case возвращается только bounded metadata: + +- `tool` +- `host` +- `mode` +- `status` +- `intake_id` +- `package_path` +- `sha256` +- `report_dir` +- `summary_html` +- `timeline_path` +- `manifest_path` + +Не возвращаются: + +- сырые EVTX +- полный timeline body +- полный Sigma output + +## Где настраивается + +Windows: + +- `aw_windows_hayabusa_auto_upload_enabled` +- `aw_windows_hayabusa_auto_upload_interval_hours` +- `aw_windows_hayabusa_auto_upload_hours_back` +- `aw_windows_hayabusa_auto_upload_mode` + +Server: + +- `aw_hayabusa_auto_case_enabled` +- `aw_hayabusa_auto_case_min_severity` +- `aw_hayabusa_telegram_enabled` +- `aw_hayabusa_telegram_min_severity` +- `aw_hayabusa_telegram_bot_token` +- `aw_hayabusa_telegram_chat_ids` + +## Связанные документы + +- [Runbook](../runbook.md) +- [Windows EVTX Export](../windows-hayabusa-evtx-export.md) +- [Security analytics stack v1](../security-analytics-stack-v1.md) diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md index 72ac6bb..56f82ec 100644 --- a/docs/wiki/Home.md +++ b/docs/wiki/Home.md @@ -13,6 +13,9 @@ - [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, где лежат артефакты и какие у него границы +- [Hayabusa Security Analytics](Hayabusa-Security-Analytics) - текущий production-контур: auto-upload, auto-case, severity scoring и Telegram alerts +- [Security analytics stack v1](../security-analytics-stack-v1.md) - целевая v1-модель без претензии на Splunk-class SIEM +- [File 1C analytics](File-1C-Analytics) - ClickHouse/Grafana/AI Investigator контур для файловой 1С ### Компоненты - [DLP Endpoint Monitoring](DLP-Endpoint-Monitoring) - мониторинг clipboard, печати, USB @@ -37,14 +40,15 @@ ### Минимальная конфигурация ```bash -# 1. Установка на Windows workstation -.\windows\deploy-domain-users.ps1 +# 1. Развернуть сервер +cd ansible +ansible-playbook -i inventory.ini deploy_aw_server.yml -# 2. Запуск ActivityWatch Server -./aw-server/aw-server +# 2. Развернуть Windows collectors +AW_WINRM_PASSWORD='...' bash ./run_deploy_aw_windows.sh -# 3. Применение RU патчей -node aw-server/aw-ru-patch.js +# 3. Проверить операторский forensic path +powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -CaseId 30 ``` ### Полная конфигурация @@ -60,7 +64,11 @@ ansible-playbook server-setup.yml cd ../grafana-1c docker-compose up -d -# 4. Агрегация DLP событий +# 4. Для файловой 1С поднять ClickHouse/Grafana scaffold +cd ../clickhouse-1c +docker compose up -d + +# 5. Агрегация DLP событий python3 scripts/aggregate_dlp_events.py ``` @@ -73,6 +81,7 @@ ActivityWatch-Russian - это корпоративная система мон - **Аналитика** - агрегация данных и отчеты - **Мониторинг** - Prometheus + Grafana дашборды - **Автоматизация** - Ansible деплой на Windows и Linux +- **Security analytics** - Hayabusa, auto-case, severity scoring, Telegram alerts ## 🔗 Ссылки diff --git a/docs/windows-hayabusa-evtx-export.md b/docs/windows-hayabusa-evtx-export.md index f4258ed..1164980 100644 --- a/docs/windows-hayabusa-evtx-export.md +++ b/docs/windows-hayabusa-evtx-export.md @@ -70,9 +70,22 @@ Example with custom window: powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1 -DaysBack 1 ``` +Current production path: + +```powershell +powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -CaseId 30 +``` + +This wrapper: + +- builds the EVTX package; +- uploads `.caseid` if provided; +- uploads `.meta.json`; +- uploads the `zip` to the AW-server drop directory. + ## 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 +- server-side Hayabusa execution happens later on `10.10.10.13` +- only bounded Hayabusa metadata returns into the case layer