feat(1c): add file-based analytics stack scaffold
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ Playbook:
|
||||
- выполняет API smoke-check bucket `aw-watcher-window_<COMPUTERNAME>` и ожидает свежие события (по умолчанию включено);
|
||||
- запускает `validate-deployment.ps1`;
|
||||
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation-<USER>` по умолчанию).
|
||||
- настраивает 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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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С.
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
CREATE DATABASE IF NOT EXISTS analytics_1c;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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: "Нетипичные проводки по счетам"
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -0,0 +1,3 @@
|
||||
clickhouse-connect>=0.7.16
|
||||
PyYAML>=6.0.2
|
||||
python-dateutil>=2.9.0
|
||||
@@ -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`
|
||||
@@ -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}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
---
|
||||
|
||||
+42
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
+16
-7
@@ -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
|
||||
|
||||
## 🔗 Ссылки
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user