Commit 0997f97a authored by Vitaly Lipatov's avatar Vitaly Lipatov

Add vaultwarden-lavtomate bridge design doc

Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
parent 4fe1973b
# Связка lavtomate `password_request` ↔ Vaultwarden
Инструкция по реализации брокера паролей: когда Claude вызывает `password_request`,
пользователь одобряет в web UI, и **lavtomate сам достаёт секрет из Vaultwarden** своим
сервисным аккаунтом, ограниченным одной коллекцией. Claude не получает мастер-ключ,
plaintext не хранится, каждый доступ — через одобрение человека и в аудите.
Модель и обоснование: см. memory `feedback_password_request_only.md`.
---
## 0. Действующие лица
- **Vaultwarden**: https://bitwarden.eterfund.ru
- **lavtomate**: CT 653 на enceladus (`lavtomate.office.etersoft.ru`), код в
`gitlab.eterfund.ru/etersoft/...lavtomate`. MCP-сервер, отдаёт инструмент
`password_request` (service + description → confirmation → пароль).
- **Claude Code**: вызывает `password_request`, использует пароль разово.
Принцип тиринга: только секреты из коллекции **`infra-claude`** достижимы через брокер.
Критичное (AD admin, CA-ключи, root) в эту коллекцию НЕ кладём — оно недостижимо в принципе.
---
## 1. Сторона Vaultwarden (админская работа)
1. **Организация** (если нет): `Etersoft Infra`.
2. **Коллекция** внутри неё: `infra-claude`.
3. **Сервисный аккаунт** `claude-broker@etersoft.ru`:
- создать пользователя, пригласить в организацию;
- роль **User** (не Admin), доступ **только** к коллекции `infra-claude`, право **read-only** (can view, нельзя edit/manage);
- задать стойкий мастер-пароль (он же bootstrap-секрет, см. §3).
4. **API-key** сервисного аккаунта: Account Settings → Security → Keys → **View API Key**
получить `client_id` (`user.<uuid>`) и `client_secret`. Нужны для неинтерактивного `bw login`.
5. **Занести секреты** в коллекцию `infra-claude` как Login-итемы. Имя итема = то, что Claude
передаёт в `service` (договоримся об именах, напр.):
- `kuma` (https://kuma.office.etersoft.ru/)
- `grafana-telegraf` (https://telegraf.office.etersoft.ru/)
- `ha-divserver` (http://192.168.1.5:8123)
Значения (login/пароль) — из admin password store: `ssh rooter@server pass show <name>` (не вписывать plaintext в этот файл).
> Имена итемов — это контракт. Зафиксировать список «service → item» в этом файле по мере роста.
---
## 2. Сторона хоста lavtomate: Bitwarden CLI
Ставить и настраивать под сервис-пользователем lavtomate (не root, если сервис не от root).
```bash
# CT 653 enceladus
ssh root@lavtomate.office.etersoft.ru # или ssh root@<ip CT 653>
# 1. CLI. На ALT — через epm; если пакета нет, официальный bw из npm:
epm install bitwarden-cli || npm i -g @bitwarden/cli # бинарь `bw`
# (альтернатива — rbw, но bw ближе к официальному API)
# 2. Указать свой сервер Vaultwarden (один раз):
bw config server https://bitwarden.eterfund.ru
# 3. Логин по API-key (неинтерактивно, без мастер-пароля на этом шаге):
export BW_CLIENTID='user.<uuid>'
export BW_CLIENTSECRET='<client_secret>'
bw login --apikey
# → статус "locked": залогинен, но vault зашифрован
```
`bw login --apikey` устанавливает сессию аккаунта, но vault остаётся **locked**
для чтения нужен `bw unlock` мастер-паролем (§3).
---
## 3. Bootstrap-секрет (мастер-пароль) — храним правильно
Это единственный секрет, который нельзя спрятать за `password_request` (курица-яйцо).
Минимизируем ущерб: аккаунт видит только `infra-claude`.
**Рекомендуемо — systemd credential (на диск в открытую не ложится):**
```bash
# зашифрованный credential, читается только этим юнитом:
systemd-ask-password "BW master for claude-broker" | \
systemd-creds encrypt --name=bw_master - /etc/lavtomate/bw_master.cred
chmod 600 /etc/lavtomate/bw_master.cred
```
В unit lavtomate:
```ini
[Service]
LoadCredentialEncrypted=bw_master:/etc/lavtomate/bw_master.cred
Environment=BW_CLIENTID=user.<uuid>
LoadCredentialEncrypted=bw_clientsecret:/etc/lavtomate/bw_clientsecret.cred
# мастер-пароль доступен процессу как файл $CREDENTIALS_DIRECTORY/bw_master
```
**Минимум-вариант** (если без systemd-creds): env-файл `0600`, владелец — сервис-пользователь:
```
/etc/lavtomate/bw.env (chmod 600, chown lavtomate)
BW_CLIENTID=user.<uuid>
BW_CLIENTSECRET=...
BW_MASTER=...
```
Хуже, чем systemd-creds (лежит расшифрованным на диске), но приемлемо как первый шаг.
**Никогда** не коммитить эти файлы и не класть в коллекцию `infra-claude`.
---
## 4. Управление сессией unlock
`bw unlock` возвращает `BW_SESSION` — ключ для расшифровки на время жизни процесса.
Держать его **только в памяти** процесса lavtomate, не писать на диск.
```bash
# при старте сервиса (псевдо):
export BW_SESSION="$(bw unlock --passwordfile "$CREDENTIALS_DIRECTORY/bw_master" --raw)"
```
- Перед каждым чтением проверять `bw status` (`unlocked` / `locked`); при `locked` — повторить unlock.
- Раз в N минут `bw sync` (подтянуть новые/изменённые итемы).
- При рестарте сервиса unlock выполняется заново из credential.
---
## 5. Логика обработчика `password_request` (точка интеграции в lavtomate)
На **approve** confirmation выполнить чтение из Vaultwarden со **строгой проверкой области**.
Псевдокод (адаптировать под реальный язык lavtomate):
```python
ALLOWED_COLLECTION = "infra-claude"
def on_password_request_approved(service: str, ctx):
# 1. найти итем по имени == service
items = bw_list_items(search=service, session=BW_SESSION) # `bw list items --search`
item = exact_match(items, name=service)
if not item:
return deny(f"no item '{service}'")
# 2. ЖЁСТКО проверить, что итем в разрешённой коллекции
if ALLOWED_COLLECTION not in collection_names(item):
audit("DENY", service, ctx, reason="out of scope")
return deny("out of allowed collection")
# 3. вернуть только пароль (не весь итем) на ОДИН запрос
pw = item["login"]["password"] # `bw get password <id>`
audit("ALLOW", service, ctx, item_id=item["id"])
return pw
```
Критично:
- проверка коллекции — **на стороне lavtomate**, не полагаться только на права аккаунта
(defense-in-depth);
- сопоставление имени — **точное** (не подстрока), чтобы `ha` не выдал `ha-admin`;
- отдавать **только** поле password, не username/notes/totp без явной надобности.
---
## 6. Что показывать в confirmation (контекст для решения человека)
`password_request` уже носит `service` + `description`. В web UI показать:
- какой `service` (итем) запрашивается;
- `description` — зачем;
- идентификатор сессии/агента, время, короткий timeout (300с).
Чтобы пользователь мог распознать аномальный запрос и нажать **Deny**.
---
## 7. Аудит
- lavtomate: писать в лог каждый запрос — `service`, decision (allow/deny), сессия, время, item_id.
- Vaultwarden: организация ведёт Event Logs (вход сервис-аккаунта, доступ к итемам) — включить.
Двухслойный аудит: даже при ошибке в одном слое второй фиксирует доступ.
---
## 8. Тестирование
1. Завести тест-итем `test-secret` в `infra-claude`, значение известное.
2. Из Claude: `password_request(service="test-secret", ...)` → approve → должен вернуть значение.
3. **Негатив-тест области:** создать итем `test-forbidden` ВНЕ `infra-claude`
`password_request(service="test-forbidden")` → approve → должен быть **deny** (out of scope).
4. **Deny-тест:** запросить и нажать Deny → Claude получает отказ, ничего не утекает.
5. Перезапустить lavtomate → убедиться, что unlock восстанавливается из credential.
---
## 9. После запуска связки
- Перенести текущие пароли (kuma, grafana-telegraf, ha-divserver) в `infra-claude`.
- **Удалить плоский `.claude/secrets/credentials.md`** (он gitignored).
- Критичные секреты (AD admin, CA-ключи) в `infra-claude` НЕ заводить — для них остаётся
ручной ввод в `password_request` или паттерн «lavtomate сам выполняет действие».
---
## 10. Откат
- Связка изолирована в обработчике `password_request`; при сбое — вернуть прежнее поведение
(ручной ввод пароля в confirmation), сервис-аккаунт Vaultwarden отозвать (revoke API-key,
убрать из организации). Секреты в `infra-claude` остаются доступны тебе через обычный web UI.
---
## Разделение работ
- **§1 (Vaultwarden), §9 (перенос/чистка)** — админ (могу сделать я).
- **§2–§7 (CLI на хосте + код обработчика)** — изменение lavtomate → его мейнтейнеры
(`gitlab.eterfund.ru/etersoft/...lavtomate`). Claude — администратор, код API не правит.
......@@ -75,6 +75,7 @@ ansible-playbook -i /root/.ansible/hosts ansible/playbooks/NAME.yml
- `nfs.md` — NFS серверы aspetos/spacer, manage-gids, FS-Cache
- `epm.md` — epm/serv команды для ALT Linux
- `host03.md` — хостинг host03.eterhost.ru (nginx + Apache CT 390), процедура добавления HTTPS
- `vaultwarden-lavtomate-bridge.md` — связка password_request ↔ Vaultwarden (брокер паролей): инструкция по реализации
- Ты помощник системного администратора и выполняешь задачи по моему указанию. Ведёшь себя осмотрительно и спрашиваешь при выполнении команд.
- Подключение к удалённым серверам либо без пользователя и потом sudo su -, либо сразу root@
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment