İçeriğe geç
academia.sh

Ders 07 / 14

Erişim Denetimi ve İmzalı Bağlantılar

Süreli ve dar erişim vermenin ölçülmesi: kurgu anahtarla imzalı bağlantı üretilip süre, yöntem ve kapsam denetimlerinin gerçekten doğrulanması; bağlantı ömrünün taranarak yeniden üretme yüküyle sızıntı penceresinin karşılaştırılması, kapsam genişliğinin açtığı yüzeyin sayılması ve imzalı bağlantının geri alınamazlığının iptal patlamasıyla gösterilmesi.

İçindekiler

Altı ders verinin nereye konacağını ölçtü. Hiçbiri tek bir soruyu sormadı: bu veriyi kim okuyabilir. Kurgu ölçüm ağında saha ekipleri fotoğrafları telefondan açar, denetçiler fatura belgelerini dışarıdan ister ve iki grubun da kova erişimi yoktur. Kimlik vermek kalıcı bir kapı açar; oysa istenen şey genellikle süreli ve dar bir kapıdır — bu nesne, bu kadar süre, yalnız okuma.

İmzalı bağlantı (presigned URL) bunu sunar. Bir anahtarla imzalanmış bağlantı, taşıdığı alanların değiştirilmediğini kanıtlar; depo bağlantıyı doğrular ve nesneyi verir. Bağlantıyı taşıyan kişinin hesabı, kimliği, yetkisi yoktur — bağlantının kendisi yetkidir. Bu, hem gücünü hem tehlikesini aynı cümleye koyar: bağlantı kime giderse yetki de oraya gider.

  • DT37 — İmza anahtarı kurgudur, gerçek bir sır değildir; saat sabittir ki çıktı koşumdan koşuma aynı kalsın.
  • DT38 — Kanonik dize yöntem, kapsam, kaynak ve son geçerlilik alanlarından oluşur; imza bu dizenin anahtarlı özetidir.
  • DT39 — Doğrulama dört denetim yapar: imza, saat, yöntem ve kapsam.
// imza/imzala.mjs — imzali baglanti uretilir ve sure ile kapsam GERCEKTEN dogrulanir.
// Anahtar kurgudur, gercek bir sir degildir; saat sabittir ki cikti kosumdan kosuma ayni kalsin.
import { createHmac, timingSafeEqual } from "node:crypto";

const ANAHTAR = Buffer.from("kurgu-imza-anahtari-ornek-gecersiz");   // gercek sir yazilmaz
const SIMDI = 1_774_000_000;                        // sabit saat (saniye); Date.now() kullanilmaz
const kanonik = (y, kapsam, kaynak, son) => [y, kapsam, kaynak, son].join("\n");
const imzala = (y, kapsam, kaynak, omurSn) => {
  const son = SIMDI + omurSn;
  const imza = createHmac("sha256", ANAHTAR).update(kanonik(y, kapsam, kaynak, son)).digest("hex");
  return { y, kapsam, kaynak, son, imza };
};
// Dogrulama: imza tutuyor mu, sure gecmis mi, istenen anahtar kapsamin icinde mi, yontem ayni mi.
const dogrula = (b, istenenYontem, istenenAnahtar, saat) => {
  const beklenen = createHmac("sha256", ANAHTAR).update(kanonik(b.y, b.kapsam, b.kaynak, b.son)).digest("hex");
  const a = Buffer.from(beklenen, "hex"), c = Buffer.from(b.imza, "hex");
  if (a.length !== c.length || !timingSafeEqual(a, c)) return "imza tutmuyor";
  if (saat > b.son) return `suresi ${saat - b.son} sn once doldu`;
  if (b.y !== istenenYontem) return "yontem disi";
  if (!istenenAnahtar.startsWith(b.kapsam)) return "kapsam disi";
  return "gecerli";
};

