Kontainer Bukan Batas Keamanan yang Sempurna
๐ซ Kesalahpahaman yang berbahaya
Kontainer berbagi kernel dengan host. Ini berbeda mendasar dari mesin virtual yang punya kernelnya sendiri. Kerentanan pada kernel atau kesalahan konfigurasi bisa membuat proses di dalam kontainer keluar ke host. Perlakukan kontainer sebagai lapisan isolasi, bukan sebagai jaminan keamanan mutlak.
Kontainer berbagi kernel dengan host. Ini berbeda mendasar dari mesin virtual yang punya kernelnya sendiri. Kerentanan pada kernel atau kesalahan konfigurasi bisa membuat proses di dalam kontainer keluar ke host. Perlakukan kontainer sebagai lapisan isolasi, bukan sebagai jaminan keamanan mutlak.
1. Jangan Pernah Berjalan sebagai Root
FROM node:22-alpine
# Buat pengguna khusus dengan UID tetap
RUN addgroup -g 10001 -S aplikasi && \
adduser -u 10001 -S aplikasi -G aplikasi
WORKDIR /app
COPY --chown=aplikasi:aplikasi . .
RUN npm ci --omit=dev
USER 10001 # pakai UID numerik, bukan nama
EXPOSE 8080
CMD ["node", "server.js"]
๐ Kenapa UID numerik, bukan nama pengguna
Kubernetes
Kubernetes
runAsNonRoot: true memeriksa UID, dan ia tidak bisa menerjemahkan nama pengguna menjadi UID sebelum kontainer berjalan. Menuliskan USER 10001 membuat pemeriksaan itu berhasil. Pakai UID di atas 10000 untuk menghindari bentrok dengan pengguna sistem.
2. Pilih Base Image Sekecil Mungkin
| Base image | Ukuran kira-kira | Ada shell? | Catatan |
|---|---|---|---|
ubuntu:24.04 | ~78 MB | Ya | Permukaan serangan besar |
debian:bookworm-slim | ~75 MB | Ya | Pilihan seimbang |
alpine:3 | ~8 MB | Ya | musl libc โ bisa bermasalah untuk sebagian binary |
gcr.io/distroless/static | ~2 MB | Tidak | Sangat aman; hanya untuk binary statis |
scratch | 0 MB | Tidak | Kosong total โ ideal untuk Go |
# Contoh Go: dari builder ke scratch
FROM golang:1.26-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /server ./cmd/server
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /server /server
USER 10001
ENTRYPOINT ["/server"]
# Hasil akhir: beberapa MB saja, tanpa shell, tanpa package manager,
# tanpa satu pun utilitas yang bisa dipakai penyerang.
โ ๏ธ Tanpa shell, debugging berubah caranya
Anda tidak bisa lagi
Anda tidak bisa lagi
docker exec -it โฆ sh pada image distroless atau scratch. Gunakan docker debug, atau di Kubernetes pakai ephemeral container: kubectl debug -it pod/nama --image=busybox --target=nama-kontainer. Keamanan yang lebih baik memang menukar sedikit kemudahan debug.
3. Batasi Kemampuan Saat Runtime
docker run -d \
--read-only \ # filesystem tidak bisa ditulisi
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--cap-drop=ALL \ # buang SEMUA capability
--cap-add=NET_BIND_SERVICE \ # lalu tambahkan yang benar-benar perlu
--security-opt=no-new-privileges:true \
--memory=512m --cpus=1.0 \
--pids-limit=200 \
--user 10001:10001 \
app:2.4.0
๐ก Apa arti tiap opsi
--read-onlyโ penyerang tidak bisa menuliskan skrip atau binary ke dalam kontainer.--cap-drop=ALLโ mencabut hak istimewa kernel seperti memuat modul dan mengubah aturan jaringan.--no-new-privilegesโ mencegah eskalasi lewat binary ber-setuid.--pids-limitโ mencegah fork bomb menghabiskan kapasitas proses host.
4. Rootless Mode
Secara bawaan, daemon Docker berjalan sebagai root. Siapa pun yang bisa mengakses soket Docker pada dasarnya memiliki hak root di host โ cukup dengan menjalankan kontainer yang me-mount direktori root host.
curl -fsSL https://get.docker.com/rootless | sh
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
systemctl --user enable --now docker
๐ซ Jangan pernah me-mount soket Docker ke dalam kontainer
-v /var/run/docker.sock:/var/run/docker.sock # โ setara memberi akses root
Ini sering ditemukan pada kontainer CI dan alat pemantauan. Bila kontainer tersebut diretas, penyerang langsung menguasai seluruh host. Bila memang butuh membangun image di dalam kluster, pakai builder tanpa daemon seperti Kaniko atau BuildKit dalam mode rootless.
5. Pemindaian Kerentanan
# Docker Scout (bawaan Docker Desktop & CLI)
docker scout quickview app:2.4.0
docker scout cves app:2.4.0
docker scout recommendations app:2.4.0 # saran base image yang lebih aman
# Trivy โ pemindai open source yang sangat populer
trivy image --severity HIGH,CRITICAL app:2.4.0
trivy image --exit-code 1 --severity CRITICAL app:2.4.0 # gagalkan pipeline
trivy fs --scanners secret,misconfig . # pindai kode & konfigurasi juga
# Di GitHub Actions
- name: Pindai image
uses: aquasecurity/trivy-action@master
with:
image-ref: app:${{ github.sha }}
format: sarif
output: trivy.sarif
severity: CRITICAL,HIGH
exit-code: '1'
ignore-unfixed: true # jangan gagalkan build karena CVE tanpa perbaikan
โ ๏ธ Kebijakan pemindaian harus realistis
Menggagalkan build untuk setiap CVE terdengar disiplin, tetapi hasilnya tim akan mematikan pemindaiannya sama sekali. Pendekatan yang berkelanjutan: gagalkan hanya untuk CRITICAL yang sudah ada perbaikannya (
Menggagalkan build untuk setiap CVE terdengar disiplin, tetapi hasilnya tim akan mematikan pemindaiannya sama sekali. Pendekatan yang berkelanjutan: gagalkan hanya untuk CRITICAL yang sudah ada perbaikannya (
--ignore-unfixed), laporkan sisanya sebagai peringatan, dan jadwalkan pembaruan base image secara berkala โ itu saja biasanya sudah menutup sebagian besar temuan.
6. Menandatangani & Melacak Asal Image
# Bangkitkan SBOM dan bukti asal (provenance)
docker buildx build --sbom=true --provenance=true -t app:2.4.0 --push .
# Tandatangani image dengan cosign (keyless, memakai OIDC)
cosign sign registry.contoh.com/app:2.4.0
cosign verify registry.contoh.com/app:2.4.0
SBOM (Software Bill of Materials) mencatat setiap paket beserta versinya di dalam image. Ketika muncul kerentanan besar seperti Log4Shell, SBOM memungkinkan Anda menjawab "image mana saja yang terdampak" dalam hitungan menit, bukan berhari-hari.
7. Daftar Periksa
| Periksa | Kenapa |
|---|---|
USER non-root dengan UID numerik | Membatasi dampak bila proses dibajak |
| Base image minimal & tag spesifik | Mengecilkan permukaan serangan; :latest tidak dapat direproduksi |
.dockerignore memuat .git, .env | Mencegah rahasia & riwayat ikut tersalin |
| Rahasia lewat secret mount, bukan ARG | Rahasia tidak masuk ke layer |
--cap-drop=ALL saat runtime | Mencabut hak istimewa kernel |
| Batas memori & PID disetel | Mencegah satu kontainer menghabiskan host |
| Pemindaian di pipeline CI | Menangkap CVE sebelum sampai produksi |
| Soket Docker tidak pernah di-mount | Mencegah eskalasi ke root host |