1 puan yazan GN⁺ 3 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Tedarik zinciri saldırıları, yazılım dağıtım maliyeti çok düştüğü ve derleme/dağıtım otomasyonu yaygınlaştığı için daha büyük bir sorun haline geldi
  • 1970’lerde yeniden kullanılabilir yazılım üretmenin zor olduğu bir yazılım krizi vardı; bugün ise paket depoları ve paket yöneticileri yalnızca ad ve sürümle kodu alıp derliyor
  • Otomatik bağımlılık güncellemeleri, kötü niyetli değişikliklerin CI üzerinden hızla yayılmasına yol açıyor; iyi bir tedarik zinciri saldırısı, CI runner’larının çalıştığı hızda yayılıyor
  • Tüm bağımlılıkları proje deposunun içine birlikte koyan vendoring, depoyu büyütse de otomatik değişiklikleri engelliyor ve bağımlılıkların ölçeğini ile maliyetini daha görünür kılıyor
  • Her yazılım için uygun bir çözüm olmasa da, birçok küçük yazılım dışarıdan aniden değişebilecek bağımlılıklarını 2–3 seviyeye indirmenin faydasını görebilir

Sorun

  • Tedarik zinciri saldırıları, yalnızca yazılımın ya da bakımın doğası değiştiği için değil; yazılım paylaşımı ve dağıtımının maliyet modeli aşırı ucuzladığı için giderek daha büyük bir sorun oldu
  • Dağıtım maliyeti o kadar düştü ki, israf olsa bile yoğun otomasyon kullanılır oldu; otomasyonun kendisi ise faydalı
  • Birkaç ayda bir yeni bir tedarik zinciri saldırısı ortaya çıkıyor ve dünya çapındaki kodların büyük bir bölümünü bozabiliyor

