Nginx ile Çoklu Sertifika Otoritesi (Multi-CA) Pinning ve HPKP Yapılandırması

Sertifika güvenliği söz konusu olduğunda çoğu sysadmin “Let’s Encrypt koydum, SSL tamam” diye geçiştiriyor. Ama üretim ortamlarında, özellikle finans, sağlık veya kurumsal uygulamalarda bu yeterli değil. HTTP Public Key Pinning (HPKP) ve Multi-CA pinning tam da burada devreye giriyor. Bugün Nginx üzerinde bu yapılandırmayı gerçek dünya senaryolarıyla ele alacağız.

Temel Kavramları Netleştirelim

Başlamadan önce kafamızdaki kavramları oturtmak lazım.

Public Key Pinning nedir? Bir web sunucusu, tarayıcıya şunu söyler: “Benim sertifikam sadece şu public key’lerden birine sahip olabilir. Başkasını görürsen bağlanma.” Bu mekanizma, sertifika otoritesinin (CA) ele geçirilmesi ya da sahte sertifika düzenlenmesi durumunda kullanıcıyı korur.

HPKP (HTTP Public Key Pinning) ise bu bilgiyi HTTP header’ı aracılığıyla tarayıcıya ileten standarttır. RFC 7469 ile tanımlanmıştır.

Multi-CA Pinning ise birden fazla sertifika otoritesinin public key’ini pinlemek anlamına gelir. Neden gerekli? Çünkü tek bir CA’ya bağlı kalmak operasyonel risk yaratır. Birincil CA’nız bir sorun yaşarsa yedek CA’nıza geçiş yapabilmek için o CA’nın pin’ini de önceden tanımlamış olmanız gerekir.

Önemli Uyarı: HPKP, yanlış yapılandırıldığında sitenizi tamamen erişilemez hale getirebilir. Google Chrome 67’den itibaren bu header’ı desteklemeyi kaldırdı, ancak Firefox hala destekliyor ve özellikle API endpoint’leri ile mobil uygulamalarda hala kritik bir role sahip. Kurumsal ortamlarda ise custom certificate stores ile birlikte kullanılmaya devam ediliyor.

Gereksinimler ve Ortam Hazırlığı

Elimizde şunlar olduğunu varsayalım:

  • Ubuntu 22.04 LTS üzerinde Nginx 1.24+
  • Let’s Encrypt sertifikası (birincil CA: ISRG Root X1)
  • Yedek olarak DigiCert sertifikası (ikincil CA)
  • Bir production domain: uygulama.sirket.com
# Nginx versiyonunu kontrol et
nginx -v

# OpenSSL versiyonunu kontrol et
openssl version

# Gerekli araçları yükle
apt-get install -y openssl curl python3-cryptography

Public Key Hash’lerini Hesaplamak

HPKP’nin kalbi burada. Her pin, bir sertifikanın ya da CA sertifikasının public key’inin SHA-256 hash’idir ve Base64 ile encode edilmiştir. Bu hash’i hesaplamak için birkaç farklı yöntem var.

Mevcut Sertifikadan Hash Hesaplama

# Sertifika dosyasından pin hesapla
openssl x509 -in /etc/letsencrypt/live/uygulama.sirket.com/cert.pem 
  -pubkey -noout | 
  openssl pkey -pubin -outform der | 
  openssl dgst -sha256 -binary | 
  base64

# Chain sertifikasından (intermediate CA) pin hesapla
openssl x509 -in /etc/letsencrypt/live/uygulama.sirket.com/chain.pem 
  -pubkey -noout | 
  openssl pkey -pubin -outform der | 
  openssl dgst -sha256 -binary | 
  base64

Canlı Sunucudan Hash Hesaplama

# Uzaktaki sunucunun sertifikasından pin hesapla
echo | openssl s_client -servername uygulama.sirket.com 
  -connect uygulama.sirket.com:443 2>/dev/null | 
  openssl x509 -pubkey -noout | 
  openssl pkey -pubin -outform der | 
  openssl dgst -sha256 -binary | 
  base64

# Intermediate CA pin'ini de al
echo | openssl s_client -servername uygulama.sirket.com 
  -connect uygulama.sirket.com:443 -showcerts 2>/dev/null | 
  awk '/-----BEGIN CERTIFICATE-----/{i++}i==2' | 
  openssl x509 -pubkey -noout | 
  openssl pkey -pubin -outform der | 
  openssl dgst -sha256 -binary | 
  base64

