İçeriğe geç
academia.sh

Ders 20 / 23

Politika Tabanlı Yetkilendirme

Erişim kararının uygulama kodundan ayrılıp yorumlanan bir politika belgesine taşınması, karar noktası ile uygulama noktası ayrımı, dört modelin aynı senaryo kümesindeki kararlarının karşılaştırılması ve politikanın rapor kipinde sınanması.

İçindekiler

Üç ders üç model kurdu ve üçü de aynı sekiz senaryoda farklı yerlerden eksik kaldı. Rol tabanlı model kaydın kimin olduğunu göremedi; öznitelik tabanlı model paylaşım bağını göremedi; ilişki tabanlı model saati göremedi. Eksikler örtüşmüyor, birbirini tamamlıyor.

Bu gözlem dördüncü bir modele değil, başka bir soruya götürür: bu kurallar nerede yazılı duracak? Şimdiye kadarki üç değerlendirici de birer betikti; gerçek bir uygulamada aynı kurallar uç nokta işleyicilerinin içine, if bloklarına dağılmış olurdu. Politika tabanlı yetkilendirme (policy-based authorization) kuralları koddan ayırıp yorumlanan bir belgeye taşır.

Karar Noktası ve Uygulama Noktası

Ayrımın iki tarafı vardır. Karar noktası (policy decision point), isteği ve ilgili verileri alıp izin ya da ret üreten bileşendir; alan mantığı taşımaz, yalnız politikayı yorumlar. Uygulama noktası (policy enforcement point), kararı uygulayan yerdir: uç nokta işleyicisi ya da ara katman. Kararı vermez, sorar ve sonucuna uyar.

Bu ayrımın üç somut kazancı vardır. Kural değişikliği kod değişikliği olmaktan çıkar; politika belgesi ayrı sürümlenebilir ve dağıtımı koddan bağımsız yapılabilir. Kararın gerekçesi tek bir yerde üretilir, dolayısıyla kütüğe yazılabilir ve sınanabilir. Aynı politika birden çok servis tarafından kullanılabilir; kural kopyalanmaz.

Aşağıdaki motor bu ayrımı kuruyor. POLITIKA bir veri yapısıdır ve ayrı bir dosyada tutulabilir; motor onu yorumlar ve kütüphaneye özgü hiçbir bilgi taşımaz.

cat > politika.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 OZNE = {
  "U-1001": { rol: "uye" }, "U-1002": { rol: "uye" }, "U-1005": { rol: "uye" },
  "P-2001": { rol: "gorevli" }, "Y-3001": { rol: "sube_yoneticisi" },
};

// --- Politika belgesi: veri, kod degil. Ayri tutulur ve surumlenir. ---
const POLITIKA = {
  surum: "2026-03-01",
  kurallar: [
    { ad: "kendi kaydini gorur", etki: "izin", eylem: ["goruntule"],
      nesne: ["odunc", "liste"], iliski: [["sahip"]] },
    { ad: "sube gorevlisi", etki: "izin", eylem: ["goruntule", "guncelle", "olustur"],
      nesne: ["odunc"], rol: ["gorevli", "sube_yoneticisi"],
      iliski: [["calisir", "~ait"], ["yonetir", "~ait"]] },
    { ad: "yonetici ceza siler", etki: "izin", eylem: ["sil"], nesne: ["ceza"],
      rol: ["sube_yoneticisi"], iliski: [["yonetir", "~ait"]] },
    { ad: "paylasilan liste", etki: "izin", eylem: ["goruntule"], nesne: ["liste"],
      iliski: [["~paylasildi"], ["uyesi", "~paylasildi"]] },
    { ad: "mesai disi yazma yasak", etki: "ret", eylem: ["guncelle", "olustur", "sil"],
      nesne: ["odunc", "ceza", "liste"], saat_disinda: ["08:30", "18:00"],
      rol_haric: ["sube_yoneticisi"] },
  ],
};

