Mutual TLS (mTLS) Nedir ve Nasıl Uygulanır?

Güvenli iletişim kurmanın en sağlam yollarından biri olan mTLS, standart TLS’in bir adım ötesine geçerek hem sunucunun hem de istemcinin kimliğini doğrular. Klasik HTTPS bağlantılarında yalnızca sunucu kendini kanıtlamak zorundayken, mTLS’de istemci de sertifikasını sunmak durumundadır. Bu basit ama güçlü mekanizma, özellikle mikroservis mimarileri, API güvenliği ve sıfır güven (zero trust) ağ modellerinde kritik bir rol üstlenir. Eğer “sadece API anahtarı yeterli değil mi?” diye soruyorsan, bu yazıyı okuduktan sonra fikrini değiştirebilirsin.

mTLS Nasıl Çalışır?

Standart TLS握手sürecini hatırlayalım: İstemci sunucuya bağlanır, sunucu sertifikasını gösterir, istemci bunu doğrular ve şifreli kanal kurulur. mTLS’de el sıkışma süreci birkaç adım daha içerir.

Standart TLS akışı:

  • İstemci ClientHello mesajı gönderir
  • Sunucu sertifikasını paylaşır
  • İstemci sertifikayı doğrular
  • Şifreli bağlantı başlar

mTLS akışı:

  • İstemci ClientHello mesajı gönderir
  • Sunucu sertifikasını paylaşır ve istemciden de sertifika talep eder (CertificateRequest)
  • İstemci kendi sertifikasını sunar
  • Sunucu, istemci sertifikasını kendi CA’sına (Certificate Authority) göre doğrular
  • Her iki taraf da kimliğini kanıtladıktan sonra şifreli kanal kurulur

Bu süreçte kilit nokta şudur: Her iki tarafın sertifikaları da aynı CA tarafından imzalanmış olmak zorundadır ya da her iki CA birbirini tanımak durumundadır. Bunu bir anlamda özel kulüp kartı sistemi gibi düşünebilirsin; kapıya gelirken hem kapıcı sana kimliğini gösterir hem de sen kapıcıya.

Gereksinimler ve Ortam Hazırlığı

Uygulamaya geçmeden önce ihtiyacımız olanları listeleyelim:

  • CA sertifikası ve özel anahtarı (kendi CA’nı oluşturacağız)
  • Sunucu sertifikası (CA tarafından imzalanmış)
  • İstemci sertifikası (CA tarafından imzalanmış)
  • OpenSSL (paket yöneticisiyle kolayca kurulabilir)
  • Nginx veya diğer bir web sunucusu (test ortamı için)
# Ubuntu/Debian
sudo apt update && sudo apt install openssl nginx -y

# RHEL/CentOS/Rocky Linux
sudo dnf install openssl nginx -y

# OpenSSL versiyonunu kontrol et
openssl version -a

Adım Adım Kendi CA’nı Oluşturma

Üretim ortamlarında genellikle kurumsal bir CA (HashiCorp Vault, CFSSL, ya da ticari bir CA) kullanılır. Ancak dahili mikroservis iletişimi veya test ortamları için kendi CA’nı oluşturmak hem pratik hem de öğretici.

# Çalışma dizinini oluştur
mkdir -p ~/mtls-lab/{ca,server,client}
cd ~/mtls-lab

# CA özel anahtarını oluştur (4096 bit RSA)
openssl genrsa -aes256 -out ca/ca.key 4096

# CA sertifikasını oluştur (10 yıl geçerli)
openssl req -new -x509 -days 3650 -key ca/ca.key 
  -out ca/ca.crt 
  -subj "/C=TR/ST=Istanbul/L=Istanbul/O=MyCompany CA/CN=MyCompany Root CA"

# Oluşturulan CA sertifikasını kontrol et
openssl x509 -in ca/ca.crt -text -noout | head -40

Burada -aes256 parametresi CA anahtarını parola ile şifreler. Üretim ortamında bu parolayı güvenli bir kasada (HashiCorp Vault, AWS Secrets Manager vb.) saklamalısın. Test ortamında kolaylık için parolasız anahtar da oluşturabilirsin, ama bu alışkanlığı üretime taşıma.

Sunucu Sertifikası Oluşturma

# Sunucu özel anahtarı
openssl genrsa -out server/server.key 2048

# Sunucu için CSR (Certificate Signing Request) oluştur
# SAN (Subject Alternative Names) eklemek önemli, modern tarayıcılar bunu zorunlu tutar
cat > server/server.cnf <<EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[dn]
C=TR
ST=Istanbul
L=Istanbul
O=MyCompany
CN=api.mycompany.local

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = api.mycompany.local
DNS.2 = localhost
IP.1 = 127.0.0.1
EOF

# CSR oluştur
openssl req -new -key server/server.key 
  -out server/server.csr 
  -config server/server.cnf

# CA ile sunucu sertifikasını imzala
openssl x509 -req -days 365 
  -in server/server.csr 
  -CA ca/ca.crt 
  -CAkey ca/ca.key 
  -CAcreateserial 
  -out server/server.crt 
  -extensions v3_req 
  -extfile server/server.cnf

# Doğrula
openssl verify -CAfile ca/ca.crt server/server.crt

İstemci Sertifikası Oluşturma

İstemci sertifikaları, sunucu sertifikalarından yapısal olarak farklı değildir. Ancak Extended Key Usage alanında clientAuth olması gerekir.

# İstemci özel anahtarı
openssl genrsa -out client/client.key 2048

# İstemci CSR'ı
cat > client/client.cnf <<EOF
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[dn]
C=TR
ST=Istanbul
L=Istanbul
O=MyCompany
CN=service-a.mycompany.local

[v3_req]
extendedKeyUsage = clientAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = service-a.mycompany.local
EOF

openssl req -new -key client/client.key 
  -out client/client.csr 
  -config client/client.cnf

# CA ile istemci sertifikasını imzala
openssl x509 -req -days 365 
  -in client/client.csr 
  -CA ca/ca.crt 
  -CAkey ca/ca.key 
  -CAcreateserial 
  -out client/client.crt 
  -extensions v3_req 
  -extfile client/client.cnf

# İstemci sertifikasını doğrula
openssl verify -CAfile ca/ca.crt client/client.crt

Nginx ile mTLS Yapılandırması

Artık sertifikalarımız hazır. Nginx’i mTLS destekleyecek şekilde yapılandıralım.

# /etc/nginx/sites-available/mtls-api
server {
    listen 443 ssl;
    server_name api.mycompany.local;

    # Sunucu sertifikası ve anahtarı
    ssl_certificate     /root/mtls-lab/server/server.crt;
    ssl_certificate_key /root/mtls-lab/server/server.key;

    # CA sertifikası - istemci doğrulama için
    ssl_client_certificate /root/mtls-lab/ca/ca.crt;

    # mTLS zorunlu hale getirilir (on = zorunlu, optional = isteğe bağlı)
    ssl_verify_client on;
    ssl_verify_depth 2;

    # Modern TLS ayarları
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # OCSP Stapling (üretim için önerilen)
    ssl_stapling on;
    ssl_stapling_verify on;

    location / {
        # İstemci sertifika bilgilerini backend'e ilet
        proxy_set_header X-SSL-Client-CN   $ssl_client_s_dn_cn;
        proxy_set_header X-SSL-Client-DN   $ssl_client_s_dn;
        proxy_set_header X-SSL-Verify      $ssl_client_verify;
        
        proxy_pass http://localhost:8080;
    }

    # Sertifika olmadan bağlananlar için özel hata sayfası
    error_page 495 496 /ssl_error.html;
    location /ssl_error.html {
        return 403 "Client certificate required";
    }
}
# Nginx yapılandırmasını test et ve uygula
sudo nginx -t
sudo systemctl reload nginx

mTLS Bağlantısını Test Etme

Sertifikaların ve yapılandırmanın düzgün çalışıp çalışmadığını test edelim.

# Sertifikasız bağlantı (başarısız olmalı)
curl -v --cacert ~/mtls-lab/ca/ca.crt 
  https://api.mycompany.local/

# Sertifikalı bağlantı (başarılı olmalı)
curl -v 
  --cacert ~/mtls-lab/ca/ca.crt 
  --cert ~/mtls-lab/client/client.crt 
  --key ~/mtls-lab/client/client.key 
  https://api.mycompany.local/

# OpenSSL ile daha detaylı el sıkışma takibi
openssl s_client 
  -connect api.mycompany.local:443 
  -CAfile ~/mtls-lab/ca/ca.crt 
  -cert ~/mtls-lab/client/client.crt 
  -key ~/mtls-lab/client/client.key 
  -state -debug 2>&1 | grep -E "SSL_connect|Verify|Certificate"

İlk curl komutu 400 Bad Request ya da SSL handshake failed döndürmeli. İkincisi ise 200 OK ile yanıt vermelidir. Bu tam olarak beklediğimiz davranış.

Gerçek Dünya Senaryosu: Mikroservis İletişimi

Diyelim ki bir e-ticaret platformun var. order-service, payment-service‘i çağırıyor. Bu iki servis arasındaki iletişimi mTLS ile güvenli hale getirelim.

Her servise kendi istemci/sunucu sertifika çifti verilir:

# payment-service için sertifika
openssl genrsa -out payment-service.key 2048
openssl req -new -key payment-service.key 
  -subj "/CN=payment-service/O=MyCompany" 
  -out payment-service.csr

openssl x509 -req -days 365 
  -in payment-service.csr 
  -CA ca/ca.crt 
  -CAkey ca/ca.key 
  -CAcreateserial 
  -out payment-service.crt

# order-service için istemci sertifikası
openssl genrsa -out order-service.key 2048
openssl req -new -key order-service.key 
  -subj "/CN=order-service/O=MyCompany" 
  -out order-service.csr

openssl x509 -req -days 365 
  -in order-service.csr 
  -CA ca/ca.crt 
  -CAkey ca/ca.key 
  -CAcreateserial 
  -out order-service.crt

Python ile basit bir mTLS istemci örneği:

import requests

# order-service'in payment-service'i çağırması
response = requests.get(
    'https://payment-service.internal:8443/process',
    cert=('/etc/certs/order-service.crt', '/etc/certs/order-service.key'),
    verify='/etc/certs/ca.crt',
    timeout=10
)
print(f"Status: {response.status_code}")

Bu yaklaşımda CN alanı servis kimliği olarak kullanılır. Nginx tarafındaki $ssl_client_s_dn_cn değişkeni order-service döndüreceğinden backend uygulamanı hangi servisin bağlandığını anlayabilir ve buna göre yetkilendirme kararları verebilirsin.

Sertifika İptal (CRL ve OCSP)

mTLS’in zafiyetlerinden biri, bir sertifika çalındığında ya da güvenliği ihlal edildiğinde nasıl iptal edeceğini bilmektir. İki temel yöntem vardır:

CRL (Certificate Revocation List): Periyodik olarak güncellenen iptal listesi. Nginx ile kullanımı:

# CRL oluştur
openssl ca -config openssl.cnf -gencrl -out ca/ca.crl

# Nginx yapılandırmasına ekle
# ssl_crl /root/mtls-lab/ca/ca.crl;

# Belirli bir sertifikayı iptal et
openssl ca -config openssl.cnf 
  -revoke client/client.crt 
  -keyfile ca/ca.key 
  -cert ca/ca.crt

# CRL'i yenile
openssl ca -config openssl.cnf -gencrl -out ca/ca.crl

# CRL içeriğini görüntüle
openssl crl -in ca/ca.crl -text -noout

OCSP (Online Certificate Status Protocol): Gerçek zamanlı sertifika durum sorgulama. CRL’e göre daha güncel bilgi sağlar ancak OCSP responder kurulumu gerektirir.

Üretim ortamları için OCSP genellikle tercih edilir çünkü CRL dosyaları zamanla büyüyebilir ve büyük listeyi kontrol etmek gecikmelere neden olabilir.

HashiCorp Vault ile mTLS Sertifika Otomasyonu

Manuel sertifika yönetimi küçük ölçekli ortamlar için işe yarasa da onlarca mikroservisin olduğu bir ortamda sertifika yenileme kabusu haline gelir. HashiCorp Vault’un PKI Secret Engine’i bu sorunu çözer.

# Vault PKI engine etkinleştir
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki

# Root CA oluştur
vault write -field=certificate pki/root/generate/internal 
  common_name="MyCompany Root CA" 
  ttl=87600h > /tmp/root_ca.crt

# Intermediate CA için
vault secrets enable -path=pki_int pki
vault write -format=json pki_int/intermediate/generate/internal 
  common_name="MyCompany Intermediate CA" 
  | jq -r '.data.csr' > /tmp/pki_int.csr

# CSR'ı root CA ile imzala
vault write -format=json pki/root/sign-intermediate 
  csr=@/tmp/pki_int.csr 
  format=pem_bundle ttl="43800h" 
  | jq -r '.data.certificate' > /tmp/intermediate.cert.pem

vault write pki_int/intermediate/set-signed 
  certificate=@/tmp/intermediate.cert.pem

# Servis sertifikaları için rol oluştur
vault write pki_int/roles/microservices 
  allowed_domains="mycompany.internal" 
  allow_subdomains=true 
  max_ttl="720h" 
  client_flag=true 
  server_flag=true

# Servis için sertifika iste
vault write pki_int/issue/microservices 
  common_name="payment-service.mycompany.internal" 
  ttl="24h"

Bu yaklaşımla sertifika süreleri çok kısa tutulabilir (24 saat, hatta birkaç saat), bu da güvenlik açısından büyük avantaj sağlar. Bir sertifika çalınsa bile birkaç saat içinde geçerliliğini yitirir.

Kubernetes’te mTLS: Service Mesh Yaklaşımı

Kubernetes ortamlarında mTLS’i manuel yönetmek oldukça zahmetlidir. Bu yüzden Istio, Linkerd gibi service mesh çözümleri popülerlik kazanmıştır.

Istio ile mTLS’i tüm namespace için zorunlu kılmak şu kadar basittir:

# strict-mtls-policy.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: production
spec:
  mtls:
    mode: STRICT
---
# Belirli bir servis için AuthorizationPolicy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: payment-service-policy
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  rules:
  - from:
    - source:
        principals:
          - "cluster.local/ns/production/sa/order-service"
  - to:
    - operation:
        methods: ["POST"]
        paths: ["/process", "/refund"]
# Politikayı uygula
kubectl apply -f strict-mtls-policy.yaml

# mTLS durumunu kontrol et
istioctl x describe pod payment-service-xxx-yyy -n production

Istio bu durumda sertifika yönetimini tamamen üstlenir. SPIFFE/SPIRE standardını kullanarak her pod’a otomatik olarak kısa ömürlü sertifikalar dağıtır ve yeniler.

Sık Karşılaşılan Sorunlar ve Çözümleri

“SSL handshake failed” hatası:

  • İstemci sertifikasının CA tarafından imzalanıp imzalanmadığını kontrol et: openssl verify -CAfile ca.crt client.crt
  • Sertifika süresinin dolmadığından emin ol: openssl x509 -in client.crt -noout -dates
  • ssl_verify_depth değerinin zincir uzunluğunu karşılayıp karşılamadığını kontrol et

“certificate has expired” hatası:

# Tüm sertifika sürelerini toplu kontrol
for cert in ~/mtls-lab/**/*.crt; do
  echo "=== $cert ==="
  openssl x509 -in "$cert" -noout -dates
done

CN doğrulama sorunu: Modern OpenSSL sürümleri CN doğrulama yerine SAN (Subject Alternative Names) doğrulaması yapar. Sertifika oluştururken mutlaka SAN eklediğinden emin ol.

Performans kaygıları: mTLS, ek el sıkışma adımları nedeniyle standart TLS’e göre biraz daha yavaştır. Ancak bu fark pratikte milisaniye mertebesindedir ve ssl_session_cache ile ssl_session_timeout değerlerini ayarlayarak oturum yeniden kullanımını (session resumption) etkinleştirerek minimize edebilirsin.

Güvenlik Kontrol Listesi

Uygulamayı tamamlamadan önce şu maddeleri gözden geçir:

  • Özel anahtarlar hiçbir zaman ağ üzerinden şifresiz iletilmemeli
  • Sertifika süreleri mümkün olduğunca kısa tutulmalı (üretimde 90 gün, mikroservisler için 24-72 saat)
  • CA özel anahtarı üretim sistemiyle aynı makinede bulunmamalı, ideal olarak bir HSM (Hardware Security Module) ya da Vault’ta saklanmalı
  • CRL veya OCSP mekanizması mutlaka devreye alınmalı
  • Wildcard sertifikası kullanmaktan kaçın mTLS ortamlarında; her servis için ayrı sertifika daha güvenli
  • Sertifika izleme için alerting kur, sertifika süresi dolmadan en az 30 gün önce uyarı al
  • ssl_protocols direktifinde TLSv1.0 ve TLSv1.1’i devre dışı bırak
  • Tüm sertifika operasyonlarını logla ve denetle

Sonuç

mTLS, özellikle sıfır güven mimarisine geçiş yapan organizasyonlar için vazgeçilmez bir güvenlik katmanıdır. Temel prensibi basittir: “Güven, ama doğrula” yerine “Önce doğrula, sonra güven.” Hem sunucu hem istemci kimlik doğrulaması yaparak geleneksel çevre güvenliğinin (perimeter security) ötesine geçersin.

Küçük ölçekli ortamlarda OpenSSL ve Nginx kombinasyonu yeterli olabilir. Ancak onlarca ya da yüzlerce mikroservisin bulunduğu ortamlarda HashiCorp Vault gibi bir PKI altyapısı ya da Istio gibi bir service mesh çözümü tercih etmelisin. Manuel sertifika yönetimi ölçeklenebilir değildir ve insan hatası riskini beraberinde getirir.

Bu yazıdaki adımları takip ederek lab ortamında mTLS’i deneyimleyebilirsin. Üretim ortamına taşımadan önce sertifika iptal mekanizmasını, izleme altyapısını ve otomatik yenileme süreçlerini mutlaka yerleştir. Güvenliğin en zayıf halkası çoğu zaman teknik eksiklik değil, operasyonel süreçlerdeki boşluklardır.

Yorum yapın