Mengamankan fungsi tanpa server: execution role Lambda dengan least privilege, resource policy dan function URL auth, environment variable vs secrets manager, proteksi API Gateway, cold start yang aman, serta pola-pola serangan khusus serverless seperti event injection yang jarang dibahas di tempat lain

Setelah di episode 12 kita mengamankan container dan Kubernetes, sekarang kita pindah ke ujung spektrum model compute: serverless. Tidak ada server untuk di-hardening — provider mengelola patching, isolasi runtime, dan availability. Tapi jangan tertipu: permukaan serangan tidak hilang, ia hanya berpindah — dari infrastruktur ke konfigurasi, identitas, dan data event.
Mengapa episode ini penting? Karena serverless punya karakteristik keamanan unik: satu fungsi bisa dipanggil ribuan kali per menit oleh event dari banyak sumber (API, queue, S3), dan setiap event itu adalah input tak terpercaya. Kesalahan IAM di serverless lebih mudah terjadi karena role sering di-generate template, bukan ditulis sadar.
Yang masih menjadi tugas kalian:
| Aspek | Provider | Kalian |
|---|---|---|
| Patch runtime & infrastruktur | Ya | - |
| Isolasi eksekusi | Ya | - |
| Identitas & izin fungsi | - | Execution role + policy |
| Input validation | - | Wajib, tiap handler |
| Secret | Penyimpanan saja | Rotasi + akses |
| Konfigurasi eksposur | - | Function URL, API Gateway auth |
Intinya: security serverless = identity configuration + input handling. Dua hal itulah yang kita kerjakan.
Kesalahan nomor satu di serverless: semua fungsi berbagi satu role besar. Ketika satu fungsi dikompromikan (misal via event injection), penyerang mendapat izin gabungan semuanya.
Pola yang benar — satu fungsi, satu role, dengan policy yang menyebut resource spesifik:
resource "aws_iam_role_policy" "order_processor" {
name = "order-processor-min"
role = aws_iam_role.order_processor.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["sqs:ReceiveMessage", "sqs:DeleteMessage"]
Resource = aws_sqs_queue.orders.arn
},
{
Effect = "Allow"
Action = ["dynamodb:GetItem", "dynamodb:UpdateItem"]
Resource = aws_dynamodb_table.orders.arn
}
]
})
}Perhatikan: tidak ada s3:*, tidak ada secretsmanager:GetSecretValue pada *. Fungsi ini hanya boleh menyentuh dua resource. Verifikasi dengan alat dari episode 3:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/order-processor \
--action-names s3:GetObject secretsmanager:GetSecretValue \
--resource-arns '*'Hasil idealnya implicitDeny untuk keduanya.
Ini topik yang paling sering luput. Di serverless, event adalah input tak terpercaya — termasuk metadata-nya. Contoh rantai nyata:
process-upload dengan event berisi key = nama file itu.s3.get_object(Bucket=bucket, Key=key).../../credentials/config.json → path traversal membaca objek lain.Mitigasinya klasik tapi wajib: validasi dan sanitasi field event sebelum dipakai, jangan pernah percaya field seperti Records[0].s3.object.key, dan batasi regex nama file ke allowlist ketat.
import re
SAFE_KEY = re.compile(r"^uploads/[a-f0-9-]{36}/[A-Za-z0-9._-]{1,128}$")
def handler(event, context):
for record in event["Records"]:
key = record["s3"]["object"]["key"]
if not SAFE_KEY.match(key):
raise ValueError(f"rejected malformed key: {key[:64]}")
process(bucket="uploads-prod", key=key)Pola sama berlaku untuk payload SQS/SNS/API Gateway: schema-validate dulu, baru proses.
Function URL (endpoint publik bawaan Lambda) praktis untuk demo, berbahaya jika lupa auth. Aturan sederhana:
AuthType: AWS_IAM; NONE hanya untuk endpoint publik yang memang disengaja dan rate-limited.aws lambda list-functions \
--query 'Functions[?FunctionUrlConfig!=null].{fn:FunctionName,url:FunctionUrlConfig.FunctionUrl,auth:FunctionUrlConfig.AuthType}'Jika output menampilkan auth: NONE, perlakukan sebagai finding HIGH — sama seperti bucket publik di episode 7.
Environment variable di Lambda nyaman tetapi bocor lewat banyak jalur: GetFunctionConfiguration, console, error stack trace, snapshot debug. Pola yang benar:
Resources:
OrderProcessor:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.12
Policies:
- Statement:
- Effect: Allow
Action: secretsmanager:GetSecretValue
Resource: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod/orders/db-*
Environment:
Variables:
DB_SECRET_ARN: !Sub arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod/orders/dbYang ada di env var hanya referensi (ARN), bukan nilai. Fungsi fetch secret saat cold start dengan cache in-memory — pola lengkapnya kita bahas di episode 14.
Important
Aktifkan log retention eksplisit (misal 90 hari) pada CloudWatch group fungsi. Default retention "never expire" membuat biaya log membengkak diam-diam dan memperluas permukaan data yang harus dilindungi saat insiden forensik.
Inti yang harus dibawa pulang:
Di episode 14 selanjutnya kita dalami topik yang sudah beberapa kali tersirat: secrets & key management — Secrets Manager vs Vault vs Parameter Store, rotation otomatis, dan pipeline CI/CD bebas kredensial statis dengan OIDC federation. Sampai jumpa!