HTTP API закрыт за вход через OIDC у Authelia
- шаг схемы закрывает поверхность, которую хранилище приносит открытой: собственную регистрацию, вход по паролю и одноразовый код — без этого закрытие приёма обходилось двумя запросами - продление сессии выключено, срок семь суток: иначе отзыв доступа у провайдера до сервиса не доходит никогда - файл записи отдаётся вошедшему по токену файла — пересмотр ADR-2026-08-12-file-link-open-but-not-logged
This commit is contained in:
+64
-20
@@ -2,14 +2,20 @@
|
||||
|
||||
## Периметр
|
||||
|
||||
**Сервис открыт наружу: HTTP-порт опубликован в интернет через обратный прокси, и
|
||||
аутентификации не делает ни прокси, ни само приложение.** Находки строятся против
|
||||
этого — сегодняшнего — периметра.
|
||||
**Сервис открыт наружу, но не анонимен: HTTP-порт опубликован в интернет через
|
||||
обратный прокси, а приём записи, опрос готовности и файл записи требуют входа
|
||||
через OIDC у Authelia.** Вход развёрнут задачей `oidc-login` 2026-08-12. Открыты
|
||||
без входа только проба здоровья и метрики. Находки строятся против этого —
|
||||
сегодняшнего — периметра.
|
||||
|
||||
Целевой периметр: те же порты наружу, но вход через OIDC у Authelia, отдельный
|
||||
вход для программ по личным токенам, два уровня доступа — пользователь видит
|
||||
свои записи, владелец сервиса ещё и страницу расхода. Он **не** развёрнут;
|
||||
описанное ниже разграничение доступа относится только к Telegram.
|
||||
Целевой периметр добавляет к нему отдельный вход для программ по личным токенам
|
||||
и два уровня доступа — пользователь видит свои записи, владелец сервиса ещё и
|
||||
страницу расхода. **Разграничения по владельцу нет:** всякий вошедший видит все
|
||||
записи и все расшифровки, как видел их прежде аноним. Его заводит задача
|
||||
`record-ownership`.
|
||||
|
||||
Разграничение доступа в Telegram осталось прежним — белым списком, и с учётной
|
||||
записью приложения он не связан.
|
||||
|
||||
**Целевой периметр шире сегодняшнего не только входом.** Содержимое записи
|
||||
начинает уходить на три новые стороны — языковой модели, в канал уведомлений и
|
||||
@@ -28,9 +34,18 @@
|
||||
администраторов. Задачи в беклоге у этого нет — работа принадлежит выкладке, а
|
||||
она вне модели («Что вне модели», строка про контур).
|
||||
|
||||
Отсюда главное следствие, из которого читается всё остальное: **`POST /api/audio`
|
||||
доступен кому угодно из интернета**. Отправитель не назван, не ограничен по числу
|
||||
запросов и не ограничен по размеру файла.
|
||||
**Четвёртый сдвиг — секрет клиента поселился в базе.** Задача `oidc-login`
|
||||
2026-08-12 кладёт адреса провайдера, идентификатор клиента и его секрет в
|
||||
настройки коллекции пользователей, приводя их к конфигу при каждом подъёме
|
||||
(применённый шаг схемы не переписывается, и положенный им секрет не пережил бы
|
||||
ротации). Инвариант проекта запрещает секрету попадать в git, в лог, в ответ и в
|
||||
`error_text`; база в этом перечне не значится, и запрет не нарушен. Но место
|
||||
новое: **чтение файла базы теперь равносильно чтению секрета клиента**.
|
||||
|
||||
Отсюда главное следствие, из которого читается всё остальное: **`POST
|
||||
/api/audio` требует входа, а число запросов и размер файла по-прежнему ничем не
|
||||
ограничены**. Вошедший не ограничен ни в том, ни в другом, и тратит наши деньги
|
||||
на распознавание столько, сколько захочет.
|
||||
|
||||
## Недоверенный вход
|
||||
|
||||
@@ -94,9 +109,12 @@ Telegram отправителю.
|
||||
каталогов, но это единственное, что стоит между входом и именем файла.
|
||||
- **Ключ объекта в Object Storage** — то же имя файла, то есть UUID с
|
||||
расширением. Бакет один на все записи, префикса по пользователю нет.
|
||||
- **Ссылка на файл** — `/api/files/<коллекция>/<запись>/<имя>`. Поле файла не
|
||||
помечено защищённым, поэтому ссылка сама по себе и есть право пройти по ней, а
|
||||
отзыва у неё нет. Отсюда запрет: **имя файла в хранилище в журнал не пишется**
|
||||
- **Ссылка на файл** — `/api/files/<коллекция>/<запись>/<имя>`. Поле файла
|
||||
помечено защищённым задачей `oidc-login` 2026-08-12: пройти по ссылке теперь
|
||||
можно только с коротким токеном файла, который выдаётся по сессии, и запрос
|
||||
без него получает «не найдено». Сама ссылка отзыва по-прежнему не имеет —
|
||||
токен сужает круг и живёт недолго, но выданное не отзывается. Отсюда запрет
|
||||
остаётся: **имя файла в хранилище в журнал не пишется**
|
||||
— иначе строка журнала вместе с идентификатором записи собирала бы ссылку
|
||||
целиком и работала бы бессрочно. В журнал идёт расширение своим полем.
|
||||
- **Идентификатор задачи** — 15 знаков, выдаёт хранилище. Он же единственное,
|
||||
@@ -124,18 +142,44 @@ Telegram отправителю.
|
||||
автора сообщения (`update.Message.From.String()`, то есть `@username` либо имя
|
||||
с фамилией), а не с числовым идентификатором. Имя пользователя Telegram
|
||||
меняется владельцем в любой момент: список привязан к изменяемому значению.
|
||||
- **HTTP API** — ничего. Ни ключа, ни сессии, ни ограничения по адресу.
|
||||
- **Метрики и здоровье** — `GET /metrics` и `GET /health` открыты вместе с
|
||||
остальным.
|
||||
- **HTTP API** — сессия, заведённая входом через OIDC у Authelia. Предъявляется
|
||||
кукой `transcriber_session`, живёт семь суток, обесценивается выходом.
|
||||
Продление сессии закрыто: с ним предъявитель менял бы своё значение на новое
|
||||
бессрочно, и семисуточный срок — единственное, чем отзыв доступа у провайдера
|
||||
доходит до сервиса, — не значил бы ничего.
|
||||
Предъявленный заголовок `Authorization` принимается тоже — это та же сессия и
|
||||
та же проверка, но она названа здесь отдельно, потому что это второй способ
|
||||
предъявить ту же сессию.
|
||||
- **Файл записи** — короткий токен файла, который узнанный отправитель берёт у
|
||||
хранилища, предъявив сессию. Поле файла помечено защищённым, правило просмотра
|
||||
коллекции пускает всякого вошедшего, и ссылка `/api/files/...` перестала быть
|
||||
правом пройти по ней. Браузер с одной лишь кукой файла не получает: порядок
|
||||
здесь «сессия → токен файла → ссылка».
|
||||
- **Кто допущен** — **решает Authelia, а не сервис.** Своей проверки группы
|
||||
приложение не делает: кого пускать, определяет правило провайдера на этого
|
||||
клиента. Правило живёт **вне репозитория**, в настройках выкладки, и по коду
|
||||
его не проверить. Клиент, настроенный слишком широко, открывает сервис
|
||||
всякому, у кого есть учётная запись в общей Authelia. Решение владельца от
|
||||
2026-08-12.
|
||||
- **Заведение учётной записи** — только входом у провайдера. Собственное
|
||||
создание записи, вход по паролю, одноразовый код и восстановление доступа
|
||||
выключены шагом схемы: хранилище заводит коллекцию пользователей открытой, и
|
||||
без этого закрытия вход обходился бы двумя запросами.
|
||||
- **Метрики и здоровье** — `GET /metrics` и `GET /health` открыты без сессии:
|
||||
её нет ни у пробы, ни у сборщика. Наружу их закрывает правило обратного
|
||||
прокси — работа выкладки, и сервис на неё не полагается: содержимого записей
|
||||
эти адреса не несут.
|
||||
|
||||
Владения записью в модели данных нет: у задачи нет пользователя. Пока API
|
||||
анонимен, знание UUID задачи и есть право её читать.
|
||||
Владения записью в модели данных по-прежнему нет: у задачи нет пользователя.
|
||||
Знание UUID задачи и есть право её читать — теперь для всякого вошедшего, а не
|
||||
для всякого встречного.
|
||||
|
||||
Целевой периметр заводит четыре механизма вместо одного белого списка:
|
||||
Целевой периметр заводит четыре механизма вместо одного белого списка; первый из
|
||||
них уже стоит:
|
||||
|
||||
| Механизм | Что даёт | Чья задача |
|
||||
| --- | --- | --- |
|
||||
| Сессия OIDC у Authelia | Право открыть приложение и его эндпоинты | `oidc-login` |
|
||||
| Сессия OIDC у Authelia | Право открыть приложение и его эндпоинты — **сделано 2026-08-12** | `oidc-login` |
|
||||
| Владелец у задачи и файла | Чужая запись по её идентификатору отвечает «не найдено» | `record-ownership` |
|
||||
| Личный токен | Права своего владельца программе, без браузерной сессии | `api-tokens` |
|
||||
| Признак владельца сервиса | Страницу расхода и сводку по всем пользователям | `admin-stats-screen` |
|
||||
|
||||
Reference in New Issue
Block a user