Files
crank/docs/secrets-auth-plan.md
T
2026-04-06 00:40:25 +03:00

246 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 можно считать реально реализованным.