İçeriğe geç
academia.sh

Ders 18 / 21

Sır Yönetimi

Sırlar depoda şifreli tutulduğunda anahtarın nerede durduğu, kaç öznenin kaç kaydı çözebildiği ve anahtar döndürüldüğünde kaç kaydın yeniden şifrelenip kaç dosyaya dokunulduğu gerçek şifrelemeyle sayılır. Şifreli kaydın geçmişte kalması ölçülür: eski anahtar sızarsa geçmişteki 35 sürüm çözülüyor ve 11'i bugünkü değerdir.

İçindekiler

Önceki iki ders istenen durumun depoda durmasını ve oradan kümeye taşınmasını ölçtü. İkisinde de sessizce dışarıda bırakılan bir kalem vardı: servislerin ihtiyaç duyduğu sırlar. Toplayıcının ölçüm ağı uçlarına bağlanmak için taşıdığı kimlik bilgisi, faturalamanın veritabanı parolası, iş emri servisinin arşiv anahtarı — bunlar istenen durumun parçasıdır, ama depoya düz metin konamazlar. Depoya konmazlarsa istenen durum eksiktir ve uzlaştırıcı ajan uygulayamaz.

Çözüm biçimi bellidir: sır depoda şifreli durur, çözme yetkisi ayrı bir eksende yönetilir. Bu dersin ölçtüğü şey o eksendir — anahtar nerede duruyor, kaç özne kaç kaydı çözebiliyor, anahtar döndürüldüğünde ne oluyor ve depoda kalan şifreli kaydın geçmişi ne anlama geliyor. Sır sızıntısının hangi yollardan gerçekleştiği daha önce taranmıştı — kayıt satırları, ortam değişkenleri, yapı çıktısı katmanları — ve o tarama burada tekrarlanmaz.

Ölçüm bölgesel ölçüm ağı üzerindedir ve kurgudur. Şifreleme node:crypto ile gerçekten koşar; sır değerlerinin tamamı uydurmadır ve kurgu- önekiyle yazılır.

DD13. Envanter 33 sır kaydıdır: dört servis, üç ortam, servis başına iki-üç sır türü. DD14. Şifreleme AES-256-GCM’dir, anahtar scrypt ile türetilir. Başlangıç vektörü kayıt kimliğinden türetilir ki çıktı yeniden üretilebilsin; gerçek bir dağıtımda rastgele üretilir. DD15. Üç anahtar yerleşimi karşılaştırılır: tek anahtar, ortam başına anahtar, ortam-servis başına anahtar. DD16. Depoyu okuyabilen 29 özne vardır; 14’ü hiçbir anahtar taşımaz. Her öznenin ihtiyaç duyduğu (ortam, servis) çiftleri yazılıdır. DD17. 180 günlük geçmişte her kayıt bir ile dört kez değişmiştir; her değişiklik ayrı bir işlemede durur. DD18. Üretim nöbeti iki servisin sırlarına ihtiyaç duyar; 180 günde 24 gece olayı olur ve anahtarsız kalınan her olay 35 dakika eskalasyon ekler.

Anahtar Nerede Duruyor, Kim Çözebiliyor

// sir.mjs — depoda sifreli sir kaydi: envanter, anahtar yerlesimi, cozme yetkisi.
// SIR DEGERLERI KURGUDUR, gercek bir sir yazilmaz. Sifreleme node:crypto ile gercekten kosar.
// IV kayit kimligi ve anahtar surumunden turetilir ki cikti yeniden uretilebilsin.
import { scryptSync, createCipheriv, createDecipheriv, createHash } from "node:crypto";

export const ORTAM = ["gelistirme", "hazirlik", "uretim"];
export const SERVIS = { toplayici: ["veritabani", "olcum-agi-jetonu", "onbellek"],
  dogrulayici: ["veritabani", "imzalama"], faturalama: ["veritabani", "odeme-jetonu", "imzalama"],
  isemri: ["veritabani", "bildirim-jetonu", "arsiv"] };

export const KAYIT = ORTAM.flatMap((o) => Object.entries(SERVIS).flatMap(([s, tl]) =>
  tl.map((t) => ({ id: `${o}/${s}/${t}`, ortam: o, servis: s,
    deger: `kurgu-${t}-${s}-${o}-0001` }))));             // kurgu deger; gercek sir degil

export const sayac = { turetme: 0, sifreleme: 0, cozme: 0 };
const kasa = new Map();
export const anahtar = (ad, surum = 1) => {               // parola da kurgudur
  const k = `${ad}#${surum}`;
  if (!kasa.has(k)) { sayac.turetme++;
    kasa.set(k, scryptSync(`kurgu-parola-${k}`, `tuz-${ad}`, 32, { N: 16384 })); }
  return kasa.get(k);
};

export const YERLESIM = { tek: () => "hepsi", ortam: (k) => k.ortam,
  "ortam-servis": (k) => `${k.ortam}/${k.servis}` };

export const sifrele = (kayit, ad, surum, deger = kayit.deger) => {
  const iv = createHash("sha256").update(`${kayit.id}#${ad}#${surum}#${deger}`).digest().subarray(0, 12);
  const c = createCipheriv("aes-256-gcm", anahtar(ad, surum), iv);
  const g = Buffer.concat([c.update(deger, "utf8"), c.final()]);
  sayac.sifreleme++;
  return { iv, govde: g, etiket: c.getAuthTag(), ad, surum };
};

export const coz = (k) => {
  const d = createDecipheriv("aes-256-gcm", anahtar(k.ad, k.surum), k.iv);
  d.setAuthTag(k.etiket); sayac.cozme++;
  return Buffer.concat([d.update(k.govde), d.final()]).toString("utf8");
};

// ozneler: her biri belirli (ortam, servis) ciftlerine ihtiyac duyar. depo okuru anahtarsizdir.
export const OZNE = [
  { ad: "ajan/gelistirme", n: 1, ihtiyac: [["gelistirme", "*"]] },
  { ad: "ajan/hazirlik", n: 1, ihtiyac: [["hazirlik", "*"]] },
  { ad: "ajan/uretim", n: 1, ihtiyac: [["uretim", "*"]] },
  { ad: "olcum ekibi", n: 6, ihtiyac: [["gelistirme", "toplayici"], ["gelistirme", "dogrulayici"],
    ["hazirlik", "toplayici"]] },
  { ad: "fatura ekibi", n: 4, ihtiyac: [["gelistirme", "faturalama"], ["hazirlik", "faturalama"]] },
  { ad: "uretim nobeti", n: 2, ihtiyac: [["uretim", "faturalama"], ["uretim", "toplayici"]] },
  { ad: "depo okuru", n: 14, ihtiyac: [] },
];
const uyar = (k, [o, s]) => k.ortam === o && (s === "*" || k.servis === s);
export const gerekli = (z) => KAYIT.filter((k) => z.ihtiyac.some((c) => uyar(k, c)));

export function yetki(yer) {                              // yerlesime gore cozulebilen kayitlar
  const f = YERLESIM[yer];
  return OZNE.map((z) => {
    const tasidigi = new Set(gerekli(z).map(f));          // ozneye verilen anahtar adlari
    return { z, cozer: KAYIT.filter((k) => tasidigi.has(f(k))), gerek: gerekli(z).length };
  });
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const ozneSayisi = OZNE.reduce((a, z) => a + z.n, 0);
  console.log(`envanter: ${KAYIT.length} sir kaydi (kurgu deger), ${ozneSayisi} depo okuru ozne`);
  const ornek = sifrele(KAYIT[0], "gelistirme", 1);
  console.log(`ornek kayit ${KAYIT[0].id} -> ${ornek.govde.toString("base64")} ` +
    `| cozulen: ${coz(ornek)}`);
  const B = [14, 9, 13, 15, 12, 12, 15];
  const yz = (h) => h.map((v, j) => j ? String(v).padStart(B[j]) : String(v).padEnd(B[0])).join("");
  console.log("\n" + yz(["yerlesim", "anahtar", "cozme cifti", "ihtiyac cifti", "fazlalik",
    "engellenen", "en genis ozne"]));
  for (const yer of Object.keys(YERLESIM)) {
    const y = yetki(yer);
    const anahtarlar = new Set(KAYIT.map(YERLESIM[yer])).size;
    const cift = y.reduce((a, x) => a + x.z.n * x.cozer.length, 0);
    const gerek = y.reduce((a, x) => a + x.z.n * x.gerek, 0);
    for (const x of y) if (x.cozer.length) coz(sifrele(x.cozer[0], YERLESIM[yer](x.cozer[0]), 1));
    console.log(yz([yer, anahtarlar, cift, gerek, cift - gerek,
      ozneSayisi * KAYIT.length - cift, Math.max(...y.map((x) => x.cozer.length))]));
  }
  console.log(`\nislem sayaci: ${sayac.turetme} anahtar turetme, ` +
    `${sayac.sifreleme} sifreleme, ${sayac.cozme} cozme`);
}
envanter: 33 sir kaydi (kurgu deger), 29 depo okuru ozne
ornek kayit gelistirme/toplayici/veritabani -> qvyyakRtf8xnLCnzTl5E7FgDkdh/5gAmv4t9dwQTT9kMOGXo4eVrlZUW | cozulen: kurgu-veritabani-toplayici-gelistirme-0001

yerlesim        anahtar  cozme cifti  ihtiyac cifti    fazlalik  engellenen  en genis ozne
tek                   1          495            117         378         462             33
ortam                 3          275            117         158         682             22
ortam-servis         12          117            117           0         840             11

islem sayaci: 8 anahtar turetme, 19 sifreleme, 19 cozme

Şifreleme gerçekten koştu: örnek kayıt şifrelendi, geri çözüldü ve kurgu değer aynen döndü. Asıl tablo aşağıdadır ve tek bir soruyu üç kez yanıtlar — kaç özne kaç kaydı çözebiliyor. Ölçü birimi (özne, kayıt) çiftidir; 29 özne ve 33 kayıtla olası en büyük değer 957’dir.

Anahtar deponun içinde durmaz — durursa şifrelemenin anlamı kalmaz. Anahtar öznelerin elindedir ve tablo o dağıtımın sonucunu sayar. Tek anahtarlı yerleşimde anahtarı taşıyan 15 özne 33 kaydın hepsini çözebilir: 495 çift. Bu öznelerin gerçekten ihtiyaç duyduğu çift sayısı 117’dir ve bu sayı üç yerleşimde de aynıdır, çünkü ihtiyaç anahtar yerleşiminden bağımsızdır. Aradaki 378 çift saf fazlalıktır — kimsenin istemediği, yalnız anahtar kaba olduğu için verilmiş yetki.

Ortam başına anahtara geçildiğinde fazlalık 378’den 158’e iner; ortam-servis başına anahtarda sıfırlanır ve çözebilen özne tam olarak ihtiyacı kadarını çözer. En geniş öznenin yetkisi de 33’ten 11’e düşer: bir kişinin elindeki anahtarın açtığı kayıt sayısı üç kat azalır. Bunun bedeli anahtar sayısıdır — 1’den 12’ye — ve bu sayı bir sonraki bölümün konusudur.

Anahtar Döndürmenin Maliyeti ve Geçmişte Kalan Kayıt

// dondur.mjs — anahtar dondurmenin maliyeti ve sifreli kaydin gecmiste kalmasi.
// Gecmisteki sifreli degerler gercekten cozulur; degerler kurgudur.
import { createHash } from "node:crypto";
import { KAYIT, YERLESIM, OZNE, yetki, sifrele, coz, sayac } from "./sir.mjs";

const DOSYA = (k) => `${k.ortam}/${k.servis}/sirlar`;      // kayitlar dosyalara boyle dagilir
const kez = (k) => 1 + (createHash("sha256").update(k.id).digest()[0] % 4);
const deger = (k, i) => `kurgu-${k.id.split("/")[2]}-${k.servis}-${k.ortam}-` +
  String(i).padStart(4, "0");

// 180 gunluk gecmis: her kayit kez(k) kez degisti; her degisiklik ayri bir islemede duruyor.
const gecmis = KAYIT.flatMap((k) => Array.from({ length: kez(k) }, (_, i) =>
  ({ kayit: k, ac: deger(k, i + 1) })));
const guncel = new Map(KAYIT.map((k) => [k.id, deger(k, kez(k))]));

const Y = [16, 10, 14, 10, 16];
const yz = (h) => h.map((v, j) => j ? String(v).padStart(Y[j]) : String(v).padEnd(Y[0])).join("");
console.log(`gecmis: ${gecmis.length} sifreli kayit surumu / ${KAYIT.length} kayit, ` +
  `${new Set(KAYIT.map(DOSYA)).size} dosya`);
console.log("\n" + yz(["yerlesim", "anahtar", "tek dondurme", "dosya", "hepsi dondurme"]));
for (const [yer, f] of Object.entries(YERLESIM)) {
  const hedef = f(KAYIT.find((k) => k.ortam === "uretim" && k.servis === "faturalama"));
  const etkilenen = KAYIT.filter((k) => f(k) === hedef);
  console.log(yz([yer, new Set(KAYIT.map(f)).size, etkilenen.length,
    new Set(etkilenen.map(DOSYA)).size, `${KAYIT.length} / ${new Set(KAYIT.map(DOSYA)).size} dosya`]));
}

// ortam yerlesiminde uretim anahtari 1 -> 2 dondurulur. Guncel kayitlar yeniden sifrelenir.
const f = YERLESIM.ortam;
const uretim = KAYIT.filter((k) => k.ortam === "uretim");
const gecmisSifreli = gecmis.map((g) => ({ ...g, kutu: sifrele(g.kayit, f(g.kayit), 1, g.ac) }));
const yeni = uretim.map((k) => sifrele(k, f(k), 2, guncel.get(k.id)));
console.log(`\nuretim anahtari 1 -> 2: ${yeni.length} kayit yeniden sifrelendi, ` +
  `${new Set(uretim.map(DOSYA)).size} dosyaya dokunuldu, ${new Set(uretim.map(DOSYA)).size} yeni isleme`);

// eski anahtar sizarsa gecmis ne veriyor
const eski = gecmisSifreli.filter((g) => g.kutu.ad === "uretim" && g.kutu.surum === 1);
const acilan = eski.map((g) => coz(g.kutu));
const dogru = acilan.every((a, i) => a === eski[i].ac);
const hala = eski.filter((g, i) => acilan[i] === guncel.get(g.kayit.id)).length;
const idx = eski.findIndex((g, i) => acilan[i] === guncel.get(g.kayit.id));
console.log(`eski anahtar (surum 1) sizarsa: gecmisteki ${eski.length} surum cozuluyor ` +
  `(hepsi dogru: ${dogru}); bunlarin ${hala} tanesi ${uretim.length} kaydin BUGUNKU degeri`);
console.log(`ornek: ${eski[idx].kayit.id} eski islemeden -> ${acilan[idx]}`);

// cozme yetkisi kapisinin yanlis tuttugu: gece nobetinde anahtarsiz kalinan olaylar
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const r = uretec(90210), SRV = ["toplayici", "dogrulayici", "faturalama", "isemri"];
const olay = Array.from({ length: 24 }, () => SRV[Math.floor(r() * 4)]);
const NOBET = ["faturalama", "toplayici"];                 // uretim nobetinin ihtiyac duydugu
console.log("\n" + yz(["yerlesim", "gece olayi", "anahtarsiz", "ek dakika", "engellenen cift"]));
for (const yer of Object.keys(YERLESIM)) {
  const tasir = (s) => yer === "ortam-servis" ? NOBET.includes(s) : true;
  const yok = olay.filter((s) => !tasir(s)).length;
  const cift = yetki(yer).reduce((a, x) => a + x.z.n * x.cozer.length, 0);
  console.log(yz([yer, olay.length, yok, yok * 35,
    OZNE.reduce((a, z) => a + z.n, 0) * KAYIT.length - cift]));
}
console.log(`\nislem sayaci: ${sayac.turetme} anahtar turetme, ${sayac.sifreleme} sifreleme, ` +
  `${sayac.cozme} cozme`);
