Çoğu projede veritabanı kullanıcı yönetimi şöyle işler: bir kullanıcı oluşturulur, ona ALL PRIVILEGES ON *.* verilir ve konu kapanır. Bu, uygulamanın çalışması için gereken en kısa yoldur ama aynı zamanda bir SQL enjeksiyon açığının tüm veritabanı sunucusunu ele geçirmesine giden yoldur da. MySQL kullanıcı ve yetki yönetimi (GRANT), doğru kullanıldığında bu riski dakikalar içinde küçültür; yanlış kullanıldığında ise "Access denied" hatalarıyla dolu bir gece yaşatır.
Bu rehberde MySQL'in yetki modelinin neden kullanıcı adından değil, kullanıcı adı ve host çiftinden oluştuğunu açıklayacağım; kullanıcı oluşturmayı, kimlik doğrulama eklentilerini, yetki seviyelerini ve GRANT sözdizimini adım adım göreceğiz. Ardından rollerle yetki yönetimini nasıl sadeleştireceğini, hesap sınırlarıyla kaynak tüketimini nasıl frenleyeceğini ve en sık yaşanan erişim hatalarının kökenini anlatacağım. Örnekler MySQL 8 üzerinedir; MariaDB farklılıklarını gerektiği yerde ayrıca belirteceğim.
Yetki Modelinin Temeli: Kullanıcı Adı Artı Host#
MySQL'de bir kimlik iki parçadan oluşur ve bunu anlamadan hiçbir yetki sorununu çözemezsin: kullanici@host. uygulama@localhost ile [email protected] tamamen farklı iki hesaptır; ayrı parolaları, ayrı yetkileri olabilir. Bir bağlantı geldiğinde MySQL, bağlanan istemcinin adresine göre hangi hesabın devreye gireceğine karar verir.
Host bileşeninde joker karakter kullanabilirsin:
| Host değeri | Kimlere uyar |
|---|---|
| localhost | Yalnızca Unix soketi üzerinden gelen yerel bağlantılar |
| 127.0.0.1 | Yerel TCP bağlantıları (soket değil) |
| 185.12.34.60 | Yalnızca o tek IP |
| 185.12.34.% | O ağ bloğundaki tüm adresler |
| % | Her yerden (üretimde kaçın) |
localhost ile 127.0.0.1 arasındaki ayrım, en çok kafa karıştıran noktadır. MySQL istemcisi -h localhost verildiğinde TCP değil Unix soketi kullanır; -h 127.0.0.1 verildiğinde ise TCP kullanır. Yalnızca localhost için yetki verdiysen, 127.0.0.1 ile bağlanan bir uygulama "Access denied" alır ve sen parolayı defalarca kontrol edersin. Bu hatayla karşılaştıysan MySQL access denied 1045 hatası yazısı adım adım teşhis akışını anlatıyor.
Bir diğer önemli kural, eşleşme önceliğidir. Aynı kullanıcı adı için hem uygulama@% hem de [email protected] tanımlıysa, o IP'den gelen bağlantıda MySQL daha spesifik olanı seçer. Yani genel bir % kaydı, daha dar bir kayıt varken devreye girmez. Yetki verdiğin hâlde çalışmayan bir durumda, önce hangi hesabın gerçekten kullanıldığına bak:
SELECT USER() AS baglanan, CURRENT_USER() AS eslesen;
-- baglanan: [email protected]
-- eslesen: [email protected].%
USER() bağlanmaya çalıştığın kimliği, CURRENT_USER() ise MySQL'in eşleştirdiği hesabı gösterir. İkisi farklıysa yetkiler beklediğin hesaptan gelmiyordur.
Kullanıcı Oluşturma ve Kimlik Doğrulama Eklentileri#
MySQL 8 ile birlikte GRANT komutu artık olmayan bir kullanıcıyı otomatik oluşturmaz; önce CREATE USER çalıştırman gerekir. Bu, eski alışkanlıkla gelen bir hata kaynağıdır.
CREATE USER 'uygulama'@'185.12.34.60' IDENTIFIED BY 'GucluBirParola';
MySQL 8'in varsayılan kimlik doğrulama eklentisi caching_sha2_password'dür ve daha güvenlidir; ancak eski istemci kütüphaneleri bunu desteklemeyebilir. Uygulaman "Authentication plugin cannot be loaded" gibi bir hata veriyorsa, önce istemci kütüphanesini güncellemeyi dene; mümkün değilse eski eklentiyi seçebilirsin:
-- Yalnızca eski istemci desteği gerekiyorsa
ALTER USER 'uygulama'@'185.12.34.60'
IDENTIFIED WITH mysql_native_password BY 'GucluBirParola';
Parolayı sonradan değiştirmek ve süre sınırı koymak da aynı komutla yapılır:
ALTER USER 'uygulama'@'185.12.34.60' IDENTIFIED BY 'YeniParola';
ALTER USER 'raporcu'@'%' PASSWORD EXPIRE INTERVAL 90 DAY;
Parola karmaşıklığını sunucu düzeyinde zorlamak istiyorsan validate_password bileşenini etkinleştirebilirsin. Bu bileşen kurulduktan sonra zayıf parolalarla kullanıcı oluşturulamaz:
INSTALL COMPONENT 'file://component_validate_password';
SHOW VARIABLES LIKE 'validate_password%';
Güçlü parola üretmek için tarayıcıdan çıkmadan şifre üretici aracımızı kullanabilirsin; parolayı bir yapılandırma dosyasına yazacaksan o dosyanın izinlerini 600 yapmayı da unutma.
Yetki Seviyeleri ve GRANT Sözdizimi#
MySQL yetkileri dört ayrı kapsamda verilebilir ve doğru kapsamı seçmek, en az yetki ilkesinin özüdür.
| Kapsam | Sözdizimi | Nerede saklanır |
|---|---|---|
| Global | ON . | mysql.user |
| Veritabanı | ON magaza.* | mysql.db |
| Tablo | ON magaza.siparis | mysql.tables_priv |
| Sütun | SELECT(ad, eposta) ON magaza.musteri | mysql.columns_priv |
Tipik bir web uygulaması için doğru yaklaşım, tek bir veritabanı üzerinde veri işleme yetkileri vermek ve şema değiştirme yetkilerini dışarıda bırakmaktır:
-- Uygulama kullanıcısı: veri okur, yazar; şemaya dokunamaz
GRANT SELECT, INSERT, UPDATE, DELETE ON magaza.* TO 'uygulama'@'185.12.34.60';
Bu tek satır, bir SQL enjeksiyon açığının etkisini "veriyi okuyup değiştirebilir" seviyesinde tutar; DROP TABLE ya da CREATE USER çalıştırılamaz. Migration çalıştıran ayrı bir kullanıcı tanımlamak ve onu yalnızca dağıtım sırasında kullanmak çok daha sağlıklıdır:
CREATE USER 'gocmen'@'185.12.34.60' IDENTIFIED BY 'BaskaBirParola';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, REFERENCES
ON magaza.* TO 'gocmen'@'185.12.34.60';
Raporlama için yalnızca okuma yetkisi veren, hatta belirli sütunları gizleyen bir kullanıcı da yapabilirsin:
CREATE USER 'raporcu'@'185.12.34.%' IDENTIFIED BY 'RaporParolasi';
GRANT SELECT ON magaza.siparis TO 'raporcu'@'185.12.34.%';
GRANT SELECT (id, ad, sehir) ON magaza.musteri TO 'raporcu'@'185.12.34.%';
Son satır sütun bazlı yetkidir: raporcu kullanıcısı musteri tablosundan yalnızca üç sütunu okuyabilir, telefon ve e-posta alanlarını göremez. Bu, kişisel veriyi sınırlamak için etkili ve az bilinen bir yöntemdir.
MySQL 8, klasik yetkilerin yanına dinamik yetkiler ekledi. Bunlar *.* kapsamında verilir ve belirli yönetim işlerine izin verir; ALL PRIVILEGES vermek yerine yalnızca gerekli olanı seçmeni sağlar:
-- Yedekleme kullanıcısına root vermeye gerek yok
GRANT BACKUP_ADMIN, RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT
ON *.* TO 'yedekci'@'localhost';
-- Sadece sistem değişkeni değiştirebilen bakım kullanıcısı
GRANT SYSTEM_VARIABLES_ADMIN, CONNECTION_ADMIN ON *.* TO 'bakim'@'localhost';
Yedekleme kullanıcısının hangi yetkilere ihtiyacı olduğunu Percona XtraBackup ile sıcak yedekleme yazısında ayrıntılı listeledim.
Yetkileri Görüntüleme, Değiştirme ve Geri Alma#
Bir kullanıcının neye yetkisi olduğunu görmenin tek doğru yolu SHOW GRANTS komutudur; mysql.user tablosunu elle okumak yanıltıcıdır çünkü yetkiler dört ayrı tabloya dağılmıştır.
SHOW GRANTS FOR 'uygulama'@'185.12.34.60';
-- GRANT USAGE ON *.* TO `uygulama`@`185.12.34.60`
-- GRANT SELECT, INSERT, UPDATE, DELETE ON `magaza`.* TO `uygulama`@`185.12.34.60`
Çıktıdaki USAGE yetkisi "hiçbir yetki yok, sadece bağlanabilir" anlamına gelir; bir hata değildir, her kullanıcıda görünür. Sunucudaki tüm hesapları listelemek için:
SELECT user, host, plugin, account_locked, password_expired
FROM mysql.user ORDER BY user, host;
Yetki geri almak REVOKE ile yapılır ve sözdizimi GRANT ile simetriktir:
-- Tek bir yetkiyi al
REVOKE DELETE ON magaza.* FROM 'uygulama'@'185.12.34.60';
-- Her şeyi al ama kullanıcıyı silme
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'eskiCalisan'@'%';
-- Hesabı geçici olarak kilitle (silmeden)
ALTER USER 'eskiCalisan'@'%' ACCOUNT LOCK;
-- Tamamen sil
DROP USER 'eskiCalisan'@'%';
Bir çalışan ayrıldığında hesabı hemen silmek yerine önce ACCOUNT LOCK yapmak iyi bir alışkanlıktır: hangi işlerin o hesapla çalıştığını görürsün ve bir şey kırılırsa saniyeler içinde geri açabilirsin.
Sık sorulan bir konu da FLUSH PRIVILEGES komutudur. GRANT, REVOKE, CREATE USER gibi komutlar yetki tablolarını ve bellekteki kopyayı birlikte günceller; bu komutlardan sonra FLUSH PRIVILEGES gerekmez. Yalnızca mysql.user gibi tablolara doğrudan UPDATE çalıştırdıysan gereklidir — ki bu zaten yapılmaması gereken bir şeydir.
Rollerle Yetki Yönetimini Sadeleştirmek#
Beş uygulama sunucun ve üç veritabanın varsa, her kullanıcıya tek tek yetki vermek kısa sürede yönetilemez hâle gelir. MySQL 8 ve MariaDB 10.0'dan itibaren roller bu sorunu çözer: yetkiyi role verirsin, rolü kullanıcıya atarsın.
-- 1) Rolleri oluştur
CREATE ROLE 'magaza_okur', 'magaza_yazar';
-- 2) Yetkileri rollere ver
GRANT SELECT ON magaza.* TO 'magaza_okur';
GRANT SELECT, INSERT, UPDATE, DELETE ON magaza.* TO 'magaza_yazar';
-- 3) Rolleri kullanıcılara ata
GRANT 'magaza_yazar' TO 'uygulama'@'185.12.34.60';
GRANT 'magaza_okur' TO 'raporcu'@'185.12.34.%';
Kritik ve çok atlanan adım şudur: bir rol atandığında otomatik olarak etkin olmaz. Kullanıcı ya her oturumda SET ROLE çalıştırmalı ya da varsayılan rolü tanımlanmalıdır. Uygulama kullanıcıları için ikincisi tek pratik yoldur:
SET DEFAULT ROLE ALL TO 'uygulama'@'185.12.34.60';
Bunu unutursan kullanıcı bağlanır ama hiçbir tabloyu göremez ve "yetki verdim ama çalışmıyor" tuzağına düşersin. Rollerin etkin olup olmadığını kontrol etmek için:
SELECT CURRENT_ROLE();
Rollerin en büyük faydası değişiklik anında ortaya çıkar: yeni bir tablo eklendiğinde ya da bir yetki kaldırılması gerektiğinde tek bir rolü güncellemen yeterlidir, o role sahip tüm kullanıcılar anında etkilenir.
Hesap Sınırları ve Güvenlik Sertleştirmeleri#
Yetki, "ne yapabilir" sorusunun cevabıdır; hesap sınırları ise "ne kadar yapabilir" sorusunun. Kontrolden çıkan bir rapor betiğinin sunucuyu boğmasını engellemek için hesap bazında kaynak sınırı koyabilirsin:
ALTER USER 'raporcu'@'185.12.34.%'
WITH MAX_QUERIES_PER_HOUR 5000
MAX_UPDATES_PER_HOUR 0
MAX_CONNECTIONS_PER_HOUR 200
MAX_USER_CONNECTIONS 10;
MAX_UPDATES_PER_HOUR 0 değeri "hiç güncelleme yapamaz" demektir ve yetkilerin üstüne ikinci bir güvenlik katmanı ekler. MAX_USER_CONNECTIONS ise tek bir hesabın bağlantı havuzunu tüketmesini engeller — bu sınır, MySQL too many connections hatası yaşayan sistemlerde tek bir uygulamanın diğerlerini kilitlemesini önler.
Şifreli bağlantı zorunluluğu da hesap seviyesinde tanımlanır:
-- Bu hesap yalnızca TLS ile bağlanabilsin
ALTER USER 'uygulama'@'185.12.34.60' REQUIRE SSL;
-- Daha katısı: geçerli bir istemci sertifikası da istensin
ALTER USER 'yonetici'@'185.12.34.61' REQUIRE X509;
Ağ üzerinden bağlanan her hesap için REQUIRE SSL bugün varsayılan olmalıdır; kurulum ayrıntıları için MySQL'de SSL bağlantıyı zorunlu kılma yazısına bak. Son olarak, yeni kurulan bir sunucuda temizlik yapmayı unutma:
sudo mysql_secure_installation
Bu betik anonim kullanıcıları siler, test veritabanını kaldırır, root'un uzaktan girişini kapatır ve parola politikası sorar. Beş dakikalık bu iş, sunucunun güvenlik seviyesini belirgin biçimde yükseltir.
Sık Yapılan Hatalar#
Birinci ve en pahalı hata, uygulamaya ALL PRIVILEGES ON *.* vermektir. Uygulamanın DROP DATABASE ya da CREATE USER çalıştırmaya ihtiyacı yoktur; bu yetkilerin varlığı yalnızca bir açığın etkisini büyütür. Uygulama kullanıcısına dört veri yetkisi (SELECT, INSERT, UPDATE, DELETE) tek bir veritabanı üzerinde vermek neredeyse her zaman yeterlidir.
İkinci hata, host bileşenini % yapmaktır. Sunucunun 3306 portu dışarı açıksa bu, dünyanın her yerinden parola denemesi yapılabilmesi demektir. Uygulama sunucusunun IP'sini ya da en azından ağ blokunu yaz. Portu güvenlik duvarıyla da sınırla:
sudo ufw allow from 185.12.34.60 to any port 3306 proto tcp
Üçüncü hata, rol atayıp SET DEFAULT ROLE komutunu unutmaktır. Kullanıcı bağlanır, SHOW GRANTS çıktısında rol görünür ama hiçbir tabloya erişemez. Bu, yetki sorunlarının en kafa karıştırıcısıdır çünkü her şey doğru görünür.
Dördüncü hata, aynı kullanıcı adı için çakışan host kayıtları bırakmaktır. uygulama@% ve uygulama@localhost birlikte varsa ve ikisinin parolası farklıysa, hangi hesabın devreye gireceği bağlantı yöntemine göre değişir; sonuç, "bazen çalışıyor bazen çalışmıyor" gibi görünen bir hatadır. Kullanıcı listesini düzenli olarak gözden geçir ve gereksiz kayıtları sil.
Beşinci hata, parolayı komut geçmişine bırakmaktır. mysql -u root -pParola yazdığında parola hem kabuk geçmişine hem de süreç listesine düşer. Bunun yerine -p parametresini boş bırakıp parolayı istendiğinde yaz ya da izinleri kısıtlanmış bir ~/.my.cnf dosyası kullan. Paylaşımlı barındırmada çalışıyorsan bu işlerin çoğu panel üzerinden yapılır; cPanel MySQL kullanıcı yetkileri yazısı o arayüzdeki karşılıklarını anlatıyor.
Sıkça Sorulan Sorular#
MySQL kullanıcı yetkilerini nasıl kontrol ederim#
En doğru yöntem SHOW GRANTS FOR 'kullanici'@'host'; komutudur. Bu komut, o hesaba verilmiş tüm yetkileri global, veritabanı, tablo ve sütun seviyesinde birleştirip okunabilir biçimde listeler. Şu anda hangi kimlikle bağlı olduğunu görmek için SELECT CURRENT_USER();, tüm hesapları görmek için SELECT user, host FROM mysql.user; sorgularını kullanabilirsin. mysql.user tablosunu elle okumak yetkilerin tamamını göstermez, çünkü tablo ve sütun yetkileri ayrı tablolarda saklanır.
GRANT sonrası FLUSH PRIVILEGES gerekli mi#
Hayır. GRANT, REVOKE, CREATE USER, ALTER USER ve DROP USER komutları hem yetki tablolarını hem de bellekteki önbelleği birlikte günceller, bu yüzden ek bir komut gerekmez. FLUSH PRIVILEGES yalnızca mysql.user gibi sistem tablolarına doğrudan INSERT veya UPDATE çalıştırdıysan gereklidir; bu da tavsiye edilmeyen bir yöntemdir. Alışkanlıktan yazmanın zararı yoktur ama gerekli olduğunu sanmak yanlış teşhise yol açar.
localhost ile 127.0.0.1 arasındaki fark nedir#
MySQL istemcisi localhost verildiğinde TCP yerine Unix soketi üzerinden bağlanır, 127.0.0.1 verildiğinde ise normal bir TCP bağlantısı açar. MySQL bunları iki ayrı host olarak değerlendirdiği için yetkileri de ayrı ayrı vermelisin. Uygulaman 127.0.0.1 ile bağlanıyor ve sen yalnızca localhost için yetki verdiysen erişim reddedilir; bu, "Access denied" hatalarının en sık nedenlerinden biridir.
Bir kullanıcıyı silmeden nasıl devre dışı bırakırım#
ALTER USER 'kullanici'@'host' ACCOUNT LOCK; komutu hesabı kilitler; kullanıcı var olmaya ve yetkileri durmaya devam eder ama bağlantı kuramaz. Geri açmak için ACCOUNT UNLOCK kullanırsın. Bu yöntem, ayrılan bir çalışanın hesabını kapatırken hangi otomasyonların o hesabı kullandığını görmek için idealdir; bir şey kırılırsa saniyeler içinde geri alabilirsin. Yetkileri korumak isteyip yalnızca erişimi kesmek istediğinde silme yerine daima kilitlemeyi tercih et.
Uygulama kullanıcısına hangi yetkileri vermeliyim#
Tipik bir web uygulaması için tek bir veritabanı üzerinde SELECT, INSERT, UPDATE, DELETE yetkileri yeterlidir. Şema değiştiren migration işlemlerini ayrı bir kullanıcıya taşıyıp o kullanıcıya CREATE, ALTER, INDEX, DROP yetkilerini vermek çok daha güvenlidir, çünkü bu hesap yalnızca dağıtım sırasında kullanılır. Global *.* kapsamında yetki vermekten kaçın; uygulamanın diğer veritabanlarını görmesi için hiçbir sebep yoktur.
MariaDB'de yetki komutları aynı mı#
Büyük ölçüde aynıdır; CREATE USER, GRANT, REVOKE, SHOW GRANTS ve roller MariaDB'de de aynı sözdizimiyle çalışır. Başlıca farklar kimlik doğrulama eklentilerinde ve MySQL 8'e özgü dinamik yetkilerdedir: MariaDB'de BACKUP_ADMIN gibi yetkiler bulunmaz, bunların yerine klasik RELOAD ve SUPER yetkileri kullanılır. Ayrıca MariaDB, yerel root erişimi için varsayılan olarak unix_socket eklentisini kullanır, bu yüzden root parolası yerine sistem kullanıcısı kimliği devreye girer.
Kapanış#
Yetki yönetimi, veritabanı güvenliğinin en ucuz ve en çok karşılık veren parçasıdır: kurulum sırasında harcayacağın on beş dakika, ileride yaşanabilecek bir olayın etkisini kat kat küçültür. Aklında kalması gereken dört alışkanlık şu: uygulamaya asla ALL PRIVILEGES ON *.* verme, host bileşenini % yerine gerçek IP ya da ağ bloğu yaz, rol kullanıyorsan SET DEFAULT ROLE adımını atlama ve ağ üzerinden bağlanan hesaplarda REQUIRE SSL şartını standart hâline getir. Kullanıcı listesini altı ayda bir gözden geçirip kullanılmayan hesapları kilitlemek de listeye eklenmeye değer.
Bu ayarların tamamını uygulayabilmek için root erişimine ihtiyacın var; VDS ve bulut sunucu paketlerimiz kendi MySQL yapılandırmanı kurmana izin verir. Paylaşımlı barındırmada çalışıyorsan cPanel'in veritabanı sihirbazı üzerinden aynı yetki kapsamlarını seçebilirsin; web hosting ve kurumsal hosting paketlerimizde bu araç hazır gelir. Yetkilendirme ve sertleştirmeyi uzman gözüyle kurgulamak istersen sunucu yönetimi hizmetimize göz atabilirsin.