Bir şey mi arıyorsunuz?

Bir konu, bir kelime, aklınızda kalan bir cümle.

Yazılara dön

ERP’de herkes her şeyi görmesin: KOBİ için yetki matrisi

KOBİ'lerde ERP ve CRM yetkileri nasıl kurulur? İşlem ve kapsam bazlı matris, görev ayrılığı, KVKK tedbirleri ve Laravel policy örneğiyle adım adım rehber.

ERP'de herkes her şeyi görmesin: KOBİ için yetki matrisi yazısının kapak görseli

KOBİ’lerde ERP ya da CRM’e geçişin ilk haftalarında yetki konusu genellikle tek cümleyle kapanır: “Şimdilik herkes yönetici olsun, sonra düzeltiriz.” O “sonra” çoğu zaman gelmez. Bir yıl sonra satış temsilcisi bütün müşteri listesini Excel’e aktarabiliyor, ayrılan çalışanın hesabı hâlâ açık, ödemeyi hazırlayan kişi onayı da kendisi veriyor.

Bu yazının tezi şu: Yetki sorunu teknik değil, tanım sorunudur. Roller kişilere göre değil, işlem × kayıt kapsamı ekseninde bir matriste yazılmalı; varsayılan cevap “hayır” olmalı; birbiriyle çatışan görevler aynı kişide birleşmemeli ve bu kural elle değil, düzenli çalışan bir kontrolle denetlenmeli. Aşağıda bunu küçük bir işletmenin de uygulayabileceği adımlara bölüyorum.

Sorun ne kadar yaygın?

OWASP’ın 2025 listesinde “Bozuk Erişim Kontrolü” (Broken Access Control) yine ilk sırada. OWASP’ın aynı sayfada verdiği veriye göre test edilen uygulamaların yüzde 100’ünde bir tür erişim kontrolü hatası bulundu ve kategori 40 ayrı CWE ile eşleşiyor (OWASP A01:2025). Bu, her uygulamanın açık olduğu anlamına gelmez; ama erişim kontrolünün “bir kere yazılıp unutulan” bir konu olmadığını gösterir.

Türkiye’de konunun bir de hukuki boyutu var. 6698 sayılı Kanun’un 12. maddesi veri sorumlusuna, kişisel verilere hukuka aykırı erişimi önlemek için “gerekli her türlü teknik ve idari tedbirleri” alma yükümlülüğü getiriyor (KVKK). Kurum’un Kişisel Veri Güvenliği Rehberi de (Nisan 2025 baskısı) iki somut şey söylüyor: erişim hakkı verilirken “Yasaklanmadıkça Her Şey Serbesttir” değil “İzin Verilmedikçe Her Şey Yasaktır” prensibiyle hareket edilmeli ve veri sorumluları bir “erişim yetki ve kontrol matrisi” oluşturmalı (KVKK Rehberi). CRM’deki müşteri kartları, ERP’deki personel ve cari bilgileri bu kapsamın tam ortasında duruyor.

Neden zor?

Yetki matrisini kâğıda dökmek kolay görünür; zorluk, işletmenin gündelik akışında matrisin yavaş yavaş bozulmasıdır. Dört tipik sebep var:

  • Roller kişiye göre büyür. “Ayşe Hanım rolü” diye başlayan bir rol, Ayşe Hanım’ın üstlendiği her yeni işle genişler. Bir süre sonra kimse o rolün neyi kapsadığını bilmez.
  • Ekran yetkisi kayıt yetkisi değildir. Menüde “Teklifler” görünmüyor diye kullanıcı teklife erişemez sanılır. Oysa adres çubuğundaki numarayı değiştirerek başka şubenin teklifine ulaşılabiliyorsa yetki yoktur, yalnızca menü gizlenmiştir. OWASP bunu güvensiz doğrudan nesne erişimi (IDOR) örneği olarak sayıyor.
  • Yetkiler birikir. Görev değiştiren çalışana yeni rol eklenir, eskisi kaldırılmaz. Vekâlet için verilen yetki, vekâlet bitince unutulur.
  • “Herkes yönetici” kolaydır. Kısıtlı yetki her gün küçük bir sürtünme üretir (“bunu neden göremiyorum?”). Geniş yetki ise sorunu görünmez kılar; bedeli bir veri sızıntısında ya da bir hatalı ödemede ödenir.

