Serverless Security: Defending Cloud Functions When There's No Server to Patch
Serverless sering disangka otomatis aman karena kita tak lagi urus server, padahal justru kode, IAM, secret, dan input tetap jadi pintu masuk. Artikel ini menceritakan bagaimana gue sadar fungsi cloud punya lubang sendiri, dan cara praktis mengamankannya tanpa perlu jadi ahli kriptografi.
Prolog: Malam di mana gue pikir "tanpa server berarti tanpa masalah"
Gue ingat pertama kali kenal serverless. Waktu itu gue lagi pusing karena tim ops minta bikin endpoint kecil buat resize gambar upload-an user. Kalau pakai server biasa, gue harus provision instance, pasang nginx, jaga supaya nggak mati pas trafik naik, dan patch OS tiap minggu. Ribet. Terus ada temen bilang, "bro, pake Lambda aja, nggak usah mikirin server."
Gue tertarik. Beneran, konsepnya kedengaran kayak sihir: lo cuma nulis fungsi, cloud yang jalanin, lo bayar cuma pas eksekusi. Nggak ada server buat di-patch, nggak ada OS buat di-update, nggak ada port buat dijaga. Di kepala gue saat itu muncul pikiran naif: kalau nggak ada server, berarti nggak ada yang perlu di-secure. Kebakaran keamanan selesai, kan?
Spoiler alert: salah besar.
Tiga bulan kemudian, gue duduk di ruang meeting bareng lead security, muka merah, karena ada satu fungsi Lambda yang gue deploy buat "tes doang" ternyata punya akses admin ke seluruh akun cloud kami. Dan fungsi itu ke-trigger oleh request dari internet lewat API Gateway. Artinya, siapa pun yang nemuin endpoint itu — dan bot scanner internet nemuin endpoint terbuka dalam hitungan jam — punya kunci virtual ke kerajaan kami. Gue nggak nyadar karena gue mikir, "lah ini kan serverless, aman dong."
Cerita ini yang bikin gue akhirnya serius belajar soal serverless security. Dan jujur, materinya nggak sebanyak VM security, tapi lubangnya justru sering kelupaan justru karena orang mikir serverless itu "otomatis aman." Artikel ini gue tulis buat lo yang mungkin juga punya ilusi sama kayak gue dulu: bahwa menjauh dari server fisik berarti menjauh dari masalah keamanan. Kita bakal bedah kenapa itu mitos, dan gimana cara beneran ngamanin fungsi-fungsi lo di cloud.
Ilusi "no server, no problem"
Mari kita perbaiki satu miskonsepsi besar dulu. Banyak yang bilang serverless itu lebih aman karena "provider yang urus infrastruktur." Betul, lo nggak perlu patch kernel Linux tiap minggu. Tapi keamanan itu bukan cuma soal OS. Serverless cuma mindahin beban dari server lo ke server provider — tapi kode lo tetep jalan di atasnya, dan kode itulah yang sering jadi pintu masuk.
Provider nge-jaga hypervisor, runtime, dan isolasi antar fungsi. Tapi mereka nggak tau logika fungsi lo. Mereka nggak tau kalau lo naruh eval() di dalem, atau kalau lo baca input user langsung masuk ke query database tanpa sanitasi. Shared responsibility model di cloud itu nyata: mereka jaga bawah, lo jaga atas. Di serverless, "atas" itu makin tipis dan makin gampang lo salah sangka sebagai udah aman.
Gue pernah kerja di proyek di mana fungsi serverless-nya jadi pintu belakang paling lemah. Kenapa? Karena semua fokus security ke arah database dan VM, sementara fungsi kecil-kecil ini luput. Padahal fungsi itulah yang paling sering langsung ketemu internet. Ironisnya, semakin kecil dan "tak penting" kelihatannya sebuah komponen, semakin jarang dia di-audit.
Ketika fungsi punya kunci kerajaan: IAM yang kegedean
Ini dosa nomor satu di dunia serverless, dan gue pelaku langsung. Pas bikin fungsi pertama, gue males mikirin permission. Jadi gue kasih aja role dengan akses administrator. Alasannya? "Liat dulu jalan nggak, nanti dikecilin." Nah, "nanti" itu hampir nggak pernah datang. Fungsi itu jalan terus, dan dari lupa jadi kebiasaan.
Bahayanya gini: kalau fungsi lo kebobol — misal lewat input yang nggak divalidasi — penyerang nggak cuma dapet akses fungsi. Dia dapet akses ke semua yang bisa diakses role itu. Baca semua bucket S3? Bisa. Mutar kunci enkripsi KMS? Bisa. Bikin user IAM baru biar dia bisa masuk lagi besok? Bisa banget. Fungsi kecil jadi pivot point menuju seluruh akun.
Solusinya: per-function role dengan least privilege. Jangan pakai satu role raksasa buat semua fungsi. Fungsi yang cuma perlu baca dari satu tabel database, ya cuma kasih akses baca ke tabel itu, bukan ke seluruh database apalagi ke akun. Gue sekarang punya aturan: sebelum deploy fungsi, gue tanya diri sendiri, "kalau fungsi ini diambil alih hacker, dia bisa ngerusak apa paling parah?" Terus gue kecilin aksesnya sampai jawabannya cuma "dia cuma bisa ngerusak tugas spesifik fungsi ini."
Ada trik praktis: pakai policy yang dibatasi per resource ARN, bukan pakai wildcard di mana-mana. Dan matiin akses yang nggak dipakai. Serverless itu ephemeral, jadi nggak ada alasan buat kasih akses permanen yang lebar.
Rahasia di dalam kantong celana: secret yang kelihatan
Masalah kedua yang gue temui: secret. Gue pernah lihat orang taruh API key langsung di environment variable fungsi, terus kaget pas tau key itu kebaca orang lewat error message yang ke-leak ke log. Di serverless, environment variable itu kelihatan buat siapa aja yang punya akses baca ke konfigurasi fungsi — termasuk lewat bug di kode lo sendiri kalau lo secara nggak sengaja print semuanya pas debugging.
Terus ada lagi kebiasaan buruk: naruh file rahasia di dalam package fungsi. Gue pernah kecelep karena lupa hapus file saat build, dan dia ke-upload bareng bundle fungsi. Untungnya ketahuan pas review, bukan pas udah di production. Kalau lo pakai framework kayak Serverless Framework atau SAM, pastiin file rahasia di-exclude pas packaging, bukan di-include.
Cara bener: ambil secret dari secret manager saat fungsi butuh, di runtime, bukan disimpen statis. Dan jangan pernah log isi secret. Gue nambahin aturan di tim: kalau ada perintah print ke environment variable di kode fungsi, itu otomatis ditolak pas code review. Kedengaran ketat, tapi sekali secret ke-leak, lo harus muter-muter ganti semua, dan itu jauh lebih capek.
Dependensi yang bisu: supply chain di dalam fungsi
Fungsi serverless itu jarang ditulis dari nol. Gue pakai library buat parse JSON, buat kompresi, buat HTTP client. Tiap instalasi package itu nambahin dependensi, dan tiap dependensi itu berpotensi lubang. Masalahnya di serverless, lo nggak nge-patch OS, tapi lo tetep punya dependency yang harus dijaga.
Gue pernah kena karena satu package kecil buat nanganin format data. Ada kerentanan di dalemnya, dan karena fungsi gue pakai package itu buat parse input user, penyerang bisa trigger eksekusi kode berbahaya lewat payload khusus. Gue baru tahu pas ada alert dari layanan pemindai dependensi kami. Pelajarannya: dependensi itu bagian dari attack surface lo, bukan bagian yang bisa lo lupain.
Praktiknya: jalanin pemindai kerentanan di pipeline tiap kali ada deploy. Batasi dependensi cuma yang beneran perlu — semakin sedikit, semakin kecil permukaan serangan. Dan kalau bisa, kunci versi dependensi biar lo tahu persis apa yang jalan, bukan "versi terbaru pas build." Di serverless, bundle kecil itu bukan cuma soal performa dan biaya, tapi soal keamanan juga.
Input adalah musuh: event injection
Nah, ini bagian yang gue rasa paling sering dilupain orang. Di serverless, fungsi lo di-trigger oleh event. Bisa dari API Gateway, dari storage pas ada file baru, dari antrian pesan, atau dari jadwal. Masalahnya: banyak yang mikir event itu "internal" dan aman. Salah.
Gue pernah bikin fungsi yang ke-trigger pas ada file baru di bucket. Fungsi itu baca nama file terus eksekusi perintah berdasarkan nama itu. Karena gue anggap "siapa yang upload ke bucket kita pasti kita sendiri," gue nggak sanitasi nama file. Ternyata bucket-nya ke-setting terbuka, dan orang luar bisa upload file dengan nama yang nyuntik perintah. Fungsi yang disitu murni otomatis jalanin itu.
Intinya: anggap setiap event sebagai input yang nggak bisa dipercaya. Validasi, sanitasi, dan jangan pernah langsung masukin event ke perintah shell atau query tanpa escaping. API Gateway sama bahayanya kayak endpoint web biasa — semua aturan keamanan web berlaku: injection, cross-site scripting kalau lo render balik, dan server-side request forgery kalau lo fetch URL dari input. Serverless nggak membebaskan lo dari ini; malah sering bikin lo lengah.
Cold start, file sementara, dan sisa-sisa memori
Ada satu sisi serverless yang jarang dibahas: sifat ephemeral-nya. Fungsi jalan di container yang dibuat pas perlu, dan bisa dipakai ulang buat panggilan berikutnya. Artinya, state dari eksekusi sebelumnya — file di folder sementara, variabel global, koneksi — bisa nangkring di eksekusi selanjutnya.
Gue pernah nemuin bug di mana fungsi nyimpen data user di file temporary, dan karena container di-reuse, user berikutnya bisa baca file punya user sebelumnya. Ini kebocoran data antar-user yang nggak kelihatan kalau lo cuma tes sekali jalan. Solusinya: jangan nyimpen data sensitif di folder sementara antar pemanggilan, dan selalu bersihin state di akhir fungsi. Anggep container itu kamar hotel yang bakal ditempati orang lain sesudah lo checkout.
Terus soal logging: di serverless, log itu emas buat deteksi. Tapi log juga bisa jadi tempat secret ke-leak. Gue selalu set logging yang capture error dan anomali, tapi filter apa yang boleh masuk log. Dan karena fungsi jalan cepet dan banyak, gue pasang alarm kalau ada lonjakan eksekusi tiba-tiba — itu sering tanda fungsi lagi di-loop atau diserang.
Guardrails praktis tanpa bikin pusing
Lo nggak perlu jadi ahli kriptografi buat ngamanin serverless. Gue pakai beberapa kebiasaan simpel yang bikin tenang:
Pertama, pasang pemindai otomatis di CI. Baik itu cek IAM policy, cek dependensi, maupun cek konfigurasi. Biar robot yang ingetin, bukan manusia yang lagi ngantuk.
Kedua, batasi siapa yang bisa deploy fungsi. Jangan kasih akses deploy ke semua orang di tim. Dan kalau bisa, deploy lewat pipeline, bukan lewat perintah deploy dari laptop masing-masing. Ini bikin audit trail jelas: siapa, kapan, fungsi apa.
Ketiga, matiin yang nggak dipakai. Fungsi yang udah nggak ada panggilannya tapi masih nyala itu permukaan serangan gratis. Gue rutin review dan hapus fungsi yatim.
Keempat, pahami batas isolasi. Provider bilang fungsi terisolasi, tapi jangan bangun arsitektur seolah-olah fungsi itu benteng yang nggak bisa tembus. Tetap taruh fungsi di jaringan pribadi kalau dia perlu akses resource internal, dan jangan bikin fungsi publik kalau tugasnya internal.
Penutup: serverless tetep butuh defender
Balik ke cerita di awal. Sesudah insiden fungsi admin itu, gue nggak langsung hapus dan lupa. Gue duduk, bikin aturan: setiap fungsi wajib punya role minimal, secret cuma lewat manager, dan pemindai wajib di pipeline. Tiga bulan kemudian gue bisa tidur tenang karena tau setiap deploy fungsi udah lewat gerbang yang nggak pernah lengah.
Serverless itu bukan sihir yang bikin security hilang. Dia cuma mindahin tanggung jawab. Lo lepas urusan patch OS, tapi lo makin bertanggung jawab soal kode, IAM, secret, dan input. Dan jujur, setelah semua guardrail-nya rapi, kerjaan gue malah lebih tenang — gue nggak lagi deg-degan tiap ada alert aneh di Jumat malam.
Jadi kalau lo lagi pindah ke serverless atau baru mulai main cloud function, jangan terjebak ilusi "tanpa server = tanpa masalah." Server memang nggak lo pegang, tapi keamanan tetep di tangan lo. Mulai dari hal kecil: kecilin akses fungsi lo hari ini, sebelum dia jadi kunci kerajaan yang lo sendiri nggak sadar.