PostgreSQL SECURITY DEFINER vs INVOKER: Ne Zaman Hangisini Kullanmalı?

Supabase'de yazdığınız fonksiyon RLS'yi bypass mı ediyor? SECURITY DEFINER ve INVOKER arasındaki farkı, hangi durumda hangisini kullanacağınızı basitçe anlattık.

Sorun: Fonksiyon Tüm Verileri Gösteriyor

Supabase’de Row Level Security (RLS) kurdunuz. Her kullanıcı sadece kendi şubesinin verilerini görüyor. Her şey güzel — ta ki bir fonksiyon yazana kadar:

CREATE FUNCTION toplam_stok_sayisi()
RETURNS integer AS $$
  SELECT COUNT(*) FROM v3_stock;
$$ LANGUAGE sql;

Bu fonksiyonu çağırdığınızda tüm şubelerin stok sayısını döndürüyor. RLS çalışmıyor!

Neden? Çünkü fonksiyonun güvenlik modu yanlış.

İki Mod Var

SECURITY INVOKER (Varsayılan — Güvenli)

Fonksiyon, onu çağıran kullanıcının yetkileriyle çalışır. Yani RLS kuralları geçerlidir:

CREATE FUNCTION benim_stoklarim()
RETURNS SETOF v3_stock
LANGUAGE sql
SECURITY INVOKER   -- Çağıran kişinin yetkileri geçerli
AS $$
  SELECT * FROM v3_stock;
$$;

Ahmet bu fonksiyonu çağırırsa → sadece Ahmet’in görebildiği satırlar döner.

SECURITY DEFINER (Dikkatli Kullanın!)

Fonksiyon, fonksiyonu oluşturan kişinin (genelde süper yetkili postgres) yetkileriyle çalışır. RLS devre dışı kalır:

CREATE FUNCTION tum_stoklari_getir()
RETURNS SETOF v3_stock
LANGUAGE sql
SECURITY DEFINER   -- ⚠️ postgres yetkisiyle çalışır, RLS yok!
AS $$
  SELECT * FROM v3_stock;
$$;

Kim çağırırsa çağırsın → tüm satırlar döner.

Basit Kural

%90 fonksiyonunuz → INVOKER olsun (RLS korunur)
%10 fonksiyonunuz → DEFINER gerekebilir (aşağıdaki durumlarda)

Ne Zaman DEFINER Kullanılır?

SenaryoNeden DEFINER?
Audit log yazmaKullanıcı audit tablosuna yazamaz ama fonksiyon yazabilmeli
Tüm şubelerin toplamını göstermeYönetici raporu — toplam rakam gerekli
Sistem bakım işlemleriKullanıcı tetikleyebilmeli ama tüm veriye erişmemeli

DEFINER Kullanırken Güvenlik

DEFINER fonksiyonlarda güvenliği siz sağlamalısınız. RLS size yardımcı olmaz:

CREATE FUNCTION sube_ozet_raporu(hedef_sube text)
RETURNS TABLE(toplam_stok bigint, toplam_deger numeric)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public   -- ← Güvenlik için zorunlu
AS $$
BEGIN
  -- 1. Yetki kontrolü: Bu kullanıcı bu şubeyi görebilir mi?
  IF NOT EXISTS (
    SELECT 1 FROM kullanici_subeleri
    WHERE kullanici_id = auth.uid()
    AND sube_kodu = hedef_sube
  ) THEN
    RAISE EXCEPTION 'Bu şubeye erişim yetkiniz yok';
  END IF;

  -- 2. Yetkili — veriyi getir
  RETURN QUERY
  SELECT COUNT(*)::bigint, SUM(miktar * birim_fiyat)
  FROM v3_stock
  WHERE depo = hedef_sube;
END;
$$;

3 kritik kural:

  1. SET search_path = public — SQL injection koruması
  2. Fonksiyon içinde yetki kontrolü yapın
  3. Kullanıcı girdisini doğrudan SQL’e koymayın

Mevcut Fonksiyonlarınızı Kontrol Edin

Veritabanınızdaki fonksiyonların hangisi DEFINER, hangisi INVOKER — bir bakın:

SELECT 
  proname AS fonksiyon_adi,
  CASE prosecdef 
    WHEN true THEN '⚠️ DEFINER' 
    ELSE '✅ INVOKER' 
  END AS guvenlik_modu
FROM pg_proc
JOIN pg_namespace ON pronamespace = pg_namespace.oid
WHERE nspname = 'public'
ORDER BY prosecdef DESC;

DEFINER olan fonksiyonlarınız varsa, gerçekten DEFINER olmaları gerektiğinden emin olun.

Özet

  • INVOKER = güvenli varsayılan. RLS çalışır, her kullanıcı kendi verisini görür.
  • DEFINER = güçlü araç ama dikkatli kullanın. RLS bypass eder.
  • DEFINER kullanıyorsanız, fonksiyon içinde kendiniz yetki kontrolü yapın.

Çoğu projede INVOKER yeterlidir. DEFINER sadece özel durumlarda gerekir.


Bu yazı AstaFlow Case Study serisinin bir parçasıdır.