Monolit vs isolasi build Rust untuk kurangi risiko supply chain pada dasarnya adalah soal membatasi radius ledakan ketika sebuah dependency, build script, atau tool CI berperilaku jahat atau disusupi. Jika semua crate, tool build, secret, dan target rilis hidup dalam satu proses build yang sama, maka satu titik kompromi bisa menjangkau terlalu banyak hal sekaligus.

Di sisi lain, mengisolasi build tidak gratis. Anda menukar kenyamanan satu pipeline sederhana dengan biaya operasional, workflow developer yang lebih rumit, cache CI yang terpecah, dan maintenance jangka panjang yang lebih berat. Artikel ini fokus pada keputusan praktis: kapan cukup memakai build monolit, kapan crate sensitif perlu diisolasi, dan kapan pipeline CI terpisah benar-benar masuk akal.

Masalah inti: supply chain attack di Rust tidak hanya datang dari runtime

Dalam ekosistem Rust, risiko supply chain tidak berhenti di crate yang dieksekusi saat aplikasi berjalan. Ada beberapa jalur lain yang sering luput:

  • Build script seperti build.rs yang dapat menjalankan kode saat kompilasi.
  • Procedural macro yang dieksekusi compiler selama proses build.
  • Dependency dev/test yang hanya dipakai di CI, tetapi tetap punya akses ke lingkungan build.
  • Tooling seperti generator kode, formatter kustom, wrapper build, atau script release.
  • Credential CI untuk publish artifact, sign binary, atau deploy.

Artinya, model ancamannya bukan hanya “apakah binary final aman dijalankan”, tetapi juga “apa yang boleh disentuh oleh dependency selama proses build”. Dari sini, pilihan arsitektur build menjadi penting.

Tiga pendekatan utama

1. Monolit build: satu workspace, satu pipeline, satu konteks kepercayaan

Pendekatan paling umum adalah satu Cargo workspace besar dengan satu atau beberapa job CI yang membangun semuanya. Semua crate dibangun dalam konteks yang relatif sama, sering kali memakai cache yang sama, secret yang sama untuk tahap tertentu, dan tooling yang sama.

Kelebihan:

  • Paling sederhana untuk developer.
  • Dependency graph mudah dipahami dari satu tempat.
  • Cache build biasanya lebih efektif.
  • Refactor lintas crate lebih mudah.
  • Cocok untuk tim kecil dengan sedikit boundary keamanan.

Kekurangan:

  • Sulit membatasi capability dependency tertentu.
  • Jika satu crate butuh akses sensitif saat build atau release, konteks kepercayaan bisa melebar ke crate lain.
  • Dependency dev/test berisiko mempengaruhi pipeline yang lebih sensitif.
  • Build script dan proc-macro dari area non-kritis bisa ikut berjalan pada pipeline yang memegang kredensial penting.

Pendekatan ini aman relatif jika Anda benar-benar menjaga pipeline release seminimal mungkin dan tidak mencampur job umum dengan job yang punya secret sensitif.

2. Isolasi crate sensitif: workspace tetap ada, tapi boundary build diperketat

Pendekatan tengah adalah tetap memakai monorepo atau workspace yang sama, tetapi crate yang sensitif diperlakukan berbeda. Misalnya:

  • crate untuk penandatanganan artifact, enkripsi, atau deployment dibangun pada job terpisah,
  • crate yang memerlukan secret hanya dibangun dalam lingkungan yang lebih ketat,
  • dependency untuk tool internal tidak diberi jalan masuk ke pipeline release.

Ini sering menjadi kompromi terbaik. Anda masih mempertahankan produktivitas workspace Rust, tetapi mulai memisahkan capability berdasarkan fungsi.

3. Pipeline CI terpisah: pemisahan build sebagai kontrol keamanan utama

Pada model ini, komponen sensitif dipisah lebih tegas: bisa tetap dalam monorepo, tetapi dibangun oleh pipeline berbeda; atau dipindah ke repo berbeda jika memang perlu pemisahan yang lebih keras. Tujuannya bukan sekadar organisasi kode, tetapi membatasi apa yang bisa dijangkau sebuah dependency atau job CI.

Contoh:

  • pipeline umum hanya menjalankan test, lint, dan build non-release tanpa secret,
  • pipeline release hanya menerima artifact atau source subset yang sudah lolos verifikasi,
  • tool untuk publish crate atau sign binary hidup dalam proyek terpisah dengan dependency minimal.

