Caddy ile Request Header Manipülasyonu

HTTP isteklerinin başlıklarını (header) yönetmek, modern web altyapısının en kritik parçalarından biri. Güvenlik politikalarını uygulamak, backend servislerine doğru bilgiyi iletmek, proxy zincirlerinde kimlik bilgisi aktarmak ya da gereksiz başlıkları temizlemek… Bunların hepsi header manipülasyonu kapsamına giriyor. Caddy, bu konuda hem çok güçlü hem de son derece sade bir sözdizimi sunuyor. Bu yazıda Caddy’nin request_header direktifini ve ilgili araçlarını gerçek dünya senaryolarıyla birlikte inceleyeceğiz.

Neden Request Header Manipülasyonu Gerekir?

Backend uygulamalarınız genellikle şunlara ihtiyaç duyar:

  • Gerçek istemci IP adresini öğrenmek (X-Real-IP, X-Forwarded-For)
  • İsteğin hangi protokol üzerinden geldiğini bilmek (X-Forwarded-Proto)
  • Belirli bir tenant veya ortam bilgisini almak (X-Environment: production)
  • Güvenlik açısından tehlikeli başlıkların temizlenmesi (X-Admin-Bypass gibi saçma şeyler)
  • Rate limiting veya loglama için özel başlıklar eklemek

Caddy bunu yaparken Apache’deki mod_headers karmaşasından ya da Nginx’in add_header / proxy_set_header ayrımından çok daha temiz bir yol sunuyor.

Temel Kavramlar: request_header Direktifi

Caddy’de istemciden gelen isteklerin başlıklarını değiştirmek için request_header direktifi kullanılır. Bu direktif üç temel işlem yapabilir:

  • Eklemek: Yeni bir başlık ekler (+ prefix)
  • Silmek: Mevcut bir başlığı kaldırır (- prefix)
  • Değiştirmek: Mevcut değeri yeni değerle değiştirir (prefix yok, ya da regex replace)

Basit bir Caddyfile yapısında şöyle görünür:

example.com {
    request_header X-Custom-Header "merhaba-backend"
    request_header +X-Forwarded-By "caddy-proxy"
    request_header -X-Powered-By

    reverse_proxy localhost:3000
}

Yukarıdaki örnekte:

  • X-Custom-Header başlığı merhaba-backend değerine set ediliyor (varsa üzerine yazar)
  • X-Forwarded-By başlığına caddy-proxy değeri ekleniyor
  • X-Powered-By başlığı istekten siliniyor

Gerçek Dünya Senaryosu 1: Reverse Proxy Arkasında IP Yönetimi

Caddy’yi bir reverse proxy olarak kullandığınızda, backend uygulamanızın gerçek istemci IP’sini görmesi gerekiyor. Varsayılan olarak Caddy, isteği kendi IP’si üzerinden iletir. trusted_proxies ve request_header kombinasyonuyla bu sorunu çözebilirsiniz.

{
    servers {
        trusted_proxies static 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
    }
}

api.sirket.com {
    # Gerçek IP'yi backend'e ilet
    request_header X-Real-IP {remote_host}
    request_header X-Forwarded-For {remote_host}
    request_header X-Forwarded-Proto {scheme}
    request_header X-Forwarded-Host {host}

    # Potansiyel olarak sahte başlıkları temizle
    request_header -X-Forwarded-For-Fake

    reverse_proxy localhost:8080
}

Burada {remote_host}, {scheme} ve {host} Caddy’nin yerleşik placeholder’larıdır. Bunlar istek anında dinamik olarak doldurulur.

Önemli not: X-Forwarded-For başlığı zaten Caddy tarafından otomatik ekleniyor, ancak bunu açık şekilde kontrol altına almak iyi bir pratik.

Gerçek Dünya Senaryosu 2: Multi-Tenant Uygulama Yönetimi

Diyelim ki tek bir backend uygulamanız var ama birden fazla müşteriye hizmet veriyorsunuz. Her müşteri kendi subdomain’i üzerinden geliyor ve backend hangi tenant olduğunu bilmek istiyor.

musteri1.uygulama.com {
    request_header X-Tenant-ID "musteri1"
    request_header X-Tenant-Plan "enterprise"
    request_header X-Environment "production"
    
    reverse_proxy backend-cluster:3000
}

