Skip to content

[sdds-infra]: Публикация npm и trusted publishing #3002

Description

@Yakutoc

CI: переход публикации npm-пакетов на Trusted Publishing (OIDC)

Что такое Trusted Publishing

Trusted Publishing (TP) — механизм при котором публикация выполняется без npm-токена. При каждом релизе CI получает от GitHub короткоживущий OIDC-токен — подписанный JWT с данными о репозитории и workflow, — а реестр обменивает его на одноразовый publish-credential, если эти данные совпадают с конфигурацией Trusted Publisher пакета (org/repo + имя workflow-файла).

Преимущества:

  • Нет долгоживущего секрета. NPM_REGISTRY_TOKEN становится не нужен: нечего красть, ротировать и нечему утекать. Компрометация CI или секретов не даёт постоянного доступа к публикации — именно через украденные npm-токены проходили крупные supply-chain-атаки последних лет.
  • Минимальная область действия. Обменянный credential живёт минуты и действует на один пакет — в отличие от общего токена с правом публикации во все пакеты организации.
  • Provenance-аттестации автоматически. Каждая версия получает подписанное подтверждение (SLSA), из какого репозитория, коммита и workflow она собрана; на странице пакета появляется бейдж Provenance, потребители могут проверять подписи (npm audit signatures).
  • Публикация только из зафиксированного workflow. После включения на пакетах «Require 2FA and disallow tokens» опубликовать версию мимо CI — в том числе украденным токеном — невозможно.

Что сделано

  1. Единый входной workflow publish-npm.yml, объединивший publish-latest / publish-next / publish-rc / publish-canary (старые файлы удалены). Вся логика публикации по-прежнему в reusable publish-common.yml:
    • push в master → latest-релиз (+ changelog и комментарии в PR);
    • pull_request → canary-версии;
    • workflow_dispatch с input release → rc (с текущей ветки) или next-ветка.
  2. permissions: id-token: write в publish-jobs входного workflow и в publish-common.yml — право на выпуск OIDC-токена (npm требует его в обоих звеньях цепочки).
  3. Поле repository во всех публичных package.json (33 файла): канонический git+https://github.com/salute-developers/plasma.git + directory. Добавлено 11 отсутствовавших, исправлены битые: чужой репозиторий (pashka) в plasma-tokens-utils, неверные directory в plasma-hope и sdds-finai, невалидный ssh://-формат в остальных. Это требование provenance: реестр сверяет поле с фактическим репозиторием сборки, при расхождении публикация падает с 422.
  4. Нормализовано contributors (9 пакетов) → ["salute.developers@gmail.com"].
  5. Убран pull_request_target из publish-цепочки: canary публикуется только для PR из основного репозитория. Выдавать id-token: write в контексте, где исполняется код из форка, недопустимо.

Почему именно так

Почему один входной workflow. У пакета может быть ровно одна TP-конфигурация, и npm валидирует workflow, запущенный событием (claim workflow_ref), а не файл, где физически выполняется npm publish. Подтверждено экспериментом на @salutejs/plasma-web: регистрация publish-common.yml → все обмены падают с OIDC token exchange error - package not found; регистрация входного файла → обмен успешен. Покрыть четыре старых входа одной конфигурацией невозможно, поэтому вход один.

Почему миграция ничего не ломает. lerna 9 при неудачном OIDC-обмене молча продолжает публикацию с прежним токеном из .npmrc. Пакеты без настроенного TP публикуются по-старому — раскатывать конфигурации можно постепенно, релизы не блокируются.

Дальнейшие шаги

  • Убрать NPM_REGISTRY_TOKEN из publish-цепочки и шаг npm whoami из publish-common.yml
  • Включить пакетам «Require two-factor authentication and disallow tokens»
  • Задокументировать регламент нового пакета: первая публикация токеном → npm trust → проверка аттестации

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions