İçeriğe geç
academia.sh

Ders 22 / 23

Çok Kiracılı Yetkilendirme

Kiracı sınırının sorguya taşınması, süzgeç unutulduğunda oluşan sızıntının ölçülmesi, kiracı kimliğinin kimliği doğrulanmış bağlamdan alınması, üç katmanlı savunma ve fiziksel yerleşim seçenekleri.

İçindekiler

Önceki ders kapsamı bir istemcinin yetkisini daraltmak için kullandı. Bütün örneklerde arka planda tek bir kütüphane vardı: bir üye kümesi, bir görevli kadrosu, bir kayıt havuzu. Aynı sunucu birden çok kuruma hizmet verdiğinde araya yeni bir sınır girer.

Bu sınırın önceki modellerden farkı, veriyle çizilmesidir. Kuzey Kütüphanesi’nin görevlisi kendi kurumunun bütün kayıtlarını görebilir; Güney Kütüphanesi’nin aynı roldeki görevlisi de kendi kurumunun kayıtlarını görebilir. İkisinin rolü, izinleri ve belirteç kapsamları aynıdır. Ayıran tek şey, sorgunun hangi satırlara dokunduğudur.

Yalıtım Rolle Değil Sorguyla Kurulur

Çok kiracılı bir sistemde her kayıt bir kiracıya (tenant) aittir. Kiracı, verinin sahibi olan kurumdur ve şemada bir sütun olarak durur. Yetki denetimi bu sütuna bakmadığında rol denetimi geçen bir istek, komşu kurumun kaydını döndürür.

Aşağıdaki hesap iki depo gerçekleştirimini aynı veri üzerinde çalıştırır: biri kiracı süzgecini unutmuş, diğeri her sorguda zorunlu tutuyor.

cat > kiraci.mjs <<'EOF'
// kiraci.mjs — kiraci yalitiminin sorguya tasinmasi ve denetlenmesi
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync(":memory:");
db.exec(`
  CREATE TABLE uye (uye_id TEXT PRIMARY KEY, kiraci TEXT NOT NULL, ad TEXT NOT NULL);
  CREATE TABLE odunc (odunc_id TEXT PRIMARY KEY, kiraci TEXT NOT NULL, uye_id TEXT NOT NULL,
                      isbn TEXT NOT NULL, durum TEXT NOT NULL);
  INSERT INTO uye VALUES ('U-1','kuzey','Ayse Yilmaz'), ('U-2','kuzey','Deniz Kaya'),
                         ('U-9','guney','Selin Aydin');
  INSERT INTO odunc VALUES ('O-1','kuzey','U-1','978-0201896831','ACIK'),
                           ('O-2','kuzey','U-2','978-0262033848','KAPALI'),
                           ('O-7','guney','U-9','978-0131103627','ACIK'),
                           ('O-8','guney','U-9','978-0201896831','ACIK');
`);

// 1. Kiraci suzgeci unutulmus depo: yalnizca durum kosulu var.
const gevsek = {
  ad: "kiraci suzgeci yok",
  acikOduncler: () => db.prepare("SELECT odunc_id, kiraci FROM odunc WHERE durum = 'ACIK'").all(),
  uyeBul: (id) => db.prepare("SELECT uye_id, kiraci, ad FROM uye WHERE uye_id = ?").all(id),
};

// 2. Kiraci suzgeci zorunlu depo: her sorgu baglamdaki kiraciyi alir.
const siki = {
  ad: "kiraci suzgeci zorunlu",
  acikOduncler: (k) => db.prepare("SELECT odunc_id, kiraci FROM odunc WHERE kiraci = ? AND durum = 'ACIK'").all(k),
  uyeBul: (k, id) => db.prepare("SELECT uye_id, kiraci, ad FROM uye WHERE kiraci = ? AND uye_id = ?").all(k, id),
};

const KIRACI = "kuzey";                       // istegi yapan gorevlinin kiracisi
const sizan = (satirlar) => satirlar.filter((s) => s.kiraci !== KIRACI).length;

console.log("depo                    sorgu             donen  sizan");
for (const [depo, satirlar] of [
  [gevsek, gevsek.acikOduncler()],
  [siki, siki.acikOduncler(KIRACI)],
]) {
  console.log(`${depo.ad.padEnd(23)} ${"acikOduncler".padEnd(17)} ${String(satirlar.length).padStart(5)} ${String(sizan(satirlar)).padStart(6)}`);
}
for (const [depo, satirlar] of [
  [gevsek, gevsek.uyeBul("U-9")],
  [siki, siki.uyeBul(KIRACI, "U-9")],
]) {
  console.log(`${depo.ad.padEnd(23)} ${"uyeBul('U-9')".padEnd(17)} ${String(satirlar.length).padStart(5)} ${String(sizan(satirlar)).padStart(6)}`);
}

// 3. Kiracinin kaynagi: istekten mi, belirtecten mi?
function baglamKur(kaynak, belirtec, istek) {
  return kaynak === "istek" ? istek.kiraci : belirtec.kiraci;
}
const belirtec = { ozne: "P-2001", kiraci: "kuzey" };
console.log("\nkiraci kaynagi   istekteki deger  kullanilan  donen kayit  sizan");
for (const kaynak of ["istek", "belirtec"]) {
  const istek = { kiraci: "guney", uye_id: "U-9" };       // istemci baska kiraci yaziyor
  const k = baglamKur(kaynak, belirtec, istek);
  const satirlar = siki.uyeBul(k, istek.uye_id);
  console.log(`${kaynak.padEnd(16)} ${istek.kiraci.padEnd(16)} ${k.padEnd(11)} ${String(satirlar.length).padStart(11)} ${String(sizan(satirlar)).padStart(6)}`);
}

// 4. Statik denetim: kiraci sutunu tasiyan tablolara giden sorgularda suzgec var mi?
const KIRACILI_TABLOLAR = ["uye", "odunc"];
const SORGULAR = [
  { ad: "acikOduncler/gevsek", sql: "SELECT odunc_id FROM odunc WHERE durum = 'ACIK'" },
  { ad: "acikOduncler/siki",   sql: "SELECT odunc_id FROM odunc WHERE kiraci = ? AND durum = 'ACIK'" },
  { ad: "uyeBul/siki",         sql: "SELECT ad FROM uye WHERE kiraci = ? AND uye_id = ?" },
  { ad: "sayim",               sql: "SELECT count(*) FROM odunc" },
  { ad: "kitap listesi",       sql: "SELECT isbn FROM kitap" },
];
console.log("\nsorgu                  tablo   kiracili  suzgec  bulgu");
let bulgu = 0;
for (const s of SORGULAR) {
  const tablo = /FROM\s+(\w+)/i.exec(s.sql)?.[1] ?? "-";
  const kiracili = KIRACILI_TABLOLAR.includes(tablo);
  const suzgec = /\bkiraci\s*=\s*\?/i.test(s.sql);
  const sorunlu = kiracili && !suzgec;
  if (sorunlu) bulgu++;
  console.log(`${s.ad.padEnd(22)} ${tablo.padEnd(7)} ${(kiracili ? "evet" : "hayir").padEnd(9)} ${(suzgec ? "var" : "yok").padEnd(7)} ${sorunlu ? "EKSIK SUZGEC" : "-"}`);
}
console.log(`denetim bulgusu: ${bulgu} / ${SORGULAR.length}`);
EOF
node kiraci.mjs
depo                    sorgu             donen  sizan
kiraci suzgeci yok      acikOduncler          3      2
kiraci suzgeci zorunlu  acikOduncler          1      0
kiraci suzgeci yok      uyeBul('U-9')         1      1
kiraci suzgeci zorunlu  uyeBul('U-9')         0      0

kiraci kaynagi   istekteki deger  kullanilan  donen kayit  sizan
istek            guney            guney                 1      1
belirtec         guney            kuzey                 0      0

sorgu                  tablo   kiracili  suzgec  bulgu
acikOduncler/gevsek    odunc   evet      yok     EKSIK SUZGEC
acikOduncler/siki      odunc   evet      var     -
uyeBul/siki            uye     evet      var     -
sayim                  odunc   evet      yok     EKSIK SUZGEC
kitap listesi          kitap   hayir     yok     -
denetim bulgusu: 2 / 5

Unutulan Süzgecin Gözlenebilir Sonucu

Çıktının ilk bölümü sızıntıyı sayıyor. Süzgeçsiz depo, açık ödünçler sorgusunda üç kayıt döndürüyor ve bunların ikisi başka kiracıya ait. Üye arama sorgusu daha çarpıcıdır: istenen kimlik komşu kurumun üyesine aittir ve süzgeçsiz sorgu o kaydı döndürür — rol denetimi geçilmiştir, çünkü görevlinin “üye okuma” izni vardır. İzin doğrudur; yanlış olan, iznin hangi satırlar üzerinde geçerli olduğunun sorulmamasıdır.

Süzgeçli depoda aynı iki sorgu sıfır sızıntı veriyor. İkinci sorgunun sonucu boştur: kayıt vardır ama bu kiracı için yoktur. Boş sonuç ile yetkisizlik yanıtı arasındaki seçim bir tasarım kararıdır ve genellikle boş sonuç tercih edilir; “bu kimlik başka bir kurumda var” bilgisi bile kiracılar arası bir sızıntıdır.

Kiracı Bilgisi Nereden Gelir

İkinci bölüm daha ince bir hatayı gösteriyor. Kiracı kimliği isteğin gövdesinden ya da yol parametresinden okunursa, istemci onu değiştirebilir: istek guney yazar, sorgu o kiracıyı kullanır ve komşu kurumun kaydı döner. Aynı istek, kiracı kimliği belirteçten alındığında sıfır kayıt döndürür.

