feat: complete Epic 1 production foundation
This commit is contained in:
@@ -2,6 +2,37 @@
|
||||
|
||||
Crank может вызывать REST API без авторизации или с авторизацией через сохраненный секрет.
|
||||
|
||||
## Администратор и browser sessions
|
||||
|
||||
Первый production-admin не создаётся из static startup password. Оператор
|
||||
создаёт локальный bootstrap-контракт командой:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth bootstrap-create --email owner@example.local
|
||||
```
|
||||
|
||||
Команда выводит одноразовый token. Token вводится на `/login` вместе с первым
|
||||
паролем; повторное использование или истёкший token отклоняется одинаковой
|
||||
ошибкой без раскрытия причины.
|
||||
|
||||
После login сервер устанавливает HttpOnly session cookie и возвращает
|
||||
`csrf_token`. Все unsafe browser mutations под `/api/auth/*` и `/api/admin/*`
|
||||
требуют `x-csrf-token`; cross-origin `/api/*` requests отклоняются по
|
||||
умолчанию. Смена пароля отзывает активные browser sessions и CSRF state.
|
||||
|
||||
Если Admin password потерян, оператор выполняет recovery локально. Команда
|
||||
проверяет active master-key identity, заменяет verifier существующего Admin
|
||||
account и отзывает все browser sessions; старый пароль, Secrets и key material
|
||||
не раскрываются:
|
||||
|
||||
```bash
|
||||
crank-migrate admin-auth recover \
|
||||
--email owner@example.local \
|
||||
--password-file /secure/new-admin-password.txt \
|
||||
--password-pepper-file /secure/password-pepper.txt \
|
||||
--master-key-file /secure/current-master.key
|
||||
```
|
||||
|
||||
## Секреты
|
||||
|
||||
Поддерживаемые типы:
|
||||
@@ -11,7 +42,70 @@ Crank может вызывать REST API без авторизации или
|
||||
- значение HTTP-заголовка;
|
||||
- произвольный JSON.
|
||||
|
||||
Секреты шифруются ключом `CRANK_MASTER_KEY`. После создания или ротации значение нельзя прочитать через UI или API.
|
||||
Секреты шифруются ключом `CRANK_MASTER_KEY`. Значение должно быть не короче
|
||||
32 bytes; слабый или пустой ключ отклоняется до product readiness. После
|
||||
создания или ротации значение секрета нельзя прочитать через UI или API.
|
||||
|
||||
PostgreSQL хранит только encrypted secret value, `key_version` и master-key
|
||||
epoch. Сам `CRANK_MASTER_KEY`, derived key bytes и plaintext Secret не
|
||||
сохраняются и не выводятся в diagnostics.
|
||||
|
||||
## Master-key identity и rotation
|
||||
|
||||
После миграции schema до текущей версии первый secret-using process
|
||||
регистрирует в PostgreSQL non-secret identity активного master key:
|
||||
|
||||
- epoch;
|
||||
- fingerprint;
|
||||
- cipher contract.
|
||||
|
||||
`admin-api` и `mcp-server` проверяют эту identity до product readiness. Если
|
||||
process запускается на populated database без identity, он сначала доказывает,
|
||||
что текущий key расшифровывает все существующие Secret versions, и только затем
|
||||
регистрирует fingerprint. Если процесс запущен с другим `CRANK_MASTER_KEY`,
|
||||
startup завершается безопасной ошибкой `master_key_identity_mismatch`;
|
||||
сохранённые Secrets при этом не перезаписываются.
|
||||
|
||||
Ротация master key выполняется только локальной операторской командой
|
||||
`crank-migrate master-key ...`. Ключевой материал передаётся через локальные
|
||||
файлы, а не через аргументы со значениями:
|
||||
|
||||
```bash
|
||||
crank-migrate master-key preflight \
|
||||
--current-key-file /secure/current.key \
|
||||
--target-key-file /secure/target.key \
|
||||
--backup-ref offline-backup-ref
|
||||
|
||||
crank-migrate master-key rotate \
|
||||
--current-key-file /secure/current.key \
|
||||
--target-key-file /secure/target.key \
|
||||
--backup-ref offline-backup-ref
|
||||
|
||||
crank-migrate master-key verify --target-key-file /secure/target.key
|
||||
crank-migrate master-key promote --target-key-file /secure/target.key
|
||||
```
|
||||
|
||||
`preflight` read-only: проверяет текущий ключ, decryptability существующих
|
||||
Secret versions, уникальность target fingerprint и отсутствие активной
|
||||
rotation. `rotate` создаёт durable rotation record и target ciphertext, не
|
||||
удаляя текущий ciphertext. Если команда прерывается, повторный `rotate`
|
||||
продолжает обработку по сохранённому checkpoint. `verify` должен успешно
|
||||
расшифровать все target ciphertext до `promote`.
|
||||
|
||||
До promotion новые create/rotate Secret writes fail-closed с
|
||||
`master_key_rotation_in_progress`. После `promote` активный epoch меняется
|
||||
атомарно; процессы должны быть перезапущены с target key. Старый key после
|
||||
этого не проходит readiness.
|
||||
|
||||
Если ошибка возникла до promotion:
|
||||
|
||||
```bash
|
||||
crank-migrate master-key abort --rotation-id master-key-e1-to-e2
|
||||
```
|
||||
|
||||
Abort оставляет текущий epoch активным и очищает staged target ciphertext.
|
||||
`backup_ref` — только opaque ссылка на внешний защищённый backup; Crank не
|
||||
хранит key bytes в product backup set.
|
||||
|
||||
## Профили авторизации
|
||||
|
||||
@@ -29,5 +123,6 @@ Crank может вызывать REST API без авторизации или
|
||||
- Не вставляйте токены в статические заголовки операции.
|
||||
- Используйте секреты и профили авторизации для всех чувствительных данных.
|
||||
- Ротируйте секрет при подозрении на утечку.
|
||||
- Ротируйте `CRANK_MASTER_KEY` только через `crank-migrate master-key`; не
|
||||
меняйте значение в `.env` без preflight/rotate/verify/promote.
|
||||
- Не экспортируйте реальные секреты вместе с YAML-конфигурациями.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user