İçeriğe geç
academia.sh

Ders 19 / 23

İlişki Tabanlı Erişim Denetimi

Özneler ve nesneler arasındaki bağların birinci sınıf veri hâline gelmesi, erişim sorusunun çizge üzerinde bir ulaşılabilirlik sorgusuna dönüşmesi, geçişken erişim ve tek nesne denetimiyle listeleme sorgusunun maliyet farkı.

İçindekiler

Önceki iki derste S7 yanlış karara bağlandı. U-1001, dokuz numaralı okuma listesini U-1002 ile paylaşmıştı; U-1002 bu listeyi görebilmeliydi. Ne rol tabanlı model ne öznitelik tabanlı model bunu ifade edebildi, çünkü paylaşım ne öznenin ne nesnenin bir özelliğidir — ikisi arasındaki bağdır.

İlişki tabanlı erişim denetimi (relationship-based access control) bu bağları birinci sınıf veri hâline getirir. Sistemdeki özneler ve nesneler bir çizgenin düğümleri, aralarındaki bağlar da adlandırılmış kenarlarıdır. Erişim sorusu, bu çizge üzerinde bir ulaşılabilirlik sorgusuna dönüşür: özneden nesneye, belirli bir kenar dizisini izleyerek gidilebiliyor mu?

Çizge, Kenarlar ve Yol Desenleri

Kütüphane ödünç servisinin ilişkileri az sayıda kenar türüyle yazılabilir. sahip, bir üyeyi kendi kaydına bağlar. ait, bir kaydı şubesine bağlar. calisir ve yonetir, personeli şubesine bağlar. paylasildi, bir listeyi paylaşıldığı kişiye ya da gruba bağlar. uyesi, bir kişiyi gruba bağlar.

Erişim kuralı, bu kenarlardan oluşan bir yol desenidir. “Kendi ödünç kaydını görüntüleyebilir” kuralı tek adımlık bir desendir: sahip. “Şube görevlisi şubesinin kayıtlarını görüntüleyebilir” kuralı iki adımlıdır ve ikinci adımda kenar ters yönde izlenir: personelden şubeye calisir, şubeden kayda ait kenarının tersi.

Aşağıdaki değerlendirici bu desenleri yürütüyor. Her adımda bir düğüm kümesinden komşu kümesine geçiliyor; bu, Veri Yapıları kursundaki enine aramanın kenar türüne göre süzülmüş biçimidir.

cat > rebac.mjs <<'EOF'
const KENARLAR = [                      // [kaynak, iliski, hedef]
  ["U-1001", "sahip", "odunc:501"], ["U-1002", "sahip", "odunc:502"],
  ["U-1005", "sahip", "odunc:503"], ["U-1001", "sahip", "liste:9"],
  ["odunc:501", "ait", "S-02"], ["odunc:502", "ait", "S-02"],
  ["odunc:503", "ait", "S-01"], ["ceza:77", "ait", "S-02"],
  ["P-2001", "calisir", "S-02"], ["Y-3001", "yonetir", "S-02"],
  ["liste:9", "paylasildi", "U-1002"], ["liste:9", "paylasildi", "grup:okuma-kulubu"],
  ["U-1006", "uyesi", "grup:okuma-kulubu"],
];

// Desendeki "~iliski", kenarin ters yonde izlenmesi demektir.
const DESENLER = {
  "goruntule:odunc": [["sahip"], ["calisir", "~ait"], ["yonetir", "~ait"]],
  "guncelle:odunc":  [["calisir", "~ait"], ["yonetir", "~ait"]],
  "olustur:odunc":   [["calisir", "~ait"], ["yonetir", "~ait"]],
  "sil:odunc":       [["yonetir", "~ait"]],
  "sil:ceza":        [["yonetir", "~ait"]],
  "goruntule:liste": [["sahip"], ["~paylasildi"], ["uyesi", "~paylasildi"]],
};

function adim(dugumler, iliski) {
  const ters = iliski.startsWith("~");
  const ad = ters ? iliski.slice(1) : iliski;
  const sonuc = new Set();
  for (const [k, i, h] of KENARLAR) {
    if (i !== ad) continue;
    if (!ters && dugumler.has(k)) sonuc.add(h);
    if (ters && dugumler.has(h)) sonuc.add(k);
  }
  return sonuc;
}

function rebac(ozne, eylem, nesne) {
  for (const desen of DESENLER[`${eylem}:${nesne.split(":")[0]}`] ?? []) {
    let cephe = new Set([ozne]);
    for (const iliski of desen) cephe = adim(cephe, iliski);
    if (cephe.has(nesne)) return [true, desen.join(" -> ")];
  }
  return [false, "ulasilabilir yol yok"];
}

const SENARYOLAR = [
  ["S1", "U-1001", "goruntule", "odunc:501", true],
  ["S2", "U-1001", "goruntule", "odunc:502", false],
  ["S3", "P-2001", "goruntule", "odunc:502", true],
  ["S4", "P-2001", "goruntule", "odunc:503", false],
  ["S5", "P-2001", "guncelle",  "odunc:502", false],
  ["S6", "Y-3001", "sil",       "ceza:77",   true],
  ["S7", "U-1002", "goruntule", "liste:9",   true],
  ["S8", "U-1005", "goruntule", "liste:9",   false],
];

console.log("senaryo | ReBAC | dogru | uyum | bulunan yol");
console.log("--------|-------|-------|------|--------------------------");
let uyan = 0;
for (const [ad, ozne, eylem, nesne, dogru] of SENARYOLAR) {
  const [karar, yol] = rebac(ozne, eylem, nesne);
  if (karar === dogru) uyan++;
  console.log(`${ad}      | ${(karar ? "izin" : "ret").padEnd(5)} | ` +
    `${(dogru ? "izin" : "ret").padEnd(5)} | ${(karar === dogru ? "+" : "-").padEnd(4)} | ${yol}`);
}
console.log(`\nReBAC dogru karar: ${uyan}/${SENARYOLAR.length}`);

console.log("\ngecisken erisim (ortak kume disinda):");
for (const kod of ["U-1006", "U-1005"]) {
  const [karar, yol] = rebac(kod, "goruntule", "liste:9");
  console.log(`  ${kod} -> liste:9 : ${karar ? "izin" : "ret"}  (${yol})`);
}
EOF
node rebac.mjs
senaryo | ReBAC | dogru | uyum | bulunan yol
--------|-------|-------|------|--------------------------
S1      | izin  | izin  | +    | sahip
S2      | ret   | ret   | +    | ulasilabilir yol yok
S3      | izin  | izin  | +    | calisir -> ~ait
S4      | ret   | ret   | +    | ulasilabilir yol yok
S5      | izin  | ret   | -    | calisir -> ~ait
S6      | izin  | izin  | +    | yonetir -> ~ait
S7      | izin  | izin  | +    | ~paylasildi
S8      | ret   | ret   | +    | ulasilabilir yol yok

ReBAC dogru karar: 7/8

gecisken erisim (ortak kume disinda):
  U-1006 -> liste:9 : izin  (uyesi -> ~paylasildi)
  U-1005 -> liste:9 : ret  (ulasilabilir yol yok)

S7 üç derste ilk kez doğru nedenle çözüldü: listeye U-1002’den bir paylasildi kenarı gidiyor. S8 aynı desenle reddedildi, çünkü U-1005’e giden kenar yok.

Son iki satır modelin asıl gücünü gösteriyor. U-1006 listeye doğrudan bağlı değil; okuma kulübü grubunun üyesi ve liste o grupla paylaşılmış. İki adımlık yol bu bağı kuruyor. Öznitelik tabanlı modelde aynı sonuca ulaşmak için grubun tüm üyelerinin nesnenin öznitelik alanına kopyalanması ve grup her değiştiğinde bu kopyanın güncellenmesi gerekirdi. Çizgede grup üyeliği tek bir kenardır ve kenarın eklenmesi tüm listeleri etkiler.

Modelin Ulaşamadığı Yer

S5 bu modelde yanlış. Ödünç kaydının mesai dışında güncellenmemesi bir bağlam kısıtıdır ve çizgede karşılığı yoktur — saat, iki düğüm arasındaki bir kenar değildir.

Bu, önceki dersin aynasıdır. Öznitelik tabanlı model bağlamı görüyor ama ilişkiyi göremiyordu; ilişki tabanlı model ilişkiyi görüyor ama bağlamı göremiyor. İki modelin eksikleri örtüşmüyor, tamamlıyor. Bu gözlem sonraki dersin çıkış noktası.

