# Codex: cosign в поставке и работающий автозапуск проверки релиза → `v0.1.0-alpha.15` Задание самодостаточно: истории работы по этому репозиторию у вас нет, всё нужное ниже. ## Репозиторий - https://github.com/ochenstarik-ui/server-monitor-manager - База: `main` @ `0405b0b` (merge PR #42) - Ветка: `codex/cosign-provisioning` Server Monitor Manager — Windows-клиент, ASP.NET Core Control Hub, Linux-агент и WireGuard-mesh с политиками nftables. На серверы ставится bash-скриптом `deploy/ochenstarik-server-monitor-manager.sh` (bootstrap); `deploy/smm-setup.sh` — обёртка, скачивающая релизные ассеты и дающая команды `install-hub` и `install-node`. ## Правила репозитория - `main` защищён: PR обязателен, обязательны статусы `build-and-test` и `build`, force-push запрещён; - один PR — одна тема; - описание PR заполняется **после** завершения CI, со ссылками на конкретные прогоны; - отсутствие проверки не является дефектом отчёта; утверждение о проверке, противоречащее статусу CI, — является; - `docs/release-policy.md` запрещает двигать опубликованные теги и заменять ассеты: исправление выпускается следующей версией; - `.github/workflows/linux-release.yml` — единственный публикатор релизов. --- ## Что произошло `v0.1.0-alpha.14` выпущен и содержит полный набор из 17 ассетов, включая `server-monitor-manager-manifest.pem`. Это первый релиз, подпись которого проверяема. `Release Verification` против него **упал**, и это ровно тот результат, ради которого workflow писался. Он нашёл два независимых дефекта. ### Дефект 1 — cosign не поставляется, установка на чистый сервер невозможна Прогон [31894428693](https://github.com/ochenstarik-ui/server-monitor-manager/actions/runs/31894428693), шаг `Run Positive Installation`: ``` ochenstarik-server-monitor-manager.sh: OK [ochenstarik-server-monitor-manager] Supported platform: ubuntu 24.04, x86_64 [ochenstarik-server-monitor-manager] ERROR: Required command is missing: cosign ``` `verify_manifest` в bootstrap (`deploy/ochenstarik-server-monitor-manager.sh:298`) вызывает `require_command cosign`. При этом: - `cosign` не входит в стандартные репозитории Ubuntu; - `install_dependencies` ставит только `wireguard-tools nftables iproute2`; - `smm-setup.sh` не упоминает cosign вообще; - `docs/linux-bootstrap.md` объясняет, **зачем** нужен сертификат, но нигде не говорит, **откуда взять** cosign. Следствие: `install-hub` на чистой Ubuntu скачивает все файлы, доходит до проверки подписи и умирает. Владелец проекта не может поставить систему на свои серверы — при том, что установка «одной командой» и была смыслом PR #42. Дополнительно, в CI отказ произошёл по смежной причине, которую тоже надо устранить: шаг `Setup cosign` (`sigstore/cosign-installer`) кладёт бинарь в каталог, который добавляется в `PATH` через `GITHUB_PATH`, а тест вызывает bootstrap через `sudo`, и `secure_path` в `/etc/sudoers` эту запись отбрасывает. То есть даже установленный на runner'е cosign невидим для проверяемого кода. ### Дефект 2 — `Release Verification` никогда не запускается сам `.github/workflows/release-verification.yml` объявлен с `on: release: types: [published]`. Релиз создаёт `linux-release.yml` под `GITHUB_TOKEN`, а события, порождённые этим токеном, по правилам GitHub не запускают другие workflow — защита от рекурсии. Прогон по `alpha.14` пришлось запускать вручную через `workflow_dispatch`. Механизм выглядит настроенным и не может сработать ни разу. Пункт «`Release Verification` отработал автоматически» в приёмке предыдущего задания был недостижим в принципе — это дефект задания, не исполнителя. --- ## Часть 1 — обеспечить cosign Требование: **оператор на чистой Ubuntu выполняет одну команду и получает установленную систему с проверенной подписью.** Ни ручной установки cosign, ни `SMM_ALLOW_UNSIGNED`. Реализовать провижининг cosign в поставке: - закреплённая версия и закреплённая контрольная сумма **на каждую архитектуру**, обе константами в скрипте, а не вычисляемые на лету; - скачивание бинаря публичным `curl` с `https://github.com/sigstore/cosign/releases/download//cosign-linux-`; - сверка sha256 с закреплённой константой **до** запуска бинаря; несовпадение — отказ; - установка в `/usr/local/bin/cosign` режимом `0755` (каталог входит в `secure_path`, поэтому cosign станет виден и под `sudo`); - если пригодный `cosign` уже в `PATH` — не трогать существующий, но проверить, что он запускается; - версия и её контрольные суммы записываются в `docs/linux-bootstrap.md`, чтобы обновление было осознанным действием, а не молчаливым дрейфом. Где размещать вызов — ваше решение, но оно должно закрывать оба пути: короткий (`install-hub` / `install-node` в `smm-setup.sh`) и сквозной (`preflight` / `verify-release` в bootstrap). Второй используется в `tests/acceptance/three-server-mesh.sh`. Отдельно предусмотреть отказ с внятным текстом, если скачать cosign невозможно (нет сети, недоступен github). Сообщение должно называть версию, ожидаемую сумму и путь установки — оператору должно хватить его, чтобы поставить cosign вручную. ## Часть 2 — сделать проверку честной Удалить шаг `Setup cosign` из `.github/workflows/release-verification.yml`. Пока cosign даёт runner, тест проверяет окружение GitHub, а не поставку. После удаления шага `Run Positive Installation` проходит только если cosign обеспечивает сам установщик — то есть ровно в том случае, когда пройдёт и установка у оператора. Убедиться, что негативные проверки подписи (`run-negative-tests.sh`) остаются работоспособны и продолжают отвергать подделанные manifest, подпись и сертификат. ## Часть 3 — починить автозапуск Добиться того, чтобы публикация тега давала результат проверки **без действий человека**. Два пригодных пути: - перевести `release-verification.yml` на `on: workflow_run` по завершении `Release pipeline` с фильтром по успешному завершению и по тегу — `workflow_run` срабатывает на завершение другого workflow и ограничением `GITHUB_TOKEN` не блокируется; - либо вынести проверку в отдельный job внутри `linux-release.yml`, зависящий от публикации. Первый путь сохраняет разделение и оставляет `workflow_dispatch` для ручного перепрогона; второй проще, но смешивает выпуск и проверку в одном workflow. Выберите один и обоснуйте выбор в отчёте. `workflow_dispatch` с параметром `tag` сохранить в любом случае — им перепроверяют старые релизы. ## Часть 4 — выпустить `v0.1.0-alpha.15` 1. **До тегирования** прогнать `Release pipeline` через `workflow_dispatch` на своей ветке. Шаги публикации защищены `if: startsWith(github.ref, 'refs/tags/')` и пропустятся, но сборка и подпись выполнятся. Пропуск этого шага уже сжёг два номера (`alpha.10`, `alpha.11`). 2. Создать и запушить тег обычным `git push` — не через `gh release create`: релиз создаёт сам workflow по событию `push: tags: v*`. 3. Убедиться, что `Release Verification` запустился **сам** и зелёный. Это главный результат задания. 4. Тег не двигать ни при каких обстоятельствах. ## Часть 5 — эргономика `install-hub` и `install-node` Найдено живой установкой на Ubuntu 24.04, обе вещи стоили оператору прерванных попыток. **Аргументы проверяются после скачивания.** Вызов `install-hub ВАШ-ХОСТ 178.212.13.102` — плейсхолдер вместо адреса, IP на месте порта — прошёл проверку арности, после чего скрипт скачал 24 МБ и только потом дошёл бы до разбора значений. Валидация `PUBLIC_HOST`, `HTTPS_PORT` и `WG_PORT` должна выполняться **до первого сетевого запроса**. Отдельно отвергать очевидный плейсхолдер: значение, не являющееся ни IPv4-литералом, ни синтаксически годным DNS-именем. **Скачивание идёт молча.** `curl -fsSL` на 24 МБ не печатает ничего, и оператор дважды принимал это за зависание и жал Ctrl+C. Показывать индикатор и имя файла: `curl -fL --progress-bar -o <имя>`. То же относится к любому крупному файлу, который скрипт тянет сам. **Имя `install-node` перекрыто.** У обёртки есть своя команда `install-node` без аргументов, а у bootstrap — одноимённая, принимающая путь к архиву. Оператор, скачавший файлы заранее, получает `install-node takes no arguments` и не имеет способа догадаться, что нужен разделитель `--`. Либо принимать необязательный путь к архиву и, если он задан, пропускать скачивание, либо в тексте отказа прямо называть обходной путь: `smm-setup.sh -- install-node ARCHIVE`. Первое лучше: скачивание заново — это ещё 24 МБ поверх уже имеющихся. **Проверка подписи виснет молча и без таймаута.** `verify_manifest` вызывает `cosign verify-blob ... >/dev/null 2>&1`. При медленной или частично недоступной сети cosign уходит к службам sigstore и не возвращается, а оператор видит только строку `Verifying manifest signature...` и не имеет ни малейшего указания, что происходит. Наблюдалось на живой машине. Требуется: не глушить вывод cosign (или сохранять его в лог и печатать при отказе), обернуть вызов в `timeout` с внятным сообщением по истечении, и в тексте отказа прямо называть службы, к которым нужен доступ. Отдельно задокументировать в `docs/linux-bootstrap.md`, что проверка подписи требует исходящего доступа к sigstore — сейчас об этом не сказано нигде, и оператор в закрытом контуре узнает это зависанием. **`mesh-status` печатает сырой Unix-time.** Колонка `HANDSHAKE` показывает `1786843345`. Оператор смотрит в неё ровно затем, чтобы понять, свежий ли handshake, и обязан считать это в уме. Выводить относительное время (`12s ago`, `3m ago`) либо `never`, если рукопожатия не было. Покрыть контрактным тестом: неверный порт и плейсхолдер в `PUBLIC_HOST` отвергаются без обращения в сеть; `install-node` с путём к архиву не приводит к сообщению об аргументах. ## Часть 6 — записать судьбу alpha.14 В `docs/release-policy.md`: - `v0.1.0-alpha.14` — первый релиз с полным набором подписи, но установка из него на чистый хост невозможна: cosign не поставляется. Проверяем, не устанавливаем; - `v0.1.0-alpha.15` — первый релиз, устанавливаемый на чистый хост. --- ## Границы Antigravity ведёт проверку подписи на стороне Desktop. **Не трогать:** `src/ServerMonitorManager.Desktop/**`, `tests/ServerMonitorManager.Desktop.Security.Tests/**`, `.github/workflows/windows-build.yml`. Ваша область: `deploy/**`, `.github/workflows/linux-release.yml`, `.github/workflows/release-verification.yml`, `tests/release-verification/**`, `tests/bootstrap/**`, `docs/linux-bootstrap.md`, `docs/release-policy.md`. ## Критерий приёмки - cosign обеспечивается поставкой; закреплены версия и контрольные суммы обеих архитектур; - подделанная контрольная сумма cosign даёт отказ — покрыто тестом; - шаг `Setup cosign` из `release-verification.yml` удалён; - `Release Verification` по `v0.1.0-alpha.15` запустился **по событию, без `workflow_dispatch`**, и зелёный; ссылка на прогон в отчёте; - `workflow_dispatch` с параметром `tag` продолжает работать; ссылка на прогон по произвольному тегу; - `install-hub` и `install-node` доводят установку до конца на хосте без cosign; - неверные аргументы отвергаются до первого сетевого запроса; крупные загрузки показывают индикатор; - `SMM_ALLOW_UNSIGNED` не используется ни в одном новом пути и остаётся тем, чем был — аварийным флагом; - `workflow_dispatch`-прогон `Release pipeline` выполнен до тегирования, ссылка в отчёте; - судьба `alpha.14` записана в `docs/release-policy.md`; - всё через PR, CI зелёный, PR не смержен до зелёного. ## Отчёт Раздельно: что проверено локально, что в CI со ссылками на прогоны, что не проверялось и почему. Физическая приёмка на реальных серверах в задание не входит — её выполняет владелец после выпуска релиза.