Pendekatan ini paling kuat dari sisi reduksi blast radius, tetapi juga paling mahal dari sisi operasi.

Mengapa isolasi build bisa mengurangi risiko

Membatasi capability dependency

Gagasan pentingnya adalah: dependency tidak hanya dibatasi oleh API, tetapi juga oleh lingkungan tempat ia dibangun atau dijalankan. Jika sebuah crate dibangun dalam job yang tidak punya akses ke secret, credential registry, kunci signing, atau artifact produksi, maka kompromi pada crate tersebut dampaknya lebih kecil.

Ini sejalan dengan prinsip least privilege. Dalam konteks build Rust, capability yang patut dipikirkan antara lain:

  • akses jaringan saat build,
  • akses filesystem di luar workspace,
  • akses ke environment variable sensitif,
  • akses ke token publish/deploy,
  • akses ke key signing atau storage artifact release,
  • hak menjalankan tool tambahan di runner CI.

Walaupun Cargo sendiri bukan sistem sandbox capability penuh, Anda tetap bisa membangun boundary praktis melalui pemisahan job, runner, image, secret, dan tahap pipeline.

Memisahkan tool build dari produk yang dirilis

Kesalahan umum adalah menyatukan semua hal berikut dalam satu jalur build:

  • generator kode,
  • tool release,
  • test utility,
  • binary produksi,
  • langkah signing dan publish.

Padahal tool build sering memiliki dependency yang lebih “liar” atau cepat berubah daripada binary produksi. Bila tool semacam itu berada di jalur yang sama dengan proses rilis sensitif, risiko supply chain ikut terbawa.

Praktik yang lebih aman adalah menganggap tool build sebagai produk terpisah. Jika perlu, letakkan dalam crate atau proyek berbeda dengan dependency minimal dan boundary CI yang jelas.

Pola implementasi yang praktis

Pola A: Monolit build yang diperketat

Ini cocok jika tim masih kecil dan belum siap menanggung kompleksitas isolasi besar. Fokusnya adalah mengurangi risiko tanpa mengubah arsitektur secara drastis.

  • Gunakan satu workspace, tetapi pisahkan job check/test dari job release.
  • Jangan expose secret pada job test umum.
  • Batasi langkah release hanya untuk crate atau package yang memang perlu.
  • Audit dependency build-time seperti proc-macro dan build.rs.
  • Kurangi tool tambahan yang ikut hidup di runner release.
# Job umum tanpa secret sensitif
cargo check --workspace
cargo test --workspace
cargo clippy --workspace --all-targets

# Job release terpisah, hanya untuk package tertentu
cargo build --release -p app_core
cargo build --release -p signer_cli

Pola ini tidak memberi isolasi keras, tetapi sudah mengurangi pencampuran konteks.

Pola B: Workspace tunggal, crate sensitif diisolasi dalam pipeline berbeda

Di sini kode tetap nyaman dikelola dalam satu repo, tetapi pipeline dibelah berdasarkan tingkat sensitivitas.

  • Pipeline standar: lint, test, integration test, build developer.
  • Pipeline sensitif: build crate tertentu, signing, publish, deploy.

Agar efektif, pastikan pipeline sensitif:

  • tidak menjalankan langkah yang tidak relevan,
  • tidak memuat dependency dev/test bila tidak perlu,
  • memakai runner atau image yang lebih minimal,
  • memiliki secret yang scoped ketat.
# Contoh target crate sensitif
cargo build --locked --release -p signing_service

# Hindari build workspace penuh jika tidak perlu
cargo build --locked --release -p api_gateway

Mengapa ini bekerja? Karena dependency dari seluruh workspace tidak otomatis mendapat jalur eksekusi dalam konteks yang sama. Semakin sempit paket yang dibangun, semakin kecil peluang dependency lain ikut mengeksekusi kode build-time.

Pola C: Tool build dipisah dari aplikasi utama

Jika Anda memiliki tool internal seperti generator schema, codegen, atau utility release, pertimbangkan memisahkannya dari aplikasi utama. Ini bisa tetap satu repo, tetapi dengan ownership, lockfile, atau pipeline berbeda; atau menjadi repo terpisah bila risikonya cukup tinggi.