const ONEK = "saha/2026/03/14/";
const TEK = ONEK + "foto-00412.jpg";
const baglanti = imzala("oku", TEK, TEK, 900);      // tek nesneye, 15 dakikalik
const onekli = imzala("oku", ONEK, TEK, 900);       // bir gunun tum fotograflarina
const DENEME = [
  ["dogru anahtar, sure icinde", baglanti, "oku", TEK, SIMDI + 300],
  ["sure dolduktan sonra", baglanti, "oku", TEK, SIMDI + 1200],
  ["baska nesne istenirse", baglanti, "oku", ONEK + "foto-00413.jpg", SIMDI + 300],
  ["okuma baglantisiyla yazma", baglanti, "yaz", TEK, SIMDI + 300],
  ["imzanin son harfi degisirse", { ...baglanti, imza: baglanti.imza.slice(0, -1) + (baglanti.imza.endsWith("a") ? "b" : "a") },
    "oku", TEK, SIMDI + 300],
  ["onek kapsami, kapsam icinde", onekli, "oku", ONEK + "foto-09999.jpg", SIMDI + 300],
  ["onek kapsami, kapsam disinda", onekli, "oku", "saha/2026/03/15/foto-00001.jpg", SIMDI + 300],
];
console.log(`kurgu anahtarla imzalandi; sabit saat ${SIMDI}. Imza uzunlugu ${baglanti.imza.length} onaltilik basamak.`);
console.log(`tek nesne baglantisi: kapsam ${baglanti.kapsam}, son ${baglanti.son} (${baglanti.son - SIMDI} sn omur)`);
console.log(`onek baglantisi     : kapsam ${onekli.kapsam}`);
console.log("\ndeneme                        | istenen anahtar                | sonuc");
console.log("------------------------------|--------------------------------|---------------------");
for (const [ad, b, y, anahtar, saat] of DENEME)
  console.log(`${ad.padEnd(29)} | ${anahtar.padEnd(30)} | ${dogrula(b, y, anahtar, saat)}`);
console.log("\nRed gerekceleri ayri: biri imzadan, biri saatten, ikisi kanonik dizeye yazilan alanlardan geliyor.");
console.log("Imza yalnizca baglantinin degistirilmedigini soyluyor; ne kadar yasadigini ve neyi actigini");
console.log("kapsam ve son alanlari soyluyor — ikisi de imzanin icinde, disaridan degistirilemiyor.");
kurgu anahtarla imzalandi; sabit saat 1774000000. Imza uzunlugu 64 onaltilik basamak.
tek nesne baglantisi: kapsam saha/2026/03/14/foto-00412.jpg, son 1774000900 (900 sn omur)
onek baglantisi     : kapsam saha/2026/03/14/

deneme                        | istenen anahtar                | sonuc
------------------------------|--------------------------------|---------------------
dogru anahtar, sure icinde    | saha/2026/03/14/foto-00412.jpg | gecerli
sure dolduktan sonra          | saha/2026/03/14/foto-00412.jpg | suresi 300 sn once doldu
baska nesne istenirse         | saha/2026/03/14/foto-00413.jpg | kapsam disi
okuma baglantisiyla yazma     | saha/2026/03/14/foto-00412.jpg | yontem disi
imzanin son harfi degisirse   | saha/2026/03/14/foto-00412.jpg | imza tutmuyor
onek kapsami, kapsam icinde   | saha/2026/03/14/foto-09999.jpg | gecerli
onek kapsami, kapsam disinda  | saha/2026/03/15/foto-00001.jpg | kapsam disi

Red gerekceleri ayri: biri imzadan, biri saatten, ikisi kanonik dizeye yazilan alanlardan geliyor.
Imza yalnizca baglantinin degistirilmedigini soyluyor; ne kadar yasadigini ve neyi actigini
kapsam ve son alanlari soyluyor — ikisi de imzanin icinde, disaridan degistirilemiyor.

Yedi deneme dört denetimi ayrı ayrı gösteriyor. On beş dakikalık bağlantı üç yüzüncü saniyede geçerlidir, bin ikiyüzüncü saniyede “süresi 300 sn önce doldu” der — süre bir öneri değil, doğrulamanın parçasıdır. Aynı bağlantıyla komşu nesne istendiğinde kapsam denetimi devreye girer; okuma bağlantısıyla yazma denendiğinde yöntem denetimi. İmzanın son basamağı değiştirildiğinde dördü de çalışmaz, imza denetiminde durur.

