Ders 10 / 23
API Anahtarları
Makine çağıranlar için üretilen uzun ömürlü kimlik bilgisinin entropisi, önek ve son karakterlerin işletim değeri, karma olarak saklanması, geçiş penceresiyle döndürülmesi ve kapsamla sınırlanması.
İçindekiler
Buraya kadarki bütün akışlarda karşıda bir insan vardı: parola giren, onay ekranını okuyan, ikinci etmeni sağlayan biri. Kütüphane servisini çağıranların bir bölümü ise insan değildir. Gece çalışan bir toplu iş, komşu kurumun katalog eşitleyicisi, bir gösterge panosu — bunların ne parolası vardır ne de bir onay ekranını okuyabilirler.
Bu çağıranlar için kullanılan kimlik bilgisine API anahtarı denir. Yapısı yalındır: uzun, rastgele bir dizgi. Yalınlığı hem gücü hem zayıflığıdır — taşınması kolaydır, sızması da kolaydır.
Anahtar Neyi Kanıtlar
API anahtarı kim olduğunu kanıtlar, ne yapabileceğini değil. Bu ayrım kursun ilk dersindeki ayrımın makineler için geçerli biçimidir: anahtar doğrulanır, yetki ayrıca sınırlanır.
Anahtarın belirteçten farkı ömrüdür. Belirteç kısa yaşar ve yenilenir; anahtar aylarca aynı kalır ve yapılandırma dosyalarında, sürekli tümleştirme ortamlarında, kap görüntülerinde durur. Bu yüzden anahtarın tasarımında iki soru öne çıkar: sızdığında ne kadar zarar verir ve ne kadar hızlı değiştirilebilir?
Aşağıdaki hesap anahtarın üretiminden döndürülmesine kadar bütün yaşam döngüsünü çalıştırır.
cat > anahtar.mjs <<'EOF' // anahtar.mjs — API anahtarinin uretimi, saklanmasi, dogrulanmasi ve dondurulmesi import { randomBytes, createHash, timingSafeEqual } from "node:crypto"; import { DatabaseSync } from "node:sqlite"; // 1. Uretim: onek + rastgele govde. Onek anahtarin nereye ait oldugunu okunur kilar. const ONEK = "kutup_"; function anahtarUret(bayt) { const govde = randomBytes(bayt).toString("base64url"); return ONEK + govde; } const entropi = (bayt) => bayt * 8; console.log("govde bayt entropi (bit) ornek uzunluk kaba kuvvet icin gereken deneme (2^n)"); for (const b of [8, 16, 24, 32]) { console.log(`${String(b).padStart(10)} ${String(entropi(b)).padStart(14)} ${String(anahtarUret(b).length).padStart(14)} 2^${entropi(b) - 1}`); } // 2. Saklama: anahtarin kendisi degil karmasi tutulur; onek ve son dort karakter aranabilir. const db = new DatabaseSync(":memory:"); db.exec(`CREATE TABLE api_anahtari ( kimlik TEXT PRIMARY KEY, sahip TEXT NOT NULL, karma TEXT NOT NULL, kuyruk TEXT NOT NULL, durum TEXT NOT NULL, gecerlilik_sonu INTEGER)`); const karmala = (a) => createHash("sha256").update(a).digest("hex"); function kaydet(kimlik, sahip, anahtar, gecerlilikSonu = null) { db.prepare("INSERT INTO api_anahtari VALUES (?,?,?,?,?,?)") .run(kimlik, sahip, karmala(anahtar), anahtar.slice(-4), "etkin", gecerlilikSonu); } const eski = anahtarUret(24); kaydet("AK-1", "katalog-esitleyici", eski); // Karma ve kuyruk her uretimde farklidir; burada yalniz uzunluklari yazdirilir. console.log("\nsaklanan kayit (anahtarin kendisi yok):"); for (const s of db.prepare("SELECT kimlik, sahip, length(karma) AS karma_uzunluk, length(kuyruk) AS kuyruk_uzunluk, durum FROM api_anahtari").all()) console.log(" " + JSON.stringify(s)); // 3. Dogrulama: sabit sureli karsilastirma; eslesme yoksa neden acikca yazilmaz. const SAAT = 1_770_000_000; function dogrula(sunulan, saat) { const k = karmala(sunulan); const satir = db.prepare("SELECT * FROM api_anahtari WHERE kuyruk = ?").all(sunulan.slice(-4)) .find((s) => { const a = Buffer.from(s.karma, "hex"), b = Buffer.from(k, "hex"); return a.length === b.length && timingSafeEqual(a, b); }); if (!satir) return "red: gecersiz anahtar"; if (satir.durum !== "etkin") return `red: anahtar ${satir.durum}`; if (satir.gecerlilik_sonu !== null && saat >= satir.gecerlilik_sonu) return "red: suresi gecmis"; return `kabul (${satir.sahip})`; } console.log("\ndogrulama denemeleri"); console.log(" gecerli anahtar -> " + dogrula(eski, SAAT)); console.log(" rastgele baska anahtar -> " + dogrula(anahtarUret(24), SAAT)); // 4. Dondurme: yeni anahtar uretilir, eski bir gecis penceresi boyunca gecerli kalir. const yeni = anahtarUret(24); kaydet("AK-2", "katalog-esitleyici", yeni); db.prepare("UPDATE api_anahtari SET gecerlilik_sonu = ? WHERE kimlik = 'AK-1'").run(SAAT + 3600); const asamalar = [ ["dondurmeden once", SAAT - 10], ["gecis penceresinde", SAAT + 60], ["pencere kapandiktan sonra", SAAT + 7200], ]; console.log("\nasama eski anahtar yeni anahtar"); for (const [ad, saat] of asamalar) console.log(` ${ad.padEnd(26)} ${dogrula(eski, saat).padEnd(23)} ${dogrula(yeni, saat)}`); // 5. Sizinti sonrasi: iptal aninda etkili olur, sureye bakilmaz. db.prepare("UPDATE api_anahtari SET durum = 'iptal' WHERE kimlik = 'AK-2'").run(); console.log("\niptalden sonra yeni anahtar -> " + dogrula(yeni, SAAT + 60)); // 6. Kapsam ve kaynak sinirlamasi: anahtar tek basina yetki degildir. const SINIR = { "AK-1": { kapsamlar: ["katalog:oku"], agBloklari: ["10.0.0.0/8"] } }; console.log("\nanahtar basina sinir: " + JSON.stringify(SINIR["AK-1"])); console.log(" anahtar dogrulanir, yetki ayrica kapsamla ve kaynak agiyla sinirlanir."); EOF node anahtar.mjs
govde bayt entropi (bit) ornek uzunluk kaba kuvvet icin gereken deneme (2^n)
8 64 17 2^63
16 128 28 2^127
24 192 38 2^191
32 256 49 2^255
saklanan kayit (anahtarin kendisi yok):
{"kimlik":"AK-1","sahip":"katalog-esitleyici","karma_uzunluk":64,"kuyruk_uzunluk":4,"durum":"etkin"}
dogrulama denemeleri
gecerli anahtar -> kabul (katalog-esitleyici)
rastgele baska anahtar -> red: gecersiz anahtar
asama eski anahtar yeni anahtar
dondurmeden once kabul (katalog-esitleyici) kabul (katalog-esitleyici)
gecis penceresinde kabul (katalog-esitleyici) kabul (katalog-esitleyici)
pencere kapandiktan sonra red: suresi gecmis kabul (katalog-esitleyici)
iptalden sonra yeni anahtar -> red: anahtar iptal
anahtar basina sinir: {"kapsamlar":["katalog:oku"],"agBloklari":["10.0.0.0/8"]}
anahtar dogrulanir, yetki ayrica kapsamla ve kaynak agiyla sinirlanir.
Üretim: Entropi ve Önek
Çıktının ilk tablosu gövde uzunluğunun entropiye çevrimini gösteriyor. On altı baytlık bir gövde yüz yirmi sekiz bit entropi taşır; kaba kuvvetle bulunması pratikte olanaksızdır. Daha uzun anahtarlar ek güvenlik getirmez, yalnız yapılandırma dosyalarını şişirir.
Önek ise güvenlik değil işletim içindir. Anahtar bir günlüğe ya da kaynak koduna sızdığında, önek onun hangi servise ait olduğunu tek bakışta söyler; sızıntı taramaları da bu önekle çalışır. Aynı nedenle anahtarın son birkaç karakteri kayıtta saklanır: kullanıcı “hangi anahtarı iptal edeyim” sorusunu ancak böyle yanıtlayabilir.
Saklama: Anahtarın Kendisi Tutulmaz
İkinci bölüm saklanan kaydı gösteriyor ve kayıtta anahtarın kendisi yok. Tutulan şey karması, arama için son dört karakteri ve durum bilgisidir.
Bunun sonucu, anahtarın yalnız bir kez gösterilebilmesidir: üretildiği anda kullanıcıya verilir, sonra bir daha okunamaz. Kaybedilen anahtar kurtarılmaz, yenisi üretilir. Bu kısıt bir kolaylık kaybı gibi görünür ama veritabanı sızıntısında anahtarların doğrudan kullanılabilir olmasını engeller.
Parola saklamadan bir fark vardır: API anahtarı yüksek entropili ve rastgeledir, sözlük saldırısına açık değildir. Bu yüzden yavaş karma gerekmez; hızlı bir karma yeterlidir ve her istekte doğrulanacağı için tercih edilir. Doğrulamanın sabit süreli karşılaştırmayla yapılması ise yine gereklidir.
Döndürme: Geçiş Penceresi
Dördüncü bölüm döndürmeyi aşama aşama gösteriyor. Yeni anahtar üretilir ve eski anahtara bir son tarih verilir; geçiş penceresinde ikisi de geçerlidir. Bu, çağıran tarafın yapılandırmasını güncellemesi için zaman tanır.
Pencere olmadan döndürme bir kesintidir: anahtar değiştiği anda eski yapılandırmayla çalışan her iş düşer. Pencerenin uzunluğu, çağıran tarafın dağıtım sıklığına göre seçilir ve pencerenin sonunda eski anahtarın gerçekten kullanılmadığı ölçülerek doğrulanır — son kullanım zamanı kayıtta tutulur.
Beşinci bölüm istisnayı gösteriyor: sızıntı durumunda pencere yoktur. İptal anında etkili olur ve süre denetiminden önce çalışır; kesinti, sızmış bir anahtarın açık kalmasına yeğdir.
Anahtar Tek Başına Yetki Değildir
Son bölüm anahtar başına sınırları listeliyor. Doğrulanmış bir anahtar, çağıranın kim olduğunu söyler; ne yapabileceği kapsamla, nereden çağırabileceği kaynak ağı kısıtıyla sınırlanır. Salt okuma yapan bir eşitleyicinin anahtarı yazma uç noktalarına erişememelidir — bu, Kapsamlar ve İzinler dersindeki kuralın makine çağıranlar için uygulanmasıdır.
Anahtarın nerede saklandığı da tasarımın parçasıdır. Kaynak koduna, kap görüntüsüne ya da sürüm kontrolüne yazılan anahtar, o depoya erişebilen herkese açıktır. Doğru yer, çalışma zamanında okunan bir sır deposudur; Gizli Değerlerin Ayrılması dersindeki sınır burada da geçerlidir.
Özet
- API anahtarı, onay ekranı olmayan makine çağıranlar için kullanılan uzun ömürlü bir kimlik bilgisidir; kim olduğunu kanıtlar, yetkiyi kapsam belirler.
- Önek ve son karakterler işletim içindir: sızıntı taraması ve “hangi anahtarı iptal edeyim” sorusu bunlarla çalışır.
- Kayıtta anahtarın kendisi değil karması tutulur; anahtar yalnız üretildiği anda gösterilir. Yüksek entropili olduğu için hızlı karma yeterlidir, karşılaştırma sabit sürelidir.
- Döndürmede eski ve yeni anahtar bir geçiş penceresinde birlikte geçerlidir; sızıntıda pencere yoktur, iptal anında etkilidir.
- Anahtar sır deposunda durur; kaynak koduna, kap görüntüsüne ya da sürüm kontrolüne yazılmaz.
Sonraki Adım
Anahtar, makine çağıranın tek kanıtıdır: onu elinde tutan çağırandır. İnsan kullanıcılarda ise tek kanıta dayanmak yetersizdir — parola ele geçtiğinde hesabın tamamı açılır. Kursun bu konudaki son dersi ikinci bir kanıt katmanını ele alıyor: hangi etmen türleri vardır, zaman tabanlı kodun doğrulaması nasıl yapılır, saat kayması nasıl karşılanır ve kurtarma kodları neden tek kullanımlıktır?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.