71 lines
No EOL
4.9 KiB
Markdown
71 lines
No EOL
4.9 KiB
Markdown
# Криптографический аудит харнеса (t-260930083716)
|
||
|
||
Запрос Alex (m-516173240): у харнеса свой приватный ключ; каждое событие
|
||
стора и каждая команда подписаны. Цели: полная аудируемость, доказуемое
|
||
авторство («сделал харнес»), защита стора и командной шины от инъекций.
|
||
|
||
## Схема
|
||
|
||
- **Алгоритм:** ed25519 (библиотека `cryptography`).
|
||
- **Ключи:** прив. `/root/simbiot-harness/.audit/ed25519.key` (0600, создан
|
||
атомарно tmp+rename), публ. рядом `ed25519.pub` (0644, отдавай внешним
|
||
аудиторам). `key_id` = первые 16 hex sha256 от публ. ключа.
|
||
- **Активные ключи (созданы 2026-10-02):**
|
||
- pubkey (raw b64): `P8puVpwg9UtKtEnYUKCCGz/NA4zXU1ubgkdurp2V+CY=`
|
||
- key_id: `faf8d19d9aed71fe`
|
||
|
||
## Что подписывается
|
||
|
||
1. **События стора** — `es.append_event` подписывает каждое событие при
|
||
записи. Плайлоад: `aggregate|aggregate_id|event_type|canonical_json(data)|
|
||
timestamp|version|prev_sig|key_id` (`\x1f`-сшит). В колонки события
|
||
пишутся `sig, key_id, prev_sig`.
|
||
2. **Тампер-цепочка:** `prev_sig` каждого события = подпись предыдущего —
|
||
переписать середину стора, не сломав голову цепочки, нельзя.
|
||
3. **Команды CLI** — `cli.py audit-command '{"op":…}'` → конверт
|
||
`{envelope, payload_b64, signature, key_id}`; вход в командную шину
|
||
верифицируется `cli.py verify-command` (выход 0/2).
|
||
|
||
## Верификация
|
||
|
||
- `cli.py verify` — полный аудит цепочки (выход 0/2): криптопроверка
|
||
каждой подписанной строки + link-check `prev_sig`. Может выполнять
|
||
любая внешняя сторона с публичным ключом.
|
||
- Проекции (`es.run_projections`) проверяют хвост-дельту: событие с битой
|
||
подписью или разрывом цепочки ⇒ RuntimeError — подделка не попадает в
|
||
read model (проверено тестом).
|
||
- `cli.py audit-key` — публичный ключ/отпечаток/пути.
|
||
|
||
## Граница доверия: genesis и legacy
|
||
|
||
- Все события **до** внедрения подписи (2026-10-02, 465 255 шт.) помечены
|
||
ретро-маркером `sig='genesis:<id>'` (водяной знак `projection_meta.
|
||
sig_genesis_upto` — раз и навсегда). Это честная граница: до неё
|
||
авторство не доказывалось криптоключом.
|
||
- Подписанная строка после границы, чей `prev_sig` указывает на дого-
|
||
генезисное состояние, не бракуется по link-check (граница сбрасывает
|
||
проверку сцепки), но её payload-подпись строго проверяется — контент
|
||
послегенезисной строки тамперить нельзя.
|
||
- Неподписанные послегенезисные строки (старый код демонов до их рестарта)
|
||
= `legacy`: аудит их считает, ошибкой является только тампер
|
||
**подписанной** строки. После рестарта сервисов (просьба к Alex в
|
||
дайджесте дня) всё новое гарантированно подписано.
|
||
|
||
## Инвариант защиты
|
||
|
||
| Атака | Чем ловится |
|
||
|---|---|
|
||
| Правка data/timestamp события | подпись payload (verify) |
|
||
| Правка middle-строки всего стора | подпись + цепочка (full audit) |
|
||
| Вставка фейкового события | нет валидной подписи → legacy-статус, но не «валидный харнес»; в хвосте проекций — RuntimeError |
|
||
| Подмена команды | конверт-подпись (verify-command) |
|
||
| Кража подписи | плайлоад привязан к содержимому; ключ 0600 |
|
||
|
||
## Файлы
|
||
|
||
- `harness/audit.py` — ключи, каноника, sign/verify, командные конверты
|
||
- `harness/es.py` — миграция (sig-колонки/genesis), подпись в
|
||
`append_event`, хвост-верификация в `run_projections`
|
||
- `cli.py` — `verify`, `audit-command`, `verify-command`, `audit-key`
|
||
- Тест: `tests/test_audit.py` (17 проверок, hermetic: тампер/genesis/
|
||
legacy/конверты/CLI) |