CSR’dan Backup Pin Oluşturma

Henüz kullanmadığınız bir yedek anahtar çifti için pin hesaplıyorsunuz. Bu kritik çünkü HPKP’nin temel gereksinimlerinden biri en az bir backup pin bulundurmanızdır.

# Yedek RSA private key oluştur
openssl genrsa -out /etc/ssl/private/backup-key.pem 4096

# Bu anahtardan CSR oluştur (henüz imzalamana gerek yok)
openssl req -new -key /etc/ssl/private/backup-key.pem 
  -out /etc/ssl/certs/backup.csr 
  -subj "/CN=uygulama.sirket.com"

# CSR'dan public key hash'ini hesapla
openssl req -in /etc/ssl/certs/backup.csr 
  -pubkey -noout | 
  openssl pkey -pubin -outform der | 
  openssl dgst -sha256 -binary | 
  base64

Çıktı olarak şuna benzer bir değer alırsınız:

abc123XYZexample+hash/değeri=

Bu değerleri bir yere not edin, Nginx yapılandırmasında kullanacağız.

Nginx HPKP Yapılandırması

Şimdi asıl iş kısmına geldik. Tüm pin değerlerini topladıktan sonra Nginx konfigürasyonumuzu yazabiliriz.

server {
    listen 443 ssl http2;
    server_name uygulama.sirket.com;

    # SSL sertifika dosyaları
    ssl_certificate /etc/letsencrypt/live/uygulama.sirket.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/uygulama.sirket.com/privkey.pem;

    # Modern SSL yapılandırması
    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;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # HSTS - HPKP'den önce HSTS aktif olmalı
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Multi-CA HPKP Header
    # pin-sha256 değerleri: 
    # 1. Mevcut sertifikanın public key hash'i
    # 2. Let's Encrypt Intermediate CA hash'i  
    # 3. DigiCert Intermediate CA hash'i (backup CA)
    # 4. Yedek private key hash'i (acil durum için)
    add_header Public-Key-Pins '
        pin-sha256="MEVCUT_SERTIFIKA_HASH_DEGERI=";
        pin-sha256="LETSENCRYPT_INTERMEDIATE_HASH=";
        pin-sha256="DIGICERT_INTERMEDIATE_HASH=";
        pin-sha256="YEDEK_ANAHTAR_HASH_DEGERI=";
        max-age=2592000;
        includeSubDomains;
        report-uri="https://uygulama.sirket.com/hpkp-report"
    ' always;

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Report-Only Modu ile Test Etme

Yeni bir ortamda direkt olarak HPKP aktif etmek intihar gibi bir harekettir. Önce report-only modunu kullanın. Bu mod, ihlalleri raporlar ama bağlantıyı engellemez.

server {
    listen 443 ssl http2;
    server_name uygulama.sirket.com;

    ssl_certificate /etc/letsencrypt/live/uygulama.sirket.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/uygulama.sirket.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;

    # HPKP Report-Only modu - sadece raporlar, engellemez
    add_header Public-Key-Pins-Report-Only '
        pin-sha256="MEVCUT_SERTIFIKA_HASH_DEGERI=";
        pin-sha256="YEDEK_ANAHTAR_HASH_DEGERI=";
        max-age=86400;
        report-uri="https://uygulama.sirket.com/hpkp-report"
    ' always;

    # Report endpoint için location bloğu
    location /hpkp-report {
        # POST metoduna izin ver
        limit_except POST {
            deny all;
        }
        
        # Raporları dosyaya yaz
        access_log /var/log/nginx/hpkp-violations.log;
        
        # Basit bir 204 response dön
        return 204;
    }

    location / {
        proxy_pass http://localhost:8080;
    }
}

Violation raporları JSON formatında gelir ve şuna benzer:

{
  "date-time": "2024-01-15T10:30:00Z",
  "hostname": "uygulama.sirket.com",
  "port": 443,
  "effective-expiration-date": "2024-02-15T10:30:00Z",
  "include-subdomains": true,
  "noted-hostname": "uygulama.sirket.com",
  "served-certificate-chain": ["..."],
  "validated-certificate-chain": ["..."],
  "known-pins": ["pin-sha256=..."]
}

Gerçek Dünya Senaryosu: Let’s Encrypt’ten DigiCert’e Geçiş

En çok sorulan soru şu: “Sertifika yenileme zamanı geldiğinde ne olacak?” İşte tam senaryo:

Şirketiniz Let’s Encrypt kullanıyor, ancak compliance gereksinimleri nedeniyle DigiCert’e geçmeniz gerekiyor. HPKP olmadan bu geçiş sorunsuz. HPKP varsa, önceden hazırlık yapmazssanız siteniz erişilemez hale gelir.

Doğru yaklaşım şu adımları izler:

Adım 1: DigiCert sertifikasını satın al ve özel anahtarı oluştur, ama henüz yayına alma.

Adım 2: DigiCert intermediate CA’nın pin değerini hesapla ve mevcut HPKP header’ına ekle. max-age değerini düşük tut (örneğin 86400 saniye = 1 gün).

# DigiCert Intermediate CA'yı indir
curl -O https://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt

# Hash hesapla
openssl x509 -in DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt 
  -pubkey -noout | 
  openssl pkey -pubin -outform der | 
  openssl dgst -sha256 -binary | 
  base64

Adım 3: Güncellenmiş HPKP header’ı ile birkaç gün bekle. Tüm cached pin’lerin expire olması için yeterli süre geçsin.

Adım 4: Sertifika geçişini yap.

Monitoring ve Pin Doğrulama Scripti

Pin değerlerinin doğru çalışıp çalışmadığını düzenli olarak kontrol etmeniz gerekiyor. Bunun için bir bash scripti yazalım:

#!/bin/bash
# /usr/local/bin/check-hpkp-pins.sh
# HPKP pin'lerini doğrulayan monitoring scripti

DOMAIN="uygulama.sirket.com"
ALERT_EMAIL="[email protected]"
LOG_FILE="/var/log/hpkp-check.log"

# Renk kodları
RED='33[0;31m'
GREEN='33[0;32m'
YELLOW='33[1;33m'
NC='33[0m'

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Sunucudaki mevcut sertifikanın pin'ini hesapla
get_current_pin() {
    echo | openssl s_client -servername "$DOMAIN" 
        -connect "$DOMAIN:443" 2>/dev/null | 
        openssl x509 -pubkey -noout | 
        openssl pkey -pubin -outform der | 
        openssl dgst -sha256 -binary | 
        base64
}

# HPKP header'ından pin'leri çek
get_header_pins() {
    curl -sI "https://$DOMAIN" | 
        grep -i "public-key-pins" | 
        grep -oP 'pin-sha256="[^"]*"' | 
        sed 's/pin-sha256="//;s/"//'
}

# Sertifikanın kaç gün sonra expire olacağını hesapla
check_cert_expiry() {
    expiry_date=$(echo | openssl s_client -servername "$DOMAIN" 
        -connect "$DOMAIN:443" 2>/dev/null | 
        openssl x509 -noout -enddate | 
        cut -d= -f2)
    
    expiry_epoch=$(date -d "$expiry_date" +%s)
    current_epoch=$(date +%s)
    days_left=$(( (expiry_epoch - current_epoch) / 86400 ))
    
    echo "$days_left"
}

# Ana kontrol
log "HPKP pin kontrolü başlıyor: $DOMAIN"

CURRENT_PIN=$(get_current_pin)
log "Mevcut sertifika pin'i: $CURRENT_PIN"

# Header'daki pin'lerle karşılaştır
PIN_FOUND=false
while IFS= read -r header_pin; do
    if [ "$header_pin" = "$CURRENT_PIN" ]; then
        PIN_FOUND=true
        log "${GREEN}[OK]${NC} Mevcut pin, HPKP header'ında bulundu."
        break
    fi
done <<< "$(get_header_pins)"

if [ "$PIN_FOUND" = false ]; then
    log "${RED}[KRITIK]${NC} Mevcut sertifika pin'i HPKP header'ında YOK!"
    echo "KRITIK: $DOMAIN için HPKP pin uyumsuzluğu tespit edildi!" | 
        mail -s "HPKP PIN UYUMSUZLUĞU - $DOMAIN" "$ALERT_EMAIL"
fi

# Sertifika süresini kontrol et
DAYS_LEFT=$(check_cert_expiry)
log "Sertifika süresi: $DAYS_LEFT gün kaldı"

if [ "$DAYS_LEFT" -lt 30 ]; then
    log "${YELLOW}[UYARI]${NC} Sertifika 30 günden az sürede expire olacak!"
    echo "UYARI: $DOMAIN sertifikasının süresi $DAYS_LEFT gün içinde doluyor." | 
        mail -s "Sertifika Süresi Uyarısı - $DOMAIN" "$ALERT_EMAIL"
fi

log "Kontrol tamamlandı."

Bu scripti cron’a ekleyin:

chmod +x /usr/local/bin/check-hpkp-pins.sh

# Crontab'a ekle - her gün saat 09:00'da çalışsın
echo "0 9 * * * root /usr/local/bin/check-hpkp-pins.sh" > /etc/cron.d/hpkp-check

Nginx Upstream Proxy Senaryosunda Multi-CA

Yük dengeleyici arkasında birden fazla uygulama sunucunuz varsa yapılandırma biraz farklılaşır:

# /etc/nginx/conf.d/multi-ca-upstream.conf

# Ortak SSL güvenlik ayarları için map
map $scheme $hpkp_header {
    https '
        pin-sha256="BIRINCIL_CA_HASH=";
        pin-sha256="IKINCIL_CA_HASH=";
        pin-sha256="BACKUP_KEY_HASH=";
        max-age=2592000;
        includeSubDomains
    ';
}

upstream app_backend {
    least_conn;
    server 10.0.1.10:8080 weight=3;
    server 10.0.1.11:8080 weight=3;
    server 10.0.1.12:8080 backup;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name uygulama.sirket.com api.sirket.com;

    ssl_certificate /etc/ssl/certs/sirket-wildcard.pem;
    ssl_certificate_key /etc/ssl/private/sirket-wildcard.key;

    # OCSP Stapling - HPKP ile birlikte kullanılması önerilen güvenlik önlemi
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/ssl/certs/ca-chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header Public-Key-Pins $hpkp_header always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

Sık Yapılan Hatalar ve Çözümleri

Tek pin ile yapılandırma: RFC 7469 en az iki pin gerektiriyor. Biri mevcut, biri backup olmalı. Tek pin tanımlarsanız tarayıcılar bu header’ı dikkate almaz.

max-age değerini çok yüksek tutmak: Yeni başlayanlar için max-age değerini 86400 (1 gün) ile başlatın. Her şey yolunda gidince 2592000 (30 gün) ve ardından 63072000 (2 yıl)’a çıkarın.

Backup key’i güvenli saklamama: Yedek private key’inizi şifreli olarak ve birden fazla lokasyonda saklayın. Bu anahtarı kaybederseniz ve birincil sertifikanızda sorun olursa site tamamen erişilemez hale gelir.

includeSubDomains kullanımında dikkat: Alt domain’lerinizden birinde farklı bir CA kullanıyorsanız includeSubDomains direktifini kullanmayın. Sadece ana domain için pinleme yapın.

Yapılandırmayı Test Etme

# HPKP header'larını kontrol et
curl -I https://uygulama.sirket.com 2>/dev/null | grep -i "public-key-pins"

# SSL Labs'dan detaylı rapor al (API kullanımı)
curl "https://api.ssllabs.com/api/v3/analyze?host=uygulama.sirket.com&all=done" | 
  python3 -m json.tool | grep -A5 "hpkp"

# Yerel test için openssl
echo | openssl s_client -connect uygulama.sirket.com:443 -servername uygulama.sirket.com 2>/dev/null | 
  openssl x509 -noout -text | grep -A2 "Subject:"

Sonuç

HPKP ve Multi-CA pinning, doğru uygulandığında güçlü bir güvenlik katmanı sunar. Ancak bu güç, beraberinde operasyonel sorumluluk getirir. Yanlış yapılandırılmış bir HPKP, sitenizi aylar boyunca erişilemez bırakabilir ve bunu geri almanın da kolay yolu yoktur.

Pratik önerilerim şunlar: Her zaman report-only modundan başlayın, en az 2-3 hafta violation raporlarını izleyin, backup key’lerinizi birden fazla güvenli lokasyonda tutun, max-age değerini kademeli olarak artırın ve sertifika yenileme prosedürünüze HPKP pin güncellemesini de dahil edin.

Kurumsal ortamlarda özellikle API gateway’ler, mobil uygulama backend’leri ve fintech sistemlerinde Multi-CA pinning hala aktif olarak kullanılıyor ve kullanılmaya devam edecek. Chrome’un desteği kaldırması bu teknolojiyi öldürmedi, sadece use case’ini daralttı. Siz de sisteminizin gerçek gereksinimlerine göre bu yapılandırmayı adapt edin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir