AizuDemy

Tutorial Hardening Docker Container: Amankan Server Production

Tutorial Hardening Docker Container: Amankan Server Production

1. Celah Keamanan Docker Default

Instalasi default Docker didesain untuk kenyamanan pengembangan aplikasi (Developer Experience), bukan untuk keamanan lingkungan production. Secara bawaan, engine Docker berjalan dengan asumsi bahwa lingkungan eksekusi dapat dipercaya penuh. Hal ini meninggalkan celah keamanan kritis yang siap dieksploitasi jika container langsung di-deploy ke server publik tanpa proses hardening terlebih dahulu.

Vektor Risiko Utama Konfigurasi Default Docker

Root Privilege (UID 0 Mapping): Proses di dalam container secara bawaan berjalan sebagai user root (UID 0). Karena container membagi Linux Kernel dengan host OS, UID 0 di dalam container secara efektif terpetakan sebagai UID 0 di host system jika User Namespaces tidak diaktifkan. Ketika peretas berhasil melakukan eksploitasi container escape (seperti kerentanan runc CVE-2019-5736), peretas langsung mendapatkan kendali penuh atas sistem operasi host.

Shared Kernel: Berbeda dengan Virtual Machine (VM) yang memiliki OS dan kernel terisolasi via hypervisor, seluruh container pada satu host menggunakan kernel yang sama. Kerentanan pada kernel Linux (Kernel Vulnerability) dapat dimanfaatkan oleh peretas untuk menembus isolasi antar-container maupun mengambil alih server host.

Capabilities Linux yang Terlalu Longgar: Docker container default mengaktifkan berbagai Linux Capabilities secara otomatis, seperti CAP_NET_RAW, CAP_SYS_CHROOT, CAP_MKNOD, dan CAP_AUDIT_WRITE. Fitur ini memberikan hak akses tingkat rendah pada sistem operasi yang sebenarnya tidak dibutuhkan oleh sebagian besar aplikasi web standar. Peretas dapat memanfaatkan hak akses ini untuk merekayasa lalu lintas jaringan, memanipulasi filesystem, atau melakukan eksploitasi privilege escalation.

Docker Socket Access (/var/run/docker.sock): Melakukan mounting pada file /var/run/docker.sock ke dalam container merupakan ancaman keamanan tertinggi. Docker socket adalah entry point utama untuk berkomunikasi dengan Docker daemon API. Jika container yang meng-mount socket ini berhasil diretas, peretas dapat membuat container baru ber-privilege tinggi, mengambil alih kontrol storage, dan menguasai seluruh node server.

# Verifikasi user default pada container baru
docker run --rm alpine whoami
# Output: root

Output di atas mengonfirmasi bahwa container berjalan sebagai root. Konfigurasi ini wajib diubah sebelum melakukan deployment ke infrastruktur production.

2. Praktik Non-Root User dan Multi-Stage Build

Mengamankan container dimulai dari restrukturisasi cara image dibangun. Dua langkah fundamental yang wajib diterapkan adalah menghentikan eksekusi aplikasi sebagai user root dan mengeliminasi perkakas kompilasi dari image akhir.

Membuat dan Menggunakan User Non-Root

Tambahkan group dan user berhak akses terbatas di dalam Dockerfile. Jangan biarkan proses aplikasi berjalan dengan PID 1 berstatus root.

# Dockerfile Tidak Aman (Menjalankan App sebagai Root)
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

# Dockerfile Hardened (Menjalankan App sebagai Non-Root User)
FROM node:18-alpine
# Buat system group dan user non-root khusus
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Ubah ownership direktori aplikasi ke appuser
COPY --chown=appuser:appgroup . .
# Pindah konteks eksekusi ke user non-root
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]

Instruksi USER appuser memastikan seluruh proses Node.js dieksekusi tanpa hak akses superuser. Jika aplikasi berhasil di-compromise, ruang gerak peretas terbatas hanya pada hak akses folder tempat aplikasi berjalan.

Multi-Stage Build untuk Menciptakan Image Minimalis

Image production tidak boleh menyimpan compiler, SDK, tool utilitas seperti curl, git, atau g++, maupun source code yang belum dikompilasi. Manfaatkan pola Multi-Stage Build untuk memisahkan tahap kompilasi dan tahap runtime.

# Stage 1: Build binary menggunakan SDK lengkap
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Kompilasi aplikasi menjadi single static binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o myapp .

# Stage 2: Production runtime menggunakan Distroless
FROM gcr.io/distroless/static-debian11
WORKDIR /
# Salin binary hasil kompilasi dari stage builder
COPY --from=builder /app/myapp /myapp
# Gunakan user non-root bawaan image distroless
USER nonroot:nonroot
ENTRYPOINT ["/myapp"]

Penggunaan base image Distroless memangkas attack surface secara drastis karena image tersebut tidak memiliki shell (seperti bash atau sh), package manager (seperti apk atau apt), maupun utilitas sistem lainnya. Peretas tidak akan memiliki sarana untuk menjalankan perintah eksternal jika berhasil menembus aplikasi.

3. Limitasi Resource dan Scanning Image dengan Trivy

Penerapan kontrol pada runtime dan verifikasi statis pada image merupakan benteng pertahanan krusial untuk mencegah serangan Denial of Service (DoS) dan mengeksekusi otomatisasi keamanan.

Resource Limit (Mencegah Resource Exhaustion dan DoS)

Container tanpa pembatasan alokasi sumber daya dapat mengonsumsi RAM dan CPU host secara tak terbatas. Eksploitasi sederhana atau kebocoran memori (memory leak) dapat memicu OOM (Out Of Memory) Killer pada host OS yang merusak stabilitas server.

# Menjalankan container dengan batasan resource dan immutable root filesystem
docker run -d \
  --name app-secure \
  --memory="512m" \
  --memory-swap="512m" \
  --cpus="1.5" \
  --pids-limit 100 \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid \
  myapp:latest

Penjelasan flag keamanan runtime:

--memory="512m" dan --memory-swap="512m": Membatasi penggunaan memori fisik dan swap maksimal 512MB untuk mencegah pengurasan RAM host.

--cpus="1.5": Membatasi eksekusi aplikasi maksimal 1.5 core CPU.

--pids-limit 100: Membatasi jumlah proses yang dapat dibuat di dalam container untuk mementalkan serangan Fork Bomb.

--read-only: Mengubah root filesystem container menjadi read-only. Peretas tidak dapat mengunduh malware, merubah konfig sistem, atau menulis file eksekusi.

--tmpfs /tmp:rw,noexec,nosuid: Menyediakan folder temporary berlokasi di RAM untuk kebutuhan aplikasi write file sementara, dengan opsi noexec untuk melarang eksekusi binary dari folder tersebut.

Drop Unnecessary Linux Capabilities

Terapkan prinsip Least Privilege dengan membuang seluruh capability Linux bawaan, lalu tambahkan kembali hanya permission spesifik yang benar-benar dibutuhkan oleh aplikasi.

docker run -d \
  --name app-network-only \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  myapp:latest

Flag --cap-drop=ALL mencabut seluruh hak akses khusus Linux. Flag --cap-add=NET_BIND_SERVICE memberikan izin terbatas bagi aplikasi untuk melakukan bind pada low port (di bawah 1024) tanpa perlu berjalan sebagai root.

Vulnerability Scanning Menggunakan Trivy

Sebelum image didorong ke container registry atau di-deploy ke server production, lakukan pemindaian kerentanan CVE (Common Vulnerabilities and Exposures) pada OS package dan dependensi aplikasi menggunakan Trivy.

# Install Trivy CLI
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scanning image lokal secara komprehensif
trivy image myapp:latest

# Konfigurasi CI/CD Pipeline: Gagalkan build jika ditemukan kerentanan HIGH atau CRITICAL
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest

Perintah dengan opsi --exit-code 1 akan mengembalikan status error saat terdeteksi celah keamanan berbahaya, sehingga pipeline CI/CD secara otomatis membatalkan proses deployment aplikasi yang tidak aman.

4. Checklist Keamanan Docker Production

Urutan langkah pengamanan wajib yang harus diverifikasi sebelum melakukan deployment container ke lingkungan production:

Penting: Tindakan di bawah ini secara langsung membatasi akses pada level sistem. Lakukan pengujian di lingkungan staging sebelum menerapkannya di server production utama.

  1. Base Image Minimalis: Gunakan official minimal image seperti Alpine Linux atau Distroless. Lakukan pembaharuan base image secara rutin untuk menambal patch keamanan kernel dan library.
  2. Enforce Non-Root User: Deklarasikan USER non-root pada Dockerfile. Pastikan tidak ada proses aplikasi yang berjalan dengan UID 0.
  3. Filesystem Immutable (Read-Only): Aktifkan opsi --read-only pada waktu runtime. Manfaatkan tmpfs atau dedicated volume terbatas untuk lokasi penulisan log dan data sementara.
  4. Resource Quota Enforcement: Tetapkan batas maksimum --memory, --cpus, dan --pids-limit baik di CLI, Docker Compose, maupun orchestrator seperti Kubernetes.
  5. Capability Reduction: Eksekusi pengetatan hak akses dengan --cap-drop=ALL dan hanya tambahkan --cap-add sesuai fungsi spesifik aplikasi.
  6. Proteksi Socket Docker: Dilarang keras melakukan mounting /var/run/docker.sock ke container yang dapat diakses publik. Gunakan API proxy berijin khusus jika butuh akses manajemen Docker.
  7. Automated Scanning pada CI/CD: Integrasikan pemindaian Trivy pada alur integrasi berkelanjutan. Tetapkan aturan tegas untuk menggagalkan deployment jika ditemukan CVE berstatus High atau Critical.
  8. Pengelolaan Secrets Terisolasi: Jangan sekali-kali menyimpan password, API key, atau sertifikat pada Dockerfile atau Environment Variable plaintext. Manfaatkan Docker Secrets, HashiCorp Vault, atau Secret Manager resmi cloud provider.
  9. Implementasi Security Profiles: Gunakan profil Seccomp (Secure Computing Mode) dan AppArmor/SELinux bawaan atau kustom untuk membatasi system calls (syscalls) yang dapat dipanggil oleh container ke kernel host.
  10. Logging dan Monitoring Runtime: Arahkan stdout/stderr container ke centralized logging driver dan gunakan runtime security tool seperti Falco untuk mendeteksi perilaku anomali di dalam container secara real-time.

Penerapan praktik hardening ini secara konsisten memastikan lingkungan container memiliki proteksi berlapis (Defense in Depth) yang tangguh terhadap ancaman eksploitasi di server production.