musteri2.uygulama.com {
    request_header X-Tenant-ID "musteri2"
    request_header X-Tenant-Plan "starter"
    request_header X-Environment "production"
    
    reverse_proxy backend-cluster:3000
}

staging.uygulama.com {
    request_header X-Tenant-ID "internal"
    request_header X-Tenant-Plan "enterprise"
    request_header X-Environment "staging"
    request_header X-Debug-Mode "true"
    
    reverse_proxy backend-staging:3000
}

Backend uygulamanız artık X-Tenant-ID başlığına bakarak hangi müşterinin verilerine erişeceğini belirleyebilir. Bu pattern, özellikle Node.js, Django veya Laravel gibi framework’lerle çalışırken son derece temiz bir mimari sağlar.

Güvenlik Odaklı Header Temizleme

İstemciler bazen backdoor açmaya çalışmak amacıyla özel başlıklar gönderebilir. Örneğin bazı eski uygulamalar X-Admin: true veya X-Internal-Request: 1 gibi başlıkları güvenilir kaynak olarak kabul ediyorsa, bu büyük bir güvenlik açığıdır.

guvenli-api.com {
    # Dışarıdan gelen tehlikeli başlıkları temizle
    request_header -X-Admin
    request_header -X-Internal-Request
    request_header -X-Bypass-Auth
    request_header -X-Debug
    request_header -X-Forwarded-For  # Sonra güvenilir değeri ekleyeceğiz
    
    # Caddy tarafından doğrulanmış değerlerle yeniden ekle
    request_header X-Forwarded-For {remote_host}
    request_header X-Request-ID {uuid}
    request_header X-Proxy-Verified "caddy-v2"
    
    reverse_proxy localhost:4000
}

{uuid} placeholder’ı her istek için benzersiz bir ID üretir. Bu, distributed tracing ve loglama için altın değerinde.

Regex ile Başlık Değeri Dönüştürme

Caddy, mevcut bir başlığın değerini regex kullanarak dönüştürme imkanı da sunuyor. Sözdizimi biraz farklı:

panel.sirket.com {
    # User-Agent başlığındaki versiyon bilgisini normalize et
    # "Mozilla/5.0 ..." -> "browser"
    request_header >User-Agent-Type {>User-Agent} Mozilla(.+) browser
    
    # Authorization başlığındaki Bearer token'ı loglamak için maskele
    # NOT: Bu sadece log amaçlı, gerçek başlık değişmez bu örnekte
    
    # Dil kodunu kısalt: "tr-TR,tr;q=0.9" -> "tr"
    request_header >Accept-Language {>Accept-Language} ^([a-z]{2}).* {1}
    
    reverse_proxy localhost:5000
}

Regex replace sözdizimi şu şekilde çalışır:

  • > prefix’i “find and replace in existing header” anlamına gelir
  • {>HeaderName} mevcut başlık değerini okur
  • Son iki parametre sırasıyla pattern ve replacement’tır

Snippet ve Named Route ile Yeniden Kullanılabilir Header Grupları

Birden fazla site için aynı header setini uygulamak istediğinizde Caddy’nin snippet özelliğini kullanabilirsiniz:

(guvenlik_headerlari) {
    request_header -X-Admin
    request_header -X-Internal
    request_header -X-Bypass-Auth
    request_header X-Forwarded-For {remote_host}
    request_header X-Forwarded-Proto {scheme}
    request_header X-Request-ID {uuid}
    request_header X-Proxy "caddy"
}

(tenant_detection) {
    request_header X-Environment "production"
    request_header X-Region "eu-west"
}

site1.com {
    import guvenlik_headerlari
    import tenant_detection
    request_header X-Tenant "site1"
    reverse_proxy localhost:3001
}

site2.com {
    import guvenlik_headerlari
    import tenant_detection
    request_header X-Tenant "site2"
    reverse_proxy localhost:3002
}

site3.com {
    import guvenlik_headerlari
    # Bu site farklı region'da
    request_header X-Region "us-east"
    request_header X-Tenant "site3"
    reverse_proxy localhost:3003
}

Bu pattern, onlarca site yönetirken bakımı dramatik şekilde kolaylaştırıyor.

Koşullu Header Manipülasyonu: matchers Kullanımı