Buraya nasıl geldik

  • 1960’ların sonu ile 1970’lerin başında insanlar yeniden kullanılabilir yazılımı nasıl yapacaklarını pek bilmiyordu; buna yazılım krizi deniyordu
  • Yazılım talebi üstel olarak artıyordu, ancak gerekli karmaşıklığa uygun yeni yazılım üretme kapasitesi bundan daha yavaş artıyordu
  • Bu dönem modülerlik, yapısal programlama gibi araştırmalara yol açtı; 1990’dan sonra yaratılan neredeyse tüm programlama dillerinin modül sistemleri soyunu Modula-2’ye kadar götürebilir
  • 1990’lar ve 2000’lerde internet daha güçlü bir çözüm sundu; yazılım derlemek ve dağıtmak ucuzladı, ayrıca insanların gerçekten kullanmak istediği yazılımların önemli bir kısmı açık kaynaklıydı
  • CPAN, CTAN ve Linux dağıtımları temel alınarak çok sayıda paket deposu ve paket yöneticisi ortaya çıktı; bu araçlar manifest dosyası, isim ve çoğu zaman keyfi sürüm numaralarıyla yazılımı bulur, indirir ve derler
  • Elle entegrasyondan otomatik bağımlılıklara

    • Geçmişte karmaşık yazılım sistemleri kurmanın iyi yolu, çalışan parçaları elde dikkatle bir araya getirmekti; Linux dağıtımları temelde bunu yapar
    • 2003’te SDL’yi tüm özellikleriyle derlemek günler sürecek kadar sancılıydı; ama o günleri özlemeye gerek yok
    • Linux dağıtımlarının bilinen bir temel ortam sağlaması sayesinde, birçok özel yazılım kendi dünyasında çalışabilir ve sistemin geri kalanını çok fazla dert etmek zorunda kalmaz
    • Başka yazılımlarla iletişim kurduğunda ise çoğunlukla iyi bilinen protokolleri kullanan dosyalar veya ağ soketleri üzerinden yapar
    • Sıfırdan Rust ya da Go ile derlenen veya Docker konteyneri olarak dağıtılan iyi yazılımların sayısı arttı; bu tür yazılımlar sistem kütüphaneleriyle neredeyse hiç etkileşmez
    • İşletim sistemi dağıtımının sunduğu yazılım kümesine uymaktansa, gereken kütüphaneleri derleme sisteminin doğrudan getirmesi yaygınlaştı
  • Ters yöndeki kriz

    • Bugün durum 1970’lerin tersi: insanlar yazılımı fazla yeniden kullanıyor ve bu da programları daha kötü hale getiren bir krize yol açıyor
    • Yazılım dağıtımı hâlâ çok ucuz, ama yazılım kullanmanın hâlâ bir maliyeti var
    • Uzun süre en büyük maliyet, yazılımı derleyip bilgisayarda çalıştırılabilir hale getirmenin karmaşıklığıydı; bu sorun büyük ölçüde otomasyonla ortadan kalktı
    • Artık çok daha fazla yazılım derliyor, dağıtıyor ve kullanıyoruz; bunun maliyeti ise bağımlılık cehennemi, şişkinlik, uzun derleme süreleri ve paketlerin ya da paket yöneticilerinin ortadan kaybolması gibi biçimlerde ortaya çıkıyor
    • En büyük sorun ise tedarik zinciri saldırıları
  • Tedarik zinciri saldırılarının yayılma yapısı

    • Tedarik zinciri saldırıları, açık kaynak yazılım kadar eski bir sorun
    • Geçmişte Linux çekirdeğine uid == 0 yerine uid = 0 koymaya çalışan kötü niyetli yama girişimi, gerçek ortamda görülen ilk kötü niyetli çekirdek yamasıydı ve bir tedarik zinciri saldırısı girişimiydi
    • Son 10 yılda tedarik zinciri saldırılarının daha büyük ve daha sorunlu hale gelmesinin nedeni, derleme sistemlerinin kaynak kodu çekip dağıtmayı otomatikleştirmesi
    • CI sistemleri genellikle her kod değişikliğinde veya büyük değişikliklerde çalışır ve bu değişiklikler o koda bağımlı olan herkes için otomatik olarak kullanılabilir hale gelir
    • Bağımlı taraftaki CI sistemleri de bu değişiklikleri alır ve yeni eklenen kötü amaçlı kodu dahil eder; iyi bir tedarik zinciri saldırısı, CI runner’larının çalıştığı hızda orman yangını gibi yayılır
    • Bağımlılık cooldown’ı gibi tedarik zinciri saldırılarını yavaşlatma yöntemleri var, ancak bunlar politika ve sorumluluk tartışmalarını beraberinde getiriyor