gecmis: 93 sifreli kayit surumu / 33 kayit, 12 dosya

yerlesim           anahtar  tek dondurme     dosya  hepsi dondurme
tek                      1            33        12   33 / 12 dosya
ortam                    3            11         4   33 / 12 dosya
ortam-servis            12             3         1   33 / 12 dosya

uretim anahtari 1 -> 2: 11 kayit yeniden sifrelendi, 4 dosyaya dokunuldu, 4 yeni isleme
eski anahtar (surum 1) sizarsa: gecmisteki 35 surum cozuluyor (hepsi dogru: true); bunlarin 11 tanesi 11 kaydin BUGUNKU degeri
ornek: uretim/toplayici/veritabani eski islemeden -> kurgu-veritabani-toplayici-uretim-0004

yerlesim        gece olayi    anahtarsiz ek dakika engellenen cift
tek                     24             0         0             462
ortam                   24             0         0             682
ortam-servis            24            11       385             840

islem sayaci: 4 anahtar turetme, 104 sifreleme, 35 cozme

Döndürme tablosu anahtar sayısının ikinci yüzünü verir. Tek anahtarlı yerleşimde bir anahtarı döndürmek 33 kaydın hepsini yeniden şifreler ve 12 dosyaya dokunur; ortam-servis yerleşiminde aynı işlem 3 kayıt ve 1 dosyadır. Ama son sütun takası dürüst gösteriyor: bütün anahtarları döndürmek üç yerleşimde de 33 kayıt ve 12 dosyadır. İnce anahtar bölmesi toplam işi azaltmaz, tek bir sızıntının yankısını daraltır.

Üretim anahtarı 1’den 2’ye döndürüldüğünde 11 kayıt yeniden şifrelendi, 4 dosyaya dokunuldu ve 4 yeni işleme üretildi. Buraya kadar her şey beklenendir. Dersin çekirdeği bir sonraki satırdadır ve tahmin değil, çalıştırılmış bir çözmedir: eski anahtar sızarsa geçmişteki 35 şifreli sürümün hepsi çözülüyor — hepsi dogru: true satırı bunun sınandığını gösterir — ve bu 35 sürümün 11 tanesi 11 üretim kaydının bugünkü değeridir.

Sonuç şudur: anahtar döndürmek geçmişi kapatmaz. Yeni işlemede yeni şifreli değer durur, ama eski işlemede eski şifreli değer olduğu gibi kalır ve o değer eski anahtarla açıldığında bugün de geçerli olan sırrı verir. Örnek satır bunu somutlaştırıyor: toplayıcının üretim veritabanı kaydının eski işlemeden çözülen değeri, bugün çalışan servisin kullandığı değerin aynısıdır. Geçmişi gerçekten kapatan tek işlem, sırrın değerini kaynağında değiştirmektir — parolayı veritabanında döndürmek, jetonu ölçüm ağı tarafında iptal etmek. Anahtar döndürme 4 dosyaya dokunur; değer döndürme her tüketiciye dokunur ve depo tarafındaki 4 dosya bu işin en ucuz parçasıdır.

Çözme Yetkisi Kapısının Defteri

Bu konudaki kapı bir gözden geçirme ya da uzlaştırma turu değil, bir çözme yetkisidir. Defteri üç kalemde okunur.

