Apache mod_substitute ile İçerik Filtreleme ve Metin Değiştirme

Web sunucunuzda içerik filtreleme ihtiyacı duyduğunuzda aklınıza ilk gelen çözüm genellikle uygulama katmanında değişiklik yapmak olur. Ama Apache’nin mod_substitute modülü, bunu sunucu seviyesinde, uygulamaya dokunmadan yapmanızı sağlar. Özellikle legacy sistemleri yönetiyorsanız, kaynak koduna erişiminiz yoksa ya da hızlı bir geçici çözüme ihtiyaç duyuyorsanız bu modül hayat kurtarıcı olabiliyor.

mod_substitute Nedir ve Ne İşe Yarar

mod_substitute, Apache HTTP Server’ın yanıt gövdesinde (response body) arama ve değiştirme işlemi yapmanızı sağlayan bir filtreleme modülüdür. Temel olarak, sunucudan istemciye giden HTML, XML veya düz metin içeriklerini “on-the-fly” düzenleyebilirsiniz. Regex desteğiyle birlikte oldukça güçlü bir araç haline gelir.

Modül, Apache 2.2.7 sürümüyle birlikte geldi ve o günden bu yana pek çok sysadmin’in vazgeçilmezi oldu. Özellikle şu senaryolarda işinize yarar:

  • Eski HTTP linklerini HTTPS’e çevirmek (kod değiştirmeden)
  • Belirli kelimeleri veya ifadeleri gizlemek ya da değiştirmek
  • Test ortamlarında production URL’lerini maskelemek
  • İçerik enjeksiyonu veya kaldırma işlemleri
  • CDN geçişlerinde eski domain referanslarını güncellemek

Modülü Etkinleştirme

Başlamadan önce modülün aktif olup olmadığını kontrol edin:

apache2ctl -M | grep substitute

Eğer çıktıda substitute_module (shared) görmüyorsanız, etkinleştirmeniz gerekiyor.

Debian/Ubuntu üzerinde:

sudo a2enmod substitute
sudo a2enmod filter
sudo systemctl restart apache2

CentOS/RHEL üzerinde mod_substitute genellikle mod_filter ile birlikte gelir. /etc/httpd/conf.modules.d/ dizinini kontrol edin:

grep -r "substitute" /etc/httpd/conf.modules.d/
# Yoksa httpd.conf içine ekleyin:
echo "LoadModule substitute_module modules/mod_substitute.so" >> /etc/httpd/conf/httpd.conf
sudo systemctl restart httpd

mod_filter modülünün de yüklü olduğundan emin olun, çünkü mod_substitute buna bağımlıdır:

apache2ctl -M | grep filter

Temel Sözdizimi

mod_substitute direktifinin temel kullanımı şu şekildedir:

Substitute "s/arama_ifadesi/yeni_ifade/[bayraklar]"

Bayraklar (flags) aşağıdaki şekilde kullanılır:

  • i: Büyük/küçük harf duyarsız arama
  • n: Normal string arama (regex değil)
  • q: Yavaş/lazy matching (büyük içeriklerde performans için)

AddOutputFilterByType direktifiyle birleştirilmediği sürece modül tüm içerik tiplerine uygulanmaz. Bu yüzden genellikle şöyle bir yapı kullanırsınız:

<Location />
    AddOutputFilterByType SUBSTITUTE text/html
    Substitute "s/eski_metin/yeni_metin/ni"
</Location>

Gerçek Dünya Senaryo 1: HTTP’den HTTPS’e Link Güncelleme

Diyelim ki eski bir uygulamanız var ve HTML içinde hardcoded HTTP linkleri bulunuyor. Uygulamaya müdahale etmek risk taşıyor, ama kullanıcıların mixed content uyarısı almasını da istemiyorsunuz. İşte tam burada mod_substitute devreye girer:

<VirtualHost *:443>
    ServerName www.example.com
    
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
    
    AddOutputFilterByType SUBSTITUTE text/html text/css application/javascript
    Substitute "s|http://www.example.com|https://www.example.com|ni"
    Substitute "s|http://static.example.com|https://static.example.com|ni"
    
    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/example.crt
    SSLCertificateKeyFile /etc/ssl/private/example.key
