İleri ABAC: Field-Level İzinler ve Veritabanı Entegrasyonu
ABAC'ı ortam bazlı kurallar, alan seviyesinde okuma ve yazma izinleri ve tekrarlanan izin mantığını ortadan kaldıran otomatik veritabanı sorgusu filtreleme ile genişletin.
Özet
Post 104 builder pattern ile type-safe bir ABAC policy engine oluşturdu. can(user, action, resource, data?) fonksiyonu özne, kaynak ve eylem niteliklerini deklaratif koşullar aracılığıyla değerlendiriyor. Sahiplik, departman kapsamı ve kaynak durumu artık dağınık yardımcı fonksiyonlar değil, policy kuralları.
İki boşluk kaldı. Birincisi, can() bir boolean döndürüyor; kullanıcı ya kaynağın tamamını görüyor ya da hiçbir şeyi. Bir kullanıcının hangi alanları okuyup yazabileceğini kontrol etmenin yolu yok. Bir admin internalNotes alanını görüyor; bir yazar görmemeli. Bir editör content alanını güncelleyebiliyor ama publishedAt alanını güncelleyememeli. İkincisi, tüm koşullar zaten yüklenmiş nesneler üzerinde uygulama belleğinde değerlendiriliyor. Liste görünümleri için uygulama tüm kayıtları yüklüyor, her biri üzerinde can() çağırıyor ve başarısız olanları atıyor. Bu filtrelemeyi veritabanı yapmalı.
NIST SP 800-162 modeli ayrıca dördüncü bir nitelik kategorisi tanımlıyor: ortam. Post 104 bunu tanıttı ama ileriye dönük bir referans olarak bıraktı. Zaman bazlı erişim, IP kısıtlamaları ve feature flag'ler ayrı middleware kontrolleri olarak değil, policy engine içinde olmalı.
Bu yazı üç boşluğu da kapatıyor: ortam koşulları tip sistemine giriyor, alan seviyesinde izinler rol başına okuma ve yazma görünürlüğünü kontrol ediyor ve ABAC koşulları sorgu seviyesinde uygulama için veritabanı where cümlelerine dönüştürülüyor.
Ortam Bazlı Kurallar
Tip Sisteminin Genişletilmesi
Post 104'ün Condition<R> tipi (user: User, data: ResourceDataMap[R]) => boolean alıyor. Ortam nitelikleri (zaman, IP adresi, yerel ayar, feature flag'ler) hem özne hem de kaynak dışında kalıyor. Kendi tiplerine ihtiyaçları var.
can() imzası seri boyunca evrildi:
env parametresi opsiyonel. Post 104'ten mevcut koşullar değişmeden çalışmaya devam ediyor. Ortamı opsiyonel yapmak geriye dönük uyumluluğu korurken NIST dört-nitelik modelini tamamlıyor.
Pratik Örnekler
Zaman bazlı kısıtlama: fatura işlemleri yalnızca mesai saatlerinde:
IP bazlı kısıtlama: admin işlemleri ofis ağıyla sınırlı:
Feature flag geçişi: yeni işlevsellik feature flag'lerin arkasında:
Note: Ortam koşulları builder'daki diğer koşullarla aynı AND mantığını takip eder. İznin verilmesi için dizideki tüm koşulların geçmesi gerekir.
Service Layer Entegrasyonu
Service layer, Environment nesnesini istek bağlamından oluşturuyor. İstek başına bir kez oluşturur, sonra aktarır:
Önemli bir tasarım kararı: ortam verileri istekten gelir, veritabanından değil. ResourceDataMap'in parçası olmamalı. İstek bağlamını kaynak verisiyle karıştırmak NIST modelini bozar ve koşulları anlamayı zorlaştırır.
Alan Seviyesinde Okuma İzinleri
Problem
Şu alanlara sahip bir document kaynağını düşünün: id, title, content, status, authorId, departmentId, internalNotes, reviewComments, publishedAt.
Farklı roller farklı alan görünürlüğüne ihtiyaç duyar:
Alan seviyesinde izinler olmadan servis tam nesneyi döndürür ve frontend'in alanları gizlemesine güvenir. Bu belirsizliğe dayalı güvenlik. API yanıtı, UI'ın ne render ettiğinden bağımsız olarak hassas veri içerir.
Tip Sistemi Genişletmesi
Alan izin sistemi kaynakları alan adlarıyla ve koşullarla eşler:
Builder'a alan izinlerini ekle:
Konvansiyon: canReadFields girişlerinin yokluğu, rolün can() aracılığıyla read erişimi olması koşuluyla o rol için tüm alanların görünür olduğu anlamına gelir.
getVisibleFields() Fonksiyonu
filterFields() yardımcı fonksiyonu bir kaynak nesnesi ve görünür alanlar listesi alır, yalnızca o alanları içeren yeni bir nesne döndürür:
Service Layer Entegrasyonu
UI'da Alan Gizleme
İstemci tarafında, alanları koşullu olarak render etmek için getVisibleFields() kullanın:
UI gizleme UX içindir, güvenlik için değil. Service layer API yanıtındaki alanları zaten filtreledi. İstemci almadığı şeyi render edemez. Post 102'de belirlendiği gibi: sunucu güvenlik sınırıdır, istemci UX kolaylığıdır.
Alan Seviyesinde Yazma İzinleri
Builder Genişletmesi
Yazma izinleri okuma izinlerinden farklıdır. Bir editör internalNotes alanını okuyabilir ama yazamayabilir. Bir moderatör status yazabilir ama content yazamayabilir.
Yazma izin matrisi:
pickPermittedFields() Fonksiyonu
Sessiz Düşürme vs. Hata
Bir kullanıcı yasaklanmış bir alan gönderdiğinde iki yaklaşım:
- Sessiz düşürme: Alanı çıkar ve devam et. Kullanıcı alanın yok sayıldığını bilmez. İstemci için daha basit, ama hataları gizler.
- Hata: Tüm gönderimi 403 ile reddet. Daha açık, ama istemcinin göndermeden önce hangi alanlara izin verildiğini bilmesini gerektirir.
Deneyimlerime göre, API'ler için sessiz düşürme, admin bağlamları için hata iyi çalışıyor. Service layer, işlemin hassasiyetine göre hangisini kullanacağını seçer.
Create ve Update Uygulaması
Create Akışı
Create işleminde mevcut kaynak verisi yoktur. Kaynak niteliklerine referans veren koşullar (sahiplik, departman) değerlendirilemez. Create için alan izinleri koşulsuz girişler kullanmalıdır:
authorId ve status sistem tarafından yönetilen alanlardır, asla kullanıcı girdisinden gelmez. İstemci authorId gönderse bile pickPermittedFields() onu çıkarır çünkü yazarın yazma alanları arasında değildir.
Update Akışı
Update işleminde mevcut kaynak verisi koşullar için kullanılabilir:
Koşullu Form Render
UI, form alanlarını koşullu olarak render etmek için yazma alan izinlerini kullanabilir:
Form render'ı UX'tir. Sunucu tarafındaki pickPermittedFields() güvenlik sınırıdır. Kötü niyetli bir istemci form gönderisine gizli alanlar eklese bile pickPermittedFields() bunları çıkarır.
Otomatik Veritabanı Sorgusu Filtreleme
ConditionDescriptor Yaklaşımı
Post 104'ün koşulları opak fonksiyonlardır. Bellekte değerlendirilir ama SQL'e çevrilemez. Liste görünümleri için ("okuyabileceğim tüm belgeleri göster") tüm kayıtları yükleyip can() ile bir döngüde filtrelemek israf.
Fikir: her koşul fonksiyonunun yanında, veritabanı terimleriyle ne yaptığını açıklayan bir deklaratif tanımlayıcı sağla:
Bu sözdizimi MongoDB'nin sorgu formatına ve CASL'ın koşullarına benzer. Prisma, Drizzle ve diğer ORM'ler bunu basit bir adaptörle tüketebilir.
Tanımlayıcılarla güncellenmiş builder:
toWhereClause() Fonksiyonu
Warning: Boş nesne
{}venullfarklı anlamlara sahiptir.{}"filtre yok; tüm kayıtları döndür" anlamına gelir (admin/viewer durumu).null"eşleşen izin yok; erişimi reddet" anlamına gelir. Reddedilen bir rol içinnullyerine{}döndürmek tüm kayıtları döndürür. Bu ayrım güvenlik açısından kritiktir.
ORM Adaptör Katmanı
WhereClause<R> ORM-bağımsızdır. Adaptörler bunu ORM'ye özel sözdizimine dönüştürür:
Liste görünümleri için tam service layer entegrasyonu:
Veritabanına Aktarılamayan Koşullar
Tüm koşullar çevrilebilir değildir. Karmaşık mantık, kaynaklar arası koşullar ve ortam bazlı kontroller genellikle bellekte kalır:
(user, doc, env) => env.currentTime.getHours() >= 9: çalışma zamanı bağlamını içerir, kaynak niteliği değil(user, doc) => doc.tags.some(t => user.expertise.includes(t)): dizi kesişim mantığı(user, doc) => doc.wordCount > 1000 && user.role === 'senior_editor': özne ve kaynağı karıştıran bileşik mantık
toFilter alanı opsiyoneldir. Belirtilmezse koşul bellek içi değerlendirmeye geri döner. Sistem nazikçe degrade olur: çevrilebilir koşullar WHERE cümlelerine dönüşür, çevrilemeyenler post-fetch filtreleme gerektirir.
Bu, üretim yetkilendirme sistemleri tarafından kullanılan aynı kalıptır. OPA'nın Compile API'si buna "kısmi değerlendirme" diyor. Cerbos'un PlanResources API'si üç sonuç döndürüyor: ALWAYS_ALLOWED, ALWAYS_DENIED veya bir AST ile CONDITIONAL. Buradaki hafif TypeScript sürümü daha az altyapıyla aynı prensibi takip ediyor.
Birleşik Sistem
Tamamlanan sistem policy builder'ı tek doğruluk kaynağı yapıyor. Builder'daki bir koşulu değiştirmek otomatik olarak her katmana yayılır:
can(): kayıt seviyesinde erişim kontrolügetVisibleFields(): alan seviyesinde okuma izinleripickPermittedFields(): alan seviyesinde yazma izinleritoWhereClause(): veritabanı sorgusu filtreleme
Hiçbir service metodu, React bileşeni veya veritabanı sorgusunun değişmesi gerekmez.
Örnek: "Editörler artık belge review durumundaysa reviewComments alanını da görebilir." Builder'da tek bir değişiklik:
getVisibleFields()artıkstatus === 'review'olduğunda editörler içinreviewCommentsdöndürüyor- React bileşeni zaten
{fields.includes('reviewComments') && ...}içeriyor; otomatik olarak render ediyor - API yanıtı zaten
filterFields()kullanıyor; alanı otomatik olarak dahil ediyor - Service metodu değişikliği yok. Bileşen değişikliği yok. Veritabanı sorgusu değişikliği yok.
ABAC Artıları ve Eksileri
ABAC'ın Öne Çıktığı Durumlar
- Kaynak başına 3+ bağlamsal kural: İzinler sahiplik, departman, durum, zaman ve diğer niteliklere bağlıysa, ABAC Post 103'teki yardımcı fonksiyon çoğalmasını ortadan kaldırır.
- Alan seviyesinde görünürlük gereksinimleri: Farklı roller aynı kaynağın farklı alanlarını gördüğünde, alan izinleri ad-hoc alan çıkarma işleminden daha temizdir.
- Veritabanı seviyesinde uygulama gerekli: Liste görünümleri verimli olmalıysa,
toWhereClause()kalıbı bellek içi filtrelemeyi ortadan kaldırır. - Denetim gereksinimleri: Merkezi bir policy builder, dağınık yardımcı fonksiyonlardan denetlemesi daha kolaydır. "Bir editör ne yapabilir?" sorusu builder'ın bir bölümünü okuyarak cevaplanabilir.
- Policy değişiklikleri sık: İş kuralları sık değiştiğinde, builder'daki tek bir koşulu değiştirmek tüm service metotlarını ve bileşenleri güncellemekten daha hızlı ve güvenlidir.
ABAC'ın Gereksiz Olduğu Durumlar
- Basit rol bazlı erişim: İzinler yalnızca role bağlıysa ve bağlamsal koşullar yoksa, Post 103'ten RBAC daha basit ve eşit derecede doğrudur.
- Küçük ekip, az kaynak: 2-3 kaynak ve 3-4 rolle, ABAC tip sistemi ek yükü (generic'ler, builder'lar, koşul tanımlayıcıları) kurtardığı karmaşıklığı aşabilir.
- Alan seviyesinde gereksinim yok: Tüm kullanıcılar bir kaynağın tüm alanlarını görüyorsa, alan izin katmanı fayda sağlamadan karmaşıklık ekler.
- Prototipleme aşaması: ABAC'ın tip sistemi yeniden düzenlemeyi zorlaştırır. Kaynak şekillerinin sık değiştiği hızlı prototipleme sırasında daha basit kontroller daha pratiktir.
Tip: RBAC (Post 103) ile başla. Yardımcı fonksiyonlar çoğalmaya başladığında ABAC koşullarını ekle. Farklı roller farklı alan görünürlüğüne ihtiyaç duyduğunda alan seviyesinde izinleri ekle. Liste görünümleri çok fazla kayıt yüklediğinde DB sorgusu filtrelemeyi ekle.
Karar Çerçevesi
Yaygın Tuzaklar
- İç içe kaynaklarda alan filtrelemeyi unutmak: Bir belgenin
projectilişkisi varsa,document.projectyüklemek proje alan izinlerini atlar. İç içe kaynaklara dafilterFields()uygulayın. - Koşul tanımlayıcı sapması:
evaluatefonksiyonu vetoFiltertanımlayıcısı eşdeğer sonuçlar üretmelidir. Sapmayı yakalamak için her ikisini de aynı doğruluk tablosuna karşı test edin. - Alan seviyesinde izinleri aşırı kullanmak: Her kaynak alan seviyesinde kontrole ihtiyaç duymaz. Varsayılan (alan girişi yok = tüm alanlar görünür) kısıtlaması olmayan kaynaklar için işleri basit tutar.
nullve{}karıştırmak:toWhereClause()'da{}"filtre yok" (tüm kayıtlar) venull"erişim yok" (reddet) anlamına gelir. Bunu yanlış yapmak güvenlik açığıdır.- DB sorgularında
selecteksikliği:toWhereClause()satırları filtreler, sütunları değil. Gerçek DB seviyesinde alan uygulaması için bunu sütun seçimiyle birleştirin. Pratikte uygulama seviyesindefilterFields()genellikle yeterlidir.
Sırada Ne Var
İzin sistemi artık kayıt seviyesinde erişimi (can()), alan seviyesinde görünürlüğü (getVisibleFields(), pickPermittedFields()) ve veritabanı seviyesinde uygulamayı (toWhereClause()) kapsıyor. Ortam koşulları NIST dört-nitelik modelini tamamlıyor.
Post 106 kalan üretim endişelerini ele alıyor: çok kiracılık (kiracı izolasyonunu birinci sınıf bir izin kavramı olarak), izin kütüphanesi değerlendirmesi (CASL, Oso, Cerbos, Cedar; ne zaman kütüphane, ne zaman özel kod kullanılmalı) ve ekip boyutu, düzenleyici gereksinimler ve sistem karmaşıklığına göre doğru yetkilendirme yaklaşımını seçmek için son mimari karar çerçevesi.
Kaynaklar
- NIST SP 800-162: Guide to Attribute Based Access Control (ABAC) - Özne, kaynak ve eylem yanında ortam niteliklerini dördüncü ABAC kategorisi olarak tanımlayan temel NIST standardı
- CASL v6 - Restricting Fields Access -
permittedFieldsOf()ve kural tanımlarındaki alan dizileri kullanarak alan seviyesinde kısıtlamalar hakkında resmi CASL dokümantasyonu - CASL - Isomorphic Authorization JavaScript Library - JavaScript/TypeScript'te
can()+ alan kısıtlama kalıbına öncülük eden ve MongoDB tarzı koşullarla veritabanı sorgularına eşleme yapan kütüphane - Cerbos - Filtering Database Results with Query Plans - Cerbos PlanResources API'sinin adaptörlerin Prisma, MongoDB veya SQL sorgularına dönüştürdüğü bir AST döndürmesi
- Cerbos Prisma Integration V2.0 - Yetkilendirme sorgu planlarını iç içe alan desteğiyle Prisma
wherecümlelerine dönüştürme - OPA - Write Policy in OPA, Enforce Policy in SQL - Rego policy'lerini SQL WHERE cümlelerine derleyen OPA kısmi değerlendirme kalıbı
- Permit.io - Why Data Filtering Matters for Database Authorization - OPA, Cerbos ve SpiceDB genelinde kaynak seviyesi ve geçit seviyesi veri filtreleme kalıplarının analizi
- Drizzle ORM - Dynamic Query Building - Birleştirilebilir sorgu oluşturma için
$dynamic()hakkında resmi Drizzle dokümantasyonu - RBAC vs ABAC: Differences and When to Use (Oso) - ABAC karmaşıklığının ne zaman haklı olduğu ve RBAC'ın ne zaman yeterli olduğuna dair karar kriterleri
- ZenStack - Three Ways to Secure Database APIs - Veritabanı seviyesi (RLS), ORM seviyesi ve uygulama seviyesi yetkilendirme stratejilerinin karşılaştırması
- TypeScript Generics Documentation - ABAC tip sistemi boyunca kullanılan generic kısıtlama kalıpları için resmi referans
- OWASP Authorization Cheat Sheet - Merkezi yetkilendirme, varsayılan olarak reddet ve ayrıntılı erişim kontrolü önerileri dahil en iyi uygulamalar
Ölçeklenebilir İzin Sistemleri
TypeScript ve Next.js ile ölçeklenebilir izin sistemleri oluşturma rehberi. Basit kontrol mekanizmalarından RBAC ve ABAC'a, oradan multi-tenant yetkilendirme sistemlerine kadar kapsamlı bir seri.