// --- Motor: politikayi yorumlar, alan bilgisi tasimaz. ---
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) {
    if (i !== ad) continue;
    if (!ters && dugumler.has(k)) sonuc.add(h);
    if (ters && dugumler.has(h)) sonuc.add(k);
  }
  return sonuc;
}

const ulasir = (ozne, desenler, nesne) => desenler.some((desen) => {
  let cephe = new Set([ozne]);
  for (const i of desen) cephe = adim(cephe, i);
  return cephe.has(nesne);
});

function uyar(kural, { ozne, eylem, nesne, saat }) {
  const rol = OZNE[ozne]?.rol;
  if (!kural.eylem.includes(eylem)) return false;
  if (!kural.nesne.includes(nesne.split(":")[0])) return false;
  if (kural.rol && !kural.rol.includes(rol)) return false;
  if (kural.rol_haric && kural.rol_haric.includes(rol)) return false;
  if (kural.saat_disinda) {
    const [bas, bit] = kural.saat_disinda;
    if (saat >= bas && saat <= bit) return false;
  }
  if (kural.iliski && !ulasir(ozne, kural.iliski, nesne)) return false;
  return true;
}

function karar(istek) {
  const eslesen = POLITIKA.kurallar.filter((k) => uyar(k, istek));
  const ret = eslesen.find((k) => k.etki === "ret");
  if (ret) return [false, ret.ad + " (ret oncelikli)"];
  const izin = eslesen.find((k) => k.etki === "izin");
  return izin ? [true, izin.ad] : [false, "eslesen kural yok (varsayilan ret)"];
}

const SENARYOLAR = [
  ["S1", "U-1001", "goruntule", "odunc:501", "10:15", true],
  ["S2", "U-1001", "goruntule", "odunc:502", "10:16", false],
  ["S3", "P-2001", "goruntule", "odunc:502", "10:20", true],
  ["S4", "P-2001", "goruntule", "odunc:503", "10:21", false],
  ["S5", "P-2001", "guncelle",  "odunc:502", "22:40", false],
  ["S6", "Y-3001", "sil",       "ceza:77",   "11:05", true],
  ["S7", "U-1002", "goruntule", "liste:9",   "12:00", true],
  ["S8", "U-1005", "goruntule", "liste:9",   "12:01", false],
];

console.log(`politika surumu: ${POLITIKA.surum}, kural sayisi: ${POLITIKA.kurallar.length}`);
console.log("\nsenaryo | politika | dogru | uyum | karari veren kural");
console.log("--------|----------|-------|------|--------------------------------");
let uyan = 0;
for (const [ad, ozne, eylem, nesne, saat, dogru] of SENARYOLAR) {
  const [k, gerekce] = karar({ ozne, eylem, nesne, saat });
  if (k === dogru) uyan++;
  console.log(`${ad}      | ${(k ? "izin" : "ret").padEnd(8)} | ` +
    `${(dogru ? "izin" : "ret").padEnd(5)} | ${(k === dogru ? "+" : "-").padEnd(4)} | ${gerekce}`);
}
console.log(`\npolitika motoru dogru karar: ${uyan}/${SENARYOLAR.length}`);
EOF
node politika.mjs
politika surumu: 2026-03-01, kural sayisi: 5

senaryo | politika | dogru | uyum | karari veren kural
--------|----------|-------|------|--------------------------------
S1      | izin     | izin  | +    | kendi kaydini gorur
S2      | ret      | ret   | +    | eslesen kural yok (varsayilan ret)
S3      | izin     | izin  | +    | sube gorevlisi
S4      | ret      | ret   | +    | eslesen kural yok (varsayilan ret)
S5      | ret      | ret   | +    | mesai disi yazma yasak (ret oncelikli)
S6      | izin     | izin  | +    | yonetici ceza siler
S7      | izin     | izin  | +    | paylasilan liste
S8      | ret      | ret   | +    | eslesen kural yok (varsayilan ret)

politika motoru dogru karar: 8/8

