docs: define secret store and auth profile plan

This commit is contained in:
a.tolmachev
2026-04-06 00:40:25 +03:00
parent 09604c6481
commit 420074f96a
10 changed files with 428 additions and 36 deletions
+245
View File
@@ -0,0 +1,245 @@
# Secrets And Upstream Auth Plan
## 1. Назначение документа
Этот документ фиксирует полный план доведения upstream secrets и `AuthProfile` до рабочей продуктовой модели.
Сейчас UI показывает оператору конструкцию вида `${secrets.API_KEY}`, но backend/runtime не резолвит ее в реальный secret. Это создает ложный контракт. Цель этого документа - заменить placeholder UX на рабочую систему.
## 2. Текущее состояние
Что уже есть:
- `AuthProfile` как отдельная workspace-scoped сущность;
- `auth_profile_ref` в `ExecutionConfig`;
- CRUD endpoints для `auth_profiles`;
- UI поля для upstream auth headers;
- env `CRANK_SECRET_PROVIDER` и `CRANK_MASTER_KEY` в runtime docs.
Что еще не работает end-to-end:
- runtime не резолвит `auth_profile_ref` при выполнении operation;
- `secret_ref` не подтягивает фактическое значение секрета;
- wizard хранит auth как raw JSON headers;
- `${secrets.*}` в UI является только текстовым placeholder;
- отдельной страницы `Secrets` нет.
## 3. Целевая модель
### 3.1. `Secret`
Metadata-сущность для upstream credentials.
Поля:
- `id`
- `workspace_id`
- `name`
- `kind`
- `status`
- `current_version`
- `created_at`
- `updated_at`
Типы:
- `token`
- `username_password`
- `header`
- `generic`
### 3.2. `SecretVersion`
Значение секрета, зашифрованное мастер-ключом.
Поля:
- `secret_id`
- `version`
- `ciphertext`
- `key_version`
- `created_at`
### 3.3. `AuthProfile`
`AuthProfile` больше не хранит plaintext или `${secrets.*}`.
Вместо этого profile описывает:
- тип auth (`bearer`, `basic`, `api_key_header`, `api_key_query`);
- как secret применяется к запросу;
- какие `secret_id` для этого нужны.
Примеры:
- bearer -> `secret_id`
- basic -> `username_secret_id` + `password_secret_id`
- api key header -> `header_name` + `secret_id`
- api key query -> `param_name` + `secret_id`
## 4. Хранение и шифрование
### 4.1. Где хранить
Стартовый вариант:
- metadata в `PostgreSQL`
- ciphertext в `PostgreSQL`
Это проще, чем отдельный Vault, и достаточно для текущего продукта.
### 4.2. Чем шифровать
- `AES-256-GCM`
- мастер-ключ в `CRANK_MASTER_KEY`
- поддержка `key_version` для будущей ротации
### 4.3. Что нельзя делать
- не хранить plaintext в БД;
- не возвращать plaintext в list/get endpoints;
- не писать plaintext в logs;
- не включать plaintext в YAML export/import.
## 5. Backend API
### 5.1. Secrets endpoints
- `GET /api/admin/workspaces/{workspace_id}/secrets`
- `POST /api/admin/workspaces/{workspace_id}/secrets`
- `GET /api/admin/workspaces/{workspace_id}/secrets/{secret_id}`
- `POST /api/admin/workspaces/{workspace_id}/secrets/{secret_id}/rotate`
- `DELETE /api/admin/workspaces/{workspace_id}/secrets/{secret_id}`
### 5.2. Auth profiles
Нужно доработать:
- `POST /auth-profiles`
- `PATCH /auth-profiles/{auth_profile_id}`
Чтобы `config_json` ссылался на `secret_id`, а не на строковые placeholders.
### 5.3. Validation rules
- нельзя удалить secret, если на него ссылается auth profile;
- нельзя удалить auth profile, если на него ссылается опубликованная operation, без явного подтверждения migration path;
- rotate не должен ломать существующие published operations.
## 6. Runtime changes
Перед upstream-вызовом runtime должен:
1. проверить `auth_profile_ref`;
2. загрузить `AuthProfile`;
3. загрузить и расшифровать нужный secret;
4. применить auth к request:
- bearer -> `Authorization: Bearer ...`
- basic -> `Authorization: Basic ...`
- api key header -> произвольный header
- api key query -> query param
Это изменение должно жить в runtime/orchestration слое, а не в UI.
## 7. UI placement
### 7.1. Отдельная страница `Secrets`
Нужна как основное место управления:
- list
- create
- rotate
- disable/delete
- usage references
Эта страница должна появиться рядом с `API Keys`.
### 7.2. Quick create в wizard
На шаге upstream вместо raw `Auth headers` нужен selector:
- `No auth`
- `Bearer token`
- `API key header`
- `Basic auth`
Дальше:
- `Select existing auth profile`
- `Create auth profile`
- `Create secret`
Это позволяет не вырывать оператора из flow создания operation.
### 7.3. Что убрать
Нужно убрать из UX:
- `${secrets.API_KEY}` как рекомендованный путь;
- raw JSON headers как основной способ настройки auth.
Raw headers можно оставить только как advanced override, но не как primary flow.
## 8. Вертикальные срезы реализации
### Срез 1. Secret store foundation
DoD:
- таблицы `secrets` и `secret_versions`;
- encryption/decryption service;
- CRUD/rotate endpoints;
- integration tests на create/get/rotate/delete.
### Срез 2. Auth profile refactor
DoD:
- auth profile config ссылается на `secret_id`;
- миграция со старой модели `secret_ref`/placeholder;
- validation на dangling references.
### Срез 3. Runtime auth resolution
DoD:
- `auth_profile_ref` применяется при `REST`, `GraphQL` и `gRPC` вызовах;
- test-run использует тот же кодовый путь;
- runtime tests покрывают bearer/basic/api-key-header/api-key-query.
### Срез 4. Secrets UI
DoD:
- новая страница `Secrets`;
- list/create/rotate/delete;
- no plaintext leaks after creation.
### Срез 5. Wizard auth UX
DoD:
- step 2 получает auth selector;
- quick-create secret/profile flow;
- public upstreams можно оставлять без auth;
- placeholder `${secrets.*}` исчезает из primary UX.
## 9. Риски
- без аккуратной миграции можно сломать уже созданные auth profiles;
- если шифрование сделать без `key_version`, потом будет болезненная ротация;
- если secrets page сделать без quick-create в wizard, UX снова станет медленным;
- если quick-create сделать без отдельной страницы, управляемость secrets будет плохой.
## 10. Практический итог
Следующий правильный roadmap:
1. `feat/secret-store-foundation`
2. `feat/auth-profile-secret-resolution`
3. `feat/runtime-upstream-auth`
4. `feat/secrets-ui`
5. `feat/wizard-auth-selector`
Только после этого upstream auth можно считать реально реализованным.