Bazı durumlarda tüm isteklere değil, belirli path’lere veya koşullara göre header manipülasyonu yapmak istersiniz.

uygulama.com {
    # Sadece /api/* path'leri için
    @api_istekleri path /api/*
    request_header @api_istekleri X-Request-Type "api"
    request_header @api_istekleri X-Rate-Limit-Tier "standard"
    
    # Sadece /admin/* için
    @admin_istekleri path /admin/*
    request_header @admin_istekleri X-Admin-Route "true"
    request_header @admin_istekleri X-Security-Level "high"
    
    # Belirli User-Agent'lar için
    @mobil_istemci header User-Agent *Mobile*
    request_header @mobil_istemci X-Client-Type "mobile"
    request_header @mobil_istemci X-Optimize "mobile"
    
    # Belirli IP bloğundan gelen istekler için
    @ic_ag remote_ip 10.0.0.0/8
    request_header @ic_ag X-Internal-Request "true"
    request_header @ic_ag X-Skip-Rate-Limit "true"
    
    reverse_proxy localhost:8080
}

Matcher’lar Caddy’nin en güçlü özelliklerinden biri. @isim şeklinde tanımlayıp birden fazla direktifte kullanabiliyorsunuz.

Gerçek Dünya Senaryosu 3: Microservice Authentication Header Zinciri

Microservice mimarisinde servisler arası kimlik doğrulama için JWT token’larını veya servis kimliklerini header üzerinden iletmek yaygın bir pattern.

{
    order_secret_env INTERNAL_SERVICE_TOKEN
}

gateway.sirket.com {
    # Dış isteklerden gelen internal token iddialarını sil
    request_header -X-Service-Token
    request_header -X-Internal-Auth
    request_header -X-Service-Name
    
    # Hangi servisin backend olduğuna göre yönlendir
    handle /orders/* {
        request_header X-Service-Name "order-service"
        request_header X-Gateway-Version "2.1"
        request_header X-Request-ID {uuid}
        reverse_proxy order-service:3001
    }
    
    handle /inventory/* {
        request_header X-Service-Name "inventory-service"
        request_header X-Gateway-Version "2.1"
        request_header X-Request-ID {uuid}
        reverse_proxy inventory-service:3002
    }
    
    handle /users/* {
        request_header X-Service-Name "user-service"
        request_header X-Gateway-Version "2.1"
        request_header X-Request-ID {uuid}
        reverse_proxy user-service:3003
    }
}

Bu yapıyla her microservice, isteğin hangi gateway versiyonundan geldiğini, hangi servis adıyla etiketlendiğini ve benzersiz request ID’sini biliyor.

Caddy JSON Config ile İleri Düzey Header Manipülasyonu

Caddyfile yerine JSON konfigürasyon kullanıyorsanız (programmatic setup veya API üzerinden yönetim durumlarında) request_header direktifinin JSON karşılığı şöyle görünür:

# Aşağıdaki JSON'u /etc/caddy/config.json olarak kaydedin
# veya Caddy Admin API üzerinden POST edin:
# curl -X POST "http://localhost:2019/load" 
#      -H "Content-Type: application/json" 
#      -d @config.json

{
  "apps": {
    "http": {
      "servers": {
        "main": {
          "listen": [":443"],
          "routes": [
            {
              "match": [{"host": ["api.sirket.com"]}],
              "handle": [
                {
                  "handler": "headers",
                  "request": {
                    "set": {
                      "X-Real-IP": ["{http.request.remote.host}"],
                      "X-Forwarded-Proto": ["{http.request.scheme}"],
                      "X-Request-ID": ["{http.request.uuid}"]
                    },
                    "delete": ["X-Admin", "X-Internal", "X-Bypass-Auth"],
                    "add": {
                      "X-Proxy-Chain": ["caddy-v2"]
                    }
                  }
                },
                {
                  "handler": "reverse_proxy",
                  "upstreams": [{"dial": "localhost:8080"}]
                }
              ]
            }
          ]
        }
      }
    }
  }
}

JSON config ile set, add ve delete operation’larını ayrı ayrı kontrol edebiliyorsunuz.

Placeholder Referansı: Kullanabileceğiniz Dinamik Değerler

Request header’larında kullanabileceğiniz Caddy placeholder’larının en işe yarayanları:

  • {remote_host}: İstemcinin IP adresi (port olmadan)
  • {remote_port}: İstemcinin kaynak portu
  • {remote_ip}: Ham remote IP (port dahil olabilir)
  • {scheme}: http veya https
  • {host}: İstek yapılan hostname
  • {uri}: Full path + query string
  • {path}: Sadece path kısmı
  • {method}: GET, POST vb.
  • {uuid}: Her istek için üretilen benzersiz UUID
  • {header.X-Mevcut-Header}: Mevcut bir başlığın değerini okur
  • {time.now.unix}: Unix timestamp
  • {env.DEGISKEN_ADI}: Ortam değişkeni okuma

Test ve Debug: Header’ların Doğru Gidip Gitmediğini Kontrol Etme

Header manipülasyonunu test etmek için birkaç pratik yöntem:

# Basit curl ile gelen header'ları görmek için httpbin kullanın
curl -v https://uygulama.com/headers

# httpbin.org'un /headers endpoint'i isteğe gelen tüm header'ları JSON olarak döner
# Kendi local test için httpbin docker image kullanabilirsiniz:
docker run -p 8888:80 kennethreitz/httpbin

# Caddyfile'ı şöyle yapılandırın:
# test.localhost {
#     request_header X-Test "degeri-kontrol-et"
#     reverse_proxy localhost:8888
# }

# Sonra test edin:
curl http://test.localhost/headers | python3 -m json.tool

# Caddy log'larından header'ları izlemek için
# access_log direktifini aktifleştirin ve şöyle tail edin:
tail -f /var/log/caddy/access.log | jq '.request.headers'
# Caddy konfigürasyonunu doğrulama
caddy validate --config /etc/caddy/Caddyfile

# Konfigürasyonu reload etme (downtime yok)
caddy reload --config /etc/caddy/Caddyfile

# Format kontrolü ve otomatik düzeltme
caddy fmt --overwrite /etc/caddy/Caddyfile

# Admin API üzerinden mevcut konfigürasyonu görme
curl http://localhost:2019/config/ | jq .

Yaygın Hatalar ve Çözümleri

Prodüksiyonda sıkça karşılaşılan sorunlar:

+ ve değer override farkını karıştırmak: request_header +X-Foo "bar" ile request_header X-Foo "bar" farklı davranır. İlki mevcut başlığa ekler (birden fazla değer olabilir), ikincisi üzerine yazar. Authorization veya Content-Type gibi başlıkları add ile değil set ile yönetin.

Regex placeholder sözdizimi: {>Header-Adı} şeklinde > işareti olmadan okuma yapılamaz. Sık yapılan hata, sadece {Header-Adı} yazmak.

Case sensitivity: HTTP/2’de header isimleri lowercase’e normalize edilir. X-Custom-Header yazsan da backend x-custom-header olarak alabilir. Backend kodunuzu buna göre yazın.

Trusted proxy olmadan X-Forwarded-For: Güvenilir proxy tanımlamazsanız ve X-Forwarded-For’u olduğu gibi iletirseniz, istemci bu başlığı manipüle edebilir ve backend yanlış IP görür.

Sonuç

Caddy’nin request header manipülasyonu sözdizimi, bu işi hem güvenli hem de bakımı kolay yapan bir denge sunuyor. request_header direktifinin üç temel operasyonu (ekle, sil, değiştir), matcher’larla kombinasyonu ve snippet’lerle tekrar kullanılabilirliği bir araya geldiğinde, karmaşık proxy senaryolarını bile temiz ve okunabilir bir Caddyfile ile yönetmek mümkün oluyor.

Özellikle dikkat etmeniz gereken noktalar şunlar: Dışarıdan gelen güvenlik bypass header’larını her zaman silip Caddy’nin ürettiği güvenilir değerlerle değiştirin, {uuid} placeholder’ını distributed tracing için kullanın ve snippet’lerle ortak header politikalarını merkezi hale getirin. Bu üç alışkanlık bile birçok güvenlik açığını ve tutarsızlık sorununu ortadan kaldırır.

Bir sonraki adım olarak response_header direktifine bakmanızı öneririm. İstemciye giden yanıtların başlıklarını yönetmek, Content-Security-Policy, Strict-Transport-Security gibi güvenlik header’larını doğru yerden eklemek için aynı derecede kritik.

Yorum yapın