Ayrım önemlidir. İmza yalnız bir şey söyler: bağlantı üretildikten sonra değiştirilmemiştir. Bağlantının ne kadar yaşadığını ve neyi açtığını söyleyen şey imza değil, kanonik dizeye yazılan kapsam ve son geçerlilik alanlarıdır. Bu alanlar imzanın içinde olduğu için dışarıdan değiştirilemez; ama içeriklerini üretim anında kim yazıyorsa erişimin sınırını da o çiziyor. Bağlantı üretmek bir yetki devretme işlemidir ve iki sayıya iner: ömür ve kapsam.

Ömür ve Kapsam

  • DT40 — Saha uygulamasının iş yükü modeldir: günde 12.000 oturum, oturum 1.500 saniye, ekran on sekiz kez yenileniyor, ekranda kırk fotoğraf.
  • DT41 — Sızma oranı kurgudur: üretilen bağlantıların 200.000’de biri günlüğe, adres çubuğuna ya da paylaşıma sızar. Bir imza üretimi modelde 0,012 ms işlemci süresi harcar.
// imza/omur.mjs — baglantinin omru ve kapsami taranir: yeniden uretme yuku, sizinti penceresi,
// iptal patlamasi. Is yuku ve oranlar MODELDIR, rastgelelik yok.
const OTURUM = 12_000, OTURUM_SN = 1500, YENILEME = 18, EKRAN_FOTO = 40;
const SIZMA_ORANI = 1 / 200_000;                    // uretilen baglantilarin sizan payi (kurgu)
const IMZA_MS = 0.012;                              // modelde bir imza uretiminin islemci suresi
const KAPSAM = {                                    // sizan tek baglantinin actigi yuzey
  "tek nesne": { nesne: 1, gib: 0.0035 },
  "bir gunun oneki": { nesne: 3_500, gib: 12.3 },
  "kovanin tamami": { nesne: 1_678_000, gib: 184.8 },
};
const CIKIS_INTERNET = 0.085;                       // B/GiB, kurgu
const OMUR = [60, 300, 1800, 7200, 86_400, 604_800];
const g = (x, n, d = 2) => x.toFixed(d).padStart(n);
const s = (x, n) => String(x).padStart(n);
const uretim = (ttl) => OTURUM * EKRAN_FOTO * Math.min(YENILEME, Math.ceil(OTURUM_SN / ttl));

console.log(`${OTURUM} oturum/gun, oturum ${OTURUM_SN} sn, ekran ${YENILEME} kez yenileniyor, ` +
  `ekranda ${EKRAN_FOTO} fotograf. Sizma orani 1/${(1 / SIZMA_ORANI).toLocaleString("tr")} (kurgu).`);
console.log("\nomur      | uretilen baglanti/gun | islemci sn/gun | sizan/gun | acik nesne-saat | ayni anda gecerli");
console.log("----------|-----------------------|----------------|-----------|-----------------|------------------");
const U = {};
for (const ttl of OMUR) {
  const u = U[ttl] = uretim(ttl), sizan = u * SIZMA_ORANI;
  console.log(`${s(ttl + " sn", 9)} | ${s(u.toLocaleString("tr"), 21)} | ${g(u * IMZA_MS / 1000, 14, 1)} | ` +
    `${g(sizan, 9, 1)} | ${g(sizan * ttl / 3600, 15, 2)} | ${s(Math.round(u / 86400 * ttl).toLocaleString("tr"), 17)}`);
}
const dirsek = OMUR.find((t) => U[t] === U[OMUR[OMUR.length - 1]]);
console.log(`dirsek ${dirsek} sn: bu noktadan sonra uretim ${U[dirsek].toLocaleString("tr")}/gun'de sabitleniyor, ` +
  `ama acik pencere buyumeye devam ediyor —`);
console.log(`  ${dirsek} sn'de ${g(U[dirsek] * SIZMA_ORANI * dirsek / 3600, 0, 2)} nesne-saat, ` +
  `604800 sn'de ${g(U[604800] * SIZMA_ORANI * 604800 / 3600, 0, 2)} nesne-saat: ` +
  `${(U[604800] * 604800 / (U[dirsek] * dirsek)).toFixed(0)} kat. Uzun omur artik hicbir sey kazandirmiyor.`);

