- в конфиг добавлены секция [auth.test_headers] и предохранитель [server] debug: заголовки входа подставляет слой транспорта, второго процесса локальный запуск больше не требует - подкоманда devtools proxy удалена целиком: всё, ради чего её поднимали, делает сам сервис - адресного предохранителя нет по решению владельца — цена названа в ADR и в модели угроз
26 KiB
0. Чекпоинт человека
Чекпоинт пройден: развилок не осталось, и решения владельца записаны здесь и в
design.md.
- 0.1 Имена ключей конфига подтверждены дословно —
[server] debugи[auth.test_headers](необратимое: имя ключа конфига) - 0.2 Форма имён в секции имитации подтверждена: ключ — имя заголовка, набор
принимаемых имён порождается константами транспорта, неизвестный ключ
роняет старт (
design.md, решение 6) - 0.3 Адресного предохранителя не делаем — «полагаемся только на параметр
debug». Требование петлевого перечня доверенных адресов при включённом
предохранителе снято вместе с предикатом «петлевая запись»
(
design.md, решение 4) - 0.4 Подкоманда
cmd/devtools proxyубирается целиком (design.md, решение 10) - 0.5 Критерии приёмки подтверждены владельцем; пункты 2 и 7 переписаны под решение 0.3
1. Настройки
- 1.1 Завести в
ServerConfigключ предохранителя с умолчанием «выключено» вdefaultConfig(); отсутствие ключа в конфиге читается как «выключено», а негодное значение роняет старт, а не читается как «включено» - 1.2 Завести в
AuthConfigподсекцию имитации заголовков в форме, выбранной шагом 0.2; умолчание — пустая секция. Непустой секция считается при наличии хотя бы одного ключа, каким бы ни было его значение:Remote-User = ""— заполненная секция - 1.3 Вывести перечень принимаемых имён заголовков из констант транспорта
internal/controller/http/identity.go, а не вторым списком; сравнение по каноническому виду имени - 1.4 Завести проверку старта, охватывающую обе секции, методом на корневой
Config(design.md, решение 8), и звать её изcmd/transcriberрядом с имеющимися проверками - 1.5 Отказ старта при заполненной имитации и выключенном предохранителе; текст называет имя ключа предохранителя
- 1.6 Отказ старта при неизвестном имени заголовка в секции; текст называет принимаемые имена
- 1.7 Отказ старта при непустой секции без годного
Remote-User: ключ не назван либо его значение не проходитinternal/entity.AcceptProviderLogin(пустое, пробельное, сверх предела длины, с управляющими знаками); текст называет имя ключа и не называет значения - 1.8 Включённый предохранитель при пустой секции старт не роняет. Перечень доверенных адресов старту при этом не судья: боевая запись перечня включению предохранителя не мешает (решение 0.3)
2. Подстановка на входе
- 2.1 Завести слой транспорта, подставляющий заголовки, в
internal/controller/http - 2.2 Поставить слой в цепочку корня приложения между ограничителем частоты и узнаванием; при выключенном предохранителе и при пустой секции имитации слой в цепочку не встаёт вовсе — цепочка та же, что сегодня
- 2.3 Подставлять только при отсутствии
Remote-User; запрос с любым числом значений проходит нетронутым - 2.4 Подставлять только при попадании адреса пира в перечень доверенных, и
судить адрес той же функцией, какой судит узнавание (
peerAddressи сверка с перечнем изinternal/controller/http/identity.go), а не вторым списком - 2.5 Распоряжаться всей тройкой
Remote-*: названные секцией ставить, не названные удалять - 2.6 Проверить, что
TrustedHeaderIdentityне изменён: отлаживается боевая ветка узнавания
3. Журнал
- 3.1 Предупреждение при старте с включённой подстановкой: имена заголовков,
поле
capability, без значений - 3.2 Строка уровня
INFOна запросе с подставленными заголовками: адрес пира,capability,transport, без значений. Уровень боевой, и боевой журнал строка не топит: пишется она только там, где подстановка работает, а в бою предохранитель выключен умолчанием - 3.3 Предупреждение на запросе, которому подставить нельзя из-за
недоверенного адреса: адрес пира,
capability,transport, без подставляемых значений. Узнавание на этом месте молчит — заголовка нет, и оно пишет отладочную строку
4. Оснастка
- 4.1 Удалить подкоманду
proxyизcmd/devtools: веткуcase "proxy"вmain(), функциюrunProxyи её флаги-listen,-target,-user,-name,-email; неиспользуемые импорты уходят вместе с ней - 4.2 Править шапку пакета
cmd/devtools/main.go: она говорит «Подкоманд две:proxy— подставной обратный прокси,resume— …» и объясняет, зачем прокси нужен. Остаётся одна подкоманда. Абзац про имена заголовков константами вместе с прокси теряет предмет — оснастка их больше не читает - 4.3 Править
usage()вcmd/devtools/main.go: убрать строкуproxy подставной обратный прокси… - 4.4 Убрать упоминания подкоманды из документов. Адреса — все, что нашёл
grep -rn "devtools proxy", кроме каталога этого change и архиваopenspec/changes/archive/: -README.md, блок локального запуска («Локально прокси нет…» и командаgo run ./cmd/devtools proxy) — содержимое пишет шаг 6.5; -CLAUDE.md, раздел «Команды», строкаgo run ./cmd/devtools proxy, и раздел «Запреты», абзац «Локальный запуск не ходит наружу» — содержимое пишет шаг 6.4; -config.example.toml, блок «Локальный вход без Authelia» с двумя вызовами подкоманды — содержимое пишут шаги 6.1 и 6.2; -docs/conventions/config.md, абзац про закомментированный петлевой адрес «под подставной проксиcmd/devtools proxy» — содержимое пишет шаг 6.3; -docs/architecture.md, строка таблицы «Оснастка владельца |cmd/devtools»: «Подставной прокси для местного запуска и возврат остановленной записи в работу» — содержимое пишет шаг 6.7; -docs/security.md, перечень вне периметра, пункт «Машина разработчика и то, что он на ней поднимает»: «её подкомандаproxyвстаёт на место контура» — содержимое пишет шаг 6.6; -docs/review.md, запись о единственном доме подставных внешних собеседников: «на их местеcmd/devtools proxy». Документ процессный, и запись историческая: правится строкой о том, что прокси убран задачейconfig-test-headers-login, а не переписывается
5. Тесты
- 5.1 Тесты проверки старта по требованию «Настройка, открывающая вход всем,
роняет старт»: три отказа (имитация без предохранителя; неизвестное имя
заголовка; непустая секция без годного
Remote-User— не названного и в 300 знаков) и законные случаи (предохранитель без имитации; конфиг, не называющий ни одного нового ключа) - 5.2 Тесты слоя подстановки по сценариям требования «Отладочный запуск
называет пришедшего настройками», включая два значения
Remote-User, недоверенный адрес и чужойRemote-Email - 5.3 Тест: подставленное значение не встречается ни в одной журнальной записи
- 5.4 Тест:
GET /healthпри включённом предохранителе отвечает так же, как при выключенном - 5.5 Тест: ограничитель частоты работает при включённом предохранителе
- 5.6 Тест: при включённом предохранителе и пустой секции запрос без
Remote-User, но сRemote-Emailдоходит до узнавания нетронутым - 5.7 Тест: запрос без заголовка с недоверенного адреса при включённом предохранителе оставляет предупреждение с адресом пира
- 5.8 Тест: конфиг без новых ключей даёт ту же цепочку слоёв и те же ответы, что до изменения
6. Документы
-
6.1
config.example.toml: обе новые настройки с комментарием — зачем, допустимые значения и цена включения. Форма названа прямо: боевой перечень доверенных адресов остаётся рабочим значением,[server] debugстоит рабочей строкойfalse, секция[auth.test_headers]целиком закомментирована, а петлевые адреса в перечне стоят комментарием парой —127.0.0.1и::1: браузер разрешаетlocalhostв IPv6 не реже, чем в IPv4, и перечень без::1даёт неузнанный запрос без единой понятной строки -
6.2 Записать рецепт локального входа одним связным блоком — списком правок сверху вниз, а не тремя комментариями по месту: (1) добавить в перечень доверенных адресов пару петлевых, (2) поставить
debug = true, (3) раскомментировать секцию имитации и назвать в нейRemote-User. Дом блока —config.example.toml, и та же последовательность повторена ссылкой вREADME.mdиCLAUDE.md -
6.3
docs/conventions/config.md: локальный вход описан настройками; абзац про закомментированный петлевой адрес под подставной прокси приведён в соответствие с формой шага 6.1 (пара петлевых адресов, закомментированная секция имитации, рецепт одним блоком) — строкой «Расхождение:», если форма образца расходится с общим правилом секции -
6.4
CLAUDE.md: раздел «Запреты», абзац о локальном запуске — вход идёт настройками, второго процесса нет; раздел «Команды», строка подкомандыproxyубирается -
6.5
README.md: инструкция локального запуска -
6.6
docs/security.md, «Периметр»: изъятие отладочного запуска, чем оно держится (умолчание предохранителя, отказ старта при имитации без него, шаблон выкладки) и что боевая поломка машиной не исключена. Там же — пункт «Машина разработчика и то, что он на ней поднимает»: подкомандыproxyбольше нет -
6.7
docs/architecture.md: строка таблицы про оснастку разработчика (без прокси) и новый слой в перечне слоёв транспорта -
6.8
docs/passport.md, «Управление учётными записями»: сегодня раздел утверждает, что кто пришёл, сервис не решает никогда, и что исключений у этого больше нет. Назвать изъятие одной строкой с его границей — «держится предохранителем[server] debug, выключенным по умолчанию» — и ссылкой на спекуaccess -
6.9 Записать норму о подставных собеседниках — решение владельца, работа сверх прежнего перечня шагов. Одной строкой в документ канона: подставной собеседник живёт в коде или в оснастке, а в боевом бинарнике появляется только отдельным решением владельца и только под ключом, названным своим предметом. Сегодня таких два — подмена распознавания правкой
internal/adapter/recognizer/memory.goи имитация заголовков конфигом, — а записанной нормы нет вовсе, иdocs/review.mdэтот пробел отмечает вопросом темыoperations. Дом нормы —docs/architecture.md, «Принципы»: она называет, где живёт часть системы, а не как оформляют код. Запись вопроса вdocs/review.mdправится строкой о закрытом пробеле
7. Приёмка
- 7.1
task gateзелёный - 7.2 Проверить руками: сервис поднят с заполненной имитацией, приложение открыто по адресу сервиса, второго процесса нет
- 7.3 Проверить руками записанную форму рецепта: скопировать свежий
config.example.toml, применить рецепт шага 6.2 дословно и ничего сверх него, поднять сервис, открыть приложение. Отказ старта на полпути — поломка рецепта, а не копии - 7.4 Критерии приёмки из блока ниже проверены поимённо
Критерии приёмки
Рубрика ревью дизайна. Проверяется поимённо шагом 7.4.
- 1. Fail-closed по умолчанию. Отсутствие новых ключей в существующем конфиге не меняет ни одного байта поведения; умолчание предохранителя — «выключено»; ни одна ошибка разбора не даёт «включено». Проверяемо: конфиг сегодняшнего дня, не тронутый, даёт ту же цепочку слоёв и те же ответы.
- 2. У каждой комбинации новых ключей назван исход, а боевая поломка названа поимённо и не выдана за исключённую. Машина её не исключает: сервис с включённым предохранителем и заполненной имитацией поднимается на любом перечне доверенных адресов, боевом в том числе, и называет пришедшего сам всякому, чей запрос пришёл через обратный прокси. Защита названа поимённо и целиком — умолчание предохранителя «выключено», отказ старта при заполненной имитации без предохранителя, боевой конфиг, рендеримый шаблоном Ansible, — и ни один документ не утверждает, что этого набора хватает на механическую несовместимость.
- 3. Подстановка заменяет, а не дополняет. Слой либо владеет всем
набором заголовков входа целиком, либо не трогает запрос; второго значения
того же заголовка он не создаёт никогда. Проверяемо: запрос с двумя
Remote-Userостаётся отказом, запрос с чужимRemote-Emailи безRemote-Userне собирает личность из двух источников. - 4. Барьер доверенного адреса подставленный заголовок проходит наравне с пришедшим, и предикат доверия у обоих один дом. Проверяемо: запрос с недоверенного адреса не узнаётся при включённом предохранителе так же, как при выключенном; отбор адреса берётся из той же точки, что и у узнавания, а не пишется вторым списком.
- 5. Отлаживается боевая ветка. Узнавание не получает ни второго источника значений, ни ветки «если отладка»; к моменту чтения заголовок неотличим от поставленного прокси. Проверяемо: файл узнавания изменением не тронут.
- 6. Область слоя равна области узнавания и выводится из неё, а не перечисляется вторым списком. Проверяемо: проба здоровья, метрики и раздача приложения отвечают одинаково при включённом и выключенном предохранителе.
- 7. Матрица отказов старта полна и тотальна: у каждой комбинации новых
ключей назван исход, и «поднялся, но никого не узнаёт» среди исходов
нет. Отказ идёт до приёма трафика, называет имя ключа и не называет
значения. Проверяемо перечислением: предохранитель × (пустая / заполненная
/ заполненная негодно) имитация, и отдельной строкой — два ключа секции,
дающие одно каноническое имя заголовка:
Remote-Userиremote-userразличимы для TOML и неразличимы для HTTP, и одно из значений терялось бы молча. - 8. Настройка, при которой узнавание не может состояться ни при каком запросе, — отказ старта, а не запуск. Имитация без ключа логина, пустое, пробельное, слишком длинное значение и значение с управляющими знаками судятся на старте тем же правилом, каким узнавание судит пришедшее значение. Проверяемо: конфиг с логином в 300 знаков даёт отказ старта с именем ключа, а не сервис, отвечающий отказом на всё.
- 9. Отладочный запуск громкий и различимый. Одна строка при старте (без
значений) и различимость «узнан подстановкой» от «узнан прокси» на уровне
запроса. Отдельно — путь, на котором подстановка не сработала: у него
обязан быть след, иначе самый частый локальный отказ (браузер пришёл
::1, конфиг называет127.0.0.1) не отличим от поломки узнавания. - 10. Слой без состояния между запросами: исход запроса — функция конфига и самого запроса. Ничего не кэширует, ничего не заводит, в контекст не пишет; заведение учётной записи остаётся там, где было, и параллельные первые запросы с одним логином не зависят от порядка. Проверяемо: два одновременных первых запроса дают одну учётную запись, а не две и не отказ.
- 11. Предохранитель — закрытый перечень следствий, а не режим. Названо, чего он не включает; новое следствие требует отдельной нормы. Проверяемо: включённый предохранитель при пустой имитации ведёт себя ровно как выключенный, включая ограничитель частоты и уровень журнала.
- 12. Записанный рецепт и объявленная граница совпадают с задуманным.
Форма, записанная в образце конфига и в памятке, — та самая, которую
проверяют; ни один документ канона не продолжает утверждать отсутствие
механизма, который изменение заводит. Проверяемо: свежая копия
config.example.tomlдоводится до рабочего локального входа ровно теми правками, что записаны, и не даёт отказа старта на полпути.