Cocok untuk kasus seperti:

  • tool release menggunakan token publish atau akses registry privat,
  • tool signing mengakses kunci atau HSM,
  • tool codegen menarik data dari sumber eksternal,
  • tool operasi platform punya hak lebih besar daripada aplikasi biasa.

Trade-off utamanya adalah duplicasi setup dan kemungkinan friction saat perubahan lintas tool dan aplikasi.

Matriks keputusan

PilihanKapan cocokKeamanan supply chainBiaya operasionalWorkflow developerMaintainability
Monolit buildTim kecil, produk tunggal, sedikit secret sensitifSedang ke rendahRendahPaling sederhanaBaik di awal, bisa memburuk saat sistem tumbuh
Isolasi crate sensitifMulai ada komponen signing, deploy, atau data sensitifBaikSedangMasih cukup nyamanSering jadi titik tengah terbaik
Pipeline CI terpisahTim platform, compliance tinggi, blast radius harus kecilTinggiTinggiLebih rumitBaik jika dikelola disiplin, buruk jika boundary kabur

Jika boundary keamanan tidak jelas atau tidak benar-benar ditegakkan oleh pipeline, repo terpisah sekalipun belum tentu memberi manfaat berarti. Pemisahan yang efektif adalah pemisahan capability, bukan sekadar folder atau repository.

Trade-off yang sering diremehkan

1. Kecepatan CI dan efisiensi cache

Monolit build sering lebih cepat karena cache dependency dan artifact lebih terkonsolidasi. Saat pipeline dipisah, Anda mungkin kehilangan sebagian efisiensi cache, terutama jika runner atau image berbeda.

Namun build yang lebih sempit juga bisa lebih cepat untuk job sensitif karena hanya membangun package tertentu. Jadi dampaknya tidak selalu negatif. Kuncinya adalah mengukur job yang benar-benar penting, bukan hanya total menit CI secara kasar.

2. Kompleksitas workflow developer

Developer biasanya menyukai satu perintah yang “jalan semua”. Isolasi build memperkenalkan pertanyaan baru:

  • crate mana yang harus dites lokal?
  • pipeline mana yang bertanggung jawab atas artifact tertentu?
  • apakah perubahan pada crate umum mempengaruhi crate sensitif?
  • siapa yang punya akses menjalankan pipeline release?

Jika tidak didokumentasikan, tim akan mulai menambah pengecualian, lalu boundary keamanan melemah sedikit demi sedikit.

3. Maintainability jangka panjang

Boundary yang baik biasanya mengikuti tanggung jawab bisnis dan keamanan yang stabil. Boundary yang buruk lahir dari kebutuhan sementara, misalnya “pisahkan karena CI sedang lambat”. Hasilnya adalah struktur yang sulit dijelaskan setahun kemudian.

Sebelum memisahkan build, tanyakan:

  • apakah komponen ini memang punya kebutuhan trust yang berbeda?
  • apakah perbedaan ini stabil dalam jangka panjang?
  • siapa yang akan merawat pipeline tambahan ini?

Skenario praktis: tim kecil vs tim platform

Tim kecil dengan satu produk backend

Misalkan tim berisi 4 engineer mengelola satu layanan Rust, satu worker, dan beberapa utilitas internal. Belum ada signing khusus, publish artifact sederhana, dan secret hanya dipakai saat deploy.

Rekomendasi:

  • tetap gunakan monolit build atau satu workspace,
  • pisahkan job test umum dari job deploy,
  • hindari tool release yang terlalu kompleks dalam workspace yang sama jika tool itu memegang credential sensitif,
  • mulai audit dependency build-time pada crate yang menyentuh deployment.

Untuk tim seperti ini, memecah menjadi banyak pipeline sensitif sejak awal sering menjadi overengineering. Risiko mungkin nyata, tetapi biaya koordinasinya bisa lebih besar daripada manfaat praktisnya.

Tim platform atau organisasi dengan banyak layanan

Bayangkan tim platform mengelola binary agen, CLI internal, service control plane, dan pipeline release dengan signing. Ada banyak dependency, banyak contributor, dan sebagian pipeline memegang token penting.

Rekomendasi:

  • pisahkan crate atau proyek yang memegang capability sensitif,
  • gunakan pipeline release khusus untuk komponen yang dipublish atau ditandatangani,
  • minimalkan dependency pada tool release,
  • bedakan runner umum dan runner berprivilege,
  • dokumentasikan trust boundary per pipeline dan per artifact.

