локальный вход задаётся конфигом: заголовки подставляет сам сервис
- в конфиг добавлены секция [auth.test_headers] и предохранитель [server] debug: заголовки входа подставляет слой транспорта, второго процесса локальный запуск больше не требует - подкоманда devtools proxy удалена целиком: всё, ради чего её поднимали, делает сам сервис - адресного предохранителя нет по решению владельца — цена названа в ADR и в модели угроз
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# Адресного предохранителя у отладочного входа нет: держит его умолчание, а не машина
|
||||
|
||||
- **Дата:** 2026-08-23
|
||||
- **Источник:** [../../openspec/changes/archive/2026-08-23-config-test-headers-login/design.md](../../openspec/changes/archive/2026-08-23-config-test-headers-login/design.md),
|
||||
решение 4
|
||||
|
||||
## Решение
|
||||
|
||||
Отладочная подстановка заголовков входа не требует от настроек ничего сверх
|
||||
самого предохранителя `[server] debug`. Решение владельца на чекпоинте записано
|
||||
в источнике дословно:
|
||||
|
||||
> «предохранитель по адресам не делаем, полагаемся только на параметр debug».
|
||||
|
||||
Рассматривалось требование, чтобы при включённом предохранителе перечень
|
||||
доверенных адресов состоял только из петлевых записей; оно снято вместе с
|
||||
предикатом «петлевая запись», который заводился ровно ради него.
|
||||
|
||||
Согласованность с барьером узнавания при этом остаётся: подставленный заголовок
|
||||
проходит тот же перечень доверенных адресов, что и пришедший, и судит адрес та
|
||||
же функция. Предохранителем это не служит — «от конфига она не требует ничего и
|
||||
круга тех, кто мог назваться кем угодно, не расширяет».
|
||||
|
||||
## Почему
|
||||
|
||||
Решение покупает работоспособность отладочного входа там, где адрес пира не
|
||||
петлевой:
|
||||
|
||||
> отладочный вход работает **внутри контейнера** — адрес пира там принадлежит
|
||||
> сети докера, и она же стоит в боевом перечне, — а локальный прогон не
|
||||
> переставляет перечень доверенных адресов на петлевой: петлевые записи
|
||||
> добавляются к тем, что в нём уже стоят.
|
||||
|
||||
Цена названа в источнике прямо, и владелец принял именно её:
|
||||
|
||||
> Что этим потеряно, и это надо назвать прямо: боевую поломку больше не ловит
|
||||
> машина. Сервис, поднятый в бою с включённым предохранителем и заполненной
|
||||
> имитацией, отдаст архив всякому, кто дотянулся до него с доверенного адреса, —
|
||||
> а доверенный адрес в бою это адрес обратного прокси, то есть **любой запрос,
|
||||
> пришедший обычным путём**.
|
||||
|
||||
Между боевой выкладкой и открытым входом остаётся три вещи, и других нет:
|
||||
умолчание предохранителя «выключено»; отказ старта при заполненной имитации без
|
||||
предохранителя; боевой конфиг, который рендерит шаблон Ansible, а не
|
||||
копируют с машины разработчика.
|
||||
|
||||
Отвергнуты вместе с адресным предохранителем ещё два подхода. **Принудительно
|
||||
слушать петлевой адрес при включённом предохранителе** — «меняет поведение молча
|
||||
… и закрывает ровно то, что решение покупает: внутри контейнера сервис слушает не
|
||||
петлю». **Новый ключ `[server] listen`** — «публичная поверхность настроек ради
|
||||
предохранителя, которого решением владельца нет».
|
||||
|
||||
## Почему это ADR
|
||||
|
||||
Триггер — **намеренный отказ** от очевидного подхода. Требовать петлевой перечень
|
||||
при включённом отладочном входе — первое, что предлагает всякий, кто читает
|
||||
модель угроз; отказ от этого оставляет боевую поломку, которую машина не
|
||||
исключает, и объяснить его надо один раз здесь, а не на каждом ревью, которое
|
||||
эту дыру находит заново.
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` Отладочный вход работает и на машине разработчика, и внутри контейнера:
|
||||
перечень доверенных адресов остаётся границей доверия, а не признаком отладки.
|
||||
- `+` Локальный прогон не переставляет перечень на петлевой — петлевые записи к
|
||||
нему добавляются.
|
||||
- `+` Предиката «петлевая запись» в коде нет вовсе: он заводился ради одной этой
|
||||
проверки.
|
||||
- `−` **Машина не исключает боевую поломку «конфиг с `debug = true` и
|
||||
заполненной имитацией».** Такой сервис поднимется на любом перечне доверенных
|
||||
адресов и назовёт своим именем всякого, кто пришёл обычным путём. Записано это в модели
|
||||
угроз, [security.md](../security.md), «Периметр», и в спеке
|
||||
[access](../../openspec/specs/access/spec.md).
|
||||
- `−` Одна из трёх опор лежит вне репозитория: шаблон Ansible из
|
||||
`pet-project-server`. Проверить её отсюда нечем — тем же свойством обладает
|
||||
правило прокси про заголовки `Remote-*`.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Заголовки входа отладочного запуска подставляет сам сервис, а не второй процесс
|
||||
|
||||
- **Дата:** 2026-08-23
|
||||
- **Источник:** [../../openspec/changes/archive/2026-08-23-config-test-headers-login/design.md](../../openspec/changes/archive/2026-08-23-config-test-headers-login/design.md),
|
||||
решения 1, 6, 7 и 10
|
||||
|
||||
## Решение
|
||||
|
||||
Заголовки входа на машине разработчика ставит сам сервис — отдельным слоем
|
||||
цепочки корня приложения, а не вспомогательным процессом рядом:
|
||||
|
||||
> отдельный слой цепочки корня приложения, стоящий **перед**
|
||||
> `TrustedHeaderIdentity` и **после** ограничителя частоты. Он правит заголовки
|
||||
> запроса и ничего больше не делает: учётной записи не заводит, отказов не
|
||||
> выдаёт, в контекст не пишет.
|
||||
|
||||
Включают слой два новых ключа настроек — предохранитель `[server] debug` и
|
||||
секция значений `[auth.test_headers]`. Прежний вспомогательный процесс уходит:
|
||||
|
||||
> **Решено** владельцем на чекпоинте: подкоманда удаляется. Назначения у неё не
|
||||
> остаётся — всё, ради чего её поднимали, делает сам сервис, — и второго способа
|
||||
> входить локально не остаётся тоже.
|
||||
|
||||
Имена заголовков служат именами ключей секции, но набор принимаемых имён
|
||||
порождают константы транспорта: дом у имён остаётся один, а ключ, не совпавший
|
||||
ни с одним из них, роняет старт.
|
||||
|
||||
## Почему
|
||||
|
||||
Владелец назвал желаемое: один бинарник, различия между запусками — в конфиге.
|
||||
Дизайн записал это целью:
|
||||
|
||||
> Локальный запуск идёт одним процессом и одной командой; **вход** — то, кем
|
||||
> назвался пришедший, — отличает тестовый прогон от боевого содержимым файла
|
||||
> настроек, и ничем больше.
|
||||
|
||||
Довод в пользу слоя перед узнаванием, а не внутри него:
|
||||
|
||||
> Отлаживается **та же** ветка кода, что работает в бою: подставленный заголовок
|
||||
> неотличим от пришедшего от Caddy к моменту, когда его читает узнавание.
|
||||
|
||||
Отвергнуты три очевидных подхода, и у каждого названа цена. **Подстановка внутри
|
||||
`TrustedHeaderIdentity`** — «узнавание получило бы второй источник значений и
|
||||
ветку, которой в бою нет. Отлаживалась бы не боевая ветка, а её отладочный
|
||||
двойник». **Произвольная карта имён заголовков в конфиге** — «опечатка
|
||||
`Remote-Usr` даёт „сервис меня не узнаёт“ без единого следа». **Вырезать
|
||||
подстановку из боевой сборки тегом сборки:**
|
||||
|
||||
> сборка образа в гейте не проверяется вовсе (`CLAUDE.md`, «Гейт»), и тег,
|
||||
> забытый в одной ступени, дал бы ровно ту тишину, которой избегает пункт 3.
|
||||
|
||||
## Почему это ADR
|
||||
|
||||
Триггер сработал дважды. **Дорогой откат:** решение заводит два имени ключа
|
||||
настроек, а имя ключа конфига `CLAUDE.md` называет необратимым; вернуться к
|
||||
вспомогательному процессу значит поднять удалённую подкоманду, убрать оба ключа
|
||||
из настроек и переписать рецепт локального запуска, разошедшийся по образцу
|
||||
конфига, `README.md`, `CLAUDE.md` и конвенции настроек. **Намеренный отказ:**
|
||||
вырезать отладочный код из боевой сборки тегом сборки — то, что делают по
|
||||
умолчанию, и отказ от этого объясняется один раз здесь, а не на каждом вопросе
|
||||
«почему подстановка вообще есть в боевом бинарнике».
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` Локальный запуск идёт одним процессом и одной командой; приложение
|
||||
открывают по адресу сервиса, второго порта нет.
|
||||
- `+` Отлаживается боевая ветка узнавания: подставленный заголовок неотличим от
|
||||
пришедшего от прокси к моменту, когда его читают.
|
||||
- `+` Второго способа входить локально не остаётся, и документация перестаёт
|
||||
каждый раз говорить, какой из способов чей.
|
||||
- `+` Имена заголовков остаются с одним домом — константами транспорта; ключ, не
|
||||
совпавший ни с одним из них, роняет старт и называет принимаемые имена.
|
||||
- `−` **Местный инструмент больше не воспроизводит поломки контура.** Цена
|
||||
названа в источнике прямо: два значения `Remote-User`, заголовок с
|
||||
недоверенного адреса, цепочка `X-Forwarded-For` — всё это теперь
|
||||
воспроизводит только автотест, ставящий заголовок сам.
|
||||
- `−` В боевом бинарнике появляется код, называющий пришедшего без провайдера.
|
||||
Что его держит и чего у него нет — [ADR-2026-08-23-no-address-guard-for-debug-login](ADR-2026-08-23-no-address-guard-for-debug-login.md).
|
||||
- `−` У ключа `[server] debug` закрытый перечень следствий, и держать его
|
||||
придётся руками: новое поведение привязывается к ключу только отдельным
|
||||
решением владельца и получает своё требование спеки
|
||||
[access](../../openspec/specs/access/spec.md). Ключ с открытым перечнем
|
||||
следствий обрастает ими молча.
|
||||
- `−` Каждый новый логин имитации заводит учётную запись, а удалять их сервис не
|
||||
умеет. Локальная база ронится и пересоздаётся свободно, в бою подстановка
|
||||
выключена — но лишние записи копятся.
|
||||
@@ -35,6 +35,8 @@
|
||||
|
||||
| Дата | Запись | Статус |
|
||||
| --- | --- | --- |
|
||||
| 2026-08-23 | [Адресного предохранителя у отладочного входа нет: держит его умолчание, а не машина](ADR-2026-08-23-no-address-guard-for-debug-login.md) | |
|
||||
| 2026-08-23 | [Заголовки входа отладочного запуска подставляет сам сервис, а не второй процесс](ADR-2026-08-23-test-headers-substituted-by-service.md) | |
|
||||
| 2026-08-22 | [Хранилищем становится SQLite с каталогом файлов, а PocketBase уходит целиком](ADR-2026-08-22-storage-without-pocketbase.md) | |
|
||||
| 2026-08-22 | [Пришедшего называет заголовок доверенного прокси, а не собственный вход OIDC](ADR-2026-08-22-login-by-trusted-header.md) | |
|
||||
| 2026-08-15 | [Node зовётся контейнером, а не ставится на машину разработчика](ADR-2026-08-15-node-in-container-not-on-machine.md) | |
|
||||
|
||||
Reference in New Issue
Block a user