İçeriğe geç
Codebiy

Web Geliştirme

Jeton Olmadan npm'e Yayımlamak: OIDC Güvenilir Yayımlama

npm otomasyon jetonu, CI ayarlarında sonsuza kadar duran ve yayımlama yetkisi olan bir paroladır. Güvenilir yayımlama onun yerine hiçbir şey koymuyor.

Yazan: codebiy Team · Yayın · 8 dk okuma

Yakın zamana kadar CI'dan paket yayımlamak tek bir anlama geliyordu: bir npm otomasyon jetonu üret, depo sırlarına yapıştır ve umut et.

O jeton, paketine yayımlama yetkisi olan ve biri hatırlayıp döndürene kadar geçerli kalan bir kimlik bilgisi. Her depo yöneticisinin okuyabildiği bir ayar sayfasında duruyor, monoreponu böldüğünde ikinci bir depoya kopyalanıyor ve onu oluşturan kişinin ayrılmasından sonra da yaşamaya devam ediyor. Son yılların tedarik zinciri olayları — event-stream, ua-parser-js, coa — hepsi çalınmış ya da kötüye kullanılmış bir yayımlama kimlik bilgisinde bitiyor.

Güvenilir yayımlama onu ortadan kaldırıyor.

Nasıl çalışıyor

Mekanizma OpenID Connect. GitHub Actions, çalışmakta olan iş akışını tarif eden kısa ömürlü bir jeton üretebiliyor: bu depo, bu iş akışı dosyası, bu ref, bu çalıştırma. GitHub tarafından imzalanıyor ve dakikalarla ölçülen bir süre geçerli.

npm bu jetonu parola yerine kabul ediyor — hangi iş akışına güveneceğini ona söyledikten sonra. Takas şöyle işliyor:

  1. İş akışın GitHub'dan bir OIDC jetonu istiyor.
  2. Jetonu npm'e sunuyor.
  3. npm imzayı doğruluyor, iddiaları yapılandırdığın güvenilir yayımcıyla karşılaştırıyor ve o çalıştırmaya özel bir yayımlama izni veriyor.

Hiçbir yerde saklanan bir sır yok. Depo ayarlarında çalınacak bir şey yok, çünkü kimlik bilgisi iş akışı çalışana kadar var olmuyor ve bittiğinde var olmaktan çıkıyor.

Kurulum

npmjs.com'da paketini aç, Settings'e gir ve bir güvenilir yayımcı ekle: GitHub organizasyonu, depo ve iş akışı dosya adı. İş akışı adı güven kararının parçası — aynı depodaki farklı bir iş akışı yayımlayamıyor.

Sonra iş akışı:

permissions:
  id-token: write   # OIDC jetonunu üret
  contents: read

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: 22
      registry-url: https://registry.npmjs.org
  - run: npm ci
  - run: npm publish

Olmayana dikkat: NODE_AUTH_TOKEN yok, secrets.NPM_TOKEN yok. Tek yeni şey id-token: write ve o da bir sır değil, bir izin.

İzin bloğunda durmakta fayda var. GitHub Actions varsayılan olarak geniş bir izin kümesiyle geliyor; permissions'ı açıkça yazmak bu çalıştırmayı tam olarak ihtiyaç duyduğu şeye daraltıyor. Bu yazıdan tek bir şey alacaksan bunu al — üç satıra mal oluyor ve bağımlılık ağacındaki herhangi bir ele geçirilmiş action'ın verebileceği zararı sınırlıyor.

Derleme kanıtı da geliyor

Bu şekilde yayımlamak bir provenance beyanı ekliyor: tarball'ı hangi deponun, hangi commit'in ve hangi iş akışının ürettiğini söyleyen, herkese açık bir şeffaflık kaydına yazılan imzalı bir ifade.

Paket sayfasında "Built and signed on GitHub Actions" rozeti olarak, tam o çalıştırmaya bağlanarak görünüyor. Herkes doğrulayabiliyor:

npm audit signatures

Bunun kanıtladığı şey dar ve net söylemekte fayda var. Kayıttaki tarball'ın, o commit'ten o iş akışı tarafından üretildiğini kanıtlıyor. Kodun iyi olduğunu, commit'in gözden geçirildiğini ya da iş akışının ele geçirilmediğini kanıtlamıyor. Ortadan kaldırdığı şey tek bir saldırı: iddia ettiği kaynaktan hiç derlenmemiş bir tarball. Provenance öncesinde çalınmış bir jeton, senin adına her şeyi yayımlamaya yetiyordu ve kayıt sayfası birebir aynı görünüyordu. Artık uyuşmazlık görünür.

Önceden bilmekte fayda olanlar

İlk yayım hâlâ bir insan istiyor. Güvenilir yayımlama var olan bir paket üzerinde yapılandırılıyor; yani 0.0.1 iki adımlı doğrulamalı bir dizüstünden çıkıyor. İş akışını ancak ondan sonra bağlayabiliyorsun.

Kapsamlı (scoped) paketler ilk yayımda --access public istiyor, yoksa npm özel varsayıyor ve ücretli planı olmayan hesapta hata veriyor. OIDC ile ilgisi yok ama herkesi bir kez yakalıyor.

Fork'lar yayımlayamıyor, ki bütün mesele bu. Fork'tan gelen bir pull request id-token almıyor, yayımlama izni de almıyor. Doğru davranış ve bakımcının kendi fork'undan açtığı PR "gizemli biçimde" sürüm adımını atladığında ara sıra şaşırtıyor.

npm ci, CI'daki npm'in okuyabileceği bir lockfile istiyor. Güvenilir yayımlamayla ilgisi yok ama ilk denememiz aslında burada öldü: lockfile npm 12 ile yazılmıştı, runner'da npm 10 vardı ve o da Missing: <pkg> from lock file diyerek reddediyor. İş akışında npm sürümünü sabitle. Bir saatlik "OIDC kurulumu bozuk" mesaisi, üç adım öncesindeki bir sürüm uyuşmazlığı çıktı.

Değdi mi?

Yaklaşık yirmi dakika sürdü ve sürüm hattı artık çalınabilecek hiçbir kimlik bilgisi tutmuyor. Başkalarının kurup makinesinde çalıştırdığı bir paket için bu, olsa iyi olur cinsinden bir şey değil.

Asıl mesele daha genel bir şekil: sektör uzun ömürlü sırlardan kısa ömürlü, iş yüküne bağlı kimliğe geçiyor ve bu her yerde oluyor — bulut sağlayıcılar, konteyner kayıtları, paket kayıtları. Deseni küçük bir pakette bir kez öğrenmek, onunla tanışmanın ucuz yolu.