9.2 KiB
9.2 KiB
Docker Security Reference
Overview
Container security involves the Dockerfile, image composition, runtime configuration, and orchestration. Misconfigurations can lead to container escapes, privilege escalation, or exposure of sensitive data.
Dockerfile Security
Running as Root
# VULNERABLE: Running as root (default)
FROM node:18
COPY . /app
CMD ["node", "app.js"] # Runs as root
# SAFE: Non-root user
FROM node:18
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "app.js"]
# SAFE: Using numeric UID (more portable)
USER 1000:1000
Base Image Issues
# VULNERABLE: Using latest tag (unpredictable)
FROM node:latest
FROM ubuntu:latest
# VULNERABLE: Using untrusted/unverified base image
FROM randomuser/myimage
# SAFE: Pinned versions with digest
FROM node:18.19.0-alpine@sha256:abc123...
FROM python:3.11.7-slim-bookworm
# SAFE: Official images from verified publishers
FROM docker.io/library/node:18.19.0-alpine
Sensitive Data in Images
# VULNERABLE: Secrets in build args visible in history
ARG DB_PASSWORD
RUN echo $DB_PASSWORD > /config
# VULNERABLE: Copying secrets into image
COPY .env /app/.env
COPY secrets.json /app/
COPY id_rsa /root/.ssh/
# VULNERABLE: Secrets in environment variables
ENV API_KEY=sk-12345
ENV DB_PASSWORD=mysecret
# SAFE: Mount secrets at runtime
# docker run -v /secrets:/secrets:ro myimage
# Or use Docker secrets in Swarm/K8s
Build-Time Secrets
# SAFE: Multi-stage build to exclude secrets
FROM node:18 AS builder
# Use build-time secret (Docker BuildKit)
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm install
FROM node:18-alpine
COPY --from=builder /app/node_modules /app/node_modules
# Secret not in final image
# Build with: docker build --secret id=npm_token,src=.npmrc .
Package Installation
# VULNERABLE: Not cleaning up package manager cache
RUN apt-get update && apt-get install -y curl wget
# Leaves cache, increases image size and attack surface
# VULNERABLE: Installing unnecessary packages
RUN apt-get install -y vim nano curl wget git ssh
# SAFE: Minimal installation with cleanup
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# SAFE: Using minimal base images
FROM alpine:3.19
FROM gcr.io/distroless/nodejs18
FROM scratch # Empty base image
COPY vs ADD
# VULNERABLE: ADD can auto-extract and fetch URLs
ADD https://example.com/file.tar.gz /app/ # Downloads from URL
ADD archive.tar.gz /app/ # Auto-extracts
# SAFE: COPY is more explicit
COPY archive.tar.gz /app/
RUN tar -xzf /app/archive.tar.gz && rm /app/archive.tar.gz
Exposed Ports
# CHECK: Are all exposed ports necessary?
EXPOSE 22 # FLAG: SSH in container usually unnecessary
EXPOSE 3306 # FLAG: Database port exposed
EXPOSE 80 443 8080 9090 5000 # CHECK: Multiple ports
# SAFE: Only expose what's needed
EXPOSE 8080
Image Scanning
Vulnerability Patterns
# Scan for vulnerabilities
docker scan myimage
trivy image myimage
grype myimage
# Check for secrets in image
trufflehog docker --image myimage
# Or manually inspect layers
docker history --no-trunc myimage
High-Risk Packages
# FLAG: Packages that increase attack surface
RUN apt-get install -y \
openssh-server \ # SSH access
sudo \ # Privilege escalation
netcat \ # Network tools
nmap \ # Network scanning
gcc make \ # Compilers (should be in build stage only)
python3-pip # Package managers (install deps, then remove)
Runtime Security
Privileged Mode
# VULNERABLE: Running privileged (full host access)
docker run --privileged myimage
# VULNERABLE: Dangerous capabilities
docker run --cap-add=ALL myimage
docker run --cap-add=SYS_ADMIN myimage
docker run --cap-add=NET_ADMIN myimage
# SAFE: Drop all capabilities, add only needed
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myimage
# SAFE: Read-only root filesystem
docker run --read-only myimage
# SAFE: No new privileges
docker run --security-opt=no-new-privileges myimage
Volume Mounts
# VULNERABLE: Mounting sensitive host paths
docker run -v /:/host myimage # Entire host filesystem
docker run -v /etc:/etc myimage # Host config files
docker run -v /var/run/docker.sock:/var/run/docker.sock # Docker socket
# VULNERABLE: Writable mounts of sensitive paths
docker run -v /etc/passwd:/etc/passwd myimage
# SAFE: Specific paths, read-only where possible
docker run -v /app/data:/data:ro myimage
docker run -v myvolume:/app/data myimage # Named volume
Docker Socket Access
# CRITICAL: Docker socket mount = root on host
docker run -v /var/run/docker.sock:/var/run/docker.sock myimage
# Container can create privileged containers, access host
# If required, use read-only and restrict with authz plugin
# Or use Docker API proxy with limited permissions
Network Security
# VULNERABLE: Host network mode
docker run --network=host myimage # No network isolation
# SAFE: User-defined networks with isolation
docker network create --internal internal-net # No external access
docker run --network=internal-net myimage
# SAFE: Restrict inter-container communication
docker network create --driver=bridge --opt com.docker.network.bridge.enable_icc=false isolated
Resource Limits
# VULNERABLE: No resource limits (DoS risk)
docker run myimage
# SAFE: Set memory and CPU limits
docker run --memory=512m --cpus=1 myimage
# SAFE: Limit processes
docker run --pids-limit=100 myimage
Docker Compose Security
Secrets Management
# VULNERABLE: Secrets in environment
services:
app:
environment:
- DB_PASSWORD=mysecret
- API_KEY=sk-12345
# SAFE: Use secrets
services:
app:
secrets:
- db_password
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
external: true # Or file: ./secrets/db_password
Privilege Restrictions
# SAFE: Security options in compose
services:
app:
image: myimage
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
tmpfs:
- /tmp
deploy:
resources:
limits:
memory: 512M
cpus: '1'
Network Isolation
# SAFE: Internal networks for backend services
services:
frontend:
networks:
- public
- internal
backend:
networks:
- internal # Not accessible from outside
database:
networks:
- internal
networks:
public:
internal:
internal: true # No external access
.dockerignore
Required Exclusions
# SAFE: Exclude sensitive files
.env
.env.*
*.pem
*.key
id_rsa*
secrets/
credentials/
.git/
.gitignore
.dockerignore
Dockerfile
docker-compose*.yml
*.log
node_modules/
__pycache__/
.pytest_cache/
coverage/
.nyc_output/
Missing .dockerignore
# FLAG: No .dockerignore may copy secrets into image
# Check if .env, keys, or credentials are copied
Registry Security
Image Pull Policy
# VULNERABLE: Always pulling latest
image: myregistry/myimage:latest
# VULNERABLE: No digest verification
image: myregistry/myimage:1.0
# SAFE: Pinned with digest
image: myregistry/myimage@sha256:abc123...
Private Registry Auth
# VULNERABLE: Credentials in plain text
docker login -u user -p password registry.example.com
# SAFE: Use credential helpers
# ~/.docker/config.json
{
"credHelpers": {
"gcr.io": "gcloud",
"*.dkr.ecr.*.amazonaws.com": "ecr-login"
}
}
Grep Patterns for Dockerfiles
# Running as root
grep -rn "^USER" Dockerfile || echo "No USER directive - runs as root"
# Secrets in environment
grep -rn "^ENV.*PASSWORD\|^ENV.*SECRET\|^ENV.*KEY\|^ENV.*TOKEN" Dockerfile
# Secrets in build args
grep -rn "^ARG.*PASSWORD\|^ARG.*SECRET\|^ARG.*KEY" Dockerfile
# Latest tags
grep -rn "FROM.*:latest\|FROM.*@" Dockerfile | grep -v "@sha256"
# Privileged instructions
grep -rn "^ADD\|EXPOSE 22\|apt-get install.*ssh" Dockerfile
# Missing cleanup
grep -rn "apt-get install" Dockerfile | grep -v "rm -rf"
Testing Checklist
- Container runs as non-root user
- Base image is pinned with digest
- No secrets in image layers (ENV, ARG, COPY)
- Multi-stage build for secrets/build tools
- Minimal base image (alpine, distroless)
- Package manager cache cleaned
- .dockerignore excludes sensitive files
- No --privileged or dangerous capabilities
- No Docker socket mount
- Resource limits configured
- Network isolation configured
- Image scanned for vulnerabilities
- Read-only root filesystem where possible