Sekiz karar da doğru. Kazanç yeni bir modelden değil, üç modelin ifade gücünün tek bir belgede birleşmesinden geliyor: rol alanı rol tabanlı, saat_disinda öznitelik tabanlı, iliski ilişki tabanlı kısıtı taşıyor. Motor bunları ayrı kavramlar olarak değil, aynı kuralın alanları olarak görüyor.

İki tasarım kararı belgede yazılıdır. Varsayılan ret: hiçbir kural eşleşmezse karar rettir. Ret önceliği: eşleşen kurallardan biri ret diyorsa izin diyenler sayılmaz. İkisi birlikte, politikaya yeni bir kural eklemenin var olan bir yasağı sessizce gevşetememesini sağlar.

Dört Modelin Karşılaştırması

Aşağıdaki blok dört modeli de aynı kümede çalıştırıp kararları yan yana koyuyor.

cat > karsilastirma.mjs <<'EOF'
const OZNE = {
  "U-1001": { rol: "uye", sube: "S-02" }, "U-1002": { rol: "uye", sube: "S-02" },
  "U-1005": { rol: "uye", sube: "S-01" }, "P-2001": { rol: "gorevli", sube: "S-02" },
  "Y-3001": { rol: "sube_yoneticisi", sube: "S-02" },
};
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"],
];
const SENARYOLAR = [
  ["S1", "U-1001", "goruntule", "odunc:501", { sahip: "U-1001", sube: "S-02" }, "10:15", true],
  ["S2", "U-1001", "goruntule", "odunc:502", { sahip: "U-1002", sube: "S-02" }, "10:16", false],
  ["S3", "P-2001", "goruntule", "odunc:502", { sahip: "U-1002", sube: "S-02" }, "10:20", true],
  ["S4", "P-2001", "goruntule", "odunc:503", { sahip: "U-1005", sube: "S-01" }, "10:21", false],
  ["S5", "P-2001", "guncelle",  "odunc:502", { sahip: "U-1002", sube: "S-02" }, "22:40", false],
  ["S6", "Y-3001", "sil",       "ceza:77",   { sahip: "U-1002", sube: "S-02" }, "11:05", true],
  ["S7", "U-1002", "goruntule", "liste:9",   { sahip: "U-1001", sube: "S-02" }, "12:00", true],
  ["S8", "U-1005", "goruntule", "liste:9",   { sahip: "U-1001", sube: "S-02" }, "12:01", false],
];
const tur = (n) => n.split(":")[0];
const mesai = (s) => s >= "08:30" && s <= "18:00";
const YAZAN = ["guncelle", "olustur", "sil"];

// 1. Rol tabanli: karar yalniz role ve kaynak turune bakar.
const IZIN = {
  uye: ["goruntule:odunc", "goruntule:liste"],
  gorevli: ["goruntule:odunc", "goruntule:liste", "guncelle:odunc", "olustur:odunc"],
  sube_yoneticisi: ["goruntule:odunc", "goruntule:liste", "guncelle:odunc",
                    "olustur:odunc", "sil:ceza", "sil:odunc"],
};
const rbac = (o, e, n) => IZIN[OZNE[o].rol].includes(`${e}:${tur(n)}`);

// 2. Oznitelik tabanli: sahiplik, sube ve saat karsilastirmalari.
function abac(o, e, n, oz, saat) {
  const s = OZNE[o];
  if (YAZAN.includes(e) && !mesai(saat) && s.rol !== "sube_yoneticisi") return false;
  if (e === "goruntule" && oz.sahip === o) return true;
  if (["gorevli", "sube_yoneticisi"].includes(s.rol) && oz.sube === s.sube &&
      ["goruntule", "guncelle", "olustur"].includes(e)) return true;
  return s.rol === "sube_yoneticisi" && oz.sube === s.sube && e === "sil" && tur(n) === "ceza";
}

