# Выгрузка и целостность

> Экспорт журнала в CSV и JSON Lines по текущему фильтру, формат файла и проверка цепочки хэшей своими средствами.

Журнал выгружается кнопками **«Экспорт CSV»** и **«Экспорт JSON»** в разделе. Выгружается **всё, что подходит под текущие фильтры**, — без ограничения объёма: сервис проходит журнал страница за страницей до конца, а не отдаёт «первые 10 000 строк». Файл собирается в браузере и скачивается сразу; никаких ссылок «действует 24 часа» и писем на почту.

Сама выгрузка — тоже событие: в журнале появляется `auditlog.export` с форматом, фильтрами и числом строк.

_Иллюстрация: Раскрытая запись: все поля, детали в JSON, хэш записи и хэш предыдущей. Сверху — результат «Проверить целостность»._

## CSV

Для таблиц и отчётов. Кодировка UTF-8 с BOM (Excel открывает кириллицу без настроек), разделитель — запятая, строки — от старой к новой. Столбцы:

| Столбец | Содержимое |
| --- | --- |
| ID | номер записи |
| Время (UTC) | ISO 8601 с микросекундами, `2026-09-06T01:22:33.482911+00:00` |
| Время (местное) | то же в вашем часовом поясе — для чтения |
| Организация, Проект | где произошло |
| Вид исполнителя, Исполнитель | участник / коннектор / оператор платформы / система; логин или идентификатор |
| Сессия | идентификатор сессии участника |
| IP, Браузер | адрес и User-Agent подключения |
| Действие, Название | код события и его описание из [каталога](/docs/audit-log/events) |
| Операция, Важность | класс действия и важность |
| Тип объекта, Объект | на что направлено действие |
| Описание | строка, которую видит человек |
| Результат, Причина отказа | успех / отказ и причина |
| Детали (JSON) | структурированные параметры |
| Хэш предыдущей записи, Хэш записи | звенья цепочки целостности |

## JSON Lines

Для SIEM, скриптов и архивов: одна запись — одна строка JSON, ровно в том виде, как её хранит сервер, от старой к новой. Расширение файла — `.jsonl`. Поля:

```json
{"id":12,"at":"2026-09-06T01:22:34.101532+00:00","org":"buff-demo","project":null,
 "actor_kind":"user","actor":"demo-lead","session":"c2a5…","gid":"9821…","ip":"147.45.197.19",
 "user_agent":"Mozilla/5.0 …","action":"auditlog.export","operation":"access","severity":"notice",
 "target_kind":null,"target":null,"summary":"Выгружен журнал аудита (json)",
 "detail":{"op":"auditlog_list","format":"json","filters":{},"rows":9},"ok":true,"error":null,
 "prev_hash":"e5ac…","hash":"81df…"}
```

`actor_kind` принимает значения `user`, `connector`, `operator`, `system`; `severity` — `info`, `notice`, `warning`, `critical`; `operation` — `create`, `access`, `modify`, `remove`, `authentication`, `transfer`, `execute`.

## Целостность

Записи каждой организации образуют **цепочку**: хэш записи считается от хэша предыдущей записи и содержимого текущей (SHA-256). Изменение, вставка или удаление любой записи в середине разрывает цепочку — все последующие хэши перестают сходиться.

**В приложении.** Кнопка **«Проверить целостность»** пересчитывает цепочку на сервере и отвечает: сколько записей проверено и цела ли цепочка; если нет — номер первой записи, которая не сходится. Проверка тоже записывается в журнал (`auditlog.verify`).

**Своими средствами.** Выгруженный JSON Lines содержит всё, что нужно, чтобы проверить цепочку без нас. Хэш записи считается так:

1. Взять поля записи `at, org, project, actor_kind, actor, session, ip, user_agent, action, operation, severity, target_kind, target, summary, detail, ok, error` (в `detail` — объект как есть).
2. Сериализовать их в JSON **с ключами по алфавиту, без пробелов, без экранирования не-ASCII** (`ensure_ascii=False` в Python).
3. Хэш = SHA-256 от строки `prev_hash + "\n" + сериализация` (для первой записи организации `prev_hash` пустой).

Скрипт проверки для файла `audit-log.jsonl`:

```python
import hashlib, json

FIELDS = ["at", "org", "project", "actor_kind", "actor", "session", "ip", "user_agent",
          "action", "operation", "severity", "target_kind", "target", "summary", "detail",
          "ok", "error"]

prev = None
for line in open("audit-log.jsonl", encoding="utf-8"):
    e = json.loads(line)
    body = json.dumps({k: e[k] for k in FIELDS}, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
    h = hashlib.sha256(((prev or "") + "\n" + body).encode("utf-8")).hexdigest()
    if e["prev_hash"] != prev or e["hash"] != h:
        raise SystemExit(f"цепочка нарушена на записи #{e['id']}")
    prev = e["hash"]
print("цепочка цела, последний хэш", prev)
```

Скрипт ожидает **полную** выгрузку организации от первой записи («Всё время», без фильтров): цепочка идёт по всем записям подряд, и выгрузка с фильтром проверяется только начиная с той записи, чей `prev_hash` вам известен. Храните последний хэш каждой выгрузки: следующая выгрузка должна на него опираться — так вы убедитесь, что между ними ничего не исчезло.

## Что стоит знать

- **Адрес — как его увидел периметр.** IP берётся из соединения с нашим сервером (заголовок пограничного узла), не со слов клиента. Пользователь за корпоративным прокси или VPN будет виден адресом этого прокси.
- **Часовой пояс.** В файле время в UTC; в интерфейсе — в часовом поясе вашего браузера. Сравнивайте по столбцу «Время (UTC)».
- **Объём.** Выгрузка идёт страницами по 500 записей до конца; для журнала в десятки тысяч записей это несколько секунд. Кнопка блокируется на время выгрузки, по завершении показывается число строк.