İki Ayrı Soru ve Maliyetleri

İlişki tabanlı modelde iki farklı soru sorulur ve maliyetleri aynı değildir.

Birincisi denetimdir: “bu özne bu nesneye erişebilir mi?” Yanıt bulunduğu anda arama durur. İkincisi listelemedir: “bu öznenin erişebildiği nesneler hangileri?” Bu soru bir liste sayfası çizmek için gerekir ve tüm desenlerin yürütülmesini gerektirir.

cat > rebac-maliyet.mjs <<'EOF'
const KENARLAR = [
  ["U-1001", "sahip", "odunc:501"], ["U-1002", "sahip", "odunc:502"],
  ["U-1005", "sahip", "odunc:503"], ["U-1001", "sahip", "liste:9"],
  ["odunc:501", "ait", "S-02"], ["odunc:502", "ait", "S-02"],
  ["odunc:503", "ait", "S-01"], ["ceza:77", "ait", "S-02"],
  ["P-2001", "calisir", "S-02"], ["Y-3001", "yonetir", "S-02"],
  ["liste:9", "paylasildi", "U-1002"], ["liste:9", "paylasildi", "grup:okuma-kulubu"],
  ["U-1006", "uyesi", "grup:okuma-kulubu"],
];
const DESENLER = {
  "goruntule:odunc": [["sahip"], ["calisir", "~ait"], ["yonetir", "~ait"]],
  "goruntule:liste": [["sahip"], ["~paylasildi"], ["uyesi", "~paylasildi"]],
};

let kenarOkuma = 0;
function adim(dugumler, iliski) {
  const ters = iliski.startsWith("~"), ad = ters ? iliski.slice(1) : iliski;
  const sonuc = new Set();
  for (const [k, i, h] of KENARLAR) {
    kenarOkuma++;
    if (i !== ad) continue;
    if (!ters && dugumler.has(k)) sonuc.add(h);
    if (ters && dugumler.has(h)) sonuc.add(k);
  }
  return sonuc;
}

const yuru = (ozne, desen) => {
  let cephe = new Set([ozne]);
  for (const iliski of desen) cephe = adim(cephe, iliski);
  return cephe;
};

function denetle(ozne, izin, nesne) {              // ilk eslesen desende durur
  for (const desen of DESENLER[izin] ?? []) if (yuru(ozne, desen).has(nesne)) return true;
  return false;
}

function erisilebilirler(ozne, izin) {             // tum desenler yurunur
  const hepsi = new Set();
  for (const desen of DESENLER[izin] ?? []) for (const d of yuru(ozne, desen)) hepsi.add(d);
  const tur = izin.split(":")[1] + ":";
  return [...hepsi].filter((d) => d.startsWith(tur)).sort();
}

console.log("tek nesne denetimi (ilk eslesen desende durur):");
for (const [ozne, nesne] of [["P-2001", "odunc:502"], ["U-1001", "odunc:501"],
                             ["U-1005", "odunc:502"]]) {
  kenarOkuma = 0;
  const k = denetle(ozne, "goruntule:odunc", nesne);
  console.log(`  ${ozne} -> ${nesne} : ${(k ? "izin" : "ret").padEnd(4)} ` +
    `(${String(kenarOkuma).padStart(2)} kenar okumasi)`);
}

console.log("\nerisilebilir nesnelerin listesi (tum desenler yurunur):");
for (const [ozne, izin] of [["P-2001", "goruntule:odunc"], ["U-1001", "goruntule:odunc"],
                            ["U-1006", "goruntule:liste"], ["U-1005", "goruntule:liste"]]) {
  kenarOkuma = 0;
  const liste = erisilebilirler(ozne, izin);
  console.log(`  ${ozne} ${izin.padEnd(16)} -> [${(liste.join(", ") || "bos").padEnd(22)}]` +
    ` (${kenarOkuma} kenar okumasi)`);
}
EOF
node rebac-maliyet.mjs
tek nesne denetimi (ilk eslesen desende durur):
  P-2001 -> odunc:502 : izin (39 kenar okumasi)
  U-1001 -> odunc:501 : izin (13 kenar okumasi)
  U-1005 -> odunc:502 : ret  (65 kenar okumasi)