Dalam organisasi seperti ini, isolasi build bukan sekadar optimasi keamanan, tetapi bagian dari desain platform.

Contoh struktur yang masuk akal

repo/
  Cargo.toml
  crates/
    app/
    worker/
    shared/
    signing_client/
  tools/
    release-tool/
    schema-gen/
  .ci/
    pipeline-test.yml
    pipeline-release-app.yml
    pipeline-release-signing.yml

Interpretasinya:

  • app, worker, dan shared boleh diuji di pipeline umum.
  • signing_client atau komponen yang berkaitan dengan signing dibangun di pipeline lebih ketat.
  • release-tool tidak otomatis ikut hidup di semua job.
  • pipeline release tidak perlu menjalankan seluruh tool developer jika tidak relevan.

Struktur ini bukan aturan mutlak, tetapi membantu memvisualisasikan pemisahan tanggung jawab.

Kesalahan umum saat menerapkan isolasi build Rust

  • Memisahkan repo tanpa memisahkan secret atau runner. Ini memberi rasa aman palsu.
  • Masih membangun seluruh workspace pada pipeline sensitif. Boundary jadi lemah karena terlalu banyak kode ikut dieksekusi.
  • Mencampur tool release dengan dependency eksperimental. Tool sensitif seharusnya dependency-nya konservatif.
  • Tidak membedakan dependency runtime dan build-time. Proc-macro dan build.rs sering terlewat dari threat model.
  • Terlalu cepat memecah tanpa ownership yang jelas. Pipeline tambahan butuh pemilik.

Checklist adopsi bertahap

  1. Petakan crate sensitif. Tandai crate yang menyentuh signing, publish, deploy, credential, atau data rahasia.
  2. Identifikasi capability CI. Secret apa saja yang tersedia di tiap job? Job mana yang punya akses jaringan, artifact store, atau registry?
  3. Pisahkan job umum dan job sensitif. Lakukan ini dulu sebelum memecah repo atau workspace.
  4. Batasi target build pada pipeline sensitif. Bangun package spesifik, bukan workspace penuh, jika memungkinkan.
  5. Kurangi tool build di jalur release. Lepaskan utility yang tidak perlu dari pipeline sensitif.
  6. Audit dependency build-time. Perhatikan proc-macro, build.rs, dan dev-dependencies yang aktif di CI.
  7. Dokumentasikan trust boundary. Tulis crate mana yang boleh dibangun di pipeline mana dan alasannya.
  8. Evaluasi ulang setelah beberapa iterasi. Jika boundary terlalu menyulitkan tanpa manfaat jelas, sederhanakan lagi.

Kapan ini menjadi overengineering?

Isolasi build Rust bisa menjadi overengineering jika:

  • tim sangat kecil dan semua engineer sudah memegang akses produksi yang sama,
  • produk hanya satu layanan sederhana tanpa proses signing atau publish yang kompleks,
  • Anda belum punya pipeline release sensitif yang benar-benar berbeda dari build biasa,
  • biaya koordinasi dan debugging CI tambahan lebih besar daripada pengurangan risiko yang realistis,
  • pemisahan dilakukan hanya karena tren, bukan karena threat model yang jelas.

Jika ancaman utamanya justru berasal dari secret management yang buruk, review dependency yang lemah, atau runner CI bersama yang tidak aman, maka memperbanyak pipeline belum tentu menyelesaikan masalah terbesar Anda.

Rekomendasi praktis

Untuk kebanyakan proyek Rust, langkah paling rasional bukan langsung berpindah dari monolit ke isolasi total. Mulailah dari monolit yang disiplin, lalu isolasi bagian yang benar-benar sensitif.

  • Jika proyek masih sederhana, pertahankan workspace tunggal tetapi pisahkan release dari test umum.
  • Jika ada crate atau tool dengan capability sensitif, pindahkan ke pipeline terpisah.
  • Jika organisasi Anda besar atau kebutuhan trust boundary tinggi, gunakan pemisahan pipeline sebagai kontrol utama, lalu minimalkan dependency pada komponen sensitif.

Intinya, keputusan monolit vs isolasi build Rust untuk kurangi risiko supply chain bukan soal arsitektur yang paling modern, melainkan soal blast radius, biaya operasional, dan disiplin menjaga capability tetap sempit. Pilih boundary yang bisa dijelaskan, ditegakkan, dan dirawat dalam jangka panjang.