İçindekiler
- 1 Ofiste Küçük Bir Panik, Büyük Bir Aydınlanma: mTLS’e Giden Yol
- 2 mTLS Nedir, Niye Bu Kadar Rahatlatır?
- 3 Kendi Küçük CA’nı Kur: Sertifika Üretimi ve Dağıtımın İlk Adımı
- 4 Nginx’te mTLS: Sunucu, Doğrulama ve Uygulamaya Bilgi Aktarma
- 5 Caddy’de mTLS: Caddyfile ile Doğal, Yalın ve Tatlı
- 6 API ve Mikroservislerde mTLS’i Günlük Hayata Katmak
- 7 Sertifika Rotasyonu, İptal Listeleri ve Küçük Tuzaklar
- 8 Test, Log ve Alarm: “Oldu mu?” Sorusu İçin Pratik Adımlar
- 9 Gerçek Hayattan Küçük Bir Örnek: Yönetim Panelleri ve Sakin Uykular
- 10 Operasyon İpuçları: Dağıtım, Otomasyon ve Küçük Alışkanlıklar
- 11 Sorun Giderme: “El Sıkışamadık” Dediğinde Ne Yapmalı?
- 12 Kapanış: mTLS ile İçiniz Rahat, Trafik Akışınız Sakin
Ofiste Küçük Bir Panik, Büyük Bir Aydınlanma: mTLS’e Giden Yol
Hiç başınıza geldi mi? Bir sabah API’lerden birinin grafiği deliler gibi oynamaya başlar, loglar şişer, uykunuzdan uyanıp ekrana bakarsınız. İşte o sabahlardan birinde, içeride sadece bizim konuşması gereken bir mikroservisin, dışarıdan bir istekle tetiklendiğini gördüğümde içim hafifçe sızladı. Güvenlik duvarı vardı, IP kısıtları vardı, ama yine de içeri sızmanın bir yolunu bulmuşlardı. O gün karar verdim: Sunucu sertifikası tek başına yetmiyor, istemcinin de gerçekten “o” olduğundan emin olmalıyız. Kısacası, mTLS’e geçme zamanıydı.
Bugün bu yazıda, Nginx ve Caddy ile mTLS (Karşılıklı TLS) kurulumunu baştan sona konuşacağız. Kendi küçük sertifika otoritenizi nasıl kuracağınızı, istemci sertifikalarını nasıl dağıtacağınızı ve API ile mikroservislerin birbirini nasıl doğrulayacağını adım adım anlatacağım. Mesela düşünün, sipariş servisi sadece stok servisine konuşabiliyor; başka kimseye değil. İmza gibi çalışan sertifikalarla bunu tatlı tatlı nasıl sağladığımızı göreceksiniz. Arada gerçek hayattan küçük ipuçları, bazen de “şurada tıkandım, şöyle çözdüm” diyeceğim. Hadi başlayalım.
mTLS Nedir, Niye Bu Kadar Rahatlatır?
mTLS’i güvenlikteki çift yönlü tokalaşma gibi düşünün. Normal TLS’te sunucu kendini kanıtlar, istemci sadece “tamam, sensin” der. mTLS’te ise istemci de kendini kanıtlar; sunucu “sen kimsin?” diye sorar ve sertifikanın imzasını, geçerlilik süresini ve hangi otorite tarafından verildiğini kontrol eder. Böylece sadece sunucuyu değil, karşısındaki konuşma partnerini de doğruluyoruz. Mikroservislerin birbirine fısıldadığı bir mimaride bu his, gerçekten içini rahatlatıyor.
Avantajı çok net: İçerideki servisler yanlış kimlikle konuşamıyor, dışarıdaki birisi içeri sızsa bile imzasız kalıyor. Dezavantajı ise gayet insani: Sertifikalar yönetilmek ister, süreleri dolur, yenilenmesi gerekir, rotasyon ister. Biraz sevgi ve düzenle bu gayet akıcı hale geliyor. Mesela bakım pencerelerini planladığınız gibi, sertifika yenilemelerini de küçük adımlarla ve otomasyonla halledince mTLS günlük hayatın doğal bir parçasına dönüşüyor.
Kendi Küçük CA’nı Kur: Sertifika Üretimi ve Dağıtımın İlk Adımı
İlk iş, bir özel CA (Certificate Authority) oluşturmak. Sunucu sertifikasını Let’s Encrypt gibi bir sağlayıcıdan alabilirsiniz; ama istemci sertifikalarında genellikle kendi CA’nızla ilerlersiniz. Böylece kimin içeri gireceğine siz karar verirsiniz. Mesela ekipteki her mikroservise birer istemci sertifikası dağıtırsınız; devrede olmayan bir servisi emekliye ayırırken sadece sertifikasını iptal edersiniz.
Basit bir OpenSSL akışıyla gidelim. Aşağıdaki komutlar test ortamı için şahane. Üretimde anahtar izinleri, güvenli saklama ve rotasyon süreçlerine daha özen gösterin.
# 1) CA için özel anahtar ve self-signed sertifika
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650
-subj "/C=TR/ST=Istanbul/O=OrnekCA/CN=Ornek Root CA"
-out ca.crt
# 2) Sunucu sertifikası (ör. api.ornek.local)
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
-subj "/C=TR/ST=Istanbul/O=Ornek/CN=api.ornek.local"
# Subject Alternative Name (SAN) dosyası
cat > san.cnf <<EOF
subjectAltName = @alt_names
[alt_names]
DNS.1 = api.ornek.local
DNS.2 = api
EOF
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial
-out server.crt -days 825 -sha256 -extfile san.cnf
# 3) İstemci sertifikası (ör. stok-servisi)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
-subj "/C=TR/ST=Istanbul/O=Ornek/CN=stok-servisi"
echo "extendedKeyUsage = clientAuth" > client-ext.cnf
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial
-out client.crt -days 365 -sha256 -extfile client-ext.cnf
Burada önemli olan, istemci sertifikasında clientAuth uzantısının bulunması. Sunucu tarafında da serverAuth beklendiğini unutmayın. Ayrıca SAN alanları, sunucu adının doğru eşleşmesi için kritik. Küçük bir not: Üretimde kök CA’yı çevrimdışı tutup bir ara CA ile imzalama yapmak iyi bir alışkanlık. Daha temiz bir PKI akışı için Smallstep ile küçük bir CA kurma rehberlerine göz atmak hoş oluyor.
Nginx’te mTLS: Sunucu, Doğrulama ve Uygulamaya Bilgi Aktarma
Nginx tarafında iki farklı akış var. Birincisi doğrudan Nginx’in terminasyon yaptığı ve istemci sertifikasını kendisinin doğruladığı akış. İkincisi ise Nginx’in doğrulayıp kimlik bilgilerini arka uç uygulamaya başlıklarla aktarması. Ben genellikle ikinci yaklaşımı seviyorum; uygulama tarafı kimlik doğrulamayı görmek isteyebiliyor.
Aşağıdaki örnek, mTLS’i zorunlu kılıyor ve istemci sertifikası kabul edilmezse 400 ile kapıyı kapatıyor. Bir yandan da uygulamaya konu ile ilgili bilgileri header olarak iletiyor.
server {
listen 443 ssl http2;
server_name api.ornek.local;
ssl_certificate /etc/ssl/server.crt;
ssl_certificate_key /etc/ssl/server.key;
# İstemci sertifikalarını doğrulamak için CA
ssl_client_certificate /etc/ssl/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
# Güzel kısıtlar (örnek)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# İstemci bilgilerini upstream'e aktar
proxy_set_header X-Client-Cert $ssl_client_cert;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_set_header X-Client-Subject $ssl_client_s_dn;
proxy_set_header X-Client-Subject-CN $ssl_client_s_dn_cn;
proxy_set_header X-Client-Issuer $ssl_client_i_dn;
location /healthz {
# Sağlık kontrolü için mTLS'i opsiyonel yapmayı isteyebilirsiniz
# ssl_verify_client off; # Ya da ayrı bir server bloğu kullanın
return 200 'ok';
}
location / {
proxy_pass http://backend_app;
}
}
upstream backend_app {
server 127.0.0.1:3000;
}
Bazı durumlarda mTLS’i opsiyonel yapmak istersiniz. Mesela yalnızca bazı endpoint’lerde istiyorsanız, ssl_verify_client optional ile gelen sertifika var mı yok mu bakabilirsiniz, sonra $ssl_client_verify değişkenine göre koşula bağlayabilirsiniz. Başka bir ipucu: Sertifika iptallerini yönetiyorsanız, Nginx’e CRL dosyası göstermek için ssl_crl kullanabilirsiniz. Detaylı yönergeleri Nginx SSL modülü belgelerinde bulmak rahatlatır.
Uygulamaya aktardığımız başlıkları basitçe kontrol etmek mümkün. Mesela Express.js ile şöyle bir kontrol düşünün; sertifika doğrulandıysa gelen CN’ye göre rol atarsınız, değilse isteği geri çevirirsiniz. Minik bir örnek:
// Node.js/Express örneği
app.use((req, res, next) => {
const verified = req.headers['x-client-verify'] === 'SUCCESS';
const cn = req.headers['x-client-subject-cn'];
if (!verified) return res.status(401).json({ error: 'mTLS gerekli' });
req.client = { cn };
next();
});
app.get('/stok', (req, res) => {
if (req.client.cn !== 'stok-servisi') return res.status(403).json({ error: 'izinsiz' });
res.json({ durum: 'stok verisi' });
});
Bu yöntem, ağ sınırındaki mTLS’i uygulama katmanında ince ayarlarla birleştirir. İstemcinin kim olduğunu öğrendiğiniz an, yetkilendirme oyun alanı genişler.
Caddy’de mTLS: Caddyfile ile Doğal, Yalın ve Tatlı
Caddy’nin sade yapısını seviyorum. Tek bir Caddyfile ile sertifikaları otomatik al, mTLS koşulunu koy, reverse proxy ayarla; hepsi birkaç satıra bakıyor. Sunucu sertifikasını ACME ile alırken, istemci doğrulaması için kendi CA’nızı gösterebilirsiniz.
api.ornek.local {
encode gzip
# Sunucu sertifikasını otomatik alır (Let's Encrypt)
tls {
client_auth {
mode require_and_verify
trusted_ca_cert_file /etc/ssl/ca.crt
}
}
@health path /healthz
handle @health {
respond "ok" 200
}
reverse_proxy 127.0.0.1:3000 {
header_up X-Client-Verify {tls_client_verified}
header_up X-Client-Subject {tls_client_subject}
header_up X-Client-Cert {tls_client_cert}
}
}
Burada {tls_client_verified} gibi yerleşik değerlerle istemci doğrulamasını uygulamaya geçiriyoruz. Sunucu sertifikası için ek bir şey yapmanıza gerek kalmıyor; Caddy otomatikleştiriyor. Daha fazla ipucu için Caddy tls ve client_auth dokümanlarını okumak iyi geliyor.
Sağlık kontrolleri için ben genelde mTLS’i esnetiyorum. İki çözüm hoşuma gidiyor: Ya health endpoint’ini ayrı bir site bloğunda, mTLS’siz açmak; ya da aynı blokta path’e özel bir handle yazarak kolaylaştırmak. Trafiğin kalanında ise mTLS’i zorunlu tutmak, içeriyi ferahlatıyor.
API ve Mikroservislerde mTLS’i Günlük Hayata Katmak
Gerçek dünyada işin en tatlı yanı, mTLS’i sadece bir kapı kilidi gibi değil; kimlik kartı gibi kullanmak. Mesela sipariş servisi stok servisine konuştuğunda, stok servisi “kim konuşuyor?” diye sormaz; mTLS zaten konuşanın stok-servisi olup olmadığını kanıtlar. Uygulama katmanı bu kimliği alıp erişim kurallarına dökebilir. Bu, gereksiz token doğrulamalarını azaltabildiği gibi, ağ trafiğini sadeleştirir.
Bir başka akış: Harici bir entegrasyonla konuşuyorsunuz ama o tarafa client sertifikası veremiyorsunuz. Burada mTLS iç ağda devrede kalsın, dış dünyayla ise JWT veya başka bir kimlik katmanı kullanın. Nginx/Caddy mTLS’i içeriye kadar taşır, API geçidi ise dış dünyayla token konuşur. Böyle hibrit çözümler pratikte çok işe yarıyor.
İç servislerin her birine farklı bir istemci sertifikası vermek güzel bir izleme disiplini yaratır. Hangi servis hangi çağrıyı yapmış, log’larda hangi CN ile giriş yapılmış, geriye dönük raporlar nasıl oluşmuş; hepsi daha okunaklı olur. Eğer izlenebilirlik gözünüze hoş geliyorsa, servislerinizde istekleri iz sürmenin yollarını konuştuğum OpenTelemetry ile izlenebilirlik rehberine göz atın; mTLS ile birlikte harika bir ikili oluyorlar.
Sertifika Rotasyonu, İptal Listeleri ve Küçük Tuzaklar
Sertifikaların süresi doluyor; bu bir gerçek. Bunu dert etmemenin yolu, kısa ömürlü sertifikalar ve düzenli otomasyon. İstemci sertifikalarını 3-6 ay aralığında tutup, süresi dolmadan birkaç gün önce yenileyen küçük bir iş akışı kurmak çok rahatlatıyor. Eski ve yeni sertifika bir süre birlikte geçerli olduğunda, kesintisiz geçişler mümkün oluyor.
İptal konusu bazen ıskalanıyor. Yanlışlıkla sızmış bir anahtar varsa, CRL (Certificate Revocation List) yayınlayıp Nginx’e gösterebilirsiniz. Örnek bir yapı şöyle:
# Örnek CRL üretimi (CA tarafında)
# index.txt ve serial dosyalarını kullanan bir openssl.cnf akışı varsayalım
openssl ca -gencrl -keyfile ca.key -cert ca.crt -out ca.crl
# Nginx'e tanıtın
ssl_crl /etc/ssl/ca.crl;
Caddy cephesinde, istemci sertifikası doğrularken CRL kontrolünü doğrudan Caddy’ye değil; arka taraftaki kurumsal PKI sistemine bırakmak daha yaygın bir tercih. Alternatif olarak kısa ömürlü istemci sertifikalarıyla iptal ihtiyacını azaltmak pratik bir yaklaşım oluyor.
Küçük tuzaklar her zaman var. Sunucu adı eşleşmiyorsa SAN alanlarını tekrar kontrol edin. Zincirde bir eksik varsa ara sertifikayı sunucuya tanıtın. Saat farkı yapabilecek kadar büyükse sertifika “henüz geçerli değil” der; üretim makinelerinin saatlerini NTP ile hizalamak bu yüzden önemli. Günün sonunda minik ayrıntılar büyük fark yaratıyor.
Test, Log ve Alarm: “Oldu mu?” Sorusu İçin Pratik Adımlar
mTLS’i test etmenin en hoş yolu, curl ile sertifika takıp denemek. İstemci sertifikasını ve anahtarını verince “ben buyum” diyerek tokalaşıyorsunuz. Örnek bir komut:
curl -v https://api.ornek.local/stok
--cert client.crt --key client.key
--cacert ca.crt
Bir de istemci sertifikası olmadan deneyin; 400 veya 401 gibi bir cevap almanız normal. Nginx debug log’larında $ssl_client_verify değerine bakmak güzel fikir. Caddy’de ise günlük loglar açık ve okunaklı; doğrulama durumunu net görürsünüz.
Loglar demişken, merkezi loglamayı seviyorsanız, uygulama ve reverse proxy loglarını bir araya getirip arama yapmanın rahatlığını bilirsiniz. Bu konuda Grafana Loki + Promtail ile merkezi loglama rehberinde pratik bir akış var; mTLS için kim hangi CN ile giriş yaptı, hata günlerinde hangi servis tökezledi, hepsini tek ekranda görmek içi serinletiyor.
Bir de ağ katmanını ihmal etmeyin. mTLS varken bile gereksiz portları kapatmak, oran sınırlaması koymak ve IPv6’yı düzenli tutmak dayanak noktası. Eğer güvenlik duvarı ayarlarını seviyorsanız, nftables ile VPS güvenlik duvarı rehberindeki örnekler, mTLS’in üstüne güzel bir şemsiye oluyor.
Gerçek Hayattan Küçük Bir Örnek: Yönetim Panelleri ve Sakin Uykular
İçeride en hassas yerlerden biri yönetim panelleri. Erişimi sadece bir avuç ekip üyesine verirsiniz ama yine de içiniz rahat etmez. Ben mTLS’i ilk kez bir yönetim panelinde devreye aldığımda, IP kısıtlarını ve VPN’i bozmadan, üstüne bir kimlik katmanı daha taktım. İçeri sadece bizim ekibin sertifikalarıyla giriliyor, başka hiçbir şeyle değil. Merak edenler için, bu konuda adım adım örneklerin olduğu detaylı bir yazı da var: Yönetim panellerini mTLS ile kale gibi korumak gerçekten uyku kalitesini artırıyor.
Operasyon İpuçları: Dağıtım, Otomasyon ve Küçük Alışkanlıklar
İstemci sertifikalarını dağıtırken basit bir yaşam döngüsü oluşturun. Sertifikaları bir varlık gibi görün; kimde var, ne zaman verilmiş, ne zaman bitecek, iptal durumu ne; hepsi kayıtlı olsun. Küçük bir envanter dosyası, hatta basit bir tablo bile kafi. Dağıtımı Ansible veya CI/CD hattınızla yapmak işleri tatlılaştırıyor.
Sunucu tarafında Caddy kullanıyorsanız sertifika yenileme derdi zaten hafifliyor. Nginx tarafında ise certbot veya başka bir ACME istemcisiyle yenilemeyi cron’a alıp, yenilendiğinde Nginx’i yumuşakça reload etmek yeterli. İstemci sertifikalarında ise “yenisini üret, deploy et, eskisini bir süre daha geçerli tut” yöntemini seviyorum. Böylece geçişte tıkanma yaşanmıyor.
Bir şeyi daha söylemeden geçemeyeceğim: Test ortamı ile üretim ortamını olabildiğince birbirine benzetin. Sertifika zincirleri, CA dosyaları, dosya izinleri, path’ler; hepsi aynı formda durduğunda “testte çalışıyordu” cümlesi daha az duyuluyor.
Sorun Giderme: “El Sıkışamadık” Dediğinde Ne Yapmalı?
Eğer el sıkışma olmuyorsa, üç şeye bakarım. Bir: Hata sayfasında gördüğünüz mesaj ne? Nginx’te 400 bad certificate mi? Bu genellikle CA’nın tanıtılmadığı veya istemci sertifikasının kapsamının (clientAuth) uygun olmadığı anlamına gelir. İki: Sertifikanın tarihi. Saat farkı yüzünden “henüz geçerli değil” veya “süresi dolmuş” sık karşıma çıkar. Üç: Zincir eksikliği. Ara CA’yı sunucuya düzgün vermek gerekiyor.
Komut satırıyla teşhis için iki araç çok işe yarıyor. curl -v ile ayrıntılı günlük alırsınız; openssl s_client -connect ile de sunucunun hangi sertifikayı verdiğini ve zinciri görürsünüz. Caddy tarafında hata günlükleri çoğu zaman neyin eksik olduğunu söyleyecek kadar güzel. Tıkandığınız yerde resmi belgelere dönmek iyi geliyor; Caddy tls yönergeleri ve Nginx SSL modülü sayfası kısa zamanda yol gösteriyor.
Kapanış: mTLS ile İçiniz Rahat, Trafik Akışınız Sakin
Toparlarsak; mTLS’i bir duvar değil, bir kimlik kartı gibi düşünmek işinizi kolaylaştırıyor. Nginx’te veya Caddy’de birkaç ayarla istemciyi doğruluyor, uygulamaya kimlik bilgisini nazikçe taşıyorsunuz. Mikroservisler arası konuşma daha sakin, daha güvenli ve daha izlenebilir hale geliyor. En çok sevdiğim his şu: İçerideki trafiğe baktığımda, kim konuşuyor net; kapıya yaklaşan her isteğin kimliği var ya da yok. Bu netlik operasyonda huzur veriyor.
Pratik birkaç tavsiyeyi bırakayım. Sertifikaları kısa ömürlü yapın, otomasyonu benimseyin, iptal listesini unutmayın. Logları merkezileştirin, alarmları gerçekçi kurun. Sağlık kontrollerini sade tutun, ama güvenliği oradan esnetmeyin. ve mTLS’i tek başına kahraman yapmayın; güvenlik duvarı, oran sınırlama ve uygulama katmanı kontrolleriyle birlikte bir bütün olsun. Umarım bu yazı aklınızdaki soru işaretlerini azaltmıştır. Bir sonraki yazıda görüşmek üzere, gönlünüz ve loglarınız hep ferah kalsın.