</VirtualHost>

Burada | karakterini / yerine kullandığımıza dikkat edin. URL içinde / geçtiğinde kaçış karakteri kullanmak zorunda kalmamak için sınırlayıcıyı değiştirebilirsiniz.

Gerçek Dünya Senaryo 2: CDN Geçişinde Domain Değiştirme

Bir CDN’e geçiş yapıyorsunuz ve eski domain adresini hızla yeni CDN adresiyle değiştirmeniz gerekiyor. Uygulama üzerinde deployment yapacak zamanınız yok:

<VirtualHost *:80>
    ServerName www.siteadi.com
    DocumentRoot /var/www/html
    
    <Directory /var/www/html>
        Options -Indexes
        AllowOverride None
    </Directory>
    
    AddOutputFilterByType SUBSTITUTE text/html
    
    # Eski medya sunucusunu CDN ile değiştir
    Substitute "s|http://media.siteadi.com|https://cdn.cloudfront.net/siteadi|ni"
    
    # Eski resim yollarını güncelle
    Substitute "s|/uploads/images|https://cdn.cloudfront.net/siteadi/images|ni"
    
    # JavaScript ve CSS dosyaları için de uygula
    AddOutputFilterByType SUBSTITUTE application/javascript text/css
    Substitute "s|http://media.siteadi.com|https://cdn.cloudfront.net/siteadi|ni"
</VirtualHost>

Gerçek Dünya Senaryo 3: Hassas Bilgi Maskeleme

Test ortamlarında production veritabanı bağlantı bilgileri ya da API anahtarları yanlışlıkla HTML’e yansıyorsa, bunları maskeleyin:

<VirtualHost *:80>
    ServerName test.dahilisunucu.local
    
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/
    
    AddOutputFilterByType SUBSTITUTE text/html
    
    # Kredi kartı numaralarını maskele (basit regex örneği)
    Substitute "s/[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}/****-****-****-****/i"
    
    # Belirli IP adreslerini maskele
    Substitute "s/192.168.[0-9]+.[0-9]+/[INTERNAL-IP]/i"
    
    # API anahtarlarını maskele (örnek format)
    Substitute "s/api_key=[a-zA-Z0-9]{32}/api_key=[REDACTED]/i"
</VirtualHost>

Gerçek Dünya Senaryo 4: HTML İçine Banner Enjeksiyonu

Bakım penceresi bildirimi ya da duyuru bannerı eklemek istiyorsunuz ama uygulamaya dokunmak istemiyorsunuz. Tüm sayfalara otomatik banner ekleyebilirsiniz:

<VirtualHost *:80>
    ServerName www.example.com
    DocumentRoot /var/www/html
    
    AddOutputFilterByType SUBSTITUTE text/html
    
    # Body taginin hemen ardına banner ekle
    Substitute "s|<body>|<body><div style='background:#ff6b35;color:white;padding:10px;text-align:center;'>SİSTEM BAKIMI: 25 Ocak 22:00-02:00 arası hizmet kesintisi yaşanacaktır.</div>|ni"
    
    # Veya kapanış body taginden önce footer ekle
    Substitute "s|</body>|<script src='/js/maintenance-check.js'></script></body>|ni"
</VirtualHost>

Bu yaklaşım bakım dönemlerinde çok işe yarar. Banner metni değiştiğinde sadece Apache’yi reload etmek yeterli olur, deployment gerekmez.

Gelişmiş Kullanım: SubstituteMaxLineLength ve SubstituteInheritBefore

Büyük satırlarla çalışıyorsanız veya minify edilmiş JavaScript/HTML dosyaları işliyorsanız, mod_substitute varsayılan olarak satır uzunluğunu sınırlar. Bu sınırı aşmak için:

<Location /app/>
    AddOutputFilterByType SUBSTITUTE text/html application/javascript
    
    # Maksimum satır uzunluğunu artır (varsayılan 1MB)
    SubstituteMaxLineLength 10m
    
    # Minify edilmiş dosyalarda uzun satırlar için
    Substitute "s|oldfunction()|newfunction()|ni"
</Location>

SubstituteInheritBefore direktifi ise parent konfigürasyondan gelen substitute kurallarının sırasını kontrol etmenizi sağlar:

# httpd.conf veya ana config
<Location />
    AddOutputFilterByType SUBSTITUTE text/html
    Substitute "s/OldCompanyName/NewCompanyName/ni"
</Location>

# VirtualHost içinde
<VirtualHost *:80>
    ServerName special.example.com
    
    <Location />
        SubstituteInheritBefore on
        Substitute "s/SpecialOldText/SpecialNewText/ni"
    </Location>
</VirtualHost>

.htaccess ile Kullanım

Eğer AllowOverride izinleri varsa, .htaccess dosyasında da kullanabilirsiniz. Özellikle paylaşımlı hosting ortamlarında ya da belirli dizinlere özel kurallar için:

# /var/www/html/eski-bolum/.htaccess
cat << 'EOF' > /var/www/html/eski-bolum/.htaccess
Options -Indexes
AddOutputFilterByType SUBSTITUTE text/html
Substitute "s|/eski-bolum/resimler|/yeni-bolum/resimler|ni"
Substitute "s|eski-domain.com|yeni-domain.com|ni"
EOF

Ana Apache konfigürasyonunda AllowOverride’ın açık olduğundan emin olun:

<Directory /var/www/html>
    AllowOverride FileInfo
    # veya
    AllowOverride All
</Directory>

Performans Optimizasyonu

mod_substitute her response’u işlediğinden, yanlış yapılandırıldığında performans sorunlarına yol açabilir. Şu noktalara dikkat edin:

İçerik tipini kesinleştirin: Tüm içeriklere uygulamak yerine sadece gerekli MIME tiplerine uygulayın.

Regex karmaşıklığını sınırlayın: Çok karmaşık regex ifadeleri CPU kullanımını artırır. Mümkünse n bayrağıyla normal string araması tercih edin.

Gzip sıkıştırmasına dikkat edin: mod_deflate ile birlikte kullanırken filtreleme sırası kritiktir:

<VirtualHost *:80>
    ServerName www.example.com
    
    # Önce substitute çalışmalı, sonra deflate
    # Aksi halde sıkıştırılmış içerikte arama yapamazsınız
    
    AddOutputFilterByType SUBSTITUTE;DEFLATE text/html
    
    Substitute "s/OldText/NewText/ni"
</VirtualHost>

Eğer mod_deflate zaten aktifse ve ayrı yapılandırılmışsa, filtreleme sırasını kontrol edin:

# Filtre sırasını test et
curl -v -H "Accept-Encoding: gzip" http://localhost/ 2>&1 | grep -i "content-encoding"

Önbellekleme ile uyumu: Eğer mod_cache kullanıyorsanız, substitute uygulanmış içeriklerin önbelleklenmesi sorun yaratabilir. Cache bypass ekleyin:

<Location /dinamik-icerik/>
    AddOutputFilterByType SUBSTITUTE text/html
    Substitute "s/OldText/NewText/ni"
    Header set Cache-Control "no-store, no-cache"
</Location>

Hata Ayıklama ve Test

Konfigürasyonu test etmeden production’a almayın. Şu adımları takip edin:

# Konfigürasyon syntax kontrolü
apachectl configtest

# Reload (tam restart gerekmez)
systemctl reload apache2

# Canlı test - substitute çalışıyor mu?
curl -s http://localhost/ | grep "NewText"

# Verbose çıktıyla header kontrolü
curl -v http://localhost/ 2>&1 | head -50

# Belirli bir sayfayı test et
curl -s http://localhost/sayfa.html | grep -c "NewText"

Daha detaylı debug için Apache log seviyesini geçici olarak artırın:

# VirtualHost içine ekleyin (geçici olarak)
LogLevel substitute:trace5
# Logları takip et
tail -f /var/log/apache2/error.log | grep substitute

Testi tamamladıktan sonra log seviyesini eski haline getirmeyi unutmayın, aksi halde disk dolabilir.

Yaygın Hatalar ve Çözümleri

“substitute filter not enabled” hatası: mod_filter yüklü değildir. a2enmod filter komutuyla etkinleştirin.

Değişiklik uygulanmıyor: İçerik tipi eşleşmiyordur. curl -I http://localhost/ ile Content-Type header’ını kontrol edin ve AddOutputFilterByType ile eşleştirin.