console.log("\nkapsam genisligi — tek baglanti sizarsa:");
console.log("kapsam           | acilan nesne | acilan GiB | tamami indirilirse cikis");
console.log("-----------------|--------------|------------|--------------------------");
for (const [ad, k] of Object.entries(KAPSAM))
  console.log(`${ad.padEnd(16)} | ${s(k.nesne.toLocaleString("tr"), 12)} | ${g(k.gib, 10, 3)} | ` +
    `${g(k.gib * CIKIS_INTERNET, 25)} birim`);
console.log(`kapsami tek nesneden kovaya genisletmek, tek bir sizintinin actigi yuzeyi ` +
  `${(KAPSAM["kovanin tamami"].nesne / KAPSAM["tek nesne"].nesne).toLocaleString("tr")} kat buyutuyor.`);

console.log("\niptal edilebilirlik: imzali baglanti geri alinamaz, suresi dolana kadar gecerlidir.");
console.log("Tek yol imza anahtarini dondurmektir; bu, o an gecerli butun baglantilari birden kirar:");
for (const ttl of [1800, 86_400, 604_800])
  console.log(`  omur ${s(ttl + " sn", 9)} -> anahtar dondugunde ${s(Math.round(U[ttl] / 86400 * ttl).toLocaleString("tr"), 9)} ` +
    `baglanti kiriliyor; sizan tek baglanti icin odenen bedel bu.`);

// Iki eksen birlikte: sizan baglantinin actigi yuzey omur ile kapsamin carpimidir.
console.log("\nomur x kapsam — gunluk acilan nesne-saat (sizan baglanti sayisi x omur x kapsamdaki nesne):");
console.log("omur      |" + Object.keys(KAPSAM).map((a) => a.padStart(17)).join(" |"));
console.log("----------|" + Object.keys(KAPSAM).map(() => "-".repeat(18)).join("|"));
for (const ttl of [300, 1800, 86_400, 604_800]) {
  const sizan = U[ttl] * SIZMA_ORANI;
  console.log(`${s(ttl + " sn", 9)} |` + Object.values(KAPSAM)
    .map((k) => g(sizan * ttl / 3600 * k.nesne, 17, 1)).join(" |"));
}
console.log("Kisa omur ile dar kapsam ayni yonde calisiyor: ikisi de carpimin bir carpanini kuculttugu icin");
console.log("etkileri toplanmiyor, carpiliyor — 300 sn ve tek nesne ile 604800 sn ve kova arasindaki fark budur.");
12000 oturum/gun, oturum 1500 sn, ekran 18 kez yenileniyor, ekranda 40 fotograf. Sizma orani 1/200.000 (kurgu).

omur      | uretilen baglanti/gun | islemci sn/gun | sizan/gun | acik nesne-saat | ayni anda gecerli
----------|-----------------------|----------------|-----------|-----------------|------------------
    60 sn |             8.640.000 |          103.7 |      43.2 |            0.72 |             6.000
   300 sn |             2.400.000 |           28.8 |      12.0 |            1.00 |             8.333
  1800 sn |               480.000 |            5.8 |       2.4 |            1.20 |            10.000
  7200 sn |               480.000 |            5.8 |       2.4 |            4.80 |            40.000
 86400 sn |               480.000 |            5.8 |       2.4 |           57.60 |           480.000
604800 sn |               480.000 |            5.8 |       2.4 |          403.20 |         3.360.000
dirsek 1800 sn: bu noktadan sonra uretim 480.000/gun'de sabitleniyor, ama acik pencere buyumeye devam ediyor —
  1800 sn'de 1.20 nesne-saat, 604800 sn'de 403.20 nesne-saat: 336 kat. Uzun omur artik hicbir sey kazandirmiyor.

kapsam genisligi — tek baglanti sizarsa:
kapsam           | acilan nesne | acilan GiB | tamami indirilirse cikis
-----------------|--------------|------------|--------------------------
tek nesne        |            1 |      0.004 |                      0.00 birim
bir gunun oneki  |        3.500 |     12.300 |                      1.05 birim
kovanin tamami   |    1.678.000 |    184.800 |                     15.71 birim
kapsami tek nesneden kovaya genisletmek, tek bir sizintinin actigi yuzeyi 1.678.000 kat buyutuyor.

