Modern web uygulamaları geliştirirken, frontend ve backend servislerini farklı origin’lerde çalıştırmak neredeyse kaçınılmaz hale geldi. Bu durum CORS (Cross-Origin Resource Sharing) sorunlarını beraberinde getiriyor ve her sysadmin’in başı bir şekilde bu başlıklarla derde giriyor. Caddy’yi tercih ediyorsanız, hem CORS yönetimi hem de genel API güvenliği konusunda oldukça güçlü araçlara sahipsiniz. Bu yazıda Caddy ile CORS başlıklarını doğru yapılandırmayı, yaygın hataları önlemeyi ve API güvenliğini katmanlı biçimde nasıl sağlayacağınızı ele alacağız.
CORS Nedir ve Neden Önemlidir
Tarayıcılar güvenlik nedeniyle, bir web sayfasının farklı bir origin’e (protokol + domain + port kombinasyonu) istek göndermesini varsayılan olarak engeller. Buna Same-Origin Policy denir. CORS ise sunucunun belirli dış origin’lere izin verdiğini tarayıcıya bildiren bir mekanizmadır.
Basit bir senaryo düşünelim: Frontend uygulamanız https://app.sirketiniz.com adresinde çalışıyor, API’niz ise https://api.sirketiniz.com adresinde. Tarayıcı, frontend’den API’ye yapılan her isteği önce bir “preflight” isteğiyle kontrol eder. Eğer sunucu doğru CORS başlıklarını dönmezse, tarayıcı asıl isteği hiç göndermez ve kullanıcı hata alır.
CORS başlıklarını yanlış yapılandırmak ise güvenlik açıklarına davetiye çıkarır. Access-Control-Allow-Origin: * gibi açık uçlu bir yapılandırma, herhangi bir web sitesinin API’nize istek yapabilmesine izin verir. Bu, özellikle kimlik doğrulama gerektiren endpointler için ciddi bir risk oluşturur.
Caddy’de Temel CORS Yapılandırması
Caddy, CORS başlıklarını yönetmek için birkaç farklı yaklaşım sunar. En temiz yol, header direktifini kullanmaktır. Basit bir başlangıç konfigürasyonuna bakalım:
# /etc/caddy/Caddyfile
api.sirketiniz.com {
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
header Access-Control-Max-Age "3600"
reverse_proxy localhost:8080
}
Bu yapılandırma yalnızca https://app.sirketiniz.com origin’ine izin verir. Ancak preflight isteklerini de ele almak gerekir. Tarayıcı OPTIONS metoduyla preflight isteği gönderdiğinde, bunu ayrıca işlememiz gerekir:
# Preflight isteklerini handle eden tam yapılandırma
api.sirketiniz.com {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With, X-API-Key"
header Access-Control-Allow-Credentials "true"
header Access-Control-Max-Age "86400"
respond "" 204
}
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Credentials "true"
reverse_proxy localhost:8080
}
Burada @options adında bir matcher tanımladık ve sadece OPTIONS metoduyla gelen istekler için özel bir blok oluşturduk. Bu isteklere 204 No Content yanıtı dönerek backend’e gereksiz yük binmesini engelliyoruz.
Dinamik CORS: Birden Fazla Origin Yönetimi
Gerçek dünyada tek bir origin’e izin vermek çoğu zaman yetmez. Staging ortamınız, mobil uygulama geliştirme sunucunuz ve production ortamınız ayrı origin’lere sahip olabilir. Caddy’de bunu dinamik olarak yönetmek için vars ve matcher kombinasyonunu kullanabilirsiniz:
api.sirketiniz.com {
@allowedOrigins header Origin "https://app.sirketiniz.com"
@stagingOrigin header Origin "https://staging.sirketiniz.com"
@localOrigin header Origin "http://localhost:3000"
handle @allowedOrigins {
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Credentials "true"
}
handle @stagingOrigin {
header Access-Control-Allow-Origin "https://staging.sirketiniz.com"
header Access-Control-Allow-Credentials "true"
}
handle @localOrigin {
header Access-Control-Allow-Origin "http://localhost:3000"
header Access-Control-Allow-Credentials "true"
}
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key"
reverse_proxy localhost:8080
}
Bu yaklaşım açık ve yönetilebilir olmakla birlikte, fazla origin olduğunda Caddyfile şişmeye başlar. Bu durumda Caddy’nin JSON yapılandırması veya environment variable kullanımı daha temiz bir çözüm sunar.
Environment Variable ile Esnek CORS Yapılandırması
Caddy, Caddyfile içinde environment variable kullanımını destekler. Bu özelliği CORS yapılandırmasını ortama göre değiştirmek için kullanabilirsiniz:
# /etc/caddy/Caddyfile
api.sirketiniz.com {
@corsOrigin header Origin {$ALLOWED_ORIGIN}
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "{$ALLOWED_ORIGIN}"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key"
header Access-Control-Max-Age "3600"
respond "" 204
}
header @corsOrigin Access-Control-Allow-Origin "{$ALLOWED_ORIGIN}"
header @corsOrigin Access-Control-Allow-Credentials "true"
reverse_proxy localhost:8080
}
Ardından Caddy servisini başlatırken veya systemd unit dosyasında bu değişkeni tanımlarsınız:
# /etc/systemd/system/caddy.service.d/override.conf
[Service]
Environment="ALLOWED_ORIGIN=https://app.sirketiniz.com"
Production, staging veya development ortamları için farklı değerler ayarlayarak aynı Caddyfile’ı kullanabilirsiniz. Değişiklik sonrasında systemctl daemon-reload && systemctl restart caddy komutunu çalıştırmayı unutmayın.
API Güvenliği: Rate Limiting
CORS başlıklarının ötesinde, API güvenliğinin en önemli katmanlarından biri rate limiting’dir. Caddy, üçüncü taraf bir modül olan caddy-ratelimit ile güçlü rate limiting imkanı sunar. Ancak modül olmadan da temel rate limiting senaryolarını handle direktifi ile yönetebilirsiniz.
Caddy’ye ek modül kurmak için xcaddy kullanmanız gerekir:
# xcaddy kurulumu ve özel Caddy binary derleme
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build
--with github.com/mholt/caddy-ratelimit
# Derlenen binary'yi yerleştirin
sudo mv caddy /usr/bin/caddy
sudo systemctl restart caddy
Rate limiting modülü kurulduktan sonra Caddyfile’da şöyle kullanabilirsiniz:
api.sirketiniz.com {
rate_limit {
zone api_zone {
key {remote_host}
events 100
window 1m
}
}
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization"
respond "" 204
}
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
reverse_proxy localhost:8080
}
Bu yapılandırma her IP adresine dakikada 100 istek sınırı koyar. Sınır aşıldığında Caddy otomatik olarak 429 Too Many Requests yanıtı döner.
API Anahtarı Doğrulaması
Bazı endpointler için API anahtarı zorunluluğu getirmek isteyebilirsiniz. Caddy, basicauth direktifini desteklese de API key kontrolü için header matcher ve respond kombinasyonu daha uygun bir yaklaşımdır:
api.sirketiniz.com {
# API key yoksa veya yanlışsa 401 döndür
@missingApiKey not header X-API-Key "gizli-api-anahtari-buraya"
handle @missingApiKey {
respond "Unauthorized: Gecerli bir API anahtari gereklidir" 401
}
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Methods "GET, POST, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key"
respond "" 204
}
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Credentials "true"
reverse_proxy localhost:8080
}
Burada dikkat edilmesi gereken bir nokta var: Preflight isteklerinde tarayıcı API key header’ı göndermez. Bu nedenle @options bloğunun API key kontrolünden önce veya ayrı bir şekilde ele alınması gerekir. Yukarıdaki örnekte handle @options bloğu doğrudan yanıt döndürdüğü için sıraya dikkat etmek yeterlidir.
Güvenlik Başlıkları ile Derinlemesine Savunma
CORS ve API key doğrulamasının yanı sıra, HTTP güvenlik başlıkları eklemek API güvenliğini önemli ölçüde artırır. Bu başlıklar XSS, clickjacking ve diğer saldırılara karşı ek koruma sağlar:
api.sirketiniz.com {
# Güvenlik başlıkları
header {
# Clickjacking koruması
X-Frame-Options "DENY"
# MIME type sniffing koruması
X-Content-Type-Options "nosniff"
# XSS koruması (modern tarayıcılar için)
X-XSS-Protection "1; mode=block"
# HTTPS zorunluluğu (HSTS)
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Referrer politikası
Referrer-Policy "strict-origin-when-cross-origin"
# İzin politikası
Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Sunucu bilgisini gizle
-Server
}
# CORS başlıkları
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key"
@options method OPTIONS
handle @options {
respond "" 204
}
reverse_proxy localhost:8080
}
-Server direktifi, Caddy’nin varsayılan olarak eklediği Server başlığını kaldırır. Saldırganların hangi sunucu yazılımını kullandığınızı bilmesini engellemek, güvenlik duruşunuzu iyileştirir.
Gerçek Dünya Senaryosu: Mikroservis Mimarisi
Birden fazla mikroservisin bulunduğu bir mimaride CORS ve güvenlik yapılandırmasını merkezi olarak yönetmek önemlidir. Şöyle bir senaryo düşünelim: Kullanıcı yönetimi, ürün servisi ve sipariş servisi ayrı backend’lerde çalışıyor, ama hepsi tek bir Caddy instance’ı üzerinden yayınlanıyor:
# /etc/caddy/Caddyfile
(cors_headers) {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "{args[0]}"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH"
header Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key, X-Request-ID"
header Access-Control-Allow-Credentials "true"
header Access-Control-Max-Age "86400"
respond "" 204
}
header Access-Control-Allow-Origin "{args[0]}"
header Access-Control-Allow-Credentials "true"
}
(security_headers) {
header {
X-Frame-Options "DENY"
X-Content-Type-Options "nosniff"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
}
# Kullanici servisi
users.api.sirketiniz.com {
import cors_headers "https://app.sirketiniz.com"
import security_headers
reverse_proxy localhost:8081
}
# Urun servisi
products.api.sirketiniz.com {
import cors_headers "https://app.sirketiniz.com"
import security_headers
reverse_proxy localhost:8082
}
# Siparis servisi - ek olarak rate limiting
orders.api.sirketiniz.com {
import cors_headers "https://app.sirketiniz.com"
import security_headers
reverse_proxy localhost:8083
}
Caddy’nin snippet (parçacık) özelliği burada inanılmaz derecede kullanışlı oluyor. (cors_headers) ve (security_headers) olarak tanımladığımız snippet’ler, import direktifiyle her serviste yeniden kullanılabiliyor. Böylece CORS politikasını değiştirmeniz gerektiğinde tek bir yeri düzenlemeniz yeterli oluyor.
Caddy Logları ile CORS Sorunlarını Debug Etme
Üretim ortamında CORS sorunlarını tespit etmek bazen sinir bozucu olabilir. Caddy’nin loglama özelliğini aktif ederek hangi isteklerin geldiğini, hangi başlıkların döndüğünü izleyebilirsiniz:
api.sirketiniz.com {
log {
output file /var/log/caddy/api-access.log {
roll_size 100mb
roll_keep 10
}
format json
level INFO
}
header Access-Control-Allow-Origin "https://app.sirketiniz.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization"
@options method OPTIONS
handle @options {
respond "" 204
}
reverse_proxy localhost:8080
}
Log dosyasını izlemek ve CORS hatalarını filtrelemek için:
# Gerçek zamanlı log takibi
sudo tail -f /var/log/caddy/api-access.log | jq '.'
# Sadece OPTIONS isteklerini filtrele
sudo tail -f /var/log/caddy/api-access.log | jq 'select(.request.method == "OPTIONS")'
# Belirli bir origin'den gelen istekleri izle
sudo tail -f /var/log/caddy/api-access.log |
jq 'select(.request.headers.Origin != null)'
# Caddy konfigürasyonunu test et
caddy validate --config /etc/caddy/Caddyfile
# Konfigürasyonu yeniden yükle (sıfırdan restart etmeden)
sudo systemctl reload caddy
Sık Yapılan Hatalar ve Çözümleri
Wildcard ile credentials birlikte kullanmak: Access-Control-Allow-Origin: * ile Access-Control-Allow-Credentials: true aynı anda kullanılamaz. Tarayıcı bu kombinasyonu reddeder. Credentials kullanmanız gerekiyorsa mutlaka spesifik bir origin belirtmelisiniz.
Preflight cache süresi: Access-Control-Max-Age başlığını çok düşük tutmak, her istek öncesinde preflight gönderilmesine neden olur ve gereksiz gecikme yaratır. 3600 (1 saat) veya 86400 (1 gün) makul değerlerdir.
Header eşleşme sırası: Caddy’de header direktifleri sırayla işlenir. Aynı başlığı iki kez tanımlarsanız her ikisi de eklenir. Bu özellikle Access-Control-Allow-Origin için sorun yaratabilir. Mevcut bir başlığı değiştirmek için header bloğu içinde ? prefix’ini kullanabilirsiniz:
header {
# Mevcut degeri koru, yoksa ekle
?Access-Control-Allow-Origin "https://app.sirketiniz.com"
}
OPTIONS isteklerine backend’i dahil etmemek: Preflight isteklerini backend’e iletmek hem gereksiz yük oluşturur hem de backend’in CORS başlıklarını override etmesine yol açabilir. Her zaman respond "" 204 ile bu istekleri Caddy seviyesinde sonlandırın.
Konfigürasyonu Test Etme
Yaptığınız değişikliklerin doğru çalıştığını doğrulamak için curl ile manuel test yapabilirsiniz:
# Preflight isteği simule etme
curl -X OPTIONS https://api.sirketiniz.com/endpoint
-H "Origin: https://app.sirketiniz.com"
-H "Access-Control-Request-Method: POST"
-H "Access-Control-Request-Headers: Content-Type, Authorization"
-v 2>&1 | grep -i "access-control"
# Gerçek bir cross-origin GET isteği
curl -X GET https://api.sirketiniz.com/users
-H "Origin: https://app.sirketiniz.com"
-H "Authorization: Bearer token123"
-v 2>&1 | grep -i "access-control"
# İzin verilmeyen origin ile test
curl -X OPTIONS https://api.sirketiniz.com/endpoint
-H "Origin: https://kotu-site.com"
-H "Access-Control-Request-Method: POST"
-v 2>&1 | grep -i "access-control"
Son komutun çıktısında Access-Control-Allow-Origin başlığının bulunmaması gerekir. Bu, izin verilmeyen origin’lerin bloklandığını doğrular.
Sonuç
Caddy ile CORS ve API güvenliği yapılandırması, nginx veya Apache ile karşılaştırıldığında çok daha okunabilir ve yönetilebilir bir yapıda gerçekleştirilebiliyor. Snippet mekanizması sayesinde tekrar eden konfigürasyonları merkezi olarak yönetmek mümkün, environment variable desteği ise aynı Caddyfile’ın farklı ortamlarda kullanılmasını kolaylaştırıyor.
Özetlemek gerekirse:
- CORS başlıklarını her zaman spesifik origin’lerle tanımlayın, wildcard kullanımından kaçının
- Preflight
OPTIONSisteklerini Caddy seviyesinde ele alın ve backend’e iletmeyin - Güvenlik başlıklarını CORS yapılandırmasından ayrı snippet’lerde tutun ve tüm servislerde yeniden kullanın
- Rate limiting ile API’nizi brute force ve DDoS girişimlerine karşı koruyun
- Credentials kullanıyorsanız
Access-Control-Allow-Originbaşlığında kesinlikle wildcard kullanmayın - Değişiklik sonrasında
caddy validateile konfigürasyonu test etmeyi alışkanlık haline getirin
API güvenliği tek bir katmandan ibaret değildir. CORS başlıkları, API key doğrulaması, rate limiting ve HTTP güvenlik başlıkları bir arada kullanıldığında gerçek anlamda güvenli bir API altyapısı oluşturulabilir. Caddy bu katmanların tamamını temiz bir sözdizimiyle yönetmenizi sağlıyor.