Kural tek cümledir: kiracı kimliği, kimliği doğrulanmış bağlamdan gelir; istekten gelen kiracı değeri en fazla bir filtre olabilir, hiçbir zaman yetkinin kaynağı olamaz. İstemcinin gönderdiği kiracı ile bağlamdaki kiracı çelişiyorsa istek reddedilir; bu çelişki aynı zamanda kaydedilmeye değer bir olaydır.

Süzgeci Unutmayı Zorlaştırmak

Üçüncü bölüm bir denetim yazıyor: kiracı sütunu taşıyan tablolara giden sorgular süzgeci içeriyor mu? Modeldeki beş sorgunun ikisi bulgu üretti — biri bilinçli olarak gevşek bırakılan depo, diğeri masum görünen bir sayım sorgusu. Sayım sorgusu tek bir sayı döndürür ama o sayı bütün kiracıların toplamıdır; gösterge panosunda bu, komşu kurumun iş hacmini sızdırır.

Denetimin değeri, unutmayı yapısal olarak yakalamasıdır. Aynı işi üç katman birlikte yapar ve her biri diğerinin kaçırdığını tutar:

  1. Depo katmanı: kiracı parametresi olmayan bir sorgu yazılamaz — sorguyu kuran işlev kiracıyı zorunlu argüman olarak alır.
  2. Denetim betiği: yeni yazılan sorgular sürekli tümleştirmede taranır, süzgeçsiz sorgu derlemeyi düşürür.
  3. Veritabanı katmanı: satır düzeyi güvenlik politikası, uygulama katmanı hata yapsa bile satırları süzer. Bu, İlişkisel Veritabanı Yönetimi kursundaki Satır Düzeyi Güvenlik dersinin uygulama tarafındaki karşılığıdır.

Üçü birden gerekir. Yalnız depo katmanına güvenmek, ham sorgu yazan bir raporlama betiğinde kırılır; yalnız veritabanı katmanına güvenmek, kiracı bağlamını oturuma bağlamayan bir toplu işte kırılır.

Kiracı Verisinin Fiziksel Yerleşimi

Yalıtımın üç yerleşim biçimi vardır ve seçim ölçütü aynı üç soruyla verilir: komşu kiracının verisine yanlışlıkla erişme olasılığı, kiracı başına maliyet ve göç ile yedeklemenin tanecikliği.

Paylaşılan tablo (bu dersteki model) en ucuzudur ve en çok özen ister: bütün kayıtlar aynı tablodadır, yalıtımı sorgu sağlar. Kiracı başına şema, süzgeci bağlantı düzeyine taşır; yanlış şemaya bağlanmak hâlâ olanaklıdır ama sorgu içindeki unutkanlık zararsızlaşır. Kiracı başına veritabanı en güçlü yalıtımı verir; bedeli, göç ve yedekleme işlerinin kiracı sayısıyla çarpılmasıdır.

Karar tek seferlik değildir: küçük kiracılar paylaşılan tabloda, sözleşmesi yalıtım gerektiren büyük kiracılar ayrı veritabanında tutulabilir. Bu durumda uygulama katmanının her iki yerleşimi de aynı arayüzün arkasında görmesi gerekir.

Özet

  • Çok kiracılı sistemde sınır rol ya da kapsamla değil veriyle çizilir; aynı role sahip iki görevli farklı satır kümelerini görür.
  • Kiracı süzgeci unutulduğunda rol denetimi geçen istek komşu kurumun kaydını döndürür; modeldeki iki sorgu üç kayıttan ikisini sızdırdı, süzgeçli sürüm sıfır sızdırdı.
  • Kiracı kimliği kimliği doğrulanmış bağlamdan gelir; istekten gelen değer yetkinin kaynağı olamaz — modelde istekten okunan kiracı komşu kurumun kaydını açtı.
  • Süzgeci unutmak üç katmanla zorlaştırılır: kiracıyı zorunlu argüman yapan depo, sorguları tarayan denetim ve satır düzeyi güvenlik politikası.
  • Fiziksel yerleşim (paylaşılan tablo, kiracı başına şema, kiracı başına veritabanı) yalıtım gücü ile kiracı başına maliyet arasındaki ödünleşimdir.

Sonraki Adım

Kiracı sınırı, isteğin hangi kümeye dokunabileceğini belirledi. Küme içinde ise hâlâ tek tek nesneler vardır: aynı kütüphanenin iki üyesi, birbirinin ödünç kaydını görmemelidir. Kursun son dersi bu son sınırı ele alır — adres çubuğuna başka bir kimlik yazıldığında ne olduğunu ve nesne sahipliğinin nerede denetlenmesi gerektiğini.

İ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