8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 163,
|
||
"slug": "editorial-2023-06-field-secrets-supply-chain",
|
||
"title": "Когда deploy виден, а происхождение нет: проверяем цепочку поставки",
|
||
"excerpt": "Практический разбор разрыва между исходным кодом, сборкой, digest, декларацией и deploy input. Секретная граница, отрицательный путь и критерий, после которого выпуск можно проверять дальше.",
|
||
"contentHtml": "<p>В отчёте CI есть успешная сборка. В registry лежит образ. Deploy указывает на digest. Но команда не может ответить, из какого commit собран этот digest, какой builder его выпустил и какая декларация относится именно к нему. Ошибка проявляется поздно: релиз уже обсуждают, а provenance приходится восстанавливать по разным системам.</p>\n<p>Цена разрыва — не только задержка. Команда может принять чужой образ за результат доверенной сборки. Она может отозвать не тот credential, откатить не тот digest или назвать подписанную декларацию доказательством факта, которого она не проверяет. Секрет при этом способен попасть в логи, слой образа, кеш или артефакт CI.</p>\n<p>Тезис простой: deploy сам по себе показывает только выбранный вход. Без сопоставления source revision → builder identity → secret boundary → artifact digest → declaration → deploy input нельзя утверждать происхождение результата. Каждый переход требует своего evidence. Отсутствующий переход переводит выпуск в ручной review, а не в подтверждённое provenance.</p>\n<h2>Механизм разрыва</h2>\n<p>Цепочка поставки состоит из разных утверждений. Commit отвечает на вопрос о входном коде. Builder и его identity отвечают на вопрос о процессе сборки. Secret boundary показывает, какие данные могли попасть в процесс и куда им запрещено уходить. Digest связывает байты артефакта с конкретным output. Declaration описывает claims о сборке. Deploy input показывает, что именно пытались применить.</p>\n<p>Соседнее утверждение не заменяет пропущенное. Имя job не доказывает, что job выполнила сборку. Digest не доказывает commit. Декларация не доказывает, что её subject попал в deploy. Подпись подтверждает целостность подписанного объекта при корректной проверке, но не превращает любой текст в наблюдение среды.</p>\n<p>Такой разбор нужен и для секретов. Секрет не должен проходить через Dockerfile, командную строку, переменную, которую печатает shell, или общий кеш. Значение может быть скрыто в логе, но остаться в слое образа. Оно может исчезнуть из образа, но сохраниться в артефакте или history. Поэтому проверяют не только содержимое файла, но и границы процесса.</p>\n<h2>Минимальный контракт проверки</h2>\n<p>Начните с одной карточки выпуска. В ней достаточно шести полей: commit, builder, digest, declaration subject, deploy input и граница секрета. Для каждого поля запишите источник, время получения и допустимый способ просмотра. Не копируйте значение секрета. Нужен факт его отсутствия или контролируемого использования, а не само значение.</p>\n<pre><code>const release = {\n sourceRevision: 'abc123',\n builderIdentity: 'ci.example/build-prod',\n artifactDigest: 'sha256:...',\n declarationSubject: 'sha256:...',\n deployInput: 'sha256:...'\n};\n\nconst sameArtifact =\n release.artifactDigest === release.declarationSubject &&\n release.artifactDigest === release.deployInput;\n\nif (!sameArtifact) {\n throw new Error('manual review: artifact links do not match');\n}\n\n// Учебный пример. Он не проверяет подпись, CI, registry или production.\n// Реальные форматы полей и правила доверия задаёт конкретная среда.\n</code></pre>\n<p>Код показывает только одну проверяемую связь: одинаковый digest в артефакте, декларации и deploy input. Он не доказывает, что commit действительно участвовал в сборке. Он не проверяет identity builder, подпись, policy или содержание секрета. Если хотя бы одно поле недоступно, безопасный результат этого примера — ручной review.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<table><caption>Как сузить разрыв в цепочке</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Deploy содержит digest, но нет commit</td><td>Артефакт отделён от записи сборки</td><td>Сопоставьте digest с output конкретной job</td><td>Остановите вывод о provenance до появления связи</td></tr><tr><td>Declaration есть, subject не совпадает</td><td>Выбрана декларация другого артефакта</td><td>Сравните subject digest и deploy input</td><td>Переведите выпуск в manual review</td></tr><tr><td>Builder указан именем job</td><td>Нет проверяемой identity и доверенной границы</td><td>Найдите issuer, workflow и policy проверки</td><td>Назначьте отдельную проверку builder</td></tr><tr><td>Секрет исчез из лога, но попал в image history</td><td>Значение передали в Dockerfile или командной строке</td><td>Проверьте слои, history, cache и export</td><td>Удалите секрет из build input и смените credential</td></tr><tr><td>Есть digest и подпись, но нет deploy link</td><td>Подписали объект, не проверив его использование</td><td>Сверьте точный digest с manifest deploy</td><td>Не называйте подпись доказательством release</td></tr></tbody></table>\n<h2>Секретная граница начинается до сборки</h2>\n<p>Сборка должна получать секрет только там, где он нужен, и только на время операции. Не задавайте его через <code>ARG</code>, если значение может попасть в историю слоёв. Не выводите окружение командой вроде <code>env</code> в диагностический лог. Не сохраняйте рабочий каталог с credential в артефакт CI. Не передавайте секрет в шаг, который собирает публичный output.</p>\n<p>Учебный фрагмент ниже показывает безопасную мысль, а не готовую конфигурацию конкретного CI:</p>\n<pre><code># Учебный пример: имя секрета передаётся в действие, значение не печатается.\n# Фактический синтаксис зависит от CI и secret store.\nrun: ./publish.sh\nenv:\n REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}\n\n# publish.sh не выполняет: set -x, env, printenv, cat /proc/*/environ\n# и не складывает каталог с credential в артефакты.</code></pre>\n<p>Этот пример не доказывает отсутствие утечки. Его нужно дополнить проверкой логов, временных файлов, кеша, image history и прав доступа. Если токен уже появился в публичном output или общем registry, удаление строки из конфигурации не закрывает инцидент. Сначала ограничьте доступ и смените credential по процедуре владельца.</p>\n<h2>Digest связывает байты, но не всю историю</h2>\n<p>Digest полезен потому, что связывает имя output с конкретным содержимым. Поэтому release должен хранить полный digest, а не только tag вроде <code>latest</code>. Tag может указывать на другой объект после публикации. Но digest остаётся только якорем. Он не сообщает, кто собрал образ, с каким исходным кодом и какие входы получил builder.</p>\n<p>Проверяйте цепочку в прямом порядке, даже если проблема обнаружилась на deploy. Найдите commit. Найдите запись builder. Получите digest output. Сверьте subject декларации. Сверьте manifest deploy. После этого отдельно проверьте, какие данные видел процесс сборки и где они могли сохраниться. Такой порядок не позволяет начать с красивой декларации и подогнать под неё остальные факты.</p>\n<figure><img src=\"/assets/editorial/2023/secrets-supply-chain-2023-release-decision.svg\" alt=\"Дерево проверки выпуска: сопоставление commit, builder, secret boundary, artifact digest, declaration и deploy input с переходом в manual review при отсутствующем факте\" loading=\"lazy\" /><figcaption>Схема показывает порядок сопоставления. Это учебная иллюстрация процедуры, а не CI-отчёт, policy или доказательство конкретного выпуска.</figcaption></figure>\n<h2>Отрицательный путь</h2>\n<p>Проверка должна явно описывать отказ. Если declaration subject отличается от deploy digest, не выбирайте ближайший digest по времени. Если builder не имеет проверяемой identity, не принимайте название workflow за identity. Если secret попал в слой, не ограничивайтесь удалением тега: образ и связанные кеши уже требуют отдельной обработки.</p>\n<p>Неудача проверки не всегда означает компрометацию. Она означает, что текущих данных недостаточно для заявленного вывода. Это важное различие. Статус <code>not-verified</code> честнее, чем <code>pass</code>, построенный на совпадении имён. Дальнейшее действие выбирают по риску: остановка выпуска, получение evidence, смена credential или rollback к известному digest.</p>\n<h2>Порядок действий</h2>\n<ol><li><strong>Зафиксируйте симптом.</strong> Запишите, какая связь отсутствует: commit → build, build → digest, digest → declaration или declaration → deploy.</li><li><strong>Определите спорный output.</strong> Используйте полный digest и точный manifest. Не заменяйте их tag или названием job.</li><li><strong>Проверьте источник.</strong> Сопоставьте commit, workflow, builder identity и время выполнения с одной записью сборки.</li><li><strong>Проверьте subject.</strong> Сравните digest декларации с digest артефакта и deploy input посимвольно.</li><li><strong>Проверьте secret boundary.</strong> Ищите значение и его следы в логах, слоях, history, cache, временных файлах и exported artifacts.</li><li><strong>Проверьте отрицательный путь.</strong> Подставьте другой digest, пропустите declaration или отзовите доступ к secret store. Проверка должна остановиться, а не выбрать ближайший объект.</li><li><strong>Выберите действие.</strong> При отсутствии связи остановите следующий шаг. При подтверждённой утечке ограничьте доступ и смените credential. Rollback выполняйте только к известному и совместимому кандидату.</li><li><strong>Запишите критерий.</strong> Укажите, какой новый evidence переводит статус из ручного review в проверенный результат.</li></ol>\n<h2>Ограничения</h2>\n<p>Provenance не заменяет сканирование уязвимостей, контроль доступа, защиту registry и проверку содержания артефакта. Подпись не заменяет проверку subject и trusted identity. Digest не гарантирует безопасный исходный код. Secret store не защищает от вывода значения в лог, если build step печатает окружение.</p>\n<p>Учебные примеры в статье не выполняют криптографическую проверку, не обращаются к CI, registry или production и не дают production-результатов. Формат declaration, issuer, policy и процедура отзыва зависят от ваших инструментов. Не объявляйте соответствие SLSA или SSDF по одному найденному полю. Сначала проверьте применимый профиль и границы заявленного уровня.</p>\n<p>Rollback тоже имеет границу. Он может вернуть известный deploy input, но не удаляет уже скачанный образ, не отзывает credential и не исправляет запись в чужом кеше. Эти действия требуют отдельной операционной процедуры и владельцев. Если известного кандидата нет, безопаснее остановить выпуск и сохранить минимальное evidence.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Проверка готова, если команда показывает одну карточку выпуска и отвечает на пять вопросов: какой commit вошёл в сборку, какой builder выполнил её, какой digest получен, какой declaration subject с ним совпадает и какой digest указан в deploy. Дополнительно команда показывает, где проверена секретная граница и какой отрицательный сценарий остановил выпуск.</p>\n<p>Критерий не требует утверждать больше, чем доказано. Если любой ответ опирается на имя, tag, текст в ticket или декларацию без независимого сопоставления, статус остаётся <code>not-verified</code>. Если все связи проверены допустимыми evidence, отрицательный путь блокирует несоответствие, а план обработки секрета известен, следующий шаг можно принимать в рамках policy конкретной системы.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://slsa.dev/spec/v1.0/\" target=\"_blank\" rel=\"noopener noreferrer\">SLSA v1.0: официальная спецификация</a> — модель provenance и требования к её проверке.</li><li><a href=\"https://docs.sigstore.dev/cosign/verifying/verify/\" target=\"_blank\" rel=\"noopener noreferrer\">Sigstore Cosign: verify</a> — официальная документация по проверке подписей и утверждений.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218 SSDF</a> — официальный набор практик безопасной разработки и защиты процесса поставки.</li></ul>"
|
||
}
|