253 lines
8.3 KiB
Markdown
253 lines
8.3 KiB
Markdown
# 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:
|
||
|
||
- `secret_ref` не подтягивает фактическое значение секрета;
|
||
- wizard хранит auth как raw JSON headers;
|
||
- `${secrets.*}` в UI является только текстовым placeholder;
|
||
- отдельной страницы `Secrets` нет.
|
||
|
||
Что уже работает end-to-end:
|
||
|
||
- runtime резолвит `auth_profile_ref` при выполнении operation;
|
||
- `admin-api` test-runs используют тот же auth-aware execution path, что и `mcp-server`;
|
||
- `REST`, `GraphQL`, `gRPC`, `SOAP` и streaming execution paths получают bearer/basic/api-key auth до adapter layer;
|
||
- `last_used_at` у secret обновляется при успешном auth resolution.
|
||
|
||
## 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;
|
||
- read-only usage references через `AuthProfile`;
|
||
- 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/secrets-ui`
|
||
4. `feat/wizard-auth-selector`
|
||
5. `feat/manual-regression-pass`
|
||
|
||
Сейчас `feat/wizard-auth-selector` уже закрыт: wizard использует auth selector на шаге 2, сериализует `auth_profile_ref` в `execution_config`, умеет quick-create для `Secret` и `AuthProfile`, а raw `Auth headers` убраны из primary UX. Следующий шаг - ручной regression pass по REST / GraphQL / gRPC / WebSocket / SOAP и проверка связки `Secrets -> Auth Profiles -> Test Run / MCP call`.
|