Rekayasa perangkat lunak moderen bersifat kolaboratif, dan berbasis pada penggunaan ulang dari perangkat lunak Open Source. Hal ini mengekspos target (perangkat lunak) terhadap serangan rantai pasok, yang mana proyek perangkat lunak tersebut diserang lewat dependensi pihak ketiga.
Terlepas dari proses atau tindakan teknis yang dilakukan, setiap dependensi secara tidak langsung adalah sebuah hubungan kepercayaan yang tidak dapat dihindarkan. Namun, perkakas dan rancangan Go membantu mitigasi resiko ini di berbagai tingkat.
Semua pembangunan (perangkat lunak) "dikunci"
Tidak mungkin perubahan dari dunia luar —seperti penerbitan versi baru dari sebuah dependensi— otomatis mempengaruhi pembangunan program Go.
Tidak seperti manajemen paket pada umumnya, modul Go tidak memisahkan berkas untuk daftar dependensi dan berkas untuk versi yang dikunci. Versi dari setiap dependensi yang berpengaruh pada pembangunan sebuah program Go sepenuhnya ditentukan oleh sebuah berkas go.mod dari modul utama.
Sejak Go 1.16, determinisme seperti ini telah berlaku secara baku, dan
perintah-perintah pembangunan (go build
, go test
, go install
,
go run
, …)
akan gagal bila berkas go.mod tidak komplit.
Satu-satunya perintah yang akan mengubah go.mod
(dan juga
pembangunan program) adalah go get
dan go mod tidy
.
Perintah-perintah ini seharusnya tidak berjalan secara otomatis atau
dalam sebuah sistem Continuous Integration (CI), supaya perubahan
terhadap dependensi harus dibuat dengan penuh kesadaran dan lewat
peninjauan kode yang ketat.
Hal ini sangat penting untuk keamanan (perangkat lunak), karena saat
sebuah sistem CI atau mesin yang baru menjalankan go build
, sumber
kode yang diunduh adalah sumber kebenaran untuk apa yang akan
dibangun.
Tidak mungkin untuk pihak ketiga dapat mengubahnya.
Lebih lanjut, saat sebuah dependensi ditambahkan lewat go get
,
relasi dependensi-dependensi bawaannya ditambahkan pada versi yang
dispesifikasikan dalam berkas dependensi "go.mod", bukan dari versi
terakhir mereka, ini berkat
Pemilihan versi
minimal.
Hal yang sama juga terjadi saat mengeksekusi
go install example.com/cmd/devtoolx@latest
,
yang pada beberapa ekosistem
meloncati versi yang telah disematkan.
Pada Go, versi terakhir dari example.com/cmd/devtoolx
-lah yang akan
diunduh, dan semua relasi dependensi-nya akan di unduh sesuai dengan
berkas go.mod
pada versi tersebut.
Jika sebuah modul telah terkontaminasi dan versi terbaru yang diduga berbahaya telah diterbitkan, tidak akan ada orang yang akan terserang sampai mereka secara eksplisit memperbarui dependensi mereka, menyediakan kesempatan untuk meninjau perubahan dan waktu bagi ekosistem mendeteksi kejadian tersebut.
Isi dari versi tidak pernah berubah
Bagian penting lainnya untuk memastikan pihak ketiga tidak dapat mencemarkan pembangunan program yaitu isi dari versi modul bersifat immutable, atau tidak berubah. Jika si peretas yang mencemarkan sebuah dependensi bisa mengunggah ulang versi-versi sebelumnya, maka mereka secara otomatis dapat mencemarkan semua proyek yang bergantung pada dependensi tersebut.
Itulah tujuan dari
berkas go.sum
.
Ia berisi daftar hash kriptografi dari setiap dependensi yang
berkontribusi pada pembangunan program.
Sekali lagi, sebuah go.sum
yang tidak komplit akan menyebabkan eror,
dan hanya perintah go get
dan go mod tidy
saja yang dapat
mengubahnya, sehingga setiap perubahan terhadap berkas tersebut
diikuti oleh perubahan dependensi yang disengaja.
Pembangunan dengan go.sum
yang komplit dijamin memiliki sekumpulan
checksum yang lengkap.
Hal seperti ini adalah fitur yang umum pada berkas-berkas lock
(berkas yang mengunci berkas lainnya, memastikan berkas lain tersebut
tidak pernah diubah).
Go mengembangkan fitur tersebut lebih jauh dengan adanya
Basisdata Checksum
(singkatnya "sumdb"), sebuah daftar go.sum
global yang berisi daftar
go.sum
, yang isinya hanya di-tambah saja dan diverifikasi dengan
kriptografi.
Saat go get
butuh menambahkan sebuah entri ke dalam berkas go.sum
,
ia akan mengambilnya dari sumdb bersama dengan bukti kriptografi dari
integritas sumdb.
Hal ini, selain memastikan setiap pembangunan program dari modul
tertentu menggunakan isi dependensi yang sama, juga memastikan setiap
modul di luar sana menggunakan isi dependensi yang sama juga!
Berkas sumdb membuat sebuah dependensi yang telah tercemari tidak
akan mungkin terjadi, bahkan pada infrastruktur Go itu sendiri yang
dioperasi-kan oleh Google, yang bisa saja menargetkan dependensi
tertentu dengan memodifikasi sumber kode (misalnya, dengan menambahkan
backdoor).
Anda dijamin menggunakan kode yang sama dengan yang lain, misalnya
versi v1.9.2
dari example.com/modulex
digunakan oleh semua orang
dengan isi yang sama dan telah diperiksa.
Terakhir, fitur favorit saya pada sumdb: ia tidak membutuhkan manajemen kunci (kriptografi) dari sisi penulis modul, dan ia bekerja dengan mulus secara alami pada model desentralisasi dari modul Go.
VCS adalah sumber kebenaran
Kebanyakan proyek perangkat lunak dikembangkan dengan version control system (VCS) dan kemudian, pada ekosistem yang berbeda, diunggah ke repositori paket. Ini berarti ada dua akun yang dapat tercemar, peladen VCS dan repositori paket. Repository paket jarang digunakan dan sering diabaikan oleh pengembang aplikasi. Dengan kata lain, lebih gampang menyembunyikan kode berbahaya di dalam versi paket yang diunggah ke repositori, khususnya bila sumber kode perlu diubah terlebih dahulu sebagai bagian dari (proses) sebelum mengunggah, sebagai contohnya untuk meminimalkan ukuran berkas.
Pada Go, tidak ada namanya akun untuk repositori paket.
Path pada bagian meng-"import" paket, menanam informasi
yang dibutuhkan
untuk mengunduh modul oleh perintah go mod download
secara langsung
lewat VCS, yang mana tag mendefinisikan versi.
Kita memang memiliki Salinan Go Modul, namun itu hanya proksi. Proksi tersebut menggunakan logika yang sama dengan perkakas Go (pada kenyataannya, proksi tersebut menjalankan "go mod download") untuk mengunduh dan menyimpan sebuah versi. Secara Basisdata Checksum menjamin bahwa hanya ada satu sumber asli dari sebuah versi modul, maka semua orang yang menggunakan proksi akan mendapatkan hasil yang sama dengan orang lain yang tidak menggunakan proksi, atau yang secara langsung mengambil ke VCS. (Jika sebuah versi tidak tersedia lagi di VCS atau isinya berubah, maka pengambilan secara langsung akan menyebabkan eror, namun pengambilan lewat proksi bisa saja masih bekerja, hal ini meningkatkan availabilitas dan melindungi ekosistem dari masalah left-pad).
Menjalankan perkakas VCS dari sisi klien juga memungkinkan adanya serangan keamanan. Hal ini juga di-mitigasi dengan adanya Salinan Go Modul: perkakas Go di sisi proksi berjalan dalam sandbox yang diatur untuk mendukung semua perkakas VCS, sementara perkakas Go pada sisi klien hanya mendukung dua sistem VCS utama saja (git dan Mercurial). Orang yang menggunakan proksi masih bisa mengunduh kode yang diterbitkan menggunakan sistem VCS selain git dan Mercurial, namun si peretas tidak akan dapat menjangkau dan mencemari kode tersebut.
Membangun kode tidak mengeksekusi kode
Salah satu gol dari rancangan keamanan dari perkakas Go yaitu pada saat pengunduhan atau pembangunan kode tidak akan membiarkan kode tersebut dieksekusi, bahkan pada yang kode yang berbahaya dan tidak dipercaya sekalipun. Hal ini berbeda dengan ekosistem lainnya, banyak ekosistem yang mendukung menjalankan kode pada saat paket diunduh. Mekanisme "post-install" ini telah digunakan pada waktu dulu sebagai cara yang mudah untuk menjadikan sebuah dependensi yang tercemar menjadi mesin pengembang yang tercemar, dan berkembang jadi worm lewat si penulis modul.
Jika kita mengunduh kode, sering kali kita mengeksekusi-nya nanti,
baik untuk dicoba pada mesin pengembang itu sendiri atau sebagai
bagian dari program di lingkungan production, jadi dengan
tidak adanya "post-install" hanya melambatkan si peretas.
(Tidak ada batasan keamanan dalam sebuah pembangunan: setiap paket
yang berkontribusi pada sebuah pembangunan dapat mendefinisikan fungsi
init
.)
Namun, ia bisa untuk mitigasi resiko yang berguna, secara kita mungkin
mengeksekusi program atau menguji sebuah paket yang hanya menggunakan
bagian dari dependensi modul.
Misalnya, jika kita membangun dan menjalankan program dari
"example.com/cmd/devtoolx" di macOS, maka tidak akan mungkin
dependensi yang hanya berjalan di-Windows atau sebuah dependensi
"example.com/cmd/othertool" lainnya mencemarkan mesin kita.
Pada Go, modul yang tidak berkontribusi pada pembangunan kode tertentu tidak memiliki impak keamanan.
"Sedikit menyalin lebih baik dari sedikit dependensi"
Mitigasi resiko rantai pasok terakhir dan mungkin yang paling penting dari perangkat lunak dalam ekosistem Go adalah yang paling tidak teknis: Go memiliki budaya yang menolak dependensi yang besar, dan lebih memilih menyalin kode daripada menambahkan dependensi baru. Hal ini merupakan salah satu pepatah Go: sedikit menyalin lebih baik daripada sedikit dependensi. Label "nol dependensi" secara bangga dipakai oleh Go modul yang berkualitas tinggi. Jika Anda membutuhkan sebuah pustaka, Anda kemungkinan akan menemukan pustaka tersebut tidak akan mengikutkan lusinan dependensi lain dari modul dan penulis yang berbeda.
Hal ini juga dikarenakan kayanya pustaka baku dari Go itu sendiri dan modul-modul tambahan (seperti "golang.org/x/…"), yang berisi blok-blok pembangunan yang sering digunakan, seperti pustaka HTTP, pustaka TLS, pustaka JSON, dan lainnya.
Dengan kata lain, memungkinkan membangun aplikasi yang kompleks dan kaya fitur dengan hanya beberapa dependensi. Sebagus apa pun perkakasnya, kita tidak dapat menghindari resiko dari penggunaan ulang kode, sehingga mitigasi paling kuat selalu dengan menggunakan dependensi yang sesedikit mungkin.