Tuttuğu ve yalnız kendisinin tuttuğu. Ortam başına anahtar yerleşiminde engellenen (özne, kayıt) çifti 682’dir. Bu sayının tamamı marjinaldir, çünkü 29 öznenin hepsi depoyu zaten okuyabiliyor — depo erişim kapısı bu çiftlerin hiçbirini durdurmuyor. Şifreleme kalksa 682 okuma serbest kalırdı; kaba bir yerleşimde bile kapı tek başına iş görüyor. Ortam-servis yerleşiminde aynı sayı 840’a çıkar.

Kaçırdığı. İki sınıf var. Birincisi geçmiştir: 35 şifreli sürüm kapının hiç devreye girmediği bir yoldan okunabilir, çünkü eski anahtar sızdığında ortada bir yetki denetimi yoktur. İkincisi fazlalıktır: ortam yerleşiminde 158 çift, anahtarı meşru taşıyan öznelerin ihtiyacından fazlasını açar. Kapı bunları tutmaz, açıkça geçirir — ölçüm ekibinin 6 üyesi geliştirme ortamının faturalama sırlarını da çözebilir.

Yanlış tuttuğu ve eklediği süre. Son tablo takası kapatıyor. Ortam-servis yerleşimi fazlalığı sıfırlar, ama 24 gece olayının 11’inde üretim nöbetçisi ihtiyaç duyduğu anahtarı taşımıyordu ve her olay 35 dakikalık eskalasyon ekledi: 385 dakika. Ortam yerleşiminde bu sayı sıfırdır, karşılığında 158 fazla çift açıktır. Karar bu iki sayı arasında verilir ve “daha ince anahtar daha güvenlidir” cümlesi tek başına karar taşımaz. Kapının işletim yükü de sayılıdır: 33 kaydın 180 günlük geçmişi 104 şifreleme işlemi, doğrulaması 35 çözme işlemi eder; her sır değişikliği bir şifreleme, her uzlaştırma turu ajan tarafında bir çözme ister.

Özet

  • 29 özne ve 33 kayıtla olası 957 (özne, kayıt) çiftinden tek anahtarlı yerleşim 495’ini açıyor, ortam başına anahtar 275’ini, ortam-servis başına anahtar 117’sini; ihtiyaç üç yerleşimde de 117.
  • Fazlalık yetki 378’den 158’e, oradan 0’a iniyor; en geniş öznenin çözebildiği kayıt 33’ten 11’e.
  • Bir anahtarı döndürmek tek anahtarlı yerleşimde 33 kayıt ve 12 dosya, ortam-servis yerleşiminde 3 kayıt ve 1 dosyadır; ama bütün anahtarları döndürmek üç yerleşimde de 33 kayıt ve 12 dosyadır.
  • Üretim anahtarı döndürüldükten sonra eski anahtar sızarsa geçmişteki 35 şifreli sürüm çözülüyor ve 11’i 11 üretim kaydının bugünkü değeri — anahtar döndürme geçmişi kapatmaz, değer döndürme kapatır.
  • Çözme yetkisi kapısının marjinal değeri ortam yerleşiminde 682 engellenen çifttir ve tamamı marjinaldir, çünkü 29 öznenin hepsi depoyu okuyabiliyor.
  • Kapının bedeli ölçüldü: ortam-servis yerleşiminde 24 gece olayının 11’i anahtarsız kaldı ve 385 dakika eskalasyon ekledi; ortam yerleşiminde gecikme 0, açık fazlalık 158 çifttir.

Sonraki Adım

Sırlar depoda şifreli durabiliyor ve kimin neyi çözebildiği sayılabiliyor. Bu, depodaki istenen durumun içeriğine dair bir soruydu. Bir sonraki ders içeriğin başka bir yüzüne döner: bir yayının gerçekte hangi bileşenlerden oluştuğu. Uygulama kendi kodundan ibaret değildir — doğrudan ve dolaylı bağımlılıklar, çalışma zamanı kitaplıkları, taban katman içeriği. Ölçülecek şey bu envanterin kapsamıdır: kaç bağımlılık görünüyor, kaçı dolaylı, envanteri üretmek ne kadar sürüyor ve envanteri üretmeyen bir hatta kaç bileşen hiç sayılmamış oluyor.

İ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