Adım adım yaklaşım

1. Önce işlemleri listeleyin, rolleri değil

Başlangıç noktası kişiler değil, sistemdeki işlemlerdir. Her modül için aynı fiil listesini kullanın: görüntüle, oluştur, düzenle, onayla, iptal et, dışa aktar. “Dışa aktar” özellikle ayrı bir satır olmalı; ekranda müşteri kartını görmek ile 5.000 müşteriyi tek dosyada almak aynı risk değildir. Bu envanter, KVKK rehberinin istediği matrisin satırlarını oluşturur.

2. Her yetkiye bir kapsam ekleyin

ERP ve CRM’deki çoğu sızıntı “işlem” değil “kapsam” hatasıdır. Satış temsilcisi teklif görüntüleyebilmeli, ama hangi teklifleri? Pratikte üç kademe çoğu KOBİ için yeterli: kendi kayıtları, şube/ekip kayıtları, tümü. Yetkiyi bu kapsamla birlikte adlandırın: quotes.view.own, quotes.view.branch, quotes.view.all. Böylece “bu kişi neyi görüyor?” sorusu yetki adından okunur.

3. Rolleri görev tanımından türetin

Rol, bir görev tanımının sistemdeki karşılığıdır: satın alma uzmanı, muhasebe uzmanı, finans müdürü, satış temsilcisi, satış müdürü. Kişiye rol atanır, role yetki. Kişiye doğrudan yetki vermek istisna olmalı. Laravel ekosisteminde yaygın kullanılan spatie/laravel-permission paketinin belgeleri de aynı şeyi söylüyor: kullanıcılara doğrudan yetki “nadiren” verilmeli ve kod mümkün olduğunca rolü değil yetkiyi kontrol etmeli (Spatie, Roles vs Permissions). Kodda hasRole('Satış Müdürü') yazarsanız rol değiştiğinde kodu da değiştirmek zorunda kalırsınız; can('quotes.approve-discount') yazarsanız yalnızca matris değişir.

4. Çatışan görevleri baştan yazın

Görev ayrılığı (separation of duties) büyük şirketlere özgü bir denetim terimi gibi durur ama KOBİ’de daha da önemlidir, çünkü kişi sayısı azdır ve görevler kolayca tek kişide toplanır. NIST SP 800-53’ün AC-5 kontrolü, görev ayrılığının yetkili ayrıcalıkların kötüye kullanılma ihtimaline karşı olduğunu ve işlevlerin farklı kişi ya da rollere bölünmesini örnek verdiğini belirtiyor (NIST AC-5). Bir ERP için tipik çatışma çiftleri şunlar:

  • Tedarikçi kartı açmak ↔ ödeme onaylamak
  • Ödemeyi hazırlamak ↔ ödemeyi onaylamak
  • Teklif hazırlamak ↔ aynı teklifin iskontosunu onaylamak
  • Stok sayımını girmek ↔ sayım farkını onaylamak
  • Kullanıcılara yetki vermek ↔ erişim kayıtlarını incelemek

Bence bu listenin asıl değeri yazılmış olmasıdır. Küçük bir işletmede her çatışmayı ayırmak mümkün olmayabilir; o zaman çatışma bilinçli bir istisna olarak kayda geçer ve telafi edici bir kontrol eklenir (örneğin belirli tutarın üstündeki ödemelerde ikinci onay ya da aylık mutabakat).

5. Varsayılan cevap “hayır”, kontrol sunucuda

OWASP’ın ilk önerisi kamuya açık kaynaklar dışında varsayılanın reddetmek olması; ikincisi de erişim kontrolünün kayıt sahipliğini zorlaması (“enforce record ownership”) (OWASP A01:2025). Bu, kontrolün arayüzde değil sunucu tarafında, her istekte ve tek bir mekanizmada yapılması demek. Laravel’de bunun yeri policy sınıflarıdır (Laravel Authorization).

6. Yaşam döngüsünü sürece bağlayın

Yetki matrisi bir kerelik proje değildir. Üç olayı insan kaynakları ya da yönetim süreciyle eşleştirin: işe giriş (rol, görev tanımına göre atanır), görev değişikliği (eski rol aynı gün kaldırılır, yeni rol eklenir), ayrılış (hesap aynı gün kapatılır). KVKK rehberi de ilişiği kesilen çalışanların hesabının “zaman kaybetmeksizin” silinmesini ya da girişlerinin kapatılmasını istiyor. Buna ek olarak üç ayda bir, her yöneticiye kendi ekibinin yetki listesini gönderip “bu hâlâ doğru mu?” diye onay almak birikmiş yetkileri temizler.

7. Kaydı tutun ve okuyun

Reddedilen erişim denemeleri, dışa aktarmalar, yetki değişiklikleri ve onaylar kim, ne zaman, hangi kayıt bilgisiyle kaydedilmeli. KVKK rehberi tüm kullanıcıların işlem hareketlerinin düzenli kaydını (log) ve erişim kontrolü kayıtlarının düzenli kontrol edilmesini sayıyor; OWASP da erişim kontrolü hatalarının kaydedilmesini ve gerektiğinde yöneticiye uyarı gitmesini öneriyor. Kimsenin okumadığı log, yalnızca disk doldurur; en azından tekrarlanan ret denemeleri ve olağan dışı büyüklükteki dışa aktarmalar için haftalık bir özet yeterlidir.

Örnek matris

Aşağıdaki tablo, teklif ve ödeme akışı için küçük bir matrisin nasıl görünebileceğini gösteriyor. Hücrelerde kapsam yazılı; boş hücre “yetki yok” demek.

İşlemSatış temsilcisiSatış müdürüMuhasebe uzmanıFinans müdürü
Teklif görüntüleKendiŞubeTümü (salt okunur)Tümü
Teklif hazırlaKendiŞube
İskonto onaylaŞube (kendi hazırladığı hariç)
Müşteri listesini dışa aktarŞubeTümü
Ödeme hazırlaTümü
Ödeme onaylaTümü (kendi hazırladığı hariç)
Kullanıcı yetkisi verYalnızca sistem yöneticisi rolü

Kontrol listesi

  • Her modül için işlem envanteri çıkarıldı mı (görüntüle, oluştur, düzenle, onayla, iptal, dışa aktar)?
  • Her yetkinin kapsamı (kendi, şube/ekip, tümü) adında yazıyor mu?
  • Roller görev tanımından mı türedi, kişi adından mı?
  • Kişiye doğrudan verilmiş yetkiler listelenip gerekçelendirildi mi?
  • Çatışan görev çiftleri yazıldı mı ve otomatik kontrol ediliyor mu?
  • Erişim kontrolü sunucu tarafında, her istekte ve liste sorgularında da uygulanıyor mu?
  • Ayrılan çalışanın hesabı aynı gün kapanıyor mu? Vekâlet yetkilerinin bitiş tarihi var mı?
  • Üç ayda bir yönetici onaylı yetki gözden geçirmesi yapılıyor mu?
  • Ret denemeleri, dışa aktarmalar ve yetki değişiklikleri kaydediliyor ve okunuyor mu?

Kod: Laravel’de kapsamlı policy

Aşağıdaki policy, teklif görüntüleme ve iskonto onayını kapsamla birlikte kontrol ediyor. Yetki adları matristeki adlarla aynı; $user->can() çağrıları spatie/laravel-permission gibi Laravel’in Gate yapısına bağlanan bir paketle ya da kendi Gate tanımlarınızla çalışır. Kapsam dışındaki kayıt için 403 yerine 404 dönmek, başka şubede böyle bir teklifin var olduğunu bile belli etmez; Laravel bunun için Response::denyAsNotFound() sunuyor.

<?php

namespace App\Policies;

use App\Models\Quote;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class QuotePolicy
{
    public function view(User $user, Quote $quote): Response
    {
        // Kapsam dışındaki kaydın varlığını bile belli etme: 404 dön.
        return $this->inScope($user, $quote, 'quotes.view')
            ? Response::allow()
            : Response::denyAsNotFound();
    }

    public function approveDiscount(User $user, Quote $quote): Response
    {
        if (! $this->inScope($user, $quote, 'quotes.view')) {
            return Response::denyAsNotFound();
        }

        if (! $user->can('quotes.approve-discount')) {
            return Response::deny('İskonto onaylama yetkiniz yok.');
        }

        // Görev ayrılığı: teklifi hazırlayan kişi kendi iskontosunu onaylayamaz.
        if ($quote->created_by === $user->id) {
            return Response::deny('Kendi hazırladığınız teklifin iskontosunu onaylayamazsınız.');
        }

        return Response::allow();
    }

    private function inScope(User $user, Quote $quote, string $permission): bool
    {
        return match (true) {
            $user->can("{$permission}.all") => true,
            $user->can("{$permission}.branch") => $quote->branch_id === $user->branch_id,
            $user->can("{$permission}.own") => $quote->owner_id === $user->id,
            default => false, // varsayılan: reddet
        };
    }
}

Controller tarafında tek satır yeterli; kontrol menüye değil, işlemin kendisine bağlı:

<?php

namespace App\Http\Controllers;

use App\Models\Quote;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Gate;

class QuoteController extends Controller
{
    public function approveDiscount(Quote $quote): RedirectResponse
    {
        Gate::authorize('approveDiscount', $quote);

        $quote->approveDiscount(auth()->user());

        return back();
    }
}

Policy’yi yazmak yalnızca tek kayıt erişimini korur. Liste ekranları, raporlar ve dışa aktarmalar aynı kapsam kuralını sorgu düzeyinde uygulamalı; aksi hâlde kayıt sayfası kapalıyken listede bütün şubelerin teklifleri görünür. Bu kodu PHP 8.3 ile sözdizimi kontrolünden geçirdim ve Laravel sınıflarının yerine konan basit taklitlerle altı senaryoda (kendi kaydı, başkasının kaydı, şube müdürü, başka şube müdürü, kendi teklifini onaylamaya çalışan müdür, yetkisiz temsilci) beklenen sonucu verdiğini doğruladım.

Kod: Görev ayrılığını otomatik denetlemek

Çatışma listesini belgede bırakmak yetmez; roller zamanla birikir. Aşağıdaki küçük Python betiği, rol tanımlarını ve kullanıcı atamalarını okuyup aynı kişide birleşen çatışmalı yetkileri raporluyor. Gerçek sistemde sözlükler veritabanından gelir; mantık aynı kalır.

# Görev ayrılığı (SoD) kontrolü: aynı kullanıcıda çatışan yetki var mı?
ROLLER = {
    "satin_alma_uzmani": {"tedarikci.olustur", "siparis.olustur"},
    "muhasebe_uzmani": {"fatura.kaydet", "odeme.hazirla"},
    "finans_muduru": {"odeme.onayla", "rapor.disa_aktar"},
    "satis_temsilcisi": {"teklif.hazirla", "musteri.goruntule.kendi"},
    "satis_muduru": {"teklif.iskonto_onayla", "musteri.goruntule.sube"},
}

# Aynı kişide birlikte bulunmaması gereken yetki çiftleri
CATISMALAR = [
    ("tedarikci.olustur", "odeme.onayla", "Tedarikçi açan kişi ödemeyi onaylamamalı"),
    ("odeme.hazirla", "odeme.onayla", "Ödemeyi hazırlayan kişi onaylamamalı"),
    ("teklif.hazirla", "teklif.iskonto_onayla", "Teklifi hazırlayan kendi iskontosunu onaylamamalı"),
]

KULLANICILAR = {
    "kullanici_1": ["satin_alma_uzmani"],
    "kullanici_2": ["muhasebe_uzmani", "finans_muduru"],  # vekâlet sonrası kalan rol
    "kullanici_3": ["satis_temsilcisi"],
    "kullanici_4": ["satis_temsilcisi", "satis_muduru"],
}


def yetkiler(roller):
    toplam = set()
    for rol in roller:
        toplam |= ROLLER[rol]
    return toplam


def catisma_bul(kullanicilar):
    bulgular = []
    for kullanici, roller in sorted(kullanicilar.items()):
        y = yetkiler(roller)
        for a, b, aciklama in CATISMALAR:
            if a in y and b in y:
                bulgular.append((kullanici, a, b, aciklama))
    return bulgular


