- Uzun süre çalışan backend'lere istekleri soket üzerinden ileten bir proxy protokolü olarak, mevcut HTTP handler yapısını neredeyse hiç değiştirmeden uygulanabilir
- HTTP/1.1 ters proxy kullanımında mesaj sınırlarının yorumlanması implementasyondan implementasyona kaymaya yatkındır; bu da desync ve request smuggling gibi ciddi güvenlik sorunlarını sürekli üretebilir
- FastCGI, 1996'dan beri açık mesaj çerçevelemesi sunuyor ve istemci başlıklarıyla proxy'nin eklediği güvenilir bilgileri yapısal olarak ayırıyor
- Go'nun
net/http/fcgipaketiREMOTE_ADDRdeğeriniRequest.RemoteAddralanına doldurur ve HTTPS durumunu daRequest.TLSiçine yansıtır; böylece güvenilir bilgi aktarımı ek middleware olmadan sağlanabilir - WebSockets desteğinin olmaması, zayıf araç ekosistemi ve bazı iş yüklerinde düşük throughput gibi sınırlamalar var; ancak WebSockets gerekmiyor ve performans yeterliyse hâlâ pratik bir seçenek gibi görünüyor
FastCGI'nin konumu ve uygulanma biçimi
- FastCGI, yalnızca dosya başına süreç çalıştırma modelinde değil, uzun süre çalışan daemon'lara TCP veya UNIX soketi üzerinden istek gönderen bir proxy-backend protokolü olarak da kullanılabilir
- Go'da
net/http/fcgipaketini içe aktarıphttp.Serveçağrısınıfcgi.Serveile değiştirmek kadar basit olabilir- Mevcut handler'lar olduğu gibi
http.ResponseWritervehttp.Requestkullanmaya devam eder - Uygulamanın geri kalan yapısı da korunur
- Mevcut handler'lar olduğu gibi
- Apache, Caddy, nginx, HAProxy gibi başlıca proxy'ler FastCGI backend'lerini destekler ve yapılandırmaları da görece basittir
HTTP'nin backend protokolü olarak kullanılmasındaki ayrıştırma sorunları
- HTTP reverse proxying, neredeyse bir güvenlik mayın tarlasıdır; Discord medya proxy'sindeki desync zafiyeti gibi özel ekleri gözetlemeye yol açabilen sorunlar çıkmaya devam ediyor
- HTTP/1.1 görünüşte basit bir metin protokolüdür, ancak aynı mesajı ifade etmenin aşırı fazla yolu ve çok sayıda istisna durumu olduğundan implementasyonlar arasında yorum farkı oluşması kolaydır
- En büyük sorun, HTTP mesajlarında açık çerçeveleme bulunmamasıdır
- Mesajın sonunu, mesajın kendisi birden fazla yolla tarif eder
- Farklı implementasyonlar bir mesajın bittiği ve sonraki mesajın başladığı noktayı farklı yorumlayabilir
- Bu tür uyumsuzluklar, HTTP desync attacks veya request smuggling saldırılarının temelini oluşturur; ters proxy ile backend mesaj sınırlarını farklı anladığında ciddi güvenlik sorunları ortaya çıkar
- Parser farklarını sürekli yamalamak kökten bir çözüm olmaya pek uygun değildir
- James Kettle sürekli yeni türler buluyor
- Geçen yıl ek örnekler bulduktan sonra "HTTP/1.1 must die" ifadesini bile kullandı
FastCGI ve HTTP/2'de mesaj sınırı işleme
- HTTP/2, proxy ile backend arasında tutarlı biçimde kullanıldığında mesaj sınırlarını netleştirerek desync sorununu çözebilir
- FastCGI ise bu net sınır ayrımını 1996'dan beri daha basit bir protokolle sunuyor
- nginx, ilk sürümünden beri FastCGI backend desteğine sahipti; buna karşın HTTP/2 backend desteği ancak 2025'in sonlarına doğru eklendi
- Apache'nin HTTP/2 backend desteği ise hâlâ "experimental" durumunda
Güvenilmeyen başlıklar sorunu ve FastCGI'nin ayırma yöntemi
- Sorun sadece desync değildir; HTTP ayrıca gerçek istemci IP'si, proxy'nin işlediği doğrulanmış kullanıcı adı ya da mTLS'teki istemci sertifikası bilgisi gibi proxy'nin güvenerek iletmesi gereken verileri sağlam biçimde taşımakta da yetersiz kalır
- Pratikte bu bilgiler HTTP başlıklarına konur, ancak proxy'nin eklediği güvenilir veriler ile istemcinin gönderdiği güvenilmeyen başlıklar arasında yapısal bir ayrım yoktur
X-Real-IPgibi başlıklar gerçek istemci IP'sini iletmek için sık kullanılır, ancak güvenli olması için proxy'nin büyük/küçük harf varyasyonları dâhil tüm mevcut başlıkları tamamen silip sonra yeniden eklemesi gerekir- Bu yaklaşım çok tehlikeli bir zemindir ve backend'in saldırganın yerleştirdiği verilere güvenmesine yol açan birçok yol vardır
- Proxy, yalnızca
X-Real-IPdeğil, bu amaçla kullanılan her türlü başlığı silmek zorundadır - Örneğin Chi middleware'i, istemcinin gerçek IP'sini belirlerken önce
True-Client-IPbaşlığını kontrol eder; yalnızca o yoksaX-Real-IPkullanır- Proxy
X-Real-IP'yi doğru işlese bile saldırganTrue-Client-IPgönderirse sorun çıkabilir
- Proxy
- FastCGI, istemci başlıkları ile proxy'nin eklediği bilgileri alan ayrımı yoluyla ayırır
- Her ikisi de anahtar/değer parametre listesi olarak taşınır, ancak HTTP başlık adlarında
HTTP_öneki bulunur - Böylece istemcinin gönderdiği bir başlığın proxy'nin güvenilir verisi gibi yorumlanacağı bir yapı ortaya çıkmaz
- Her ikisi de anahtar/değer parametre listesi olarak taşınır, ancak HTTP başlık adlarında
Go'da FastCGI ile güvenilir bilgi işleme
- FastCGI, gerçek istemci IP'sini iletmek için
REMOTE_ADDRgibi standart parametreler tanımlar - Go'nun
net/http/fcgipaketi bu değeri otomatik olarakhttp.RequestiçindekiRemoteAddralanına yerleştirir; dolayısıyla ek middleware gerekmez - Proxy, HTTPS kullanımı, uzlaşılan TLS cipher suite ya da istemci sertifikası gibi bilgileri standart dışı parametrelerle de iletebilir
- Go, istek HTTPS kullandığında
RequestiçindekiTLSalanını otomatik olarak nil olmayan bir değere ayarlar- Boş olsa bile HTTPS zorunluluğunu denetlemek için faydalıdır
fcgi.ProcessEnvile proxy'nin gönderdiği güvenilir parametrelerin tamamına erişilebilir
Yaygınlaşmasının yavaş olmasının nedenleri ve pratik sınırlamalar
- FastCGI daha iyiyse neden yaygın kullanılmadığı sorusuna, isminin bile eski hissettirmesi ve HTTP reverse proxy güvenlik sorunlarına dair farkındalığın az olması birlikte etki etmiş gibi görünüyor
- Watchfire, 2005'te desync saldırılarını zaten ele almış ve çözümün kolay olmadığı uyarısını yapmıştı; ancak bu saldırılar 10 yıldan uzun süre boyunca hak ettiği ilgiyi görmedi
- FastCGI bugün de gerçek kullanım için uygundur; SSLMate bunu 10 yıldan uzun süredir production ortamında kullanıyor
- Yine de eski bir teknoloji olduğu için zayıf yönleri var
- WebSockets desteği için güncellenmedi
- Araç ekosistemi yetersiz
- Örneğin curl, FTP, Gopher ve SMTP'yi bile destekliyor ama FastCGI isteği gönderemiyor
- Go FastCGI sunucusu birden çok reverse proxy arkasında benchmark edildiğinde, bazı iş yüklerinde HTTP/1.1 veya HTTP/2'den daha düşük throughput görüldü
- Bunun protokolün doğasından çok, FastCGI kod yolunun HTTP kadar optimize edilmemiş olmasının sonucu olduğu düşünülüyor
Nihai değerlendirme
- WebSockets gerekmiyorsa ve mevcut performans yeterliyse FastCGI hâlâ değerlendirilebilir bir seçenektir
- Darboğaz oluşsa bile, HTTP reverse proxying'in karmaşıklığını ve güvenlik kâbusunu üstlenmektense ek donanım kullanmayı tercih etmek daha mantıklı görünüyor
2 yorum
Lobsters yorumlarında bulduğum, Twisted’in FastCGI hakkındaki yorumu etkileyici görünüyor: https://web.archive.org/web/20160723091923/…
Hacker News görüşleri
Yazının ana fikrine katılıyorum. Bu tür bir kullanım için FastCGI'nin HTTP'den daha iyi olduğunu düşünüyorum
WAS (Web Application Socket) adlı bir protokolden de bahsetmek isterim. 16 yıl önce iş yerimde FastCGI'nin bile yeterince iyi olmadığını düşünüp bunu bizzat tasarladım
Ana soket framing'i yerine bir kontrol soketi ve ham istek/yanıt gövdeleri için iki pipe kullanıyor; hem WAS uygulaması hem de web sunucusu pipe'larda
splice()kullanabiliyorFraming gerekmiyor, istek iptali mümkün ve üç dosya tanımlayıcısının her zaman kurtarılabilmesini sağladım
Bunu yıllardır dahili uygulamalarda ve web hosting ortamlarında kullanıyoruz; PHP SAPI'sini de kendim yazdım. Epey çok web sitesi dahili olarak WAS üzerinde çalışıyor
Hepsi açık kaynak
library: https://github.com/CM4all/libwas
documentation: https://libwas.readthedocs.io/en/latest/
non-blocking library: https://github.com/CM4all/libcommon/tree/master/src/was/asyn...
our web server: https://github.com/CM4all/beng-proxy
WebDAV: https://github.com/CM4all/davos
PHP fork with WAS SAPI: https://github.com/CM4all/php-src
HTTP, tarayıcı ile sunucu gibi iki uç arasında veri taşımak içindir; FastCGI ise bu veriyi sunucu ile uygulama arasında işlemek içindir
Yazıya az önce göz attım; yazar sanki ikisi birbirinin yerine geçebilirmiş gibi kafa karıştırıcı bir dil kullanıyor. Oysa gerçekte hiç öyle değil
Bu arada ben de web müşteri hizmetlerinde fcgi'yi 10 yıldır kullanıyorum
Bu yazı, eksik bıraktıkları yüzünden daha da ilginç
FastCGI vs. SCGI vs. HTTP tartışmalarının hararetli olduğu dönemde bir Web2.0 girişimi kurup frontend stack'i kendim oluşturmuştum; sonunda HTTP'nin kazanma sebebi basitlik oldu
Gateway'de zaten işlenmesi gereken HTTP'yi olduğu gibi kullanınca stack'e başka bir protokol eklemeye gerek kalmıyordu; bu da reverse proxy'yi birden fazla katman halinde yerleştirmeyi ya da kimlik doğrulama, oturum, SSL termination, DDoS filtreleme gibi çapraz sorumlulukları role göre ayrılmış sunuculara bölmeyi çok kolaylaştırıyordu
Geliştirme ortamında uygulama sunucusuna HTTP ile doğrudan bağlanabiliyor, üretimdeyse SSL, kimlik doğrulama ve kötüye kullanım tespitini reverse proxy'ye bırakırken aynı uygulama sunucusunu aynen yeniden kullanabiliyorduk
O dönemde nginx'in çoğu FastCGI/SCGI modülünden çok daha hızlı ve kararlı olması da önemliydi. Başta
HTTP -> Lighttpd -> FastCGI -> Djangoyapısı kullanmıştık ama doğrudan nginx kullanmak çok daha hızlıydıHTTP kullanımı, web sürümünde bir End-to-End Principle gibi işliyordu. Ağın ve protokolün taşınan içeriğe kayıtsız olması, uygulama mantığının da filtreleyen ya da yönlendiren ağ düğümlerinde değil uç noktalarda bulunması gerektiği fikri
Ama yazının asıl işaret ettiği nokta şu: güvenlik açısından çoğu zaman en az ayrıcalık ilkesine uymak daha iyi oluyor. Yalnızca beklenen iletişimi allowlist ile geçirmek gerekir ki başka bir noktadaki ihlale farkında olmadan katkı sağlanmasın
Sonuçta bu ikisi arasında bir gerilim var. E2E esneklik sağlar ama bu esneklik suistimal ihtimalini de artırır; PoLP güvenlik sağlar ama yalnızca tasarladığınız şeyleri yapabildiğiniz için yeni gereksinimlere uyum zorlaşır
[1] https://en.wikipedia.org/wiki/End-to-end_principle
[2] https://en.wikipedia.org/wiki/Principle_of_least_privilege
Eğer ara gateway birçok HTTP isteğini başka bir HTTP kanalı üzerinde multiplex ediyor, o kanal listening service'e doğrudan gidiyor ve uygulama soketine gelmeden önce demultiplex edilmiyorsa, bu end-to-end mantığını çeşitli şekillerde temelden bozar
Bu benzetme ancak 1:1 bağlantı simetrisi korunduğunda bir ölçüde anlamlı olabilir
Reverse proxy açıklarının tamamının end-to-end'in ihlal edilmesinden doğrudan kaynaklandığını düşünüyorum
Eğer benzetme doğru olsaydı, birden fazla MX üzerinden yapılan SMTP iletimi de end-to-end sayılmalıydı; ama öyle değil ve reverse proxy'lerdeki sorunlara benzer şekilde, örneğin mesaj sınırı desync'i gibi pek çok problem de ortaya çıkıyor
HTTP isteğini mesaja eşlemeye yönelik niyeti anlıyorum ama gerçek TCP/HTTP semantiği ve sayısız protokol ayrıntısı yüzünden bu yaklaşım hemen çöküyor
End-to-end ilkesi semantiğin kabaca ele alınmasına izin vermez. Durum yönetimi ve taşıma katmanı sınırları konusunda çok sıkı bir disiplin ister. Kabaca end-to-end'e benzeyen bir şey, end-to-end değildir
Örneğin multiplexing, HTTP 2.0'a kadar yoktu; bu yüzden reverse proxy ile backend arasında HTTP'yi aynen kullanmak ciddi israf
Güvenlik sorunları da var. Parser'lar istek sınırının tam olarak nerede bittiğini birbirinden farklı yorumlayabiliyor
Google da uzun zamandır frontend web sunucusu ile uygulama arasında HTTP'yi kendi Stubby protokolüyle sarmalayarak kullanıyor
HTTP wire protocol'ünden çok daha hızlı ve daha çok özellik sunuyor. Çoğu şirket için fazla olur ama ölçek büyüdüğünde başka bir wire protocol ve etrafındaki araçları kendin geliştirmenin maliyeti rahatlıkla karşılığını veriyor
httpd de bir noktada yapılandırmayı gereksiz zorlaştıran bir yöne gitti ve yapılandırma formatını bir anda değiştirdiğinde onu bıraktım
Elbette alışabilirdim ama onun yerine lighttpd'ye geçtim; sonrasında ruby yapılandırma üretimini otomatikleştirdiği için teknik olarak yeniden httpd'ye dönebilirdim
Yine de dönmek istemem. Web sunucusu geliştiricileri, kullanıcılarını yeni bir formata zorla uydurma konusuna dikkatli yaklaşmalı
Yapılandırma formatını gerçekten bu kadar kolay değiştirecekseniz, en azından ek seçenek olarak yaml yapılandırması gibi bir şey sunun da insanları aniden yeni bir if-clause tarzı yapılandırma sözdizimine mecbur bırakmayın
WHATWG streams artık tarayıcılarda yaygın olduğuna göre, uzun ömürlü HTTP istekleri üzerinde WebSocket benzeri kendi çözümünüzü kurmak epey kolay
Sadece bir byte stream gönderip her mesajın önüne bir başlık koyuyorsunuz; çoğu durumda tek bir uzunluk değeri bile yeterli
Avantajları da var. WebSocket'teki gibi sunucu katmanında ayrı özel bir yol gerekmiyor, backpressure kullanılabiliyor, HTTP/2 ve HTTP/3 iyileştirmeleri bedavaya geliyor ve framing overhead'i daha düşük oluyor
Ancak AFAIK, istek gövdesini sürekli stream ederken aynı anda yanıt almak hâlâ desteklenmiyor; bu yüzden tam çift yönlü streaming için iki istek gerekiyor
Eski plain CGI'yi yeniden keşfettim; platformumuzda kullanıcıların özel sayfaları vibe code etmesini sağlamak için harika [1]
Yerleşik özellikler olarak görev listesi ve veri görüntüleyici sunuyoruz ama kullanıcılar sık sık Kanban görünümü veya veri filtreleri ve grafikler içeren özel dashboard'lar gibi çok daha ince ayarlı özelleştirmeler istiyor
Bu kutuda bir coding agent var; bu sayede bizim klasik bir report builder yapmamız yerine kullanıcı istediğini doğrudan kodlayabiliyor
Go stdlib hem sunucu tarafında hem kullanıcı alanında iyi destek veriyor; coding agent
page-name/main.gooluşturup CGI üzerinden konuşturulunca sunucu isteği oraya devrediyorVeri boyutu da sayfa görüntüleme sayısı da tamamen person scale, dolayısıyla FastCGI gibi optimizasyonlara pek ihtiyaç olmuyor
Ajan çağında eski teknoloji yeniden yeni oluyor
Go'nun CGI sunucu uygulaması
$HTTP_PROXYayarlamadığı için bu açıdan güvenli, ama yine de CGI'nin ortam değişkenleri kullanma biçimini sevmiyorumReverse proxy tarafı genelde basit işler yaptığı için Nginx'in yerleşik özellikleri çoğu zaman yeterli oluyordu
Yine de daha karmaşık bir şey gerektiğinde FastCGI kullanma fikri benim aklıma herhalde gelmezdi
Yaklaşık 10 yıl önce bazı C++ kodlarını web üzerinden çalıştırmak için biraz FastCGI kullandım ama ondan sonra neredeyse hiç kullanmadım
Uygulamanın içine doğrudan bir HTTP sunucusu gömüp, gateway olmadan gereken işi orada hallediyorsunuz
Red Hat tabanlı dağıtımlarda gelen PHP/Apache yapılandırması FPM (FastCGI Process Manager)
RHEL dağıtımlarında FastCGI'nin başka nerelerde kullanıldığını bilmiyorum
$ rpm -qi php-fpm | grep ^SummarySummary : PHP FastCGI Process ManagerFedora'nın
httpd-corepaketine dahil. RHEL'i bilmiyorum: https://packages.fedoraproject.org/pkgs/httpd/httpd-core/fed...uwsgi protocol diye bir şey de var
Bu da esasen neredeyse her şey için bir tür RPC gibi
FCGI aynı zamanda bir orkestrasyon sistemi
Yük artınca daha fazla sunucu görevi başlatıyor, yük azalınca kapatıyor ve görev ölürse yeni bir kopya başlatıyor
Bir bakıma tek sistemlik Kubernetes gibi
Kulağa hoş geliyor ama normalde düşük yükte iyi çalışırken yüksek yük geldiğinde daha fazla worker üretip belleği tüketmesi sık görülen bir şeydi
Bu yüzden sabit worker sayısı kullanmak genelde daha iyi sonuç veriyordu
Yine de gerekiyorsa crash recovery faydalı olabilir
HTTP başlıklarının saçmalığı üzerine kısa bir an düşünelim
True-Client-IPyoksaX-Real-IPkullanıyorsanız, proxyX-Real-IP'yi doğru biçimde eklese bile saldırganTrue-Client-IPbaşlığı göndererek sizi kandırabilirX-Forwarded-For,X-Real-IP, her CDN'in kendi kafasına göre koyduğu özel başlıklar var; bazıları virgülle ayrılmış listeler ve çoğu zaman bizim own LB'nin IP'si de gereksiz yere sonuna eklenmiş oluyorNeden böyle olduğunu anlıyorum ama bu hiç yardımcı olmuyor
Üstelik bu başlıkların hepsi kötü niyetli bir user-agent tarafından da enjekte edilebilir. Sanki güvenilen sunucuların boru hattı içinde kritik bilgiyi nasıl iletmesi gerektiği konusunda kimse uzlaşamamış gibi
Bu karmaşa, User-Agent başlığının saçmalığıyla da iyi uyum sağlıyor
Orada da Apple, gizlilik bahanesiyle tamamen sahte bilgiler, örneğin yanlış OS sürümü gibi saçmalıklar göndermeyi seçerek işi daha da uç noktaya taşıdı
Bu iddiada çok haklı yanlar var ama FastCGI,
PATH_INFOgibi kısımlarda CGI/1.1'i takip ettiği için bilgi kaybına yol açıyorURL decoding zorunlu olduğundan encoded slash
%2Fifade edilemiyorUygulamaya göre yoldaki
//de/olarak birleştirilebiliyor; gerçi bu pek çok HTTP uygulamasında da görülen bir sorunİfade gücü açısından HTTP'den daha zayıf ve bunun önemli olup olmadığı uygulamaya bağlı
Ben URL'lerin tam ve doğru biçimde ele alınmasını tercih ediyorum