9.4.1 Docker Security
Docker Security mencakup praktik pengamanan container dari hulu ke hilir - mulai dari image building, registri, runtime, hingga deployment. Container bukan VM yang terisolasi penuh; container berbagi kernel dengan host, sehingga satu celah keamanan bisa berdampak ke seluruh sistem.
Image Security (Vulnerability Scanning)
Image adalah fondasi container. Image yang mengandung kerentanan akan mengekspos aplikasi ke risiko serius.
Trivy - Vulnerability Scanner untuk Container
Trivy (dari Aqua Security) adalah scanner open-source yang cepat dan komprehensif:
# Install Trivy
sudo apt-get install -y trivy
# atau
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Scan image lokal
trivy image nginx:1.25
# Scan dengan output format JSON (untuk CI/CD)
trivy image --severity CRITICAL,HIGH --format json --output trivy-report.json node:18-alpine
# Scan registry langsung (tanpa pull)
trivy image --server https://registry.example.com/my-app:latest
# Scan direktori (filesystem)
trivy fs --severity CRITICAL /path/to/project
# Scan repositori git
trivy repo https://github.com/org/repo.git
Contoh output Trivy:
nginx:1.25 (debian 12.0)
=======================
Total: 45 (UNKNOWN: 2, LOW: 12, MEDIUM: 25, HIGH: 4, CRITICAL: 2)
┌──────────────┬──────────────────┬──────────┬────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │
├──────────────┼──────────────────┼──────────┼────────────────────────────────┤
│ libssl3 │ CVE-2024-xxx │ CRITICAL │ 3.0.11-1 │
│ openssl │ CVE-2024-xxx │ HIGH │ 3.0.11-1 │
│ zlib1g │ CVE-2023-xxx │ MEDIUM │ 1:1.2.13.dfsg-1 │
└──────────────┴──────────────────┴──────────┴────────────────────────────────┘
Docker Scout - Native Docker Scanning
Docker Scout terintegrasi langsung dengan Docker Desktop dan Docker Hub:
# Mengaktifkan Docker Scout di image
docker scout quickview nginx:1.25
# Analisis detail
docker scout recommendations nginx:1.25
# Perbandingan dua image
docker scout compare nginx:1.25 nginx:1.26-alpine
# Integrasi dengan CI/CD
docker scout cves image:latest --exit-code --only-severity critical
Snyk - Commercial Container Security
# Install Snyk CLI
npm install -g snyk
snyk auth
# Monitor image
snyk container monitor node:18-alpine --org=my-org
# Test image
snyk container test node:18-alpine --severity-threshold=high
Dockerfile Best Practices
1. Gunakan Multi-Stage Builds
Multi-stage build memisahkan environment build dari runtime, menghasilkan image yang lebih kecil dan aman:
# === STAGE 1: Build ===
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server -ldflags="-s -w" .
# === STAGE 2: Runtime Minimal ===
FROM alpine:3.19
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/server /server
EXPOSE 8080
USER appuser
ENTRYPOINT ["/server"]
Manfaat: Image final hanya berisi binary + minimal dependencies. Ukuran bisa turun dari 1GB+ menjadi <20MB.
2. Minimal Base Image
| Base Image | Ukuran | Use Case |
|---|---|---|
alpine:3.19 |
~7 MB | Production, binary-only apps |
debian:12-slim |
~80 MB | Need glibc compatibility |
distroless |
~5-25 MB | Maximum security (no shell, no package manager) |
scratch |
0 B | Static binary, Go/Rust apps |
Contoh Distroless:
FROM gcr.io/distroless/base-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINE ["/server"]
Distroless tidak memiliki shell, package manager, atau utility apapun - mengurangi attack surface secara drastis.
3. Non-Root User
JANGAN pernah menjalankan container sebagai root:
# ❌ BURUK - berjalan sebagai root
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
# ✅ BAIK - non-root user
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm install
EXPOSE 3000
USER appuser
CMD ["node", "server.js"]
4. Praktik Lainnya
Specific tag, jangan latest:
# ❌ BURUK - tidak reproducible
FROM node:latest
# ✅ BAIK - versi spesifik
FROM node:18.20.0-alpine3.19
Hindari menambahkan tools yang tidak perlu:
# ❌ BURUK - banyak tools yang tidak diperlukan
RUN apt-get update && apt-get install -y curl vim netcat tree wget
# ✅ BAIK - hanya yang diperlukan
RUN apt-get update && apt-get install -y --no-install-recommends curl=8.4.0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
Layer optimization - gabungkan RUN commands:
# ✅ BAIK - mengurangi jumlah layer
RUN apt-get update && apt-get install -y \
curl \
ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
HEALTHCHECK:
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://vuln.cybersecurity.or.id/health || exit 1
Runtime Security
Runtime security melindungi container saat sedang berjalan - membatasi system call, akses file, dan kemampuan proses.
Seccomp (Secure Computing Mode)
Seccomp membatasi system call yang bisa dilakukan container. Docker memiliki profil default yang memblokir ~44 system call berbahaya:
# Menggunakan profil seccomp default Docker
docker run --security-opt seccomp=default nginx
# Menggunakan profil kustom
docker run --security-opt seccomp=/path/to/custom-seccomp.json nginx
Contoh profil seccomp kustom - strict profile hanya untuk web server:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["read", "write", "readv", "writev", "open", "openat",
"close", "mmap", "munmap", "brk", "fstat", "stat",
"accept", "accept4", "bind", "listen", "connect",
"sendto", "recvfrom", "sendmsg", "recvmsg",
"epoll_create1", "epoll_ctl", "epoll_wait",
"clock_gettime", "gettid", "exit_group", "exit",
"set_robust_list", "futex"],
"action": "SCMP_ACT_ALLOW"
}
]
}
AppArmor
AppArmor adalah Mandatory Access Control (MAC) yang membatasi akses file, network, dan capability:
# Cek profil AppArmor yang aktif
sudo aa-status
# Menjalankan container dengan profil AppArmor kustom
docker run --security-opt apparmor=custom-profile nginx
Contoh profil AppArmor untuk nginx:
#include <tunables/global>
profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network tcp,
network inet tcp,
/etc/nginx/** r,
/var/log/nginx/** w,
/var/run/nginx.pid rw,
/usr/sbin/nginx mr,
deny /etc/shadow r,
deny /etc/gshadow r,
}
SELinux
Di RHEL/CentOS/Fedora, SELinux memberikan label keamanan tambahan:
# Konteks SELinux untuk container
docker run --security-opt label=type:container_t nginx
# Disable SELinux untuk container tertentu (tidak direkomendasikan)
docker run --security-opt label=disable nginx
Linux Capabilities
Container berjalan dengan set capability terbatas. Praktik terbaik: drop ALL, lalu tambahkan hanya yang diperlukan:
# ❌ Buruk - retain default capabilities
docker run nginx
# ✅ Baik - drop ALL, add specific
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
# ✅ Terbaik - tidak perlu NET_BIND_SERVICE jika port >1024
docker run --cap-drop=ALL --cap-add=CHOWN nginx
Capabilities umum yang dibutuhkan aplikasi:
| Capability | Fungsi |
|---|---|
NET_BIND_SERVICE |
Bind ke port <1024 |
CHOWN |
Mengubah file ownership |
SETUID / SETGID |
Mengubah UID/GID proses |
DAC_OVERRIDE |
Bypass file permission checks |
SYS_PTRACE |
Debug proses dengan strace/ptrace |
Resource Limits
Mencegah satu container menghabiskan resource host:
# Memory limit - 512MB maksimal
docker run --memory="512m" --memory-reservation="256m" --memory-swap="1g" nginx
# CPU limit - maksimal 1.5 core
docker run --cpus="1.5" nginx
# CPU pinning - hanya core 0 dan 1
docker run --cpuset-cpus="0,1" nginx
# Disk I/O - prioritas rendah
docker run --blkio-weight=100 nginx
# Semua limit sekaligus
docker run \
--memory="512m" \
--memory-reservation="256m" \
--cpus="1.0" \
--ulimit nofile=1024:2048 \
--ulimit nproc=100:200 \
nginx
Read-Only Root Filesystem
Membuat rootfs container read-only mencegah penyerang memodifikasi file sistem:
# Root filesystem read-only, volume writable untuk data
docker run --read-only --tmpfs /tmp --tmpfs /var/run nginx
# Dengan persistent volume untuk log
docker run --read-only -v /host/logs:/var/log nginx
Konfigurasi di Docker Compose:
services:
app:
image: myapp:latest
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=64M
- /var/run
volumes:
- app-data:/data
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
User Namespace Remapping
Memetakan ulang UID/GID sehingga root di container (UID 0) menjadi non-root di host:
// /etc/docker/daemon.json
{
"userns-remap": "default",
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Setelah restart Docker, semua container berjalan dengan UID non-privileged di host. Namun fitur ini tidak kompatibel dengan beberapa konfigurasi (host networking, volume mounting).
Docker Bench Security
Docker Bench Security adalah script audit berdasarkan CIS Benchmark for Docker:
# Menjalankan audit
docker run --rm --net host --pid host \
-v /etc:/etc:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /etc/kubernetes:/etc/kubernetes:ro \
docker/docker-bench-security
# Output dengan warna
docker run --rm --net host --pid host \
-v /etc:/etc:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
docker/docker-bench-security \
-c docker_bench_security.json
Contoh output - items yang diperiksa:
[PASS] 1.1.1 - Ensure a separate partition for containers
[PASS] 2.1 - Ensure network traffic is restricted
[WARN] 2.5 - Ensure `--live-restore` is enabled
[PASS] 4.1 - Ensure a user for the container is non-root
[FAIL] 5.4 - Ensure `--read-only` is enabled
Supply Chain Attacks pada Container
Supply chain attack terjadi ketika penyerang menyusupkan kode berbahaya di salah satu titik rantai: base image, dependencies, atau CI/CD pipeline.
1. Malicious Base Images
Penyerang mengupload image berbahaya ke Docker Hub dengan nama yang mirip dengan image resmi:
# ❌ BURUK - image dari unknown publisher (typosquatting)
docker pull node:18
# vs node:18 - mana yang resmi?
# ✅ BAIK - selalu verifikasi publisher
docker pull docker.io/library/node:18-alpine
Cek official image: Cari badge
"Official Image" di Docker Hub atau
gunakan docker trust inspect:
# Verifikasi signature image
docker trust inspect nginx:latest --pretty
2. Compromised Dependencies
# ❌ BURUK - semua dependency di-install di stage final
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "app.js"]
Mitigasi: Gunakan lock file, verifikasi signature, dan scan dependencies:
# npm audit untuk mendeteksi kerentanan di dependencies
npm audit --audit-level=high
# SBOM generation (Syft)
syft packages node:18-alpine -o spdx-json > sbom.json
# Grype - vulnerability scanner untuk SBOM
grype sbom:sbom.json
3. Image Tampering di Registry
Gunakan Docker Content Trust (Notary) untuk memastikan integritas image:
# Mengaktifkan content trust
export DOCKER_CONTENT_TRUST=1
# Push image yang ditandatangani
docker push perusahaan/myapp:1.0.0
# Pull hanya image yang ditandatangani
docker pull perusahaan/myapp:1.0.0
4. CI/CD Pipeline Poisoning
# .github/workflows/docker-build.yml
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan Dockerfile untuk secrets
run: trufflehog filesystem --json . | jq
- name: Build image
run: |
docker build -t app:latest .
docker scout quickview app:latest
- name: Scan image untuk kerentanan
run: trivy image --exit-code 1 --severity CRITICAL app:latest
- name: Sign dan Push
run: |
export DOCKER_CONTENT_TRUST=1
docker push registry.example.com/app:latest
env:
DOCKER_CONTENT_TRUST_SERVER: https://notary.example.com
Verifikasi
# Audit image dengan Trivy
trivy image myapp:latest --severity CRITICAL,HIGH
# Periksa running container
docker ps --quiet | xargs docker inspect --format '{{.Name}}: {{.HostConfig.ReadonlyRootfs}} {{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}'
# Jalankan Docker Bench Security
docker run --rm --net host --pid host \
-v /etc:/etc:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
docker/docker-bench-security
# Cek user container
docker ps --quiet | xargs docker inspect --format '{{.Name}}: User={{.Config.User}}'