erisilebilir nesnelerin listesi (tum desenler yurunur):
  P-2001 goruntule:odunc  -> [odunc:501, odunc:502  ] (65 kenar okumasi)
  U-1001 goruntule:odunc  -> [odunc:501             ] (65 kenar okumasi)
  U-1006 goruntule:liste  -> [liste:9               ] (52 kenar okumasi)
  U-1005 goruntule:liste  -> [bos                   ] (52 kenar okumasi)

Kenar okuma sayıları bu küçük çizgeye özgüdür; oranların anlamı değişmez. İki gözlem belirleyicidir.

Birincisi, ret kararı en pahalı karardır. İzin kararı ilk eşleşen desende durur; ret kararı verilebilmesi için tüm desenlerin tüketilmiş olması gerekir. U-1005’in reddi altmış beş kenar okumasına mal oldu, U-1001’in izni on üçe. Yetkisiz istek trafiğinin denetim katmanına yüklediği maliyet, yetkili trafikten yüksektir; hız sınırı bu yüzden denetim katmanının önünde durur.

İkincisi, listeleme sorgusu denetim sorgusunun tekrarı değildir. Bir liste sayfasını “tüm kayıtları çek, her biri için denetim çağır” biçiminde çizmek, sayfa boyutu kadar denetim çağrısı üretir. Doğru yapı, erişilebilir nesne kümesini bir kez üretip veri sorgusunu o kümeyle sınırlamaktır. Bu kümenin sorguya nasıl geçirileceği ise ölçeğe bağlı bir tasarım kararıdır: küçük kümeler doğrudan bir kimlik listesi olarak, büyük kümeler kaynak sorgusuna eklenen bir birleştirme koşulu olarak taşınır.

Çizgenin Bakımı

İlişki tabanlı modelin işletimdeki maliyeti kenarların bakımıdır.

Kenar, kaynak veriyle birlikte yazılır. Bir ödünç kaydı oluşturulduğunda sahip kenarı da oluşur; ikisi ayrı işlemlerde yazılırsa arada kalan istekler yanlış karara bağlanır. Kenar yazımı, kaydın yazıldığı işlemin içinde olmalıdır.

Kenar, kaynak veriyle birlikte silinir. Silinmemiş kenarlar sessizce erişim açık bırakır ve hiçbir hata üretmedikleri için fark edilmezler. Çizgede kaynağı olmayan kenarların düzenli olarak taranması, İlişkisel Veritabanı Yönetimi kursundaki yetim kayıt denetiminin buradaki karşılığıdır.

Yol uzunluğu sınırlanır. Desen ne kadar uzunsa arama o kadar geniş bir düğüm kümesine yayılır. Sınırsız derinlikli bir devralma zinciri — grubun grubu, onun da grubu — hem maliyeti öngörülemez kılar hem de kimin neye eriştiğini insan gözüyle izlenemez hâle getirir.

Özet

  • İlişki tabanlı erişim denetimi bağları birinci sınıf veri yapar; erişim sorusu çizge üzerinde bir ulaşılabilirlik sorgusuna dönüşür.
  • Erişim kuralı bir yol desenidir; kenarlar ileri ya da ters yönde izlenebilir.
  • Geçişken erişim — grupla paylaşılan bir kaydın grup üyelerine açılması — tek bir kenarla ifade edilir; öznitelik alanına kopyalama gerektirmez.
  • Model bağlam kısıtlarını ifade edemez; saat gibi bilgilerin çizgede karşılığı yoktur.
  • Ret kararı tüm desenlerin tüketilmesini gerektirdiği için izin kararından pahalıdır; listeleme sorgusu tek tek denetim çağrılarıyla değil, erişilebilir küme üretilerek yapılır.

Sonraki Adım

Üç modelin üçü de aynı senaryo kümesinde farklı yerlerden eksik kaldı ve eksikleri birbirini tamamlıyor. Bu, dördüncü bir modele değil, kuralların yazıldığı yere ilişkin bir soruya götürüyor: bu kuralların hepsi uygulama kodunun içinde dağınık biçimde mi duracak? Sonraki ders kararı koddan ayıran politika tabanlı yetkilendirmeyi ele alıyor ve dört modelin kararlarını aynı tabloda karşılaştırıyor.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat