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