Kodeloka
  1. Kodeloka
  2. Kelas Docker
  3. Materi

Fundamentals ยท Modul 20 dari 21

Keamanan Kontainer & Pemindaian Kerentanan

Mengecilkan permukaan serangan: pengguna non-root, base image minimal, rootless mode, capability, dan pemindaian otomatis di CI.

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.

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 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 imageUkuran kira-kiraAda shell?Catatan
ubuntu:24.04~78 MBYaPermukaan serangan besar
debian:bookworm-slim~75 MBYaPilihan seimbang
alpine:3~8 MBYamusl libc โ€” bisa bermasalah untuk sebagian binary
gcr.io/distroless/static~2 MBTidakSangat aman; hanya untuk binary statis
scratch0 MBTidakKosong 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 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 (--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

PeriksaKenapa
USER non-root dengan UID numerikMembatasi dampak bila proses dibajak
Base image minimal & tag spesifikMengecilkan permukaan serangan; :latest tidak dapat direproduksi
.dockerignore memuat .git, .envMencegah rahasia & riwayat ikut tersalin
Rahasia lewat secret mount, bukan ARGRahasia tidak masuk ke layer
--cap-drop=ALL saat runtimeMencabut hak istimewa kernel
Batas memori & PID disetelMencegah satu kontainer menghabiskan host
Pemindaian di pipeline CIMenangkap CVE sebelum sampai produksi
Soket Docker tidak pernah di-mountMencegah eskalasi ke root host