if __name__ == "__main__":
    for kullanici, a, b, aciklama in catisma_bul(KULLANICILAR):
        print(f"{kullanici}: {a} + {b} -> {aciklama}")

Betik çalıştırıldığında şu çıktıyı veriyor:

kullanici_2: odeme.hazirla + odeme.onayla -> Ödemeyi hazırlayan kişi onaylamamalı
kullanici_4: teklif.hazirla + teklif.iskonto_onayla -> Teklifi hazırlayan kendi iskontosunu onaylamamalı

İkinci kullanıcının durumu tipik: muhasebe uzmanıyken vekâleten finans müdürü rolü verilmiş, vekâlet bitince rol geri alınmamış. Bu kontrolü haftalık zamanlanmış bir iş olarak çalıştırıp çıktıyı yöneticiye göndermek, üç aylık gözden geçirmeyi beklemeden sorunu yakalar.

Sık yapılan hatalar

  • Menüyü gizleyip yetki verdiğini sanmak. Arayüz kolaylıktır, güvenlik sınırı değildir; her istek sunucuda kontrol edilmeli.
  • Kodda rol adı kontrol etmek. hasRole() ile dolu bir kod tabanı, organizasyon şeması değiştiğinde yeniden yazılır.
  • Tek kayıt sayfasını koruyup listeyi unutmak. Kapsam kuralı liste sorgularına, raporlara ve API uç noktalarına da uygulanmalı.
  • “Süper kullanıcı”yı günlük işte kullanmak. KVKK rehberi yönetici yetkisinin yalnızca ihtiyaç olduğunda açılmasını öneriyor; günlük işler sıradan hesapla yapılmalı.
  • Dışa aktarmayı görüntüleme ile aynı yetkiye bağlamak. Toplu veri çıkışı ayrı yetki, ayrı kayıt ister.
  • Vekâleti süresiz vermek. Geçici yetkinin bitiş tarihi olmalı ve süre dolunca kendiliğinden kalkmalı.

Bir de karşı görüşe değineyim: “Beş kişilik şirkette bu kadar ayrıntı bürokrasidir.” Kısmen haklı; beş kişide her çatışmayı ayırmak mümkün değil. Ama bence matrisi yazmanın maliyeti bir öğleden sonra, yazmamanın maliyeti ise bir sızıntıda ya da hatalı ödemede “kim, neyi, neden görebiliyordu?” sorusuna cevap verememektir. Küçük işletmede çözüm ayrıntılı rol değil, az sayıda iyi adlandırılmış rol ve yazılı istisnalardır. Müşteri verisini CRM’e taşırken gereksiz kişisel veriyi dışarıda bırakmayı Excel’den CRM’e geçiş yazısında anlatmıştım; yetki matrisi o temizliğin ikinci yarısıdır. Teklif onay akışının kendisi için de tekliften siparişe geçiş yazısına bakabilirsiniz.

Özet

  • Yetkiyi işlem × kapsam olarak adlandırın: quotes.view.own, quotes.view.branch, quotes.view.all.
  • Kişiye rol, role yetki verin; kodda rolü değil yetkiyi kontrol edin.
  • Varsayılan cevap “hayır” olsun; kontrol sunucuda, tek kayıtta ve listede birlikte yapılsın.
  • Çatışan görev çiftlerini yazın ve haftalık bir betikle denetleyin.
  • Giriş, görev değişikliği ve ayrılışı yetki sürecine bağlayın; üç ayda bir yönetici onayı alın.
  • Ret denemelerini ve dışa aktarmaları kaydedin, en azından haftalık özetini okuyun.

Sizin sisteminizde yetkiler hangi eksende tanımlı: kişiye göre mi, role göre mi, yoksa kapsamla birlikte mi? Benzer bir süreç üzerinde çalışıyorsanız deneyimlerinizi paylaşın.


Kaynaklar

Bir Cevap Yazın

Bu site istenmeyenleri azaltmak için Akismet kullanır. Yorum verilerinizin nasıl işlendiğini öğrenin.