Veritabanı güvenliği söz konusu olduğunda çoğu sysadmin ilk kurulumun ardından “çalışıyor, tamam” deyip geçiyor. MySQL’i kurup root şifresi belirliyorsunuz, uygulama bağlanıyor ve herkes mutlu. Ta ki bir gün loglarınıza bakana kadar. O güne kadar kim bilir kaç kez yetkisiz erişim denemesi gelmiş, kim bilir hangi uygulama hesabı gereksiz tablolara dokunmuş. Bu yazıda MySQL kullanıcı yetkilendirmesini ve güvenlik ayarlarını gerçek dünya senaryolarıyla ele alacağız.
Neden MySQL Güvenliğine Bu Kadar Önem Vermeliyiz
MySQL, dünya genelinde milyonlarca sunucuda çalışan bir veritabanı. Bu popülerlik beraberinde saldırganların hedef listesinde üst sıralarda yer almasını getiriyor. Bunun yanında içeriden gelen tehditler de göz ardı edilemez. Yanlış yapılandırılmış bir kullanıcı hesabı, bir geliştirici hatasının production veritabanını silmesine neden olabilir.
En az yetki prensibi burada temel felsefemiz olacak. Her kullanıcı, her uygulama sadece işini yapabilecek kadar yetkiye sahip olmalı. Ne fazlası ne eksiği.
İlk Adım: mysql_secure_installation
Yeni bir MySQL kurulumundan sonra çalıştırmanız gereken ilk şey bu komuttur. Birçok kişi bu adımı atlıyor ve sonradan pişman oluyor.
sudo mysql_secure_installation
Bu script size şu soruları sorar:
- Root şifresi belirleme veya değiştirme
- Validate Password eklentisini etkinleştirme
- Anonim kullanıcıları silme
- Root kullanıcısının uzaktan bağlanmasını engelleme
- Test veritabanını silme
- Yetki tablolarını yeniden yükleme
Özellikle anonim kullanıcı meselesi kritik. Varsayılan kurulumda şifresiz bağlanabilen anonim bir kullanıcı oluşturuluyor. Bunu hemen kapatın.
MariaDB için süreç aynıdır, aynı komutu kullanabilirsiniz.
Kullanıcı Oluşturma ve Temel Yetkilendirme
MySQL’de kullanıcı yönetimi mysql.user tablosu üzerinden çalışır. Ama biz her zaman SQL komutlarını doğrudan kullanacağız.
# MySQL root olarak bağlanın
mysql -u root -p
# Yeni kullanıcı oluşturma - sadece localhost'tan erişim
CREATE USER 'webapp_user'@'localhost' IDENTIFIED BY 'GuvenliSifre2024!';
# Belirli bir IP'den erişim için
CREATE USER 'webapp_user'@'192.168.1.100' IDENTIFIED BY 'GuvenliSifre2024!';
# Bir subnet'ten erişim için (MariaDB destekler)
CREATE USER 'webapp_user'@'192.168.1.%' IDENTIFIED BY 'GuvenliSifre2024!';
Burada @ işaretinden sonraki kısım host kısıtlamasıdır. localhost ile 127.0.0.1 aslında farklı şeylerdir. localhost Unix soketi üzerinden bağlanırken, 127.0.0.1 TCP/IP üzerinden bağlanır. Uygulamanız nasıl bağlanıyorsa ona göre belirleyin.
Yetki Türleri
MySQL’de yetkileri birkaç seviyede tanımlayabilirsiniz:
Global yetkiler tüm veritabanlarını kapsar:
- SELECT: Veri okuma
- INSERT: Veri ekleme
- UPDATE: Veri güncelleme
- DELETE: Veri silme
- CREATE: Tablo/veritabanı oluşturma
- DROP: Tablo/veritabanı silme
- INDEX: İndeks yönetimi
- ALTER: Tablo yapısını değiştirme
- SUPER: Süper kullanıcı yetkileri (tehlikeli)
- PROCESS: Çalışan sorguları görme
- RELOAD: FLUSH komutlarını çalıştırma
- REPLICATION SLAVE: Replikasyon için slave yetkisi
- GRANT OPTION: Başkalarına yetki verme hakkı
Minimal Yetki ile Uygulama Kullanıcısı Oluşturma
Gerçek dünyada tipik bir web uygulaması için şu yetkileri verin:
# Önce veritabanını oluşturun
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# Kullanıcıya sadece bu veritabanı için yetki verin
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'webapp_user'@'localhost';
# Değişiklikleri uygulayın
FLUSH PRIVILEGES;
# Yetkileri kontrol edin
SHOW GRANTS FOR 'webapp_user'@'localhost';
Dikkat edin, CREATE ve DROP yetkisi vermedik. Uygulama kodu veritabanı şemasını değiştiremesin istiyoruz. Migration’lar için ayrı bir kullanıcı tanımlayabiliriz.
Gerçek Dünya Senaryosu: Çok Katmanlı Uygulama
Diyelim ki bir e-ticaret sitesi yönetiyorsunuz. Bu sitede şu bileşenler var: web uygulaması, raporlama aracı, veritabanı yedekleme scripti ve geliştirici erişimi. Her biri için ayrı kullanıcı oluşturmalısınız.
# Web uygulaması - sadece CRUD işlemleri
CREATE USER 'eticaret_app'@'10.0.1.50' IDENTIFIED BY 'AppSifre123!';
GRANT SELECT, INSERT, UPDATE, DELETE ON eticaret_db.* TO 'eticaret_app'@'10.0.1.50';
# Raporlama aracı - sadece okuma
CREATE USER 'eticaret_report'@'10.0.2.30' IDENTIFIED BY 'ReportSifre456!';
GRANT SELECT ON eticaret_db.* TO 'eticaret_report'@'10.0.2.30';
# Yedekleme scripti - özel yetkiler
CREATE USER 'eticaret_backup'@'localhost' IDENTIFIED BY 'BackupSifre789!';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON eticaret_db.* TO 'eticaret_backup'@'localhost';
# Geliştirici - development ortamında tam yetki ama production'da kısıtlı
CREATE USER 'dev_ahmet'@'192.168.10.%' IDENTIFIED BY 'DevSifre321!';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON eticaret_db.* TO 'dev_ahmet'@'192.168.10.%';
FLUSH PRIVILEGES;
Bu yapıyla raporlama aracınız hiçbir şekilde veri değiştiremez. Yedekleme scripti sadece yedek almak için gereken minimum yetkiye sahip. Geliştirici ise şema değiştirebilir ama DROP gibi tehlikeli işlemleri yapamaz.
Stored Procedure ve View Yetkilendirmesi
Bazen uygulamanızın direkt tablo erişimi yerine sadece stored procedure veya view üzerinden çalışmasını isteyebilirsiniz. Bu ekstra bir güvenlik katmanı sağlar.
# View için yetki verme
CREATE USER 'readonly_user'@'localhost' IDENTIFIED BY 'ReadSifre!';
# Önce view oluşturun
CREATE VIEW aktif_urunler AS
SELECT urun_id, urun_adi, fiyat FROM urunler WHERE aktif = 1;
# Sadece view'a yetki verin, tabloya değil
GRANT SELECT ON eticaret_db.aktif_urunler TO 'readonly_user'@'localhost';
# Stored procedure için yetki
GRANT EXECUTE ON PROCEDURE eticaret_db.siparis_olustur TO 'webapp_user'@'localhost';
Bu yaklaşım özellikle finans veya sağlık gibi hassas sektörlerde çok işe yarıyor. Uygulama direkt tabloya dokunamaz, sadece izin verilen prosedürleri çağırabilir.
Şifre Politikası ve Güvenlik Eklentileri
MySQL 5.7 ve üzeri ile gelen validate_password eklentisi şifre kalitesini zorla denetlemenizi sağlar.
# Eklentiyi yükle
INSTALL PLUGIN validate_password SONAME 'validate_password.so';
# MySQL 8.0 için
INSTALL COMPONENT 'file://component_validate_password';
# Politika ayarları - /etc/mysql/mysql.conf.d/mysqld.cnf dosyasına ekleyin
# validate_password.policy = MEDIUM
# validate_password.length = 12
# validate_password.mixed_case_count = 1
# validate_password.number_count = 1
# validate_password.special_char_count = 1
# Mevcut ayarları görmek için
SHOW VARIABLES LIKE 'validate_password%';
Politika seviyeleri şöyle çalışır:
- LOW: Sadece uzunluk kontrolü
- MEDIUM: Uzunluk, büyük/küçük harf, rakam ve özel karakter
- STRONG: MEDIUM’a ek olarak şifre sözlük dosyası kontrolü
Bağlantı Limitleri ve Hesap Kilitleme
Brute force saldırılarına karşı bağlantı limitlerini kullanın. Bu özellik çok az sysadmin tarafından kullanılıyor ama çok değerli.
# Kullanıcı oluştururken sınırları belirleyin
CREATE USER 'sinirli_user'@'localhost'
IDENTIFIED BY 'Sifre123!'
WITH MAX_CONNECTIONS_PER_HOUR 100
MAX_QUERIES_PER_HOUR 1000
MAX_UPDATES_PER_HOUR 500
MAX_USER_CONNECTIONS 5;
# Mevcut kullanıcıya limit ekleyin
ALTER USER 'webapp_user'@'localhost'
WITH MAX_CONNECTIONS_PER_HOUR 200
MAX_USER_CONNECTIONS 10;
# MySQL 8.0 ile gelen failed login takibi
ALTER USER 'webapp_user'@'localhost'
FAILED_LOGIN_ATTEMPTS 5
PASSWORD_LOCK_TIME 1;
# 5 başarısız denemeden sonra 1 gün kilitlenir
Kilitlenen hesabı açmak için:
ALTER USER 'webapp_user'@'localhost' ACCOUNT UNLOCK;
SSL/TLS ile Şifreli Bağlantı Zorlama
Özellikle veritabanı sunucunuz uygulama sunucusundan fiziksel olarak ayrıysa ağ üzerinde veriyi şifrelemek şart.
# SSL sertifikalarını oluşturun
sudo mysql_ssl_rsa_setup --uid=mysql
# Kullanıcıya SSL zorunluluğu ekleyin
ALTER USER 'webapp_user'@'192.168.1.100' REQUIRE SSL;
# Daha sıkı: belirli bir SSL sertifikası zorunlu
ALTER USER 'webapp_user'@'192.168.1.100'
REQUIRE ISSUER '/C=TR/ST=Istanbul/O=Sirketim/CN=CA'
AND SUBJECT '/C=TR/ST=Istanbul/O=Sirketim/CN=client';
# Bağlantının SSL kullanıp kullanmadığını kontrol edin
SHOW STATUS LIKE 'Ssl_cipher';
# my.cnf ayarları
# [mysqld]
# ssl-ca=/etc/mysql/certs/ca.pem
# ssl-cert=/etc/mysql/certs/server-cert.pem
# ssl-key=/etc/mysql/certs/server-key.pem
# require_secure_transport=ON
require_secure_transport=ON ayarı tüm bağlantılarda SSL zorunlu kılar. Bu biraz agresif olabilir, özellikle localhost bağlantılarınız varsa. Durumunuza göre karar verin.
Yetki Denetimi ve Güvenlik Testi
Verdiğiniz yetkileri düzenli aralıklarla denetlemeniz gerekiyor. Şu sorguları periyodik olarak çalıştırın:
# Tüm kullanıcıları listele
SELECT user, host, authentication_string, account_locked
FROM mysql.user;
# Şifresi olmayan kullanıcıları bul (çok tehlikeli)
SELECT user, host FROM mysql.user
WHERE authentication_string = '' OR authentication_string IS NULL;
# Belirli bir kullanıcının yetkilerini görüntüle
SHOW GRANTS FOR 'webapp_user'@'localhost';
# Tüm kullanıcıların yetkilerini toplu görüntüle
SELECT
GRANTEE,
TABLE_SCHEMA,
PRIVILEGE_TYPE,
IS_GRANTABLE
FROM information_schema.SCHEMA_PRIVILEGES
ORDER BY GRANTEE, TABLE_SCHEMA;
# SUPER yetkisi olan kullanıcıları bul (minimumda tutulmalı)
SELECT user, host FROM mysql.user WHERE Super_priv = 'Y';
# Wildcard host ile tanımlı kullanıcılar (dikkatli olun)
SELECT user, host FROM mysql.user WHERE host = '%';
Host kısmı % olan kullanıcılar her yerden bağlanabilir demek. Bu çok nadiren gerçekten gereklidir. Görmemek istediğiniz bir şey.
Audit Log ile İzleme
Kimin ne zaman ne yaptığını kayıt altına almak hem güvenlik hem de uyumluluk açısından önemli.
# MySQL Enterprise Audit veya MariaDB Audit Plugin
# MariaDB için:
INSTALL SONAME 'server_audit';
# my.cnf ayarları
# server_audit_logging=ON
# server_audit_output_type=file
# server_audit_file_path=/var/log/mysql/mysql_audit.log
# server_audit_events=CONNECT,QUERY_DDL,QUERY_DML
# server_audit_excl_users=root
# Genel sorgu logunu geçici olarak açmak (performans etkisi var)
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';
# Belirli bir süre sonra kapatın
SET GLOBAL general_log = 'OFF';
Production’da general log sürekli açık tutmayın, disk dolumuna ve performans sorunlarına yol açar. Sorun giderme veya güvenlik incelemesi sırasında geçici açın.
Kullanıcı Silme ve Yetki Geri Alma
İşten ayrılan bir çalışanın veya artık kullanılmayan bir uygulamanın erişimini hemen kapatın.
# Tüm yetkileri geri al
REVOKE ALL PRIVILEGES ON eticaret_db.* FROM 'dev_ahmet'@'192.168.10.%';
REVOKE GRANT OPTION ON *.* FROM 'dev_ahmet'@'192.168.10.%';
# Kullanıcıyı sil
DROP USER 'dev_ahmet'@'192.168.10.%';
# Belirli bir yetkiyi geri al
REVOKE DELETE ON eticaret_db.siparisler FROM 'webapp_user'@'localhost';
# Değişiklikleri uygula
FLUSH PRIVILEGES;
REVOKE ALL PRIVILEGES sonrasında DROP USER yapmanız gerekiyor, biri diğerinin yerine geçmiyor.
Firewall ve Sistem Seviyesinde Güvenlik
MySQL güvenliği sadece veritabanı içinde bitmiyor. Sistem katmanında da önlem alın.
# MySQL portuna sadece belirli IP'lerden erişim
sudo ufw allow from 10.0.1.50 to any port 3306
sudo ufw allow from 10.0.2.30 to any port 3306
sudo ufw deny 3306
# iptables ile aynı iş
sudo iptables -A INPUT -p tcp --dport 3306 -s 10.0.1.50 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3306 -s 10.0.2.30 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP
# MySQL'in hangi IP'yi dinlediğini kontrol edin
# my.cnf
# [mysqld]
# bind-address = 127.0.0.1
# Sadece localhost bağlantısı kabul etmek için
# Birden fazla IP dinlemek için (MySQL 8.0+)
# bind-address = 127.0.0.1,10.0.0.5
Eğer tüm bağlantılarınız localhost üzerinden geliyorsa bind-address = 127.0.0.1 ayarı yapın ve MySQL’i ağa hiç açmayın. Bu en güvenli konfigürasyondur.
Şifre Rotasyonu ve Hesap Süresi
Özellikle servis hesapları için şifre rotasyon politikası oluşturun. MySQL 8.0 bu konuda güçlü özellikler sunuyor.
# Şifre geçerlilik süresi belirleme
ALTER USER 'webapp_user'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
# Hesabı devre dışı bırakma (silmeden)
ALTER USER 'webapp_user'@'localhost' ACCOUNT LOCK;
# Tekrar aktifleştirme
ALTER USER 'webapp_user'@'localhost' ACCOUNT UNLOCK;
# Şifre değiştirme
ALTER USER 'webapp_user'@'localhost' IDENTIFIED BY 'YeniGuvenliSifre!';
FLUSH PRIVILEGES;
# Son şifreyi tekrar kullanamama (MySQL 8.0)
ALTER USER 'webapp_user'@'localhost' PASSWORD HISTORY 5;
# Son 5 şifre tekrar kullanılamaz
Servis hesapları için şifre rotasyonunu otomatikleştirmek istiyorsanız HashiCorp Vault gibi bir secret management aracına bakmanızı öneririm. Manuel rotasyon kaçınılmaz olarak bir gün atlanır.
Sonuç
MySQL güvenliği tek seferlik bir iş değil, sürekli bakım gerektiren bir süreç. Şu ana kadar anlattıklarımı özetlersek: her kurulumdan sonra mysql_secure_installation çalıştırın, her uygulama ve her kullanıcı için ayrı hesap oluşturun, minimum yetki prensibini asla esneklikten ötürü çiğnemeyin. Host kısıtlamalarını doğru yapılandırın, % wildcard’ından kaçının. Bağlantı sınırları ve başarısız giriş kilitleme özelliklerini aktif edin. Veritabanı trafiği ağ üzerinden geçiyorsa SSL zorunlu kılın. Yetkileri ve kullanıcıları periyodik denetleyin, kullanılmayan hesapları hemen kapatın.
Birçok güvenlik ihlali karmaşık saldırılar sonucu değil, unutulmuş bir test kullanıcısı, değiştirilmemiş varsayılan şifre veya fazla yetki verilmiş bir servis hesabı yüzünden yaşanıyor. Temel hijyeni sağladıktan sonra daha gelişmiş konulara geçebilirsiniz. Ama önce temeli sağlam atın.