Veritabanı sunucularına olan uzak bağlantılar, doğru yapılandırılmadığında ciddi güvenlik açıkları doğurur. Özellikle MySQL sunucunuz farklı ağ segmentlerindeki uygulama sunucularıyla konuşuyorsa, bu trafiğin şifrelenmemesi sizi man-in-the-middle saldırılarına, kimlik bilgisi çalmaya ve veri sızıntısına açık hale getirir. Bu yazıda MySQL SSL yapılandırmasını baştan sona, gerçek dünya senaryolarıyla ele alacağız.
Neden SSL/TLS Şifrelemesi Zorunlu?
Çoğu sysadmin MySQL’i kurar, bind-address ayarını yapar ve işi bitmiş sayar. Ama şöyle düşün: uygulama sunucun ile veritabanı sunucun aynı veri merkezinde olsa bile, aynı switch üzerindeki başka bir makine ele geçirildiğinde o iç ağ trafiği dinlenebilir. ARP spoofing saldırısıyla ağ trafiğini yönlendirip açık metin MySQL paketlerini yakalamak, doğru araçlarla birkaç dakika meselesidir.
Bunu bir kez yaşadıktan sonra SSL atlamak artık bir seçenek olmaktan çıkıyor. PCI-DSS, HIPAA gibi uyumluluk standartları da veritabanı bağlantılarında şifrelemeyi zorunlu kılıyor.
Sertifika Altyapısını Hazırlamak
Ticari bir CA’ya para vermene gerek yok. MySQL için kendi Certificate Authority’ni oluşturacağız. Bu yapıda üç ayrı sertifika setine ihtiyacımız var:
- CA sertifikası: Hem sunucu hem istemci sertifikalarını imzalar
- Sunucu sertifikası: MySQL sunucusunun kimliğini kanıtlar
- İstemci sertifikası: İstemcinin sunucuya kimliğini kanıtlar (mutual TLS)
Sertifikaları üretmek için bir dizin oluşturup işe başlayalım:
mkdir -p /etc/mysql/ssl
cd /etc/mysql/ssl
# CA anahtar ve sertifikası
openssl genrsa 4096 > ca-key.pem
openssl req -new -x509 -nodes -days 3650
-key ca-key.pem
-out ca-cert.pem
-subj "/C=TR/ST=Istanbul/L=Istanbul/O=SirketAdi/CN=MySQL-CA"
# Sunucu anahtarı ve CSR
openssl req -newkey rsa:4096 -nodes
-keyout server-key.pem
-out server-req.pem
-subj "/C=TR/ST=Istanbul/L=Istanbul/O=SirketAdi/CN=db.sirket.local"
# Sunucu sertifikasını CA ile imzala
openssl x509 -req -days 3650
-in server-req.pem
-CA ca-cert.pem
-CAkey ca-key.pem
-CAcreateserial
-out server-cert.pem
# İstemci anahtarı ve CSR
openssl req -newkey rsa:4096 -nodes
-keyout client-key.pem
-out client-req.pem
-subj "/C=TR/ST=Istanbul/L=Istanbul/O=SirketAdi/CN=app-client"
# İstemci sertifikasını CA ile imzala
openssl x509 -req -days 3650
-in client-req.pem
-CA ca-cert.pem
-CAkey ca-key.pem
-CAcreateserial
-out client-cert.pem
Sertifikaları doğrulayalım, özellikle CN alanlarının doğru olduğundan emin ol:
openssl verify -CAfile ca-cert.pem server-cert.pem
openssl verify -CAfile ca-cert.pem client-cert.pem
# Sertifika detaylarını görüntüle
openssl x509 -in server-cert.pem -text -noout | grep -A 2 "Subject:"
openssl x509 -in server-cert.pem -text -noout | grep "Not After"
Şimdi dosya izinlerini ayarlayalım. Bu adımı atlarsan MySQL başlamayı reddedebilir:
chown -R mysql:mysql /etc/mysql/ssl/
chmod 750 /etc/mysql/ssl/
chmod 640 /etc/mysql/ssl/*.pem
chmod 600 /etc/mysql/ssl/server-key.pem
chmod 600 /etc/mysql/ssl/ca-key.pem
chmod 600 /etc/mysql/ssl/client-key.pem
MySQL Sunucu Yapılandırması
/etc/mysql/mysql.conf.d/mysqld.cnf veya /etc/my.cnf dosyasını düzenle (dağıtıma göre değişir):
[mysqld]
# SSL/TLS yapılandırması
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
# Minimum TLS versiyonu - TLS 1.2'nin altını kabul etme
tls_version=TLSv1.2,TLSv1.3
# Cipher suite kısıtlaması (isteğe bağlı ama önerilen)
ssl-cipher=ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
# Sunucuyu yeniden başlat
Yapılandırmayı yaptıktan sonra servisi yeniden başlat ve SSL’in aktif olup olmadığını kontrol et:
systemctl restart mysql
# SSL durumunu kontrol et
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'tls_version';"
# Bağlantı durumu
mysql -u root -p -e "SHOW STATUS LIKE 'Ssl_cipher';"
have_ssl değerinin YES göstermesi gerekiyor. DISABLED görüyorsan sertifika yollarını ve izinleri tekrar kontrol et.
SSL Zorunlu Kullanıcı Oluşturma
Sunucu SSL destekliyor ama bağlanan kullanıcıları şifreleme kullanmaya zorlayabilirsin. Bu en kritik adımlardan biri:
# SSL zorunlu kullanıcı oluştur
mysql -u root -p <<'EOF'
-- Uygulama kullanıcısı - SSL zorunlu
CREATE USER 'appuser'@'10.0.1.%' IDENTIFIED BY 'GucluSifre123!'
REQUIRE SSL;
-- Mutual TLS gerektiren kullanıcı - hem SSL hem istemci sertifikası zorunlu
CREATE USER 'appuser_mtls'@'10.0.1.%' IDENTIFIED BY 'GucluSifre456!'
REQUIRE X509;
-- Belirli bir sertifika CN'i ile gelen kullanıcı
CREATE USER 'appuser_strict'@'10.0.1.%' IDENTIFIED BY 'GucluSifre789!'
REQUIRE SUBJECT '/C=TR/ST=Istanbul/L=Istanbul/O=SirketAdi/CN=app-client'
AND ISSUER '/C=TR/ST=Istanbul/L=Istanbul/O=SirketAdi/CN=MySQL-CA';
-- Yetkilendirme
GRANT SELECT, INSERT, UPDATE, DELETE ON uygulama_db.* TO 'appuser'@'10.0.1.%';
GRANT SELECT, INSERT, UPDATE, DELETE ON uygulama_db.* TO 'appuser_mtls'@'10.0.1.%';
FLUSH PRIVILEGES;
EOF
Mevcut kullanıcıların SSL gereksinimini de değiştirebilirsin:
mysql -u root -p -e "ALTER USER 'mevcutuser'@'%' REQUIRE SSL;"
İstemci Bağlantısı Test Etme
Sunucu tarafını bitirdik, şimdi bağlantıyı test edelim. İstemci sertifikalarını uygulama sunucusuna taşıyalım:
# Uygulama sunucusuna sertifikaları kopyala
scp /etc/mysql/ssl/ca-cert.pem appserver:/etc/mysql-client/
scp /etc/mysql/ssl/client-cert.pem appserver:/etc/mysql-client/
scp /etc/mysql/ssl/client-key.pem appserver:/etc/mysql-client/
# Uygulama sunucusunda izinleri ayarla
ssh appserver "chmod 640 /etc/mysql-client/*.pem &&
chown appuser:appuser /etc/mysql-client/*.pem"
Test bağlantısı:
# Sadece CA ile SSL bağlantısı (REQUIRE SSL kullanıcısı için)
mysql -h db.sirket.local
-u appuser
-p
--ssl-ca=/etc/mysql-client/ca-cert.pem
-e "SHOW STATUS LIKE 'Ssl_cipher';"
# Mutual TLS bağlantısı (REQUIRE X509 kullanıcısı için)
mysql -h db.sirket.local
-u appuser_mtls
-p
--ssl-ca=/etc/mysql-client/ca-cert.pem
--ssl-cert=/etc/mysql-client/client-cert.pem
--ssl-key=/etc/mysql-client/client-key.pem
-e "SELECT current_user(), @@ssl_cipher;"
# SSL olmadan bağlanmaya çalış - bu başarısız olmalı
mysql -h db.sirket.local
-u appuser_mtls
-p
--ssl-mode=DISABLED
-e "SELECT 1;"
# Beklenen hata: ERROR 1045 (28000): Access denied
Uygulama Tarafında SSL Entegrasyonu
Test bağlantısı geçti, şimdi uygulamanı da bu yapıya adapte etmen gerekiyor.
PHP (PDO) için:
# PHP ile SSL bağlantısı
cat > /var/www/html/db_config.php <<'EOF'
<?php
$dsn = 'mysql:host=db.sirket.local;dbname=uygulama_db;charset=utf8mb4';
$options = [
PDO::MYSQL_ATTR_SSL_CA => '/etc/mysql-client/ca-cert.pem',
PDO::MYSQL_ATTR_SSL_CERT => '/etc/mysql-client/client-cert.pem',
PDO::MYSQL_ATTR_SSL_KEY => '/etc/mysql-client/client-key.pem',
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
];
try {
$pdo = new PDO($dsn, 'appuser_mtls', 'GucluSifre456!', $options);
echo "SSL bağlantısı başarılın";
} catch (PDOException $e) {
error_log("DB Bağlantı Hatası: " . $e->getMessage());
die("Bağlantı kurulamadı");
}
EOF
Python (mysql-connector) için:
# Python ile SSL bağlantısı
cat > /opt/app/db_connect.py <<'EOF'
import mysql.connector
import ssl
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.load_verify_locations('/etc/mysql-client/ca-cert.pem')
ssl_ctx.load_cert_chain(
certfile='/etc/mysql-client/client-cert.pem',
keyfile='/etc/mysql-client/client-key.pem'
)
ssl_ctx.verify_mode = ssl.CERT_REQUIRED
conn = mysql.connector.connect(
host='db.sirket.local',
user='appuser_mtls',
password='GucluSifre456!',
database='uygulama_db',
ssl_context=ssl_ctx,
ssl_verify_identity=True
)
cursor = conn.cursor()
cursor.execute("SHOW STATUS LIKE 'Ssl_cipher'")
print(cursor.fetchone())
conn.close()
EOF
Node.js için:
# Node.js ile SSL bağlantısı
cat > /opt/nodeapp/db.js <<'EOF'
const mysql = require('mysql2');
const fs = require('fs');
const connection = mysql.createConnection({
host: 'db.sirket.local',
user: 'appuser_mtls',
password: 'GucluSifre456!',
database: 'uygulama_db',
ssl: {
ca: fs.readFileSync('/etc/mysql-client/ca-cert.pem'),
cert: fs.readFileSync('/etc/mysql-client/client-cert.pem'),
key: fs.readFileSync('/etc/mysql-client/client-key.pem'),
rejectUnauthorized: true
}
});
connection.query('SHOW STATUS LIKE "Ssl_cipher"', (err, results) => {
if (err) throw err;
console.log('SSL Cipher:', results[0].Value);
connection.end();
});
EOF
Sertifika Yenileme Otomasyonu
Sertifikalar 3650 gün (10 yıl) ayarladık ama bunu bir otomasyon rutinine bağlamak daha sağlıklı. Prod ortamda sertifikanın sessizce expire olması sonrasında arama yapılacak anılar bırakır.
#!/bin/bash
# /usr/local/bin/mysql-cert-check.sh
CERT_DIR="/etc/mysql/ssl"
WARN_DAYS=90
ALERT_EMAIL="[email protected]"
check_cert() {
local cert_file="$1"
local cert_name="$2"
expiry=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
current_epoch=$(date +%s)
days_left=$(( (expiry_epoch - current_epoch) / 86400 ))
if [ "$days_left" -lt "$WARN_DAYS" ]; then
echo "UYARI: $cert_name sertifikasi $days_left gun sonra surecek!"
echo "UYARI: $cert_name sertifikasi $days_left gun sonra surecek!" |
mail -s "[MySQL SSL] Sertifika Suresi Uyarisi" "$ALERT_EMAIL"
else
echo "OK: $cert_name - $days_left gun kaldi"
fi
}
check_cert "$CERT_DIR/ca-cert.pem" "CA"
check_cert "$CERT_DIR/server-cert.pem" "Server"
check_cert "$CERT_DIR/client-cert.pem" "Client"
Bunu crontab’a ekle:
chmod +x /usr/local/bin/mysql-cert-check.sh
echo "0 9 * * 1 root /usr/local/bin/mysql-cert-check.sh" >> /etc/cron.d/mysql-ssl-check
Firewall ve Ağ Katmanı Güvenliği
SSL tek başına yeterli değil. Ağ katmanında da kısıtlamaları koyman gerekiyor:
# iptables ile sadece belirli IP'lere MySQL portunu aç
# Önce mevcut kuralları yedekle
iptables-save > /etc/iptables.backup
# Uygulama sunucusu IP bloğuna izin ver
iptables -A INPUT -p tcp --dport 3306 -s 10.0.1.0/24 -j ACCEPT
# Monitoring sunucusuna izin ver
iptables -A INPUT -p tcp --dport 3306 -s 10.0.2.10/32 -j ACCEPT
# Geri kalanı engelle
iptables -A INPUT -p tcp --dport 3306 -j DROP
# Kalıcı hale getir
iptables-save > /etc/iptables/rules.v4
# Firewalld kullananlar için
firewall-cmd --zone=internal --add-source=10.0.1.0/24
firewall-cmd --zone=internal --add-service=mysql
firewall-cmd --zone=public --remove-service=mysql --permanent
firewall-cmd --runtime-to-permanent
MariaDB İçin Farklar
MariaDB’de SSL yapılandırması büyük ölçüde aynı, ama birkaç farklılık var:
tls_versionparametresi MariaDB 10.4.6 ve sonrasında destekleniyor- MariaDB
mysql_ssl_rsa_setuparacını desteklemeyebilir, openssl ile elle üretmek daha güvenli REQUIRE CIPHERseçeneği MariaDB’de de çalışıyor- MariaDB’de
ssl-modeyerine--sslve--ssl-verify-server-certkullanılıyor
# MariaDB için yapılandırma kontrolü
mysql -u root -p -e "SELECT @@have_ssl, @@ssl_ca, @@ssl_cert;"
Yaygın Hatalar ve Çözümleri
Hata: SSL connection error: error:00000001:lib(0):func(0):reason(1)
Bu genellikle sertifika CN’inin bağlandığın hostname ile eşleşmediğinde çıkar. Server sertifikasını oluştururken CN olarak ne yazdıysan, o hostname veya IP ile bağlanman gerekiyor.
Hata: ERROR 2026 (HY000): SSL connection error: error:0407008A
CA sertifikasının dosya izinlerini kontrol et. mysql kullanıcısı o dosyayı okuyabilmeli.
SSL aktif ama Ssl_cipher boş geliyor:
# Hangi bağlantının SSL kullandığını kontrol et
mysql -u root -p -e "SELECT user, host, ssl_type FROM mysql.user WHERE user='appuser';"
# Aktif bağlantılarda SSL durumu
mysql -u root -p -e "SELECT * FROM information_schema.processlist WHERE id = CONNECTION_ID();"
REQUIRE SSL ayarına rağmen SSL’siz bağlanılabiliyorsa:
REQUIRE SSL kullanıcı seviyesinde SSL gerektirir. Ek olarak sunucu genelinde require_secure_transport=ON ayarını yaparak tüm bağlantıları SSL’e zorlayabilirsin. Bu ayar my.cnf içine require_secure_transport=ON olarak eklenir.
SSL Bağlantılarını İzleme
SSL trafiğini log’lamak ve anomali tespiti için şunu uygulayabilirsin:
# SSL bağlantı istatistikleri
mysql -u root -p -e "
SELECT
VARIABLE_NAME,
VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
'Ssl_accepts',
'Ssl_finished_accepts',
'Ssl_connects',
'Ssl_finished_connects',
'Ssl_cipher'
);"
# Hangi kullanıcıların SSL'siz bağlandığını bul
mysql -u root -p -e "
SELECT
user,
host,
db,
command,
ssl_cipher
FROM information_schema.processlist pl
LEFT JOIN performance_schema.threads t ON pl.id = t.processlist_id
WHERE ssl_cipher IS NULL OR ssl_cipher = '';"
Sonuç
MySQL SSL yapılandırması ilk bakışta karmaşık görünse de adımları tek tek uyguladığında yerli yerine oturuyor. Özetlersek:
- CA, sunucu ve istemci sertifikalarını openssl ile üret
- Dosya izinlerini doğru ayarla, mysql kullanıcısı anahtar dosyalarını okumalı
my.cnfiçinde SSL yollarını ve minimum TLS versiyonunu belirt- Kullanıcıları
REQUIRE SSLveyaREQUIRE X509ile oluştur - Ağ katmanında firewall ile 3306 portunu sadece yetkili IP’lere aç
- Sertifika expiry izlemesini otomasyona bağla
Mutual TLS, yani REQUIRE X509 yaklaşımı en güçlü seçenek. Sadece şifreyi değil, sertifikayı da çalmak gerekiyor bağlanmak için. Bir e-ticaret projemizde bu yapıya geçtikten sonra PCI-DSS denetiminde veritabanı bağlantısı kaleminden sorunsuz geçtik. Doğru yapılandırıldığında hem güvenlik sağlıyor hem de uyumluluk gereksinimlerini karşılıyor.