Memproses berkas media yang diunggah oleh pengguna (untrusted input) membawa risiko keamanan tingkat tinggi. FFmpeg memiliki jutaan baris kode C/C++ yang kompleks dan rentan terhadap serangan buffer overflow, out-of-bounds read/write, hingga eksekusi kode arbitrer (RCE) melalui metadata atau format demuxing yang dimanipulasi. Tanpa isolasi sistem, eksploitasi zero-day pada demuxer FFmpeg dapat memberikan penyerang akses langsung ke host, jaringan internal, dan data sensitif.
Mengamankan proses ini membutuhkan arsitektur isolasi yang seimbang antara keamanan, latensi eksekusi, dan biaya komputasi. Artikel ini membedah arsitektur isolasi transcoding video, mengevaluasi trade-off tiga model eksekusi, serta menyajikan implementasi isolasi level kernel dan Go worker.
Vektor Serangan Parsing Media dan Kebutuhan Sandbox
Parsing berkas video melibatkan manipulasi container format (misal MP4, MKV, AVI) dan bitstream decoding (H.264, HEVC, AV1). Kerentanan umum seperti CVE-2016-1897 (Server-Side Request Forgery via HLS playlist) membuktikan bahwa FFmpeg secara default dapat membuka koneksi jaringan eksternal atau membaca file lokal seperti /etc/passwd jika tidak dibatasi.
Prinsip least privilege dan isolasi kompartemen wajib diterapkan: proses transcoding harus dieksekusi tanpa hak akses root, tanpa akses jaringan (kecuali streaming input/output terkontrol), dengan kuota resource CPU/memori ketat, dan ruang akses syscall yang seminimal mungkin.
Perbandingan Model Eksekusi: Sandbox vs Biaya
Pemilihan arsitektur isolasi sangat bergantung pada volume beban kerja dan toleransi latensi sistem:
- In-Process / Persistent Worker: FFmpeg dipanggil via
exec.Commandpada node worker tetap yang berjalan terus-menerus. Sangat efisien karena tidak memiliki startup overhead dan memanfaatkan hardware acceleration (NVENC/VAAPI) secara langsung. Namun, isolasi sangat lemah. Kerentanan kernel exploit atau privilege escalation di worker dapat mengompromikan seluruh armada worker dan Redis/RabbitMQ queue yang terhubung. - Ephemeral Container (Docker / gVisor runsc): Setiap job dijalankan dalam container terisolasi yang langsung dihancurkan setelah selesai. Menggunakan gVisor (
runsc) mencegat syscall aplikasi di userspace, mencegah eksploitasi langsung ke kernel host. Memiliki startup latency berkisar antara 100ms hingga 2 detik, serta biaya compute yang moderat. Sangat ideal untuk platform video skala menengah hingga enterprise. - Serverless Isolation (AWS Lambda / WASM): Eksekusi FFmpeg didelegasikan ke function-as-a-service (AWS Lambda) atau dikompilasi ke WebAssembly. AWS Lambda mengisolasi eksekusi menggunakan microVM Firecracker dengan biaya murni pay-per-use, cocok untuk traffic bergelombang (bursty). Kelemahannya: ukuran binary terbatas, tidak ada akses GPU penuh (pada standar Lambda), dan biaya menjadi sangat tinggi jika volume transcoding konsisten dan berskala gigawatt. Sementara WASM sepenuhnya aman dari akses host, namun kehilangan akselerasi hardware dan performa komputasinya lebih lambat 2-3x lipat dibanding native C.
Penerapan Kernel Hardening: Seccomp, Namespaces, dan Unprivileged User
Jika menggunakan ephemeral container atau virtual machine worker Linux native, isolasi berbasis kernel Linux harus dikonfigurasi melalui seccomp profile dan unprivileged execution.
1. Minimal Seccomp Profile untuk FFmpeg
Profil seccomp JSON ini memblokir akses syscall berbahaya seperti ptrace, mount, pembukaan socket jaringan mentah, dan manipulasi namespace:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_AARCH64"
],
"syscalls": [
{
"names": [
"read", "write", "openat", "close", "fstat", "lseek",
"mmap", "mprotect", "munmap", "brk", "rt_sigaction",
"rt_sigprocmask", "rt_sigreturn", "ioctl", "access",
"futex", "exit_group", "getuid", "getgid", "clock_gettime",
"gettimeofday", "nanosleep", "clone", "wait4"
],
"action": "SCMP_ACT_ALLOW"
}
]
}2. Menjalankan FFmpeg dengan Hak Terbatas via Docker/Podman
Pastikan container berjalan dengan user non-root, seccomp terpasang, sistem file root bersifat read-only, dan kapabilitas Linux dihapus total (cap-drop=ALL):
docker run --rm \
--runtime=runsc \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges:true \
--security-opt seccomp=/etc/docker/seccomp-ffmpeg.json \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=512m \
--user 10001:10001 \
-v /data/media/input.mp4:/input.mp4:ro \
-v /data/media/output:/output:rw \
jrottenberg/ffmpeg:6.1-alpine \
-nostdin -v error -i /input.mp4 -c:v libx264 -crf 23 -c:a aac /output/output.mp4Implementasi Wrapper Worker Transcoding di Go
Berikut adalah implementasi Go backend worker yang mengeksekusi subproses FFmpeg dengan batas timeout (context.Context), isolasi UID/GID kernel via SysProcAttr, pembatasan resource, serta pencegahan network traversal:
package main
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"syscall"
"time"
)
type TranscodeJob struct {
InputPath string
OutputPath string
Timeout time.Duration
}
func ExecuteTranscode(ctx context.Context, job TranscodeJob) error {
if _, err := os.Stat(job.InputPath); errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("berkas input tidak ditemukan: %w", err)
}
execCtx, cancel := context.WithTimeout(ctx, job.Timeout)
defer cancel()
// Argumen aman: matikan network protocol, batasi error verbose
args := []string{
"-nostdin",
"-nostats",
"-hide_banner",
"-loglevel", "error",
"-protocol_whitelist", "file",
"-i", job.InputPath,
"-c:v", "libx264",
"-preset", "fast",
"-crf", "22",
"-c:a", "aac",
"-b:a", "128k",
"-movflags", "+faststart",
"-f", "mp4",
job.OutputPath,
}
cmd := exec.CommandContext(execCtx, "ffmpeg", args...)
// Isolasi level proses Linux
// ponytail: migrasi ke gVisor container runtime jika multi-tenant security kritis
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: 10001,
Gid: 10001,
},
Cloneflags: syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
Setpgid: true,
Pdeathsig: syscall.SIGKILL,
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
if errors.Is(execCtx.Err(), context.DeadlineExceeded) {
return errors.New("transcoding gagal: timeout terlampaui")
}
return fmt.Errorf("transcoding error: %w", err)
}
return nil
}
func main() {
job := TranscodeJob{
InputPath: "/data/incoming/raw_upload.mp4",
OutputPath: "/data/ready/optimized.mp4",
Timeout: 10 * time.Minute,
}
err := ExecuteTranscode(context.Background(), job)
if err != nil {
fmt.Fprintf(os.Stderr, "Job gagal: %v\n", err)
os.Exit(1)
}
fmt.Println("Transcoding berhasil diselesaikan secara aman.")
}Panduan Pengambilan Keputusan Arsitektur
Gunakan panduan berikut untuk menentukan strategi isolasi sesuai kapasitas infrastruktur dan anggaran:
- Gunakan Ephemeral Container (gVisor/Docker): Pilihan paling seimbang jika platform memproses ribuan video per hari dan memerlukan dukungan hardware encoder (NVENC). Biaya server terprediksi, dan sandbox runtime melindungi host dari RCE.
- Gunakan Serverless (AWS Lambda): Tepat bila volume pemrosesan video sangat sporadis atau traffic bersifat spike musiman. Biaya operasional nol saat idle, namun tidak disarankan untuk pipeline encoding resolusi 4K atau durasi panjang di atas batas timeout 15 menit.
- Gunakan In-Process Worker: Hanya valid bila file input berasal dari sumber tepercaya (trusted internal pipeline), bukan unggahan publik bebas. Tetap batasi hak akses user Linux lokal dengan seccomp dan
-protocol_whitelist file.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!