CI/CD Security Hardening: From Code Commit to Safe Deployments
Dari pengalaman nyata mengamankan pipeline CI/CD: branch protection, dependency scanning, immutable artifact, image signing, network isolation, dan audit logging. Berikut langkah konkret yang bisa diterapkan mulai hari ini.
Banyak yang ngira cybersecurity cuma soal SOC monitoring, threat hunting, atau red team yang nge-print shellcode seharian. Padahal, ada area lain yang lebih kentara dan sering dirusak: CI/CD pipeline. Setiap hari kita deploy aplikasi lewat GitHub Actions, GitLab CI, atau Jenkins. Tapi berapa kali kita nge-review security dari kode yang diketik sampai artifact yang mendarat di server produksi?
Gue cerita dari pengalaman beneran. Pertama kali gue touch CI/CD security itu bukan karena ada serangan besar, tapi karena gue lihat log pipeline yang menunjukkan seseorang bisa push kode sembarang dan langsung deploy ke staging. Tidak ada approval, tidak ada scanning, tidak ada isolasi. Cuma butuh satu commit berbahaya dan perusahaan bisa kehilangan data.
Awal Mula: Ngoding Sambil Deploy Sembarang
Di perusahaan pertama gue, tim engineering super excited. Mereka pake GitHub Actions. Setiap kali ada push ke branch main, otomatis build, test, dan deploy ke AWS ECS. Cepat, efisien, dan tanpa hambatan. Gue inget sekali gue liat workflow file yang isinya cuma dan langsung deploy. Tidak ada branch protection, tidak ada required review, tidak ada scanning dependency.
Saat itulah gue nge-realisasi: kita punya "security" di layer aplikasi, tapi pintu gerbang utama — pipeline — terbuka lebar. Kayak rumah yang punya pintu baja, tapi kuncinya tinggal gantung di luar pagar.
Perlawanan Awal: "Ini Cuma Internal Repo"
Ketika gueusulkan untuk nambahin scanning dan branch protection, tim bilang, "Ini cuma repo internal, tidak ada yang bisa akses kecuali kita." Tapi gue ingat incident yang pernah baca di GitHub Universe: attacker bisa compromised contributor account, atau bahkan compromise npm package yang kita pull di pipeline.
Dan gue juga ingat bahwa di dunia nyata, supply chain attack bukan teori konspirasi. Log4Shell, event-stream, ua-parser-js — itu semua masuk lewat dependency yang kita install otomatis di pipeline. Jika pipeline kita tidak memverifikasi integrity artifact, kita bisa deploying backdoor tanpa sadar.
Langkah Pertama: Branch Protection dan Required Review
Hal paling dasar yang gue lakuin adalah branch protection. Di GitHub, kita atur branch dan agar tidak bisa di-push secara langsung. Setiap perubahan harus melalui Pull Request, dan minimal satu review dari senior engineer.
Tapi ini cuma permulaan. Gue juga menambahkan required status checks. Pipeline tidak bisa merge sebelum semua test lolos, dependency vulnerability scan aman, dan tidak ada secret yang ter-expose di log. Di GitHub Actions, kita bisa pilih "Require status checks to pass before merging" di Settings > Branches.
Hal ini bikin tim engineering sempet kesal. "Woi, sekarang mau push fix kecil harus tunggu 10 menit pipeline." Tapi gue jelasin: 10 menit sekarang mending daripada 3 hari incident response nanti.
Dependency Scanning: Mencuri Curry dari Komunitas
Setelah branch protection, langkah berikutnya adalah dependency scanning. Di GitHub Actions, kita tambahkan step yang menjalankan found 0 vulnerabilities atau sebelum build. Tapi gue tidak cuma mau tool yang cuma scan, gue mau tool yang bisa blokir deployment jika ada vulnerability tinggi.
Gue pilih Trivy. Tool ini bagus karena support multiple ecosystem (npm, pip, gem, cargo) dan bisa scan filesystem, Git repository, maupun container image. Di pipeline, gue tambahkan step yang menjalankan Trivy terhadap seluruh codebase. Hasil scan disimpan dalam format SARIF, yang bisa di-upload ke GitHub Security tab. Dengan begitu, tim engineering bisa lihat vulnerability mana yang perlu diperbaiki, dan kita bisa set policy untuk menolak merge jika ada CRITICAL.
Tapi di sini ada tantangan. Banyak project punya dependency yang outdated tapi tidak bisa di-update karena breaking change. Jadi gue buat exception policy. Vulnerability dengan CVE tertentu bisa di-allow jika ada mitigation lain. Tapi exception harus di-document dan di-review, bukan cuma di-skip sembarang. Gue buat spreadsheet yang mencatat setiap exception, alasan, dan rencana remediasi. Jadi ketika audit datang, kita punya bukti bahwa kita tidak sembarang menerapkan risiko.
Secret Management: Jangan Percaya Environment Variable Sembarang
Masalah lain yang gue temukan: secret di dalam workflow file. Banyak orang nyimpan API key di GitHub Secrets, itu bagus. Tapi di workflow file, mereka sering mencetak secret ke log untuk debugging. Atau bahkan lebih buruk: mereka pass secret sebagai plain text ke command line. Di log GitHub, command line tercetak, dan siapa yang punya akses ke repo bisa melihat secret itu.
Gue inget sekali di perusahaan sebelumnya, ada engineer yang debug . Di log, dia tidak menyadari bahwa GitHub Actions mencetak environment variable jika ada atau error yang menampilkan command. Akhirnya, API key ter-expose di log, dan harus di-rotate.
Solusinya adalah masking dan least privilege. Di GitHub Actions, semua secret otomatis di-mask di log. Tapi kita juga harus memastikan bahwa aplikasi hanya butuh secret tertentu. Jangan beri akses ke semua secret kecuali dibutuhkan. Di GitHub, kita bisa pilih "Allow GitHub Actions to access secrets" di level repository atau organization.
Selain itu, gue juga menerapkan just-in-time secret injection. Daripada simpan semua secret di GitHub Secrets, kita pindah ke HashiCorp Vault. Pipeline hanya mengambil secret yang dibutuhkan di step tertentu, dan hanya untuk waktu yang dibutuhkan. Setelah job selesai, secret dihapus dari cache. Meskipun ini menambah kompleksitas, tapi mengurangi blast radius jika terjadi compromise.
Immutable Artifact: Jangan Build Ulang di Production
Salah satu prinsip paling penting dalam supply chain security adalah immutable artifact. Artinya, artifact yang di-build di staging adalah sama persis dengan yang di-deploy ke production. Tidak ada "build ulang" di production.
Banyak orang melanggar prinsip ini. Mereka build di staging, lalu di production mereka rebuild dari source code yang sama. Tapi apakah versi dependency yang di-download sama? Apakah compiler version sama? Apakah environment variable sama? Jawabannya: tidak. Dan di situlah supply chain attack masuk.
Gue kenal satu kasus di mana attacker compromised npm registry mirror di CI/CD. Di staging, mereka pull dari official npm registry. Di production, mereka pull dari internal mirror yang sudah dirusak. Hasilnya, di staging aman, di production malah terpasang backdoor.
Solusinya adalah artifact promotion. Build sekali di staging, scan, sign, dan promosikan ke production. Jangan build ulang. Di GitHub Actions, kita bisa upload artifact dari staging job, lalu download di production job. Artifact yang sama, tanpa rebuild. Gue pernah bikin workflow di mana staging job build Docker image, scan dengan Trivy, sign dengan Sigstore/cosign, lalu push ke Amazon ECR. Production job cuma pull image yang sudah signed dan di-tag dengan SHA digest. Tidak ada rebuild. Jika image aman di staging, aman di production.
Container Image Signing: Memastikan Origin dan Integrity
Setelah immutable artifact, langkah berikutnya adalah image signing. Gue pilih Sigstore/cosign karena open source dan tidak memerlukan infrastructure rumit. Cosign menandatangani image dengan keyless signing menggunakan OIDC identity dari GitHub Actions.
Dengan cosign, kita bisa memastikan bahwa image yang di-deploy benar-benar dibuild dari repository kita, oleh GitHub Actions, dan tidak diubah setelah di-sign. Di production, kita bisa menambahkan admission controller seperti Kyverno atau OPA Gatekeeper yang memeriksa signature sebelum menerima image baru.
Prosesnya sederhana: staging job build image dengan , scan dengan Trivy untuk vulnerability, sign dengan , lalu upload ke registry. Production job pull image dan verify signature sebelum deploy. Jika image tidak di-sign, atau signature tidak cocok dengan identity GitHub Actions, deployment ditolak. Ini mencegah scenario di mana attacker bisa push image palsu ke registry.
Network Isolation: Jangan Biarkan Pipeline Bisa Akses Segalanya
Masalah lain yang sering terabaikan: network access. Di lingkungan production, workload cuma butuh akses ke resource tertentu. Tapi di CI/CD pipeline, kita sering beri akses ke semua network, termasuk internet penuh.
Di perusahaan sebelumnya, gue lihat GitHub Actions runner yang bisa akses ke database produksi, ke internal API, dan ke cloud provider management plane. Jika attacker compromised satu commit, mereka bisa langsung pindah ke database atau delete seluruh cloud infrastructure.
Solusinya adalah network segmentation. Di GitHub Actions, kita bisa gunakan block untuk membatasi scope token. Jangan beri . Cukup yang dibutuhkan: , jika butuh push image, tapi jangan beri kecupun butuh cosign.
Jika menggunakan self-hosted runner, pastikan runner berada di subnet terpisah yang hanya bisa akses internet untuk pull dependency, dan bisa akses registry dan staging environment. Jangan biarkan self-hosted runner bisa akses production database atau management API.
Gue ingat satu kasus di mana attacker compromised npm package yang dijalankan di self-hosted runner. Karena runner bisa akses cloud provider API dengan permission tinggi, attacker berhasil delete seluruh storage bucket. Rugi ratusan juta karena data backup ikut terhapus. Insiden itu membuat gue lebih keras dalam menerapkan network isolation ke setiap pipeline yang gue kelola.
Runtime Security: Melihat Pipeline dari Sudut Pandang Penyerang
Selain scanning dan signing, kita juga perlu runtime security di dalam pipeline. Di GitHub Actions, setiap step berjalan di dalam container atau VM. Jika ada step yang compromised, attacker bisa mengakses secret atau melakukan lateral movement.
Gue biasa tambahkan step yang memverifikasi integrity container. Sebelum menjalankan build, kita cek apakah container image yang dipakai sudah di-sign dan aman. Atau minimal, kita pakai container image resmi dari GitHub dengan digest yang tetap, bukan tag yang bisa berubah kapan saja.
Juga, jangan simpan secret di dalam runner workspace. Di GitHub Actions, workspace bisa di-akses oleh step berikutnya. Jika step A mencuri secret dari workspace, step B yang berbahaya bisa mengambilnya. Gunakan secret masking dan limitasi cakupan secret hanya ke step yang membutuhkan.
Satu praktik lain yang gue terapkan adalah minimal base image. Build container pakai Alpine atau Distroless yang kecil, sehingga attack surface lebih kecil. Jika ada vulnerability di library yang kita install, surface yang diekspos jauh lebih kecil dibanding pakai image yang penuh dengan tool yang tidak dibutuhkan.
Audit Logging: Jika Terjadi, Kita Tahu Apa yang Terjadi
Terakhir, audit log. Jika terjadi breach di pipeline, kita harus bisa reconstruct apa yang terjadi. Di GitHub, kita bisa enable audit log untuk organization. Log ini mencatat siapa yang approve PR, siapa yang modify workflow file, dan apakah ada deployment yang berhasil.
Di cloud provider, kita juga enable CloudTrail atau setara untuk melihat apakah ada API call yang mencurigakan dari pipeline identity. Kita bisa setup alert jika ada API call yang tidak sesuai dengan pattern normal. Misal, jika pipeline biasanya cuma push ke ECR dan ECS, tapi ada API call untuk delete S3 bucket, itu harus diindikasikan sebagai insiden.
Gue ingat satu incident di mana workflow file diubah tanpa sepengetahuan tim. Karena audit log aktif, gue bisa melihat bahwa seseorang mengubah ke , yang memungkinkan attacker menjalankan kode berbahaya di pull request. Tanpa audit log, kita mungkin tidak akan pernah tahu bahwa ada perubahan mencurigakan di workflow. Dan tanpa notifikasi yang tepat, perubahan itu bisa bertahan berbulan-bulan sebelum akhirnya dieksploitasi.
Kerangka Pikir: Security adalah Proses, Bukan Produk
Setelah berbulan-bulan menambahkan lapisan security satu per satu, gue sadar: CI/CD security bukan tentang tool yang paling mahal atau paling canggih. Ini tentang proses yang konsisten.
Pertama, identifikasi aset. Apa yang dilindungi? Registry, cloud credentials, database connection string, API key ke layanan pihak ketiga.
Kedua, identifikasi ancaman. Siapa yang berpotensi menyusup? Contributor eksternal? Compromised dependency? Attacker yang sudah masuk ke repo?
Ketiga, implementasikan kontrol. Branch protection, dependency scanning, artifact signing, network isolation, secret management, audit logging.
Keempat, uji dan iterasi. Lakukan tabletop exercise: bayangkan jika salah satu commit berisi malware, apa yang terjadi? Apakah detection bekerja? Apakah response plan jelas?
Kesimpulan
CI/CD pipeline adalah tulang punggung modern software delivery. Jika pipeline kita diretas, attacker bisa menginfeksi seluruh production tanpa perlu menembus firewall atau mencuri password database. Yang terjadi adalah kita yang secara sukarela memberikan akses.
Dari pengalaman gue, langkah-langkah yang jelas adalah: branch protection dan required review, dependency scanning dengan Trivy, immutable artifact dengan build sekali dan promosikan, image signing dengan Sigstore/cosign, network isolation dan least privilege, runtime security dengan container image integrity yang terverifikasi, dan audit logging yang aktif.
Tapi yang terpenting: edukasi tim. Tool tidak ada gunanya jika engineer menganggapnya sebagai hambatan. Mereka harus memahami bahwa setiap lapisan security itu melindungi mereka juga. Karena pada akhirnya, kita tidak cuma mengamankan kode, tapi mengamankan cara kita mengirimkan kode.
Di dunia yang serba cepat ini, speed to market sering didahulukan daripada security. Tapi pelanggan tidak akan peduli seberapa cepat kamu deploy jika data mereka bocor. Dan regulator akan menghukum jika ketidakpatuhan terjadi. Jadi, investasi di CI/CD security bukan biaya tambahan — itu adalah asuransi.
Terus belajar, terus menguji, dan jangan pernah berhenti mempertanyakan pipeline lu. Karena di balik setiap automated deployment, ada ribuan keputusan yang bisa jadi celah. Semoga tulisan ini bermanfaat. Kalau ada yang mau diskusi lebih lanjut atau mau sharing pengalaman, tinggal kontak aja. Gue suka ngobrolin hal gini. Selamat mengamankan pipeline, kakak!