Çözüm

  • Esas fikir, npm, cargo gibi derleme sistemlerinin her seferinde ağdaki bir konumdan bağımlılıkları otomatik olarak çekmesine izin vermemek; bunun yerine tüm bağımlılıkları yazılımla birlikte tutmak
  • Projedeki tüm bağımlılıkları vendoring ile içeri almak, upstream kaynak denetim içeriğini git deposuna kopyalayıp commit etmek
  • Upstream güncellemesi geldiğinde indirip yeniden kopyalamak yeterli; bu el işi sıkıcı hale gelirse derleme aracı bunu otomatikleştirebilir
  • Zaten bir lockfile varsa, onu kaynak denetimi içindeki tam kaynak ağacına bağlamak yeterlidir
  • Her kaynak kod satırına güçlü denetim uygulayacak şekilde sahip olmak gerekir
  • Maliyetler ve ödünleşimler

    • Depo büyür, ama disk alanı ucuzdur
    • Aktarım maliyeti diskten daha az ucuzdur, ancak bu tartışmada katlanılması gereken bir unsur olarak kalır
    • Derleme süreleri artacakmış gibi görünür, ama zaten o bağımlılıkları yeniden derlediğiniz için mutlaka artmaz
    • Kodun yeniden kullanımı zorlaşabilir; ortak protokol kütüphaneleri kullanan istemci ve sunucu gibi programlarda bu gerçekten sorun olabilir
    • Bu tür programlar zaten sürüm uyumsuzluğu sorunlarına sahip ve bunları yönetmek zorunda; dolayısıyla bunun dikkat gerektirmesi uzun vadede aslında daha kötü olmayabilir
  • Tedarik zinciri saldırılarına karşı yangın şeridi

    • Bağımlılıklar otomatik güncellenmezse, ekosistemdeki her paket tedarik zinciri saldırılarına karşı bir yangın şeridi haline gelir
    • Aynı yaklaşım hata düzeltmeleri ve yamaların yayılmasını da engeller; ancak önemli bir düzeltmeyse zaten biri bunu elle arayıp bulacaktır
    • İnsanların gidip bakmadığı düzeltmeler çoğu zaman zaten çok önemli değildir
    • Benzer etki, semver’i veya “iki farklı kod aynı şekilde davranmalıdır” fikrini derleme sisteminden çıkarıp tüm sürüm numaralarını birbiriyle ilgisiz benzersiz değerler gibi ele alarak da elde edilebilir
    • Semver’in sorunu, gerçekliği değil insanların niyetini ifade etmesi ve onun da ancak bir ölçüde doğru kullanıldığında işe yaramasıdır
    • Sürüm numaralarını benzersiz ele alma yaklaşımı, bağımlılıkların kaybolması, kurcalanması veya paket içeriğinin başka şekillerde bozulması sorununu çözmez
  • Bağımlılık görünürlüğü

    • Tüm bağımlılıkları vendoring ile içeri almak, otomatik değişiklikleri yavaşlatmanın ötesinde bağımlılık kullanmanın maliyetini biraz yükseltir
    • Bu maliyet artışı geri döndürülemez düzeyde değildir; sadece upstream kod kullanırken biraz daha fazla düşünmeye zorlar
    • Yeni bir bağımlılık eklerken “gerçekten gerekli mi” sorusunu yeniden sorduran yumuşak bir mekanizma olur
    • Bağımlılıkların görünürlüğü artar ve bağımlılıkların arkasına saklanan şişkinlik daha az gizlenir
    • Yaklaşık 200 satır olacağını düşündüğünüz basit bir kütüphane eklediğinizde bunun 50.000 satır olduğunu görmek, durup nedenini sormanız gerektiğini daha açık hale getirir
    • Bağımlılıkların sihirli doğası azalır ve kod tabanındaki bir hatanın başka birinin koduna uzanan yolunu izlemek daha kolay olur
  • Bağımlılık ağacı ve paylaşım sorunu

    • Her şeyi varsayılan olarak vendoring ile içeri almak, daha düz ve daha geniş bağımlılık ağaçlarını teşvik edebilir
    • Bunun C++’taki Boost veya Qt gibi dev kütüphaneler düzeyine gitmesi arzu edilmez
    • Bu dev kütüphaneler, küçük C/C++ kütüphaneleri üretmek ve kullanmak çok zor olduğu için var
    • Buradaki varsayım, Boost ya da Qt gibi şeylerin nasıl derleneceğini herkesin tek tek çözmesindense, Linux dağıtımı gibi bir sistem entegratörünün bunu bir kez yapmasının daha iyi olduğudur
    • Asıl dezavantaj, geçişli bağımlılıkların paylaşılmamasıdır
    • lib A ve lib B’nin ikisi de Z’ye bağlıysa, tekilleştirme imkânsız değildir ama zorlaşır; ya insanların elle yapması ya da daha gelişmiş araçlar gerekir
    • Geçişli bağımlılıklar paylaşıldığında da sorunlar çıkar; zaten geçişli bağımlılık sahibi olmak problemin bir parçasıdır
    • Kütüphanelerin geçişli bağımlılık belirtmesine izin vermek, program üzerindeki denetimi başka insanlara devretmek anlamına gelir

Analiz

  • Tüm yazılımlar bu yaklaşımı kullanamaz
  • Bir web uygulaması backend dağıtımının parçası olarak Redis’in tamamını vendoring ile içeri alıp derlemek özellikle makul değildir
  • Ancak dağıtım Ansible veya Docker imajları gibi araçlarla otomatikleştiriliyorsa, muhtemelen fiilen zaten benzer bir şey yapılıyor olabilir
  • Bu yaklaşımın taşıyabileceği karmaşıklığın bir üst sınırı var; fakat Google ve Facebook gibi dev monorepo şirketleri, bu sınırın sanıldığından daha yüksek olabileceğini gösteriyor
  • Bir noktada bağımlılıklar işletim sistemiyle buluşur; işletim sistemi de kendi başına sorunları çok olan büyük bir bağımlılıktır
  • Web backend’leri için unikernel fikri çekici, fakat gerçek araç sorunları var ve henüz o noktaya gelinmedi
  • Linux dağıtımları ve derleme ortamı

    • Bu yaklaşım, Linux dağıtımı veya BSD gibi tamamen etkileşimli bir sistem kurma yöntemi değildir
    • Bu tür sistemler, birlikte çalışması gereken çok sayıda program ve kütüphane içerdiği için farklı bir problemdir
    • Bu ilke sonuna kadar götürülürse Nix ya da Guix benzeri bir yaklaşıma yaklaşılır
    • “Derleme ortamını” doğru şekilde bir araya getirmek gerektiği fikri, “yazılım nasıl derlenmeli” sorununa tembel ve yetersiz bir çözüm gibidir
    • Bu fikir, yazılımın bir minibilgisayarda bir kez derlenip ardından ikili dosya olarak geniş ölçekte paylaşıldığı dönemden kalan bir kalıntıdır
    • Günümüzde 1970’lere kıyasla çok daha fazla yazılım anında derleniyor
  • Uygulanabilir kapsam

    • Bu yaklaşım her derde deva değildir, ama birçok yazılım buna uygulanabilir ve fayda sağlayabilir
    • Yazılımların çoğu küçüktür; büyük projeler ise bu sorunların çoğunu zaten çözmek zorundadır
    • Yalnızca hesaplama yapan ya da dış dünyayla sadece dosya ve ağ soketi gibi temel, taşınabilir I/O üzerinden temas eden pek çok kütüphane vardır
    • Sıkıştırma kütüphaneleri, libcurl, TUI kütüphaneleri ve Django gibi örnekler vendoring hedefi olarak ele alınabilir
    • Vendoring uygulandığında, yeni sistemlerde dağıtım veya derleme sırasında sürüm çakışmaları ya da ani yamalarla gelen hatalar yüzünden neden olduğu bilinmeyen kırılmaların çoğu neredeyse önlenebilir
    • Amaç, dışarıdan habersizce değişebilecek bağımlılıkları 200–300 adet yerine en fazla 2–3 seviyesine indirmektir

Sonuç

  • Bağımlılıkların otomatik güncellenmesini azaltıp projenin bağımlılık kaynaklarını da doğrudan sahiplenmesi, tedarik zinciri saldırılarının otomatik yayılımını yavaşlatabilir
  • Bağımlılık kullanmanın maliyetini biraz artırıp görünürlüğü yükseltmek, gereksiz yeniden kullanımı ve gizli şişkinliği daha kolay fark etmeyi sağlar
  • Bu yaklaşım her sisteme uymaz, ancak küçük yazılımlar ve birçok kütüphane için pratik faydalar sunar

