core.vault
Явное чтение и генерация Vault KV-секретов на стороне Keeper.
core.vault работает с Vault KV на самом Keeper-е (оба состояния требуют on: keeper), а не на хосте. kv-read явно читает секрет и оставляет audit-event vault.kv-read, которого нет у implicit ${ vault(...) }; kv-present идемпотентно генерирует недостающие поля криптослучайным значением по password-policy и не перезатирает существующие. Сгенерированное значение наружу не отдаётся — ни в output, ни в аудит, ни в логи.
Требования
- Rootне требуется
- Сторона
keeper-side - Коллекция
soulstack.secrets - Категория
secrets
Версия KV-движка Vault (v1/v2) определяется на стороне Keeper-а автоматически (probe) либо задаётся override vault.kv_version. Модуль получает уже плоский payload и работает на обеих версиях одинаково; путь указывается mount-relative и без сегмента data/.
Состояния
core.vault.kv-read — Явное чтение секрета из Vault KV с записью audit-event vault.kv-read.
Никогда: read-операция, state не мутируется.
Всегда — kv-read только читает.
Параметры
| Параметр | Тип | Обяз. / дефолт | Описание |
|---|---|---|---|
path | string | required | Путь Vault KV (mount-относительный). |
fields | list<string> | optional | Какие ключи извлечь; пусто → весь секрет. |
Пример — Чтение креденшелов БД ради audit-event
- name: Read DB credentials from Vault (audit-tracked) on: keeper module: core.vault.kv-read register: db_creds params: path: secret/redis/admin fields: [username, password]Output
| Поле | Тип | Описание |
|---|---|---|
path | string | эхо запрошенного пути |
data | object | извлечённые ключ→значение (после фильтра fields) |
fields | array<string> | имена ключей в data (sorted) |
core.vault.kv-present — Generate-if-absent: гарантировать существование указанных полей секрета, недостающие сгенерировать криптослучайно по password-policy.
Только когда что-то реально сгенерировано.
Все поля уже непусты — значения не перезатираются.
Параметры
| Параметр | Тип | Обяз. / дефолт | Описание |
|---|---|---|---|
policy | map | optional | Step-level дефолт policy для всех targets: length (8..1024, default 32), charset (alphanumeric|hex|base64url|ascii-printable-safe) ЛИБО allowed_chars (взаимоисключимы). |
targets | list<map> | required | Непустой список целей {path: <Vault KV-путь без #field>, field?: <имя поля, default password>, policy?: <per-target override>}. |
Пример — Генерация паролей Redis при create (generate-if-absent)
- name: Ensure redis passwords exist in Vault (generate if absent) on: keeper module: core.vault.kv-present params: policy: length: 32 charset: alphanumeric targets: - { path: secret/redis/main, field: password } - { path: secret/redis/main/users/replica, field: password }Output
| Поле | Тип | Описание |
|---|---|---|
generated | object | map <vault-path> → отсортированный список имён сгенерированных полей (без значений); пусто, если ничего не сгенерировано |
Справочник
Пресеты алфавита (charset) генерации
kv-present генерирует значение из именованного пресета charset либо из явного allowed_chars (взаимоисключимы). Дефолт — ascii-printable-safe: пароль не должен ломать целевой конфиг.
| Пресет | Алфавит |
|---|---|
| alphanumeric | латиница обоих регистров + цифры (безопасно везде) |
| hex | строчные hex-цифры 0-9a-f |
| base64url | url-safe base64 (-/_ вместо +//, без =) |
| ascii-printable-safe (default) | печатный ASCII (0x21–0x7E) без символов, ломающих redis.conf / users.acl / shell-подстановку: пробел, двойная и одинарная кавычки, #, обратный слэш, backtick и $ |
Заметки
- kv-read существует ради audit-trail: implicit ${ vault(...) } дёшев для рендера, но только читает и не оставляет отдельной записи в аудите. kv-read — explicit-форма для compliance (PCI-DSS, SOC2); implicit-чтение при этом остаётся для render-фазы.
- kv-present — write-форма, которой у CEL-резолва нет вовсе: сервис сам генерит недостающие пароли при create, оператору не нужно пред-сеять секреты ручным vault kv put.
- Несколько targets на один путь (разные поля) сливаются в один WriteKV поверх существующих полей (read-merge-write) — соседние поля не теряются, лишние KV-версии не плодятся.
- destroy секреты не чистит: re-create переиспользует те же пароли. Ротация или удаление секрета — отдельный сценарий, это состояние их не делает.
- kv-present обязан исполниться до render-фазы задач, читающих те же секреты через ${ vault(...) } (staged-render, ADR-056): сначала генерация в Vault, потом чтение.
Безопасность и умолчания
- Сгенерированное значение никогда не уходит наружу (ADR-010): ни в register-output, ни в audit-payload, ни в логи/OTel/UI, ни в текст ошибки — только факт, path и имена сгенерированных полей. Инвариант закреплён guard-тестом.
- kv-read: сами значения секретов в audit-payload не попадают — фиксируется только path и список fields. В register-output значения присутствуют (data.*), но на write-path маскируются (логи / OTel / UI / отчёты) через MaskSecrets.
- Генерация идёт на crypto/rand (не math/rand), символ выбирается равномерно (rejection sampling, без modulo-перекоса).
- Дефолтный charset ascii-printable-safe исключает символы, ломающие redis.conf / users.acl / shell-подстановку — сгенерированный пароль не сломает целевой конфиг.
- Требует Vault-auth Keeper-а: чтение и запись идут под учёткой Keeper-кластера, модуль не принимает токен/креды в params и не повышает доступ — работает ровно там, где разрешено политикой Keeper-а в Vault.
- Keeper-side операция (on: keeper): root/capability-семантика неприменима, запуск сценария с этим шагом регулируется RBAC оператора.