From 420074f96a2f59b2baaa6afb257f48e12b863270 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Mon, 6 Apr 2026 00:40:25 +0300 Subject: [PATCH] docs: define secret store and auth profile plan --- README.md | 1 + TASKS.md | 15 ++- docs/admin-api.md | 26 ++++ docs/architecture.md | 26 +++- docs/data-model.md | 62 +++++++-- docs/database-schema.md | 44 ++++++- docs/implementation-plan.md | 16 ++- docs/module-decomposition.md | 5 + docs/runtime-config.md | 24 ++-- docs/secrets-auth-plan.md | 245 +++++++++++++++++++++++++++++++++++ 10 files changed, 428 insertions(+), 36 deletions(-) create mode 100644 docs/secrets-auth-plan.md diff --git a/README.md b/README.md index 60290f9..dc1132d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Crank - платформа для публикации внешних API в в - `docs/deployment.md` - деплой, reverse proxy и CI/CD. - `docs/demo-runbook.md` - демонстрационный сценарий. - `docs/public-smoke-targets.md` - готовые публичные upstream-сервисы и payload-ы для smoke-проверки MCP. +- `docs/secrets-auth-plan.md` - целевая модель upstream secrets, auth profiles и пошаговый план реализации. - `docs/rust-design.md` - правила распределения поведения в Rust. - `docs/development-rules.md` - правила разработки и workflow. - `docs/rust-code-rules.md` - Rust-specific coding rules. diff --git a/TASKS.md b/TASKS.md index 04e1f6f..2c4d8b6 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2,19 +2,24 @@ ## Current -### `feat/public-smoke-configs` +### `feat/secrets-auth-plan` Status: completed DoD: -- Public REST, GraphQL, and gRPC smoke targets are documented -- Repository contains ready-to-use operation payloads for MCP verification -- gRPC smoke config includes a runtime-ready descriptor set +- Docs describe the target secret store and auth profile model +- Backend, runtime, and UI gaps are captured as vertical slices +- TASKS and implementation plan reflect the new sequence ## Next -- `feat/manual-regression-pass` +- `feat/secret-store-foundation` ## Backlog +- `feat/secret-store-foundation` +- `feat/auth-profile-secret-resolution` +- `feat/runtime-upstream-auth` +- `feat/secrets-ui` +- `feat/wizard-auth-selector` - `feat/manual-regression-pass` diff --git a/docs/admin-api.md b/docs/admin-api.md index 1c1f6e4..32b5ee4 100644 --- a/docs/admin-api.md +++ b/docs/admin-api.md @@ -26,6 +26,7 @@ - `memberships` - `invitations` - `operations` +- `secrets` - `auth-profiles` - `agents` - `platform-api-keys` @@ -116,12 +117,26 @@ ### 5.5. Upstream auth profiles +- `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}` + - `GET /api/admin/workspaces/{workspace_id}/auth-profiles` - `POST /api/admin/workspaces/{workspace_id}/auth-profiles` - `GET /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}` - `PATCH /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}` - `DELETE /api/admin/workspaces/{workspace_id}/auth-profiles/{auth_profile_id}` +Контракт: + +- `POST /secrets` принимает metadata и plaintext value, но plaintext возвращается только в create/rotate request path и не выдается повторно; +- `GET /secrets` и `GET /secrets/{secret_id}` возвращают только metadata, `kind`, `status`, `current_version`, `created_at`, `updated_at`, `last_used_at` при наличии; +- `POST /secrets/{secret_id}/rotate` создает новую secret version; +- `DELETE /secrets/{secret_id}` запрещен, если secret используется опубликованными auth profiles или operations; +- `AuthProfile.config` хранит ссылки на `secret_id`, а не placeholder-строки `${secrets.*}`. + ### 5.6. Agents - `GET /api/admin/workspaces/{workspace_id}/agents` @@ -188,6 +203,8 @@ - samples; - draft generation; - gRPC descriptor upload и discovery. +- upstream auth selector; +- quick-create secret / auth profile modal. Детальные DTO и response shapes для экранов `Operations` и `Wizard` зафиксированы отдельно в: @@ -209,6 +226,15 @@ - list/create/revoke/delete platform API keys; - one-time reveal значения ключа при создании. +### Secrets + +Нужны: + +- list/create/rotate/delete upstream secrets; +- metadata-only retrieval после создания; +- связь с auth profiles; +- usage references, чтобы оператор видел, где секрет используется. + ### Logs Нужны: diff --git a/docs/architecture.md b/docs/architecture.md index 3bcca6f..df215ac 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,6 +41,7 @@ Crank - платформа для публикации внешних API в в Изолирует: - операции; +- secrets; - auth profiles; - agents; - platform API keys; @@ -84,6 +85,21 @@ Crank - платформа для публикации внешних API в в - `Invitation` - `PlatformApiKey` +### `Upstream secrets` + +Отдельный слой для доступа к внешним системам: + +- `Secret` +- `SecretVersion` +- `AuthProfile` + +Принцип: + +- секреты принадлежат workspace; +- plaintext не хранится в открытом виде; +- `AuthProfile` описывает способ применения секрета к upstream request; +- runtime резолвит `auth_profile_ref` в реальный header/query/basic auth только в момент вызова. + ### `Observability` Отдельный продуктовый слой: @@ -117,6 +133,7 @@ Crank - платформа для публикации внешних API в в - `Agent` и привязка операций к агенту. - Agent-scoped MCP endpoints. - Platform API keys. +- Workspace-scoped encrypted secrets для upstream access. - Workspace-scoped auth profiles для upstream access. - Product logs и usage aggregates. - Импорт и экспорт operation-конфигураций в `YAML`. @@ -137,9 +154,10 @@ Crank - платформа для публикации внешних API в в 1. Выбирает workspace. 2. Создает или редактирует operation. -3. Выполняет test run. -4. Публикует operation version. -5. Привязывает operation к одному или нескольким agents. +3. При необходимости выбирает или создает upstream secret / auth profile. +4. Выполняет test run. +5. Публикует operation version. +6. Привязывает operation к одному или нескольким agents. ### Оператор агентов @@ -209,6 +227,8 @@ GraphQL в MCP публикуется как фиксированная опер Базовые сущности: - `Workspace` +- `Secret` +- `SecretVersion` - `Operation` - `OperationVersion` - `Agent` diff --git a/docs/data-model.md b/docs/data-model.md index 251ded5..670bdbf 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -40,6 +40,8 @@ Минимальный набор workspace-scoped сущностей: +- `Secret` +- `SecretVersion` - `Operation` - `OperationVersion` - `AuthProfile` @@ -143,7 +145,40 @@ - `tool_description_override` - `enabled` -### 3.6. `AuthProfile` +### 3.6. `Secret` + +Секрет для доступа к внешней системе. + +Поля: + +- `id` +- `workspace_id` +- `name` +- `kind` +- `status` +- `current_version` +- `created_at` +- `updated_at` + +Значение: + +- plaintext не возвращается в list/get endpoints; +- текущее значение хранится в зашифрованном виде через `SecretVersion`; +- rotate создает новую версию секрета без потери ссылочной целостности. + +### 3.7. `SecretVersion` + +Зашифрованное значение секрета. + +Поля: + +- `secret_id` +- `version` +- `ciphertext` +- `key_version` +- `created_at` + +### 3.8. `AuthProfile` Используется только для доступа к внешним системам. @@ -155,7 +190,13 @@ - `kind` - `config` -### 3.7. `PlatformApiKey` +Принцип: + +- `AuthProfile` не хранит plaintext; +- config ссылается на `secret_id` или пару `secret_id`, если auth-схема составная; +- runtime применяет profile к запросу только в момент вызова upstream. + +### 3.9. `PlatformApiKey` Отдельная сущность для доступа к самой платформе. @@ -175,7 +216,7 @@ - полный secret показывается только один раз при создании; - в persistent storage сохраняется только `secret_hash`. -### 3.8. `User` +### 3.10. `User` Поля: @@ -192,7 +233,7 @@ - plaintext пароль не сохраняется; - верификация использует `password_pepper` из env. -### 3.9. `UserSession` +### 3.11. `UserSession` Поля: @@ -210,7 +251,7 @@ - в persistent storage сохраняется только `secret_hash`; - подпись и верификация используют `session_secret` из env. -### 3.10. `Membership` +### 3.12. `Membership` Поля: @@ -219,7 +260,7 @@ - `role` - `created_at` -### 3.11. `InvitationToken` +### 3.13. `InvitationToken` Поля: @@ -236,7 +277,7 @@ - полный invite token показывается только один раз при создании; - в persistent storage сохраняется только `token_hash`. -### 3.12. `InvocationLog` +### 3.14. `InvocationLog` Продуктовая запись о вызове tool. @@ -255,7 +296,7 @@ - `response_preview` - `created_at` -### 3.13. `UsageRollup` +### 3.15. `UsageRollup` Агрегированная статистика по периоду. @@ -321,6 +362,11 @@ Если UI требует сущность, которой нет в текущем backend, эта сущность должна быть сначала явно добавлена в эту модель данных, а уже потом в код и БД. +Для upstream credentials это означает: + +- placeholder-строки вида `${secrets.API_KEY}` не считаются реальной моделью данных; +- рабочая продуктовая модель строится только через `Secret` + `AuthProfile`. + Для `Operations` и `Wizard` дополнительный уровень контрактной детализации закреплен в: - `docs/operations-workspace-contracts.md` diff --git a/docs/database-schema.md b/docs/database-schema.md index 7fe0b05..9497258 100644 --- a/docs/database-schema.md +++ b/docs/database-schema.md @@ -26,7 +26,7 @@ ### 2.5. Секреты не хранятся в открытом виде -- upstream secrets живут за `secret_ref`; +- upstream secrets живут в отдельных таблицах и шифруются; - platform API keys хранятся как hash. ## 3. Основные таблицы @@ -36,6 +36,8 @@ - `user_sessions` - `memberships` - `invitation_tokens` +- `secrets` +- `secret_versions` - `operations` - `operation_versions` - `published_operations` @@ -134,7 +136,30 @@ - `created_at` - `finished_at` -## 6. Upstream auth +## 6. Upstream secrets and auth + +### `secrets` + +- `id` +- `workspace_id` +- `name` +- `kind` +- `status` +- `current_version` +- `created_at` +- `updated_at` + +Ограничение: + +- `unique (workspace_id, name)` + +### `secret_versions` + +- `secret_id` +- `version` +- `ciphertext` +- `key_version` +- `created_at` ### `auth_profiles` @@ -146,6 +171,11 @@ - `created_at` - `updated_at` +Назначение: + +- `config_json` хранит ссылки на `secret_id`, а не plaintext значения; +- допустимы bearer, basic, api-key-header, api-key-query профили. + Ограничение: - `unique (workspace_id, name)` @@ -308,7 +338,9 @@ 1. добавить `workspaces` и заполнить default workspace; 2. добавить `workspace_id` в `operations` и `auth_profiles`; -3. добавить `agents` и `published_agents`; -4. внедрить `platform_api_keys`; -5. добавить `invocation_logs` и `usage_rollups`; -6. перевести MCP runtime на `published_agents`, а не на глобальный список operations. +3. добавить `secrets` и `secret_versions`; +4. перевести `auth_profiles` на secret-backed config; +5. добавить `agents` и `published_agents`; +6. внедрить `platform_api_keys`; +7. добавить `invocation_logs` и `usage_rollups`; +8. перевести MCP runtime на `published_agents`, а не на глобальный список operations. diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 2513522..4aef4ce 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -109,7 +109,21 @@ DoD: - mock JSON больше не используется на критическом пути; - UI, backend и docs синхронизированы. -## 10. Этап 9. Hardening and demo readiness +## 10. Этап 9. Secret store and upstream auth + +Цель: + +- заменить UI placeholder-модель `${secrets.*}` на рабочий backend/runtime слой secrets. + +DoD: + +- есть workspace-scoped `Secrets` resource; +- secret values хранятся только в зашифрованном виде; +- `AuthProfile` ссылается на `secret_id`, а не на строковый placeholder; +- runtime умеет применять bearer/basic/api-key auth к реальному upstream request; +- wizard имеет auth selector и quick-create flow для secrets/auth profiles. + +## 11. Этап 10. Hardening and demo readiness Цель: diff --git a/docs/module-decomposition.md b/docs/module-decomposition.md index d998f73..24bd200 100644 --- a/docs/module-decomposition.md +++ b/docs/module-decomposition.md @@ -39,6 +39,7 @@ crank/ Поверх существующих crates должны появиться новые логические поддомены: - workspace/access domain; +- secret management domain; - agent publishing domain; - observability domain. @@ -61,6 +62,7 @@ crank/ - `operation` - `agent` - `auth` +- `secret` - `observability` - `errors` @@ -94,6 +96,7 @@ crank/ Назначение: - хранение workspace-scoped operations и version snapshots; +- хранение workspace-scoped secrets и secret versions; - хранение agents и agent versions; - auth profiles; - platform API keys; @@ -105,6 +108,7 @@ crank/ Назначение: - исполнение published operation; +- резолв `auth_profile_ref -> secret -> request auth`; - запись invocation events; - возврат нормализованного результата. @@ -121,6 +125,7 @@ crank/ Должен содержать сервисные группы: - `workspaces` +- `secrets` - `memberships` - `operations` - `auth_profiles` diff --git a/docs/runtime-config.md b/docs/runtime-config.md index ac9188a..3785ae6 100644 --- a/docs/runtime-config.md +++ b/docs/runtime-config.md @@ -36,22 +36,20 @@ var/crank/ ## 4. Секреты и auth profiles -Для MVP: +Для целевой модели: - operation хранит только `auth_profile_ref`; -- auth profile хранит только `secret_ref`; -- реальные секреты не должны попадать в YAML export; -- секреты не должны логироваться. +- `AuthProfile` хранит только ссылки на `secret_id`; +- plaintext секреты не должны попадать в YAML export; +- plaintext секреты не должны логироваться; +- runtime получает секрет только на короткое время перед upstream вызовом. -Допустимые варианты secret storage: +Стартовая реализация: -- env-backed secret store; -- encrypted local secret storage. - -Минимальный безопасный вариант для MVP: - -- `secret_ref` указывает на env variable alias или key в локальном secret store; -- приложение резолвит его на runtime. +- `PostgreSQL`-backed secret store; +- `ciphertext` хранится в БД; +- шифрование выполняется через `CRANK_MASTER_KEY`; +- ключ шифрования приходит только из env. ## 5. Переменные окружения @@ -165,6 +163,6 @@ Demo/deployment: - где лежит БД; - где лежат artifacts; -- как резолвятся `secret_ref`; +- как резолвятся `secret_id` и как ротируется `CRANK_MASTER_KEY`; - на каких bind-address запускаются `admin-api` и `mcp-server`; - какой transport использует MCP server. diff --git a/docs/secrets-auth-plan.md b/docs/secrets-auth-plan.md new file mode 100644 index 0000000..8e5ede4 --- /dev/null +++ b/docs/secrets-auth-plan.md @@ -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 можно считать реально реализованным.