вход переехал на доверенный заголовок Authelia вместо собственного OIDC

- пришедшего называет заголовок Remote-User от прокси, и верят ему только с
  адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни
  корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе
- учётная запись заводится первым обращением: EnsureUser в пакете хранилища,
  шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users
- cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт
  унаследованный DL3066 — пользователь образа назван числом
This commit is contained in:
av
2026-08-22 20:24:22 +03:00
parent e4441f3c49
commit 7f33c957e5
63 changed files with 5257 additions and 2305 deletions
+17 -14
View File
@@ -57,16 +57,17 @@ force_shutdown_timeout = <N> # ждать остановки ворке
Секретные поля оставляем пустыми — значение приходит из выкладки (см. «Секреты»).
*Расхождение:* адреса провайдера в секции `[auth]` образца заполнены примерами
вида `https://auth.example.com/...`, а не оставлены пустыми: пустой адрес не
говорит, какой формы значение здесь ждут. Пустым оставлен только
`client_secret` — он и есть секрет.
*Расхождение:* перечень доверенных адресов в секции `[auth]` образца заполнен
примером — подсетью docker, — а не оставлен пустым: пустое значение не говорит,
какой формы значение здесь ждут, а сервис с пустым перечнем не поднимается вовсе.
Секретов в этой секции больше нет: они ушли 2026-08-22 вместе с собственным
входом.
Там же, комментарием под секцией, стоит **второй набор значений `[auth]` — под
подставной провайдер `cmd/oidcstub`**. Они — не живая форма, и это намеренно:
образец описывает боевую выкладку, а локальный вход — способ до неё дойти, и два
рабочих набора в одном файле читались бы как выбор без указания, какой из них
чей.
Там же, комментарием под секцией, стоит **второе значение перечня — петлевой
адрес, под подставной прокси `cmd/devtools proxy`**. Оно стоит закомментированным, и это
намеренно: образец описывает боевую выкладку, а локальный вход — способ до неё
дойти, и два рабочих значения в одном файле читались бы как выбор без указания,
какое из них чьё.
## Поля по дискриминатору `type`
@@ -95,7 +96,8 @@ Ansible из `pet-project-server`). Приложение просто читае
- Секретные поля transcriber: `yandex.speech_kit_api_key`,
`yandex.object_storage_access_key_id`,
`yandex.object_storage_secret_access_key`, `auth.client_secret`.
`yandex.object_storage_secret_access_key`. Секрет клиента OIDC отсюда ушёл
2026-08-22 вместе с собственным входом.
- Отрендеренный `config.toml` (с секретами) **не коммитится**; права `0600`,
владелец — пользователь процесса (`1000:1000`).
- В `config.example.toml` секретные поля — пустые строки.
@@ -137,10 +139,11 @@ TOML. Пустые ключи Yandex ловятся в конструкторе
Секция `[auth]` — первая, у которой проверка своя и стоит на старте:
`AuthConfig.Validate()` зовётся из `cmd/transcriber` сразу после загрузки и роняет
процесс с перечнем незаполненных ключей. Причина в цене умолчания: поднявшись с
молча выключенным входом, сервис остался бы открытым наружу, а узнать об этом
было бы неоткуда. Сообщение называет **имена ключей**, а не значения — значение
`client_secret` в журнал попасть не должно.
процесс с именем незаполненного ключа. Причина в цене умолчания: поднявшись с
пустым перечнем доверенных адресов, сервис не узнавал бы никого, а узнать об
этом было бы неоткуда — все адреса приложения просто отвечали бы отказом.
Сообщение называет **имя ключа**; правило «значения в отказ не идут» остаётся в
силе для прочих секций, где секреты есть.
## Структура в коде
+8 -4
View File
@@ -31,10 +31,13 @@ OpenSpec.
{"time":"2026-08-10T11:23:45.123456Z","level":"INFO","msg":"record accepted","capability":"intake","record_id":"…","source":"api","duration_seconds":137}
```
*Расхождение:* текстовый обработчик ставят **оба** пакета `cmd/`
`slog.NewTextHandler(os.Stdout, …)` и в `transcriber`, и в `oidcstub`. У второго
это выбор, а не долг: его вывод читает человек в терминале, и разбирать его
отбором никто не станет.
*Расхождение:* текстовый обработчик ставит `cmd/transcriber`
`slog.NewTextHandler(os.Stdout, …)`.
*Изъятие:* оснастка разработчика `cmd/devtools` печатает не через `slog`, а
stdlib-логом в поток ошибок. Это выбор, а не долг: её вывод читает человек в
терминале, в сбор он не едет, а текст подсказки по командам `slog`-ом
выглядел бы хуже, чем есть.
## Сообщение
@@ -103,6 +106,7 @@ OpenSpec.
| Когда добавляем | Поля |
| --- | --- |
| на входящий HTTP-запрос | `transport` (`http`), `http.method`, `http.route`, `http.status_code`, `duration_ms` |
| на узнавание пришедшего | `http.peer_addr` — адрес того, кто открыл соединение; плюс `account_id` на заведении учётной записи. **Значения заголовка в строке нет**: им довольно назваться, чтобы стать этим человеком, а с недоверенного адреса его пишет аноним |
| на задачу | `capability` (значения — по именам заведённых capability в `openspec/specs/`), `record_id`, `file_id`, `source` |
| на запись об ошибке | `error` |
| на вызов внешнего сервиса | `ext.service`, `ext.operation`, `ext.status_code`, `duration_ms`, `retry` |
+9 -5
View File
@@ -75,8 +75,10 @@
- **Адреса обычные, а не после решётки** (`createWebHistory`). Отсюда требование
к серверу: неизвестный путь **вне корней сервиса** отдаёт `index.html`, а не
`404`; путь внутри корня в приложение не проваливается никогда. Корней
сегодня четыре`/api/` у хранилища, `/app/` у приложения, `/auth/` у входа,
`/_/` у панели, — плюс `/health` и `/metrics` отдельными адресами. Приложение
сегодня три`/api/` у хранилища, `/app/` у приложения, `/_/` у панели, —
плюс `/health` и `/metrics` отдельными адресами. Корень `/auth/` снят
2026-08-22 вместе с собственным входом, и пути под ним стали обычными путями
вне корней. Приложение
уехало из общего `/api/` решением владельца 2026-08-15: пространство
принадлежит хранилищу, и обновление библиотеки вправе занять там имя рядом с
нашим. Перечень корней сервису не описывают, а из него **порождают**
@@ -104,9 +106,11 @@
- **Обёртка — единственное место, где читается код ответа.** Она же превращает
ошибку контракта в доменную ошибку приложения; экран получает готовый текст, а
не `Response`.
- **Сессия живёт кукой `transcriber_session`**, и приложение её не читает: кука
`HttpOnly`, браузер шлёт её сам, а вошедшего экран узнаёт по ответу API. Норма
— [access](../../openspec/specs/access/spec.md).
- **Сессии у сервиса нет вовсе**, и приложение не хранит ничего: кто пришёл,
называет заголовок обратного прокси, а приложение узнаёт его ответом API.
Кук сервис не ставит — это свойство сторожится проверкой. Норма —
[access](../../openspec/specs/access/spec.md), решение —
[ADR-2026-08-22-login-by-trusted-header](../adr/ADR-2026-08-22-login-by-trusted-header.md).
## Показ ошибок и состояний