iptal edilebilirlik: imzali baglanti geri alinamaz, suresi dolana kadar gecerlidir.
Tek yol imza anahtarini dondurmektir; bu, o an gecerli butun baglantilari birden kirar:
  omur   1800 sn -> anahtar dondugunde    10.000 baglanti kiriliyor; sizan tek baglanti icin odenen bedel bu.
  omur  86400 sn -> anahtar dondugunde   480.000 baglanti kiriliyor; sizan tek baglanti icin odenen bedel bu.
  omur 604800 sn -> anahtar dondugunde 3.360.000 baglanti kiriliyor; sizan tek baglanti icin odenen bedel bu.

omur x kapsam — gunluk acilan nesne-saat (sizan baglanti sayisi x omur x kapsamdaki nesne):
omur      |        tek nesne |  bir gunun oneki |   kovanin tamami
----------|------------------|------------------|------------------
   300 sn |              1.0 |           3500.0 |        1678000.0
  1800 sn |              1.2 |           4200.0 |        2013600.0
 86400 sn |             57.6 |         201600.0 |       96652800.0
604800 sn |            403.2 |        1411200.0 |      676569600.0
Kisa omur ile dar kapsam ayni yonde calisiyor: ikisi de carpimin bir carpanini kuculttugu icin
etkileri toplanmiyor, carpiliyor — 300 sn ve tek nesne ile 604800 sn ve kova arasindaki fark budur.

Ömür taraması iki sütun arasında bir denge arıyor. Altmış saniyelik bağlantıda günde 8.640.000 imza üretilir; işlemci maliyeti 103,7 saniye/gündür ve açık pencere 0,72 nesne-saattir. Ömür uzadıkça üretim düşer, ama düşüşün bir dibi vardır: 1.800 saniyeden sonra üretim 480.000’de sabitlenir çünkü bir bağlantı zaten bir oturumun tamamını kapsar. Dirsek burasıdır. Bu noktadan sonra ömrü uzatmak hiçbir yük azaltmaz; açık pencere ise büyümeye devam eder — 1.800 saniyede 1,20 nesne-saat, 604.800 saniyede 403,20 nesne-saat, 336 kat. Yedi günlük bağlantı, otuz dakikalık bağlantının kazandırmadığı bir şey için üç yüz otuz altı kat risk taşır.

Kapsam ikinci eksendir ve ölçeği farklıdır. Sızan tek bir bağlantı, kapsamı tek nesneyse bir nesne açar; bir günün öneğiyse 3.500 nesne ve 12,3 GiB; kova tamamıysa 1.678.000 nesne ve 184,8 GiB. Bu kursun bütün baytı, tek bir dizede. Aradaki fark 1.678.000 kattır ve bağlantıyı üretmenin zorluğu üçünde de aynıdır. Önek kapsamı çoğu zaman kolaylık olsun diye seçilir — tek istek bütün ekranı doldurur — ama kolaylığın karşılığı, sızıntı hâlinde açılan yüzeyin binlerce katına çıkmasıdır.

Son tablo iki ekseni tek sayıda birleştiriyor: günlük açılan nesne-saat, sızan bağlantı sayısı ile ömrün ve kapsamdaki nesne sayısının çarpımıdır. Beş dakikalık ömür ve tek nesne kapsamında bu sayı 1,0’dır; yedi günlük ömür ve kova kapsamında 676.569.600. İki ekseni de gevşetmek riski toplamaz, çarpar; ikisini de sıkmak da aynı biçimde çarparak azaltır. Bu yüzden “bağlantı ömrünü kısa tuttuk” tek başına bir önlem değildir, kapsam da daraltılmadıkça çarpanın yalnız biri küçülmüştür.

İptal Edilemezlik

  • DT42 — İmzalı bağlantı iptal edilemez; tek yol imza anahtarını döndürmektir ve bu, o an geçerli bütün bağlantıları birden kırar.

