---
title: 'İmaj Güvenliği'
source: 'https://academia.sh/tr/kurslar/konteynerler/imaj-guvenligi'
course: Konteynerler
language: tr
updated: '2026-08-17T18:09:04+00:00'
license: 'CC BY-SA 4.0'
---

# İmaj Güvenliği

İmaj güvenliği üç parçada ölçülür: üst katmanda silinen bir dosyanın alt katmanda gerçekte kaç bayt olarak durduğu gerçek dizinlerde sayılır, bilinen bir kusur kümesinde koşan tarama için yakalanan, kaçan ve yanlış alarm sayıları çıkarılır, imzalama gerçek bir anahtar çiftiyle bir zincir olarak kurulur ve doğrulamanın atlandığı her adımda neyin kabul edildiği sayılır.

Önceki ders depoyu ölçtü ve bir soruyla bitti: özel imajın dokuz katmanı genel havuzda
okunabiliyorsa, o katmanların içinde ne var? Şimdiye kadar katmanlara birer bayt yığını olarak
bakıldı. Bu ders içeri bakar ve konuyu kapatır.

Üç ölçü vardır ve üçü de aynı sorunun parçalarıdır: durur hâldeki bir çıktının içinde ne olduğu
nasıl bilinir? Birincisi imajın kendi yapısından gelen bir sızıntıdır, ikincisi bilinen kusurları
bulma girişimidir, üçüncüsü çıktının kimden geldiğinin doğrulanmasıdır.

**IM43.** Katmanlar gerçek dizinlerdir ve birleşim görünümü alttan üste kurulur; bir dosyanın
silinmesi üst katmanda `.sil-` önekli bir işaret dosyasıyla gösterilir. **IM44.** Sır değerleri
kurgudur; gerçek bir sır kullanılmaz.

## Katmanda Kalan Sır

M22/K01 bir sırrın kaç ayrı yoldan dışarı sızabildiğini taramıştı; o tarama burada tekrarlanmaz.
Buradaki olgu tek ve katmana özgüdür: **bir katman değişmez bir nesnedir.** Bir dosyayı silen
yönerge yeni bir katman üretir; alttaki katman olduğu gibi durur, çünkü onun özeti başka imajlarda
da geçer. Silme, görünümü değiştirir; baytları değiştirmez.

```js
// olcum-agi/sizinti.mjs — ust katmanda silinen dosya alt katmanda duruyor mu (gercek dizinler)
import { mkdirSync, rmSync, writeFileSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join, dirname } from "node:path";

const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-katman");
rmSync(KOK, { recursive: true, force: true });

// Kurgu sir degerleri. Gercek bir sir kullanilmaz.
const SIR_A = "kurgu-erisim-anahtari-A1B2C3D4E5F6";
const SIR_B = "kurgu-veritabani-parolasi-Z9Y8X7";
const govde = (sir, dolgu) => Buffer.concat([Buffer.from(`${sir}\n`), Buffer.alloc(dolgu, "-")]);

const KATMANLAR = [
  { ad: "k1-temel", yaz: { "bin/toplayici": 4096, "lib/sifreleme": 8192 } },
  { ad: "k2-bagimlilik", yaz: { "lib/istemci": 6144, "lib/kuyruk": 3072 } },
  { ad: "k3-uygulama",
    yaz: { "app/toplayici.js": 5120, "app/gizli/erisim.anahtar": govde(SIR_A, 1664),
           "app/gecici/kurulum.log": govde(SIR_B, 2048) },
    ayniKatmandaSil: ["app/gecici/kurulum.log"] },   // katman kapanmadan once kaldirilir
  { ad: "k4-yapilandirma", yaz: { "app/ayarlar.json": 512 },
    ustKatmandaSil: ["app/gizli/erisim.anahtar"] },  // ust katmanda silme isareti birakilir
];

// Katmanlar gercek dizinlere yazilir; silme isareti ".sil-" onekli bos bir dosyadir.
for (const k of KATMANLAR) {
  for (const [yol, deger] of Object.entries(k.yaz)) {
    if (k.ayniKatmandaSil?.includes(yol)) continue;
    const tam = join(KOK, k.ad, yol);
    mkdirSync(dirname(tam), { recursive: true });
    writeFileSync(tam, typeof deger === "number" ? Buffer.alloc(deger, "x") : deger);
  }
  for (const yol of k.ustKatmandaSil ?? []) {
    const tam = join(KOK, k.ad, dirname(yol), ".sil-" + yol.split("/").pop());
    mkdirSync(dirname(tam), { recursive: true });
    writeFileSync(tam, "");
  }
}

const dolas = (kok, alt = "") => readdirSync(join(kok, alt), { withFileTypes: true })
  .flatMap((g) => g.isDirectory() ? dolas(kok, join(alt, g.name)) : [join(alt, g.name)]);

// Birlesim gorunumu: alttan uste; silme isareti gorunen dosyayi kaldirir.
const gorunum = new Map();
for (const k of KATMANLAR) {
  for (const yol of dolas(join(KOK, k.ad))) {
    const ad = yol.split("/").pop();
    if (ad.startsWith(".sil-")) gorunum.delete(join(dirname(yol), ad.slice(5)));
    else gorunum.set(yol, statSync(join(KOK, k.ad, yol)).size);
  }
}

const diskte = KATMANLAR.flatMap((k) => dolas(join(KOK, k.ad))
  .filter((y) => !y.split("/").pop().startsWith(".sil-"))
  .map((y) => [k.ad, y, statSync(join(KOK, k.ad, y)).size]));
const gorunumBayt = [...gorunum.values()].reduce((t, b) => t + b, 0);
const diskBayt = diskte.reduce((t, [, , b]) => t + b, 0);
const gizlenen = diskte.filter(([, y]) => !gorunum.has(y));

console.log(`birlesim gorunumu   : ${gorunum.size} dosya, ${gorunumBayt} bayt`);
console.log(`katmanlarda gercekte: ${diskte.length} dosya, ${diskBayt} bayt`);
console.log(`gorunumde yok, katmanda duruyor: ${gizlenen.length} dosya, ` +
  `${gizlenen.reduce((t, [, , b]) => t + b, 0)} bayt`);

for (const [ad, sir] of [["SIR_A", SIR_A], ["SIR_B", SIR_B]]) {
  const bulunan = diskte.filter(([k, y]) => readFileSync(join(KOK, k, y)).includes(sir));
  console.log(`${ad}: ${bulunan.length} katman dosyasinda, ` +
    `${bulunan.reduce((t, [, , b]) => t + b, 0)} bayt` +
    (bulunan.length ? ` -> ${bulunan.map(([k, y]) => `${k}/${y}`).join(", ")}` : ""));
}
```

```
birlesim gorunumu   : 6 dosya, 27136 bayt
katmanlarda gercekte: 7 dosya, 28835 bayt
gorunumde yok, katmanda duruyor: 1 dosya, 1699 bayt
SIR_A: 1 katman dosyasinda, 1699 bayt -> k3-uygulama/app/gizli/erisim.anahtar
SIR_B: 0 katman dosyasinda, 0 bayt
```

Birleşim görünümü altı dosya ve 27.136 bayt gösterir. Diskte yedi dosya ve 28.835 bayt vardır.
Aradaki **1.699 bayt**, üst katmanda silinmiş görünen anahtar dosyasıdır ve `k3-uygulama`
katmanında bütün olarak durmaktadır. Görünüme bakan bir denetim onu bulamaz; katmanı açan biri
doğrudan okur.

İki sırrın farkı ölçüyü tamamlar. `SIR_B` de yazılmış, ama **aynı katman kapanmadan**
kaldırılmıştır: hiçbir katman dosyasında geçmez, sıfır bayt. `SIR_A` bir üst katmanda
silinmiştir ve tam boyutuyla durur. Aradaki tek fark silmenin hangi katmanda yapıldığıdır ve
sonuç ikili bir farktır — 1.699 bayta karşı 0 bayt.

Bu, önceki dersin ölçümüyle birleştiğinde ağırlaşır. Depoda bir katman ancak hiçbir manifest onu
referans etmiyorsa silinebilirdi; ölçümde düşen imajların on yedi katmanından üçü referanslı
olduğu için diskte kalmıştı. Sır içeren bir katman da aynı kurala tabidir: onu referans eden
başka bir imaj varsa, sırrı taşıyan imaj silinse bile baytlar depoda durur. **Yalıtımın delindiği
yer budur** — çıktının içine alınan fark, çıktıdan çıkarılamıyor.

Kapatma yolu ölçümün içinde yazılıdır. `SIR_B`'nin sıfır baytı, değerin katman kapanmadan
kaldırılmasıyla elde edilmiştir: aynı yönerge içinde yazılıp silinen bir dosya katmana hiç
girmez. Daha sağlamı ise hiç yazmamaktır — değerin yalnız derleme adımı boyunca görünen bir
kaynaktan okunması ve çıktıya kopyalanmaması. İki yolun ölçülen sonucu aynıdır, 1.699 bayta
karşı 0 bayt; ama birincisi yönergelerin sırasına, ikincisi çıktının yapısına dayanır ve sıra
bozulduğunda birincisi sessizce geri döner.

## Tarama Neyi Görür

İkinci ölçü, imajın içindeki bileşenleri bilinen bir kusur kümesiyle karşılaştırır. Tarama bir
eşleştirmedir ve eşleştirmenin iki yönde de hatası vardır.

**IM45.** Kusur kümesi kurgudur: bir bileşenin açık kaydı olma olasılığı 0,30, kaydın düzeltmesinin
sürüme geri taşınmış olma olasılığı 0,25'tir; tohum `20260909` görünürdür. **IM46.** Görünürlük
adaya bağlıdır — paket yöneticisi olan bir temelde envanter okunur, olmayanda bileşen ikilinin
içine gömülü gelir ve sürümü sayılamaz.

```js
// olcum-agi/tarama.mjs — bilinen kusur kumesinde tarama: yakalanan, kacan, yanlis alarm (model)
const uretec = (tohum) => () =>
  (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648;

const P_KUSUR = 0.30;    // bilesenin kusur kumesinde acik bir kaydi var
const P_YAMALI = 0.25;   // kaydi var ama duzeltme surume geri tasinmis: acik degil

// gorunurluk: bilesenin envanterde surumuyle okunabilme olasiligi. Paket yoneticisi
// olmayan bir temelde envanter yoktur; bilesen ikilinin icine gomulu gelir.
const ADAY = [["tam donanimli", 148, 0.92], ["kucultulmus", 31, 0.82], ["tek ikili", 6, 0.35]];

const tara = (bilesen, gorunurluk, tohum = 20260909) => {
  const r = uretec(tohum);
  const s = { bilesen, acik: 0, uyari: 0, yakalanan: 0, kacan: 0, yanlis: 0 };
  for (let i = 0; i < bilesen; i++) {
    const kayit = r() < P_KUSUR, gorunur = r() < gorunurluk, yamali = r() < P_YAMALI;
    const acik = kayit && !yamali;          // gercekten acik kusur
    const bulur = kayit && gorunur;         // taramanin urettigi uyari
    if (acik) s.acik += 1;
    if (bulur) s.uyari += 1;
    if (acik && bulur) s.yakalanan += 1;
    if (acik && !bulur) s.kacan += 1;
    if (bulur && !acik) s.yanlis += 1;
  }
  return s;
};

console.log("aday".padEnd(15) + "bilesen".padEnd(9) + "gorunurluk".padEnd(12) +
  "acik kusur".padEnd(12) + "uyari".padEnd(7) + "yakalanan".padEnd(11) +
  "kacan".padEnd(7) + "yanlis alarm");
const sonuc = [];
for (const [ad, n, g] of ADAY) {
  const s = tara(n, g);
  sonuc.push([ad, s]);
  console.log(ad.padEnd(15) + String(n).padEnd(9) + `%${(100 * g).toFixed(0)}`.padEnd(12) +
    String(s.acik).padEnd(12) + String(s.uyari).padEnd(7) + String(s.yakalanan).padEnd(11) +
    String(s.kacan).padEnd(7) + String(s.yanlis));
}

console.log("");
const yuzde = (a, b) => (b ? `%${(100 * a / b).toFixed(0)}` : "olcusuz");
for (const [ad, s] of sonuc) {
  console.log(`${ad.padEnd(15)} yakalama orani ${yuzde(s.yakalanan, s.acik)}, ` +
    `uyarilarin dogrulugu ${yuzde(s.yakalanan, s.uyari)}`);
}
```

```
aday           bilesen  gorunurluk  acik kusur  uyari  yakalanan  kacan  yanlis alarm
tam donanimli  148      %92         31          37     29         2      8
kucultulmus    31       %82         7           7      6          1      1
tek ikili      6        %35         1           0      0          1      0

tam donanimli   yakalama orani %94, uyarilarin dogrulugu %78
kucultulmus     yakalama orani %86, uyarilarin dogrulugu %86
tek ikili       yakalama orani %0, uyarilarin dogrulugu olcusuz
```

Tam donanımlı temelde 31 açık kusur vardır; tarama 37 uyarı üretir, bunların 29'u gerçektir,
**8'i yanlış alarmdır** ve **2 açık kusur kaçmıştır.** Yanlış alarmın kaynağı, düzeltmesi sürüme
geri taşınmış bir kayıttır: sürüm numarası kusurlu aralıkta görünür, kod değildir. Kaçmanın
kaynağı ise envanterde okunamayan bileşendir.

Son satır bu dersin en sert sayısıdır. **Tek ikili temelde tarama sıfır uyarı üretir, ama bir açık
kusur vardır.** Temiz bir rapor, kusursuz bir imaj demek değildir; sayılamayan bileşen sayılmamış
olur. Önceki derste ölçülen ödünç burada ikinci kez görünür — küçük temel gerçekten daha az kusur
taşır (148 bileşene karşı 6), ama taşıdığının görünürlüğünü de düşürür.

**Taramanın bedeli sayılabilir:** 37 uyarının 8'i boşa bakılmış uyarıdır ve her biri bir bileşenin
sürümüyle kusur kaydının elle karşılaştırılmasını gerektirir. Yakalama oranını yükseltmenin yolu
daha çok uyarı değil, envanterin okunabilir olmasıdır — bileşen listesinin imajın yanında ayrı bir
belge olarak taşınması bu boşluğu kapatır ve boyutu birkaç kilobayttır.

## İmzalama Zinciri

Üçüncü ölçü kimlik sorusudur. Tarama "içinde ne var" der; imza "bu gerçekten bizim ürettiğimiz mi"
der. Zincir üç halkadır: içerik özeti, o özetin imzası ve çekme anındaki doğrulama.

**IM47.** İmzalama gerçek bir anahtar çiftiyle yapılır, ama anahtar her koşumda yeniden üretilir;
bu yüzden basılan şey imzanın uzunluğu ve doğrulama sonucudur, imzanın kendisi değil. **IM48.**
Çekmede indirilen katmanın özetinin yeniden hesaplanması isteğe bağlı bir adımdır.

```js
// olcum-agi/imza.mjs — imzalama zinciri: ozet, imza, dogrulama (gercek anahtarla)
import { generateKeyPairSync, sign, verify, createHash } from "node:crypto";

const ozetle = (b) => createHash("sha256").update(b).digest();
// Anahtar cifti her kosumda yeniden uretilir. Imza baytlari kosuma baglidir; bu yuzden
// basilan sey imzanin uzunlugu ve dogrulama sonucudur, imzanin kendisi degil.
const { publicKey, privateKey } = generateKeyPairSync("ed25519");

// Onceki dersin 17 ayri imaji: her manifest dort katman ozetinin listesidir.
const manifest = (i) => Buffer.from([1, 2, 3, 4]
  .map((k) => ozetle(Buffer.from(`katman-${k}-${i}`)).toString("hex").slice(0, 12)).join("\n"));
const MANIFEST = Array.from({ length: 17 }, (_, i) => manifest(i));
const IMZA = MANIFEST.map((m) => sign(null, ozetle(m), privateKey));

console.log(`imzalanan manifest ${MANIFEST.length}, imza uzunlugu ${IMZA[0].length} bayt, ` +
  `toplam ${MANIFEST.length * IMZA[0].length} bayt`);
console.log(`depo 176200 bayt; imzalarin payi binde ` +
  `${(1000 * MANIFEST.length * IMZA[0].length / 176200).toFixed(1)}`);

// Zincirin halkalari ayri ayri sinanir.
const m0 = MANIFEST[0];
const kurcalanmis = Buffer.from(m0.toString().replace(/^.{12}/, "ffffffffffff"));
const { publicKey: baskaAnahtar } = generateKeyPairSync("ed25519");
console.log("\ndogru manifest, dogru imza :", verify(null, ozetle(m0), publicKey, IMZA[0]));
console.log("kurcalanmis manifest       :", verify(null, ozetle(kurcalanmis), publicKey, IMZA[0]));
console.log("baska anahtarla dogrulama  :", verify(null, ozetle(m0), baskaAnahtar, IMZA[0]));

// Katman baytinin kendisi: manifest dogru olsa bile inen bayt degisebilir.
const katman = Buffer.alloc(7200, "u");
const beklenen = ozetle(katman).toString("hex");
const bozuk = Buffer.from(katman);
bozuk[42] = 0x41;                               // tek bayt degistirildi
console.log("inen katmanin ozeti tutuyor:", ozetle(bozuk).toString("hex") === beklenen);

// 24 cekme, uc disiplin: neyi kabul ederdi?
const CEKME = 24, KATMAN = 4;
console.log("\ndisiplin".padEnd(36) + "kabul edilen sahte manifest".padEnd(29) + "dogrulanmayan katman");
for (const [ad, imzaBakilir, katmanBakilir] of [
  ["imza dogrulanmiyor", false, false],
  ["yalniz imza dogrulaniyor", true, false],
  ["imza ve katman ozeti dogrulaniyor", true, true],
]) {
  console.log(ad.padEnd(36) + `${imzaBakilir ? 0 : CEKME}/${CEKME}`.padEnd(29) +
    `${katmanBakilir ? 0 : CEKME * KATMAN}/${CEKME * KATMAN}`);
}
```

```
imzalanan manifest 17, imza uzunlugu 64 bayt, toplam 1088 bayt
depo 176200 bayt; imzalarin payi binde 6.2

dogru manifest, dogru imza : true
kurcalanmis manifest       : false
baska anahtarla dogrulama  : false
inen katmanin ozeti tutuyor: false

disiplin                           kabul edilen sahte manifest  dogrulanmayan katman
imza dogrulanmiyor                  24/24                        96/96
yalniz imza dogrulaniyor            0/24                         96/96
imza ve katman ozeti dogrulaniyor   0/24                         0/96
```

**İmzanın bedeli en küçük kalemdir:** on yedi manifest için toplam 1.088 bayt, deponun binde
6,2'si. Doğrulamanın kendisi de bir özet karşılaştırmasından ibarettir.

Orta üç satır zincirin çalıştığını gösterir: doğru manifest doğru imzayla doğrulanır, manifestin
ilk on iki karakteri değiştirildiğinde doğrulama düşer, imza başka bir anahtarla sınandığında da
düşer. Dördüncü satır zincirin nerede bittiğini gösterir — indirilen katmanın tek baytı
değiştirildiğinde özet tutmaz, **ama bu ancak özet yeniden hesaplanırsa görülür.**

Son tablo doğrulamanın atlandığı her adımın açtığı yolu sayar. Doğrulama yoksa yirmi dört
çekmenin yirmi dördü değiştirilmiş bir manifesti kabul eder. Yalnız imza doğrulanıyorsa sahte
manifest sıfıra iner, ama **doksan altı katmanın doksan altısı** özeti sınanmadan kabul edilir:
manifest doğrudur, inen baytın manifeste uyduğu bilinmez. İki halka da sınandığında iki sayı da
sıfırdır.

Zincirin dışında kalan bir şey vardır ve **yalıtımın burada delindiği yer odur**: imza "bu imaj
güvenlidir" demez, "bu manifest bu anahtarla imzalanmıştır" der. Anahtarın kime ait olduğu, nerede
saklandığı ve kimin imzalayabildiği modelin dışındadır. İmzalama, güvenin nereye taşındığını
belirler; ortadan kaldırmaz.

Zincir önceki dersin izlenebilirlik merdivenine de bağlanır. Kaynak sürümü alanı imajın hangi
kaynaktan geldiğini söyler, imza o beyanın üretildikten sonra değişmediğini söyler. Alan
imzalanmamışsa, sonradan yazılabilir bir nottur; imza olmadan izlenebilirlik oranı bir kayıt
disiplinini ölçer, bir güvenceyi değil.

## Özet

- Bir katman değişmez bir nesnedir; silme görünümü değiştirir, baytları değiştirmez. Ölçümde
  birleşim görünümü 6 dosya ve 27.136 bayt gösterirken diskte 7 dosya ve 28.835 bayt vardır.
- Fark 1.699 bayttır ve üst katmanda silinmiş görünen anahtar dosyasının tamamıdır. Aynı katman
  kapanmadan kaldırılan ikinci sır ise hiçbir katman dosyasında geçmez: 1.699 bayta karşı 0 bayt.
- Sır içeren katman referanslıysa, sırrı taşıyan imaj silinse bile depoda durur; çıktının içine
  alınan fark çıktıdan çıkarılamaz.
- Tarama iki yönde de yanılır: tam donanımlı temelde 31 açık kusurun 29'u yakalanır, 2'si kaçar
  ve 37 uyarının 8'i yanlış alarmdır. Tek ikili temelde tarama 0 uyarı üretir ama 1 açık kusur
  vardır — sayılamayan bileşen sayılmamış olur.
- İmzalama ucuzdur: 17 manifest için 1.088 bayt, deponun binde 6,2'si. Doğrulama yoksa 24
  çekmenin 24'ü sahte manifesti kabul eder; yalnız imza doğrulanıyorsa 96 katmanın 96'sı özeti
  sınanmadan kabul edilir. İmza güveni ortadan kaldırmaz, anahtara taşır.

## Sonraki Adım

Bu konu boyunca çıktı küçüldü, katmanlara ayrıldı, paylaşıldı, adlandırıldı, tarandı ve imzalandı.
Ölçülen her sayı — bayt, katman, referans, uyarı, imza — **durur hâldeki** bir nesne üzerineydi.
Nesne diskte duruyordu ve hiçbir şey yapmıyordu.

Oysa bu çıktının varlık nedeni çalışmaktır. Çalışmaya başladığı anda içindeki dosyalar bir sürecin
gördüğü dosyalar olur, katmanlar üst üste bindirilip yazılabilir bir yüzeye dönüşür, imzayla
doğrulanan şey belleğe alınır. Yalıtımın hangi bölümü bu geçişten sağ çıkar? Salt okunur olduğu
ölçülen katmanlar hâlâ salt okunur mu, imajın içine alınan farkın kaçı çalışma anında yeniden
ortamdan gelir ve bir sürecin ne yapabileceğini artık ne belirler?