// 3. Iliski tabanli: cizge uzerinde yol deseni.
const DESEN = {
  "goruntule:odunc": [["sahip"], ["calisir", "~ait"], ["yonetir", "~ait"]],
  "guncelle:odunc": [["calisir", "~ait"], ["yonetir", "~ait"]],
  "sil:ceza": [["yonetir", "~ait"]],
  "goruntule:liste": [["sahip"], ["~paylasildi"]],
};
function adim(kume, il) {
  const ters = il.startsWith("~"), ad = ters ? il.slice(1) : il, c = new Set();
  for (const [k, i, h] of KENARLAR) {
    if (i !== ad) continue;
    if (!ters && kume.has(k)) c.add(h);
    if (ters && kume.has(h)) c.add(k);
  }
  return c;
}
const ulasir = (o, desenler, n) => (desenler ?? []).some((d) => {
  let c = new Set([o]);
  for (const i of d) c = adim(c, i);
  return c.has(n);
});
const rebac = (o, e, n) => ulasir(o, DESEN[`${e}:${tur(n)}`], n);

// 4. Politika tabanli: rol, iliski ve baglam tek belgede.
const POLITIKA = [
  { etki: "ret", eylem: YAZAN, saat_disinda: ["08:30", "18:00"], rol_haric: ["sube_yoneticisi"] },
  { etki: "izin", eylem: ["goruntule"], desen: [["sahip"], ["~paylasildi"]] },
  { etki: "izin", eylem: ["goruntule", "guncelle", "olustur"],
    rol: ["gorevli", "sube_yoneticisi"], desen: [["calisir", "~ait"], ["yonetir", "~ait"]] },
  { etki: "izin", eylem: ["sil"], rol: ["sube_yoneticisi"], desen: [["yonetir", "~ait"]] },
];
function politika(o, e, n, oz, saat) {
  const rol = OZNE[o].rol;
  const uyan = POLITIKA.filter((k) =>
    k.eylem.includes(e) &&
    !(k.rol && !k.rol.includes(rol)) &&
    !(k.rol_haric && k.rol_haric.includes(rol)) &&
    !(k.saat_disinda && saat >= k.saat_disinda[0] && saat <= k.saat_disinda[1]) &&
    !(k.desen && !ulasir(o, k.desen, n)));
  if (uyan.some((k) => k.etki === "ret")) return false;
  return uyan.some((k) => k.etki === "izin");
}

const MODELLER = [["RBAC", rbac], ["ABAC", abac], ["ReBAC", rebac], ["politika", politika]];
const sayac = new Map(MODELLER.map(([ad]) => [ad, 0]));
const ayirac = "--------|" + MODELLER.map(() => "---------").join("|") + "|-------";

console.log("senaryo | " + MODELLER.map(([a]) => a.padEnd(8)).join("| ") + "| dogru");
console.log(ayirac);
for (const [ad, o, e, n, oz, saat, dogru] of SENARYOLAR) {
  const hucreler = MODELLER.map(([m, fn]) => {
    const k = fn(o, e, n, oz, saat);
    if (k === dogru) sayac.set(m, sayac.get(m) + 1);
    return `${k ? "izin" : "ret "} ${k === dogru ? "+" : "-"}`.padEnd(8);
  });
  console.log(`${ad}      | ${hucreler.join("| ")}| ${dogru ? "izin" : "ret"}`);
}
console.log(ayirac);
console.log("dogru   | " + MODELLER.map(([m]) => `${sayac.get(m)}/8`.padEnd(8)).join("| ") + "|");
EOF
node karsilastirma.mjs
senaryo | RBAC    | ABAC    | ReBAC   | politika| dogru
--------|---------|---------|---------|---------|-------
S1      | izin +  | izin +  | izin +  | izin +  | izin
S2      | izin -  | ret  +  | ret  +  | ret  +  | ret
S3      | izin +  | izin +  | izin +  | izin +  | izin
S4      | izin -  | ret  +  | ret  +  | ret  +  | ret
S5      | izin -  | ret  +  | izin -  | ret  +  | ret
S6      | izin +  | izin +  | izin +  | izin +  | izin
S7      | izin +  | ret  -  | izin +  | izin +  | izin
S8      | izin -  | ret  +  | ret  +  | ret  +  | ret
--------|---------|---------|---------|---------|-------
dogru   | 4/8     | 7/8     | 7/8     | 8/8     |

Tablonun okunma biçimi önemlidir. Sağdaki sütunun soldakinden “daha iyi” olduğu sonucu çıkarılmamalıdır; çıkarılacak sonuç, her modelin ifade edebildiği kısıt türünün farklı olduğudur. Rol tabanlı modelin dört doğrusu, kararın kaynak türünde bittiği durumlarda başka hiçbir düzeneğe gerek olmadığını gösterir.

S5 ve S7 sütunları modellerin sınırlarını aynı satırda okutuyor: öznitelik tabanlı model S5’i çözüyor S7’yi çözemiyor, ilişki tabanlı model tersini yapıyor. Politika sütunu ikisini birden çözüyor, çünkü kural belgesi iki kısıt türünü de alan olarak taşıyor.

Politikanın İşletimi

Politikanın ayrı bir belge olması, kod dışı bir dağıtım nesnesi ürettiği anlamına gelir ve bunun kendi disiplini vardır.

Politika sürümlenir ve kararla birlikte kaydedilir. Kütüğe yazılan satır, kararın kendisini ve onu veren kuralın adını içerir. “Bu istek neden reddedildi” sorusunun yanıtı, aylar sonra da verilebilir olur.

Değişiklik rapor kipinde sınanır. İstemci Güvenliği konusundaki içerik güvenlik politikası için kurulan ayrım burada da geçerlidir: yeni politika, gerçek trafikte karar üretir ama kararı uygulanmaz; yalnız yürürlükteki politikayla farkı kaydedilir. Fark listesi beklenen değişikliklerden ibaretse zorlama kipine geçilir.

Senaryo kümesi politikanın testidir. Bu dersteki tablo bir sınama düzeneğidir; her politika değişikliğinde çalıştırılır ve uyum sütununda eksi çıkması değişikliği durdurur. Kümeye, her yeni kuralla birlikte o kuralın izin verdiği ve reddettiği birer senaryo eklenir.

Karar verisi karar noktasına taşınır. Motorun ilişki çizgesine ve öznitelik değerlerine erişmesi gerekir. Bu veri karar anında sorgulanıyorsa her istek bir ek gecikme taşır; önceden kopyalanıyorsa tazelik sorunu doğar. Ölçüt, öznitelik tazeliği dersindeki ölçütün aynısıdır: kararı değiştiren ve değişebilen veri karar anında okunur.

Özet

  • Politika tabanlı yetkilendirme erişim kurallarını uygulama kodundan ayırıp yorumlanan bir belgeye taşır; karar noktası politikayı yorumlar, uygulama noktası kararı uygular.
  • Belge rol, öznitelik ve ilişki kısıtlarını aynı kuralın alanları olarak taşıyabildiği için üç modelin ifade gücü birleşir.
  • Varsayılan ret ve ret önceliği birlikte, yeni bir kuralın var olan bir yasağı sessizce gevşetmesini engeller.
  • Dört model aynı senaryo kümesinde sırasıyla 4, 7, 7 ve 8 doğru karar verir; fark modelin kalitesi değil, ifade edebildiği kısıt türüdür.
  • Politika sürümlenir, kararın gerekçesiyle birlikte kütüklenir ve değişiklikler rapor kipinde sınandıktan sonra zorlama kipine alınır.

Sonraki Adım

Buraya kadarki dört model, kararı isteği yapan kişinin kimliği üzerinden verdi. Oysa isteği yapan çoğu zaman kişinin kendisi değil, kişinin yetkilendirdiği bir istemcidir: kütüphanenin mobil uygulaması, bir rapor aracı, üçüncü taraf bir okuma listesi servisi. Kişinin yetkisi ile istemciye verilen yetki aynı şey değildir. Sonraki ders bu ikinci katmanı — belirteç kapsamlarını — ele alıyor ve kapsamların uç noktalara nasıl eşlendiğini gösteriyor.

İ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