Son blok bu türün en sert kalemini yazıyor. Bir bağlantının sızdığı fark edildiğinde yapılabilecek hiçbir şey yoktur: bağlantı, süresi dolana kadar geçerlidir. Kimlik yoktur ki askıya alınsın, oturum yoktur ki kapatılsın. Tek kaldıraç imza anahtarını döndürmektir ve o kaldıraç seçici değildir. Otuz dakikalık ömürde anahtar döndürmek 10.000 bağlantıyı kırar; yirmi dört saatlik ömürde 480.000, yedi günlük ömürde 3.360.000. Tek bir sızıntının bedeli, o anda dolaşımdaki bütün bağlantıların kırılmasıdır — ve kullanıcı tarafında bu, ekranların boş gelmesi demektir.

Bu, ömür seçiminin neden bir güvenlik ayarı olmadığını açıklıyor: ömür aynı anda iptal maliyetidir. Kısa ömür sızıntı penceresini de, iptal patlamasını da küçültür; iki kolonu birden aşağı çeker. Dirsek noktasının anlamı da budur — 1.800 saniye, yeniden üretme yükünün dibe vurduğu ve iki riskin de en küçük kaldığı yerdir.

Devredilen karar tarafı burada dardır. Sağlayıcı yalnız doğrulamayı yapar: imzayı yeniden hesaplar, saate bakar, kapsamı karşılaştırır. Kimin ne kadar süreyle neye erişeceği baştan sona elde kalır ve bu kursun ölçüsüne göre üçü de geri alınamaz — üretilmiş bir bağlantının ömrü kısaltılamaz, kapsamı daraltılamaz, kendisi iptal edilemez. Çıkış tarafı da açıktır: sızan bir kova bağlantısı 184,8 GiB’in tamamının indirilmesine izin verir, karşılığında 15,71 birim çıkış ücreti doğar ve fatura sızıntıyı bulmadan önce gelir.

Özet

  • İmzalı bağlantı dört denetimden geçer: imza, süre, yöntem ve kapsam. İmza yalnız bağlantının değiştirilmediğini söyler; ne kadar yaşadığını ve neyi açtığını kanonik dizedeki alanlar söyler.
  • Ömür taramasının bir dirseği vardır: 1.800 saniyeden sonra üretim 480.000/günde sabitlenir, ömrü uzatmak hiçbir yük azaltmaz.
  • Açık pencere ömürle doğru orantılı büyür: 1,20 nesne-saatten 403,20 nesne-saate, 336 kat. Yedi günlük bağlantı, kazandırmadığı bir şey için bu farkı taşır.
  • Kapsam sızıntının ölçeğini belirler: tek nesne 1, bir günün öneği 3.500, kova tamamı 1.678.000 nesne ve 184,8 GiB açar; tamamı indirilirse 15,71 birim çıkış ücreti doğar.
  • Geri alınamayan kalem bağlantının kendisidir: iptal edilemez, süresi dolana kadar geçerlidir. Tek kaldıraç anahtar döndürmektir ve otuz dakikalık ömürde 10.000, yedi günlük ömürde 3.360.000 bağlantıyı birden kırar.

Sonraki Adım

Bu konu iki soruyu kapattı: veri nereye konacak ve ona kim erişecek. Kurgu ölçüm ağının sayaç okumaları, fatura belgeleri, saha fotoğrafları ve iş emri kayıtları artık türüne göre yerleşmiş, sınıfı seçilmiş, yaşam döngüsü yazılmış ve erişimi süreli bağlantılarla verilmiş durumda. Bir kalem hâlâ dışarıda duruyor ve altı derstir kenardan geçiyor: bu verinin sorgulanan kısmı. Tarife tabloları, iş emri durumları ve fatura satırları bir nesne deposunda değil, bir örneğe elle kurulmuş bir veritabanında duruyor — yedeğini bir zamanlanmış görev alıyor, sürüm yükseltmesini biri elle yapıyor, tek kopya çalışıyor. Sonraki konu bu katmanı kiralamayı ölçer: veritabanı bir hizmet olarak alındığında hangi karar devredilir, hangisi elde kalır, ve devredilenlerin hangisi geri alınamaz.

İ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