· kaynak dev.to (home feed)
Jenkins'teki yakın geçit olayı, gerçek saldırı yüzeyinin controller değil build agent'ları olduğunu gösterdi
Bir fork-PR build'i kısa süreliğine dahili bir metadata endpoint'ine erişti; bunun ardından yapılan denetim, asıl riskin paylaşılan agent havuzları, docker.sock mount'ları ve uzun ömürlü build VM'lerinde olduğunu ortaya koydu.

Ne oldu
dev.to'da yayımlanan, aslında yazarın kişisel blogunda yayınlanan Oleksandr Kuryzhev'in birinci ağızdan anlatımına göre, rutin bir güvenlik incelemesi, dışarıdan bir katkıda bulunanın pull request'i ile tetiklenen bir build'in kısa süreliğine dahili bir metadata endpoint'ine açık bir ağ yoluna sahip olmasıyla büyüdü. Yazarın ifadesine göre hiçbir veri sızdırılmadı, ancak marj o kadar inceydi ki ekip diğer işleri durdurdu ve tüm build altyapısını denetledi.
Gönderide anlatıldığı şekliyle önceki zihinsel modelleri güvenlik sınırını Jenkins controller'a yerleştiriyordu: rol tabanlı erişim kontrolü, matrix yetkilendirmesi ve klasör izinlerinin aşağı doğru her şeyi kapsadığı varsayılıyor, agent'lar ise aptal yürütücüler olarak görülüyordu. Denetim bu varsayımı geçersiz kıldı. Agent'lar pipeline'ın içeriği neyse onu yürütür — dahili job'lar, ama aynı zamanda yabancıların fork'larından gelen kod, build sırasında çekilen topluluk plugin'leri ve bağımlılıkların kurulum zamanı scriptleri — dolayısıyla yürütme ortamının kendisi düşmanca kabul edilmelidir.
Tek label, iki güven seviyesi
İlk bulgu yapısaldaydı. linux-docker etiketli tek bir agent havuzu, aynı imajlarla ve aynı credential-binding kapsamıyla hem dahili, güvenilir depolara hem de güvenilmeyen fork-PR build'lerine hizmet veriyordu. Jenkins'te bir label yalnızca zamanlayıcıya bir job'ın nerede çalışabileceğini söyler; hiçbir izolasyon yaratmaz. Dolayısıyla dış kod, dahili servislerin deploy credential'larını barındıran makinelerde çalışıyordu — kimsenin açıkça kabul etmediği bir maruziyet, yalnızca tek havuzun çalıştırılmasının ikisinden daha kolay olması nedeniyle korunagelmişti. Ayrıca, kısıtlamasız JNLP/Remoting, bir agent üzerinde çalışan Groovy kodunun controller JVM'ine geri dönmesi için makul bir yol sunuyordu ve bir pipeline'ın yüklediği plugin'lere ve paylaşılan kütüphanelere bağlı olarak credential store'a erişme potansiyeline sahipti.
Kök kısayolu olarak docker.sock
İkinci bulgu bir sömürüden değil, incelemenin kendisinden geldi: container imajları derleyen agent'ların /var/run/docker.sock mount'lu vardı ve yazar bunu, container'a host node üzerinde root yetkisi vermekle işlevsel olarak eşdeğer olarak tanımlıyor. Durumu, agent container'larının root olarak, hiçbir capability düşürülmemiş ve hiçbir seccomp ya da AppArmor profili uygulanmamış şekilde çalışması daha da kötüleştirdiyordu. Gönderi bu kalıbın Jenkins-on-Kubernetes eğitimlerinde son derece yaygın olduğuna dikkat çekiyor ve bir build node'unaki herhangi bir docker.sock mount'unun bir kolaylık değil, kritik bir bulgu olarak ele alınması gerektiğini savunuyor.
Hiç ölmeyen agent'lar
Üçüncü olarak, filoda aylardır, bazen yıllardır çalışan, yalnızca biri hatırladığında yamalanan statik VM'ler vardı. Bunlar beyan edilen imajlarından çok uzaklaşmış, elle kurulan araçlar, bayat SDK sürümleri ve hata ayıklama oturlarından kalma credential'lar biriktirmişti. Workspace hijyeni de aynı derecede düzensizdi ve denetim, önceki bir job'un geçici credential dosyasının bir workspace'te hayatta kalıp, alakasız bir job'un build artefaktlarında maskelenmemiş biçimde ortaya çıktığı en az bir vaka buldu. Yamalama gecikti, çünkü agent imajının sahipliği kimsenin resmi sorumluluğu değildi.
Yerine geçen mimari
Ekip, Kubernetes plugin'ini kullanarak her build'e ayrı pod veren, kısa ömürlü, pod-per-build agent'lar etrafında yeniden kuruldu: her build taze bir pod alır ve build biter bitmez pod yok edilir. Sertleştirilmiş pod şablonları root olmayan bir kullanıcı ile çalışır; salt-okunur root dosya sistemi, tüm Linux capability'leri düşürülmüş, runtime-varsayılan seccomp profili, service-account token mount'u yok ve emptyDir workspace kullanılır — ve bilinçli olarak docker.sock ya da hostPath volume bulunmaz.
Dahili ve fork-PR build'leri artık tamamen ayrı havuzlarda çalışıyor; bu, privileged container'ları, hostPath'i ve host ağ iletişimini yasaklayan kısıtlayıcı bir Pod Security Standardı altındaki Kubernetes namespace'leri üzerinden zorunlu kılınıyor. PR build'lerine hiç kalıcı cloud credential'ı verilmiyor; ihtiyaç duydukları her şey, belirli job'a kapsamlı ve dakikalar içinde süresi dolan kısa ömürlü bir OIDC veya Vault token'ı olarak geliyor. Bir NetworkPolicy agent'ların çıkış trafiğini paket registry'leriyle dahili artefakt deposuna kısıtlıyor, agent imajları yerinde yamalanmak yerine haftalık olarak yeniden inşa ediliyor ve controller'ın script konsolu iki kişilik bir admin grubuna sınırlandırılıyor. Kabul edilen ödün, imaj pull'larından kaynaklanan ek cold-start gecikmesi.
Neden önemli
Build agent'ları garip bir konum işgal eder: aynı anda rastgele üçüncü taraf kodu yürütürken kaynak koda, secret'lara, artefaktlara ve deployment hedeflerine ayrıcalıklı erişime sahiptirler. Sertleştirme çabasının çoğu controller'a — onun arayüzüne, RBAC'ına, plugin'lerine — gider, ancak bir ihlalin gerçekte ne kadar yayıldığını belirleyen şey agent yürütme ortamıdır. Burada ortaya çıkan üç kalıp egzotik yanlış yapılandırmalar değil; güven katmanları arasında paylaşılan label'lar, docker.sock mount'ları ve uzun ömürlü build VM'leri sayısız CI kurulumunda varsayılan durumda, bu yüzden bu yakın geçit olayı tek bir ekibin Jenkins kurulumunun çok ötesine genelleniyor.
- #jenkins
- #ci-cd
- #kubernetes
- #devops
- #security