1 yorum

 
GN⁺ 3 시간 전
Lobste.rs görüşleri
  • Zig paket yöneticisi bence epey iyi bir orta yol
    Tüm paketler içerik hash’iyle sabitlendiği için varsayılan olarak bir kilit dosyası var sayılır; böylece “üst depo bir anda kötü amaçlı hale geldi” sorunundan kaçınılıyor ama “üst depo ortadan kayboldu” sorunu kalıyor
    Yine de hem global/yerel önbellek var hem de içerik hash’i tabanlı olduğu için, üst depo kaybolursa yerel kopyanın tarball’unu gereken yere atmak yeterli
    “Kaynağı vendor etmek” ile “basit ve yeniden kullanılabilir yazılım” arasında iyi bir uzlaşma gibi görünüyor

    • Bu yaklaşım tüm yazılımlara da genişletilebilir ve oldukça havalı olabilir
      Tüm kaynaklar içerik adreslemeli depolama içinde tutulur ve her program girdilerinin hash’ine göre hash’lenir
    • Genel olarak katılıyorum ama bu düzenin nasıl saldırıya uğrayabileceğini biraz merak ediyorum
      Muhtemelen kilit dosyasını değiştirmek ya da hash çakışması bulmak gerekir; ikisi de kolay görünmüyor
      Yine de cargo ekosistemine alışkın olduğum için buna tamamen ısınamıyorum. Bir bağımlılığı yükselttiğinizde onun geçişli bağımlılıkları da pek haber vermeden birlikte yükselme eğiliminde oluyor ve anlamsal sürüm aralığına uyan başka şeyler de beraber değişiyor
  • Buna “tedarik zinciri saldırısı” demek için, teklif ve karşılık içeren imzalı bir sözleşme yok; o yüzden bunun bir tedarik zinciri olduğunu düşünmüyorum
    Ayrı olarak, bağımlılıkların alttan değişmemesini garanti etme açısından hash içeren kilit dosyaları ya da Go’nun minimum sürüm seçimi yaklaşımı, bağımlılık vendor etme ile aynı şey
    Vendor etmenin sürtünme yarattığı farkını anlıyorum ama işin ucuya vardığında ya kendiniz yazmaya ya da daha kötüsü bağımlılıkları doğaçlama üretilmiş kod haline getirmeye çıkıyor; bu yüzden alan uzmanlarının yazdığı ve yeterince doğrulanmış yazılımları kullanmanın daha iyi olduğunu düşünüyorum
    Facebook’ta bu tarafta çalıştım ve oradaki üçüncü taraf bağımlılık yönetimini kimseye tavsiye etmem. Belirli bir Rust crate’inin doğrudan bağımlılıklarında, fbsource genelinde anlamsal sürüm açısından uyumlu olmayan sürümlerden aynı anda en fazla iki tanesine izin veriliyor. Bir bağımlılığı güncellemek istiyorsanız fbsource’un tamamını güncelleme yükünü de üstlenmeniz gerekiyor
    Facebook için uygun olabilir ama bunun özellikle harika ya da sürdürülebilir olduğunu düşünmüyorum

    • Merak ettim, neden “en fazla iki” olduğunu bilmiyorum. Eski sürümden yeni sürüme kademeli geçiş için mi?
      “Özellikle harika ya da sürdürülebilir değil” olmasının, politikanın kendisinden çok ölçeğin bir fonksiyonu olduğundan şüpheleniyorum. Birden fazla sürüme izin verince başka sorunlar çıkıyor; çünkü TypeScript hariç çoğu modern dil ağırlıklı olarak ya da tamamen nominal tipler kullanıyor ve bu yüzden kırıcı değişikliklerde “semver trick” uygulanmadıkça sürümler arasında tiplerin yeniden kullanımı engelleniyor
      Log4Shell sırasında, sürümleri çok olan ve birçok yere dağılmış şirketlerin, sürüm sayısını az tutan ya da sabitleyen şirketlere göre yükseltmede daha çok zorlandığını net hatırlıyorum
    • Evet, o halde buna bağımlılık saldırısı diyelim <3
  • The Third Networking Truth uyarınca “yeterli itkiyle domuzlar da uçar. Ama bu, bunun iyi bir fikir olduğu anlamına gelmez”
    Google/Facebook gibi yerlerden aktarılan birçok pratik ancak bu şirketlerin yeterli itkiyi ayırabilmesi sayesinde çalışıyor
    Örneğin bu tür yerlerin bazılarında, monorepo ve bağımlılıkla ilgili tercihleri desteklemek için benim çalıştığım şirketin toplam çalışan sayısından daha büyük ekipler ayrıldığını biliyorum. Onlar bunun altından kalkabilir ama çoğumuz için bu çok zor

  • Güzel bir görüş. “Tüm bağımlılıkları vendor ederseniz bağımlılık kullanmanın maliyeti artar” noktasına güçlü şekilde katılıyorum
    Yalnız libcurl’ü kopyalayıp yapıştırmamalısınız. Çoğu kütüphane için makul bir strateji olabilir ama düşmanca girdilerle uğraşan C programları için iyi bir tavsiye değil. İşletim sisteminden daha iyi şekilde libcurl’ü güvenli tutmanız pek mümkün değil
    Daha önce hiç düşünmediğim nokta şu: apt gibi son kullanıcıya dönük paket yöneticilerinin önce, dil düzeyi paket yöneticilerinin ise sonra gelmiş olması en azından biraz tuhaf
    Bunun gerçekten birçok soruna yol açtığını düşünüyorum. 2000’lerin başındaki rubygems’e bakınca, proje bazlı yönetim yerine sistem geneli kurulumun varsayılan olduğu bir “Ruby için apt” yapılmaya çalışıldığı oldukça açık. Bu hatanın zararını geri almak için bundler eklenmesiyle onlarca yıl geçti; oysa baştan proje izolasyonu ihtiyacı kabul edilseydi bundler’a gerek kalmayabilirdi
    Python hâlâ bu karmaşayı toplamaya çalışıyor; Perl de muhtemelen öyle ama ayrıntısını bilmiyorum

    • Yani bir sınır olduğu kesin :-) zor olan tam olarak çizgiyi nereye çekeceğiniz
      Tarihsel olarak paket yöneticileri aslında sistem inşa etme yöntemiydi ve bu sistemlerde birden fazla kullanıcı, masaüstü ortamları ve birlikte çalışan çok sayıda yazılım vardı
      Yazılım derlemek çok zaman ve bellek istiyordu; ayrıca disk ve RAM’e kıyasla yazılım çok fazla olduğundan kütüphane yeniden kullanımı önemliydi
      Web uygulamaları yükselince önemli bilgisayarların çoğu ömürleri boyunca sadece az sayıda program çalıştıran sunuculara dönüştü ve disk ile RAM yeterince ucuzlayınca kod ikililerinin boyutu daha az önemli hale geldi
      Sistem kurma araçları bu değişime aynı ölçüde ayak uyduramadı; bu yüzden yazılım üreten insanların çoğu, paylaşılan kütüphanelerle dolu dev bir birbirine bağlı sistem yerine tek bir programı iyi üretmeye yarayan araçlara ihtiyaç duyar oldu
      Bu tarihle paralel giden bir de “C’de düzgün bir modül sistemi yok” hattı var ama burada daha az önemli
  • Yanılıyor olabilirim ama kopyalanmış bağımlılıkta hata olsa bile tarayıcıların bunu tespit edememesi gibi bir dezavantaj olabilir
    Bu durumda normalde uyarısını alacağınız potansiyel bir sorun sessizce kalabilir

    • Bu tarayıcıların ürettiği yanlış pozitif sayısına bakınca bu bir avantaj bile olabilir
      Tarayıcılar sorun olabilecek şeyleri göstermede çok faydalı ama tarayıcının sorun sandığı fakat gerçekte olmayan bir şeyi düzeltmek için planlanmış işi aniden ertelemek zorunda kalmak da çok can sıkıcı
  • Önerildiği gibi yazılıma tüm bağımlılıkları dahil edip, üst kaynak yönetimini git deposuna kopyalayıp commit’leyip, elle yapmak sıkıcıysa bunu build aracına otomatikleştirirseniz, bir tur dönüp yine üçüncü taraf yazılımı görmeden dahil etmiş olmuyor musunuz?

    • Okumaya devam ederseniz, build sisteminde anlamsal sürüm ya da “iki farklı kod aynı şekilde davranmalıdır” fikrini bırakıp tüm sürüm numaralarını birbirinden benzersiz ve ilişkisiz sayarak da aynı etkinin elde edilebileceği söyleniyor
      Ama bu yaklaşım bağımlılıkların ortadan kaybolması ya da kurcalanması sorununu, ya da birinin paket içeriğine başka bir şekilde müdahale etmesi sorununu çözmüyor. Bu daha çok bir optimizasyon ve bence erken optimizasyon. Belki bir gün oraya gidilebilir ama başlangıç noktası bu olmamalı