Gzip içerik değişmiyor: Filtreleme sırası yanlıştır. SUBSTITUTE filtresinin DEFLATE’den önce çalışması gerekir.

Büyük dosyalarda kısmi değişiklik: SubstituteMaxLineLength limitine takılıyorsunuzdur. Değeri artırın.

Regex özel karakterler sorun çıkarıyor: Nokta, parantez, köşeli parantez gibi karakterleri escape edin:

# Yanlış - nokta herhangi bir karakterle eşleşir
Substitute "s/192.168.1.1/[HIDDEN]/ni"

# Doğru - noktayı escape et
Substitute "s/192.168.1.1/[HIDDEN]/ni"

Güvenlik Notları

mod_substitute güçlü bir araç olduğu kadar dikkatli kullanılması gereken bir araçtır:

  • Kullanıcı girdisini substitute kurallarına hiçbir zaman doğrudan dahil etmeyin
  • XSS saldırılarına karşı dikkatli olun; içerik enjekte ederken script taglarını sanitize edin
  • Substitute kuralları response body’yi değiştirdiğinden, Content-Length header’ı yanlış olabilir. Apache bunu genellikle otomatik düzeltir ama kontrol edin
  • Production ortamında trace level logging’i aktif bırakmayın
# Content-Length uyumunu test et
curl -v http://localhost/ 2>&1 | grep -i "content-length"
# Transfer-Encoding: chunked görüyorsanız sorun yok, Apache dinamik boyutlandırma yapıyor

Pratik Bir Otomasyon Örneği

CDN geçişi veya domain değişikliği gibi durumlarda, değiştirmek istediğiniz URL sayısı fazla olabilir. Bunu bir script ile yönetin:

#!/bin/bash
# cdn-substitute-olustur.sh

CDN_KONFIGÜRASYON="/etc/apache2/conf-available/cdn-substitutes.conf"
ESKI_DOMAIN="http://media.eskisite.com"
YENI_DOMAIN="https://cdn.yenisite.com"

cat > $CDN_KONFIGÜRASYON << EOF
# Otomatik oluşturuldu: $(date)
# CDN geçiş substitute kuralları

<IfModule mod_substitute.c>
    AddOutputFilterByType SUBSTITUTE text/html text/css application/javascript application/json
    SubstituteMaxLineLength 10m
    
    Substitute "s|${ESKI_DOMAIN}/images|${YENI_DOMAIN}/images|ni"
    Substitute "s|${ESKI_DOMAIN}/css|${YENI_DOMAIN}/css|ni"
    Substitute "s|${ESKI_DOMAIN}/js|${YENI_DOMAIN}/js|ni"
    Substitute "s|${ESKI_DOMAIN}/uploads|${YENI_DOMAIN}/uploads|ni"
</IfModule>
EOF

a2enconf cdn-substitutes
apachectl configtest && systemctl reload apache2

echo "CDN substitute kuralları uygulandı."

Bu scripti çalıştırmak için:

chmod +x cdn-substitute-olustur.sh
sudo ./cdn-substitute-olustur.sh

Sonuç

mod_substitute, Apache’nin az bilinen ama son derece kullanışlı modüllerinden biri. Özellikle legacy sistem yönetimi, hızlı düzeltmeler ve geçiş süreçlerinde “uygulama katmanına dokunmadan” içerik manipülasyonu yapabilmek büyük avantaj sağlıyor.

Dikkat edilmesi gereken en önemli noktalar şunlar: filtreleme sırasını doğru ayarlamak, gzip ile uyumu sağlamak ve gereksiz yere tüm içerik tiplerine uygulamaktan kaçınmak. Bu üç konuya dikkat ettiğinizde, modül hem stabil hem de performanslı çalışır.

Tabii ki mod_substitute bir “yamak” çözümü olduğunu da akıldan çıkarmamak lazım. Uzun vadede gerçek sorunu uygulama katmanında çözmeye çalışın. Ama o deployment planlanana kadar, bu modül sizi fazlasıyla idare eder. Sysadmin’liğin tam da özü bu değil mi zaten; doğru araçla, doğru zamanda sorunu çözmek.

Yorum yapın