Chapter 8 Docker Security and Best Practices

作者
17min

Chapter 8 Docker Security and Best Practices

Learning Objectives

  1. Master Docker security threat models and attack vectors
  2. Learn to configure secure container runtime environments
  3. Implement image security scanning and vulnerability detection
  4. Master access control and permission management
  5. Understand Docker security best practices

Knowledge Point Details

8.1 Docker Security Threat Model

8.1.1 Container Security Threats

Main Threat Types:

  • Container escape
  • Privilege escalation
  • Denial of service attacks
  • Malicious images
  • Data leakage
  • Network attacks

Threat Examples:

# Dangerous privileged container execution
docker run --privileged -it ubuntu bash

# Unsafe host mounting
docker run -v /:/host ubuntu bash

# Using untrusted images
docker run untrusted/malicious-image

8.1.2 Attack Vector Analysis

Container Escape Scenarios:

# 1. Kernel vulnerability exploitation
# Check kernel version
uname -r

# 2. Docker daemon attack
# View Docker socket permissions
ls -la /var/run/docker.sock

# 3. Container configuration errors
docker inspect container_name | grep -i privileged

8.2 Container Security Configuration

8.2.1 User Permission Control

Non-root User Execution:

# Dockerfile security configuration
FROM ubuntu:20.04

# Create non-privileged user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Set working directory permissions
WORKDIR /app
COPY app.py .
RUN chown -R appuser:appuser /app

# Switch to non-privileged user
USER appuser

CMD ["python", "app.py"]

Runtime User Control:

# Specify user ID to run
docker run --user 1000:1000 myapp

# Run with username
docker run --user appuser myapp

# Verify current user
docker exec container_id whoami

8.2.2 File System Security

Read-only File System:

# Read-only root file system
docker run --read-only ubuntu

# Temporary file system mount
docker run --read-only --tmpfs /tmp ubuntu

# Read-only mount
docker run -v /host/path:/container/path:ro ubuntu

File Permission Control:

# Set secure file permissions
COPY --chmod=644 config.yml /app/
COPY --chmod=755 entrypoint.sh /usr/local/bin/

# Remove unnecessary setuid programs
RUN find / -perm /6000 -type f -exec ls -ld {} \; && \
    find / -perm /6000 -type f -exec rm {} \;

8.2.3 Capabilities Control

Remove Dangerous Capabilities:

# Remove all capabilities
docker run --cap-drop=ALL ubuntu

# Keep only necessary capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

# View container capabilities
docker run --rm ubuntu capsh --print

Common Capabilities Explanation:

# docker-compose.yml
version: '3.8'
services:
  web:
    image: nginx
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
      - CHOWN
      - SETUID
      - SETGID

8.3 Image Security

8.3.1 Image Scanning

Using Docker Scout:

# Enable Docker Scout
docker scout quickview

# Scan local image
docker scout cves nginx:latest

# Scan and get detailed report
docker scout cves --format sarif nginx:latest > report.sarif

# Compare image security
docker scout compare nginx:1.20 --to nginx:latest

Using Trivy Scanning:

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

# Scan image vulnerabilities
trivy image nginx:latest

# Scan high-risk vulnerabilities
trivy image --severity HIGH,CRITICAL nginx:latest

# Output JSON format
trivy image --format json --output result.json nginx:latest

8.3.2 Secure Base Images

Choose Secure Base Images:

# Use official minimal images
FROM nginx:alpine

# Use distroless images
FROM gcr.io/distroless/java:11

# Multi-stage build to reduce attack surface
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "server.js"]

Image Hardening:

FROM ubuntu:20.04

# Update package manager
RUN apt-get update && apt-get upgrade -y

# Remove unnecessary packages
RUN apt-get autoremove -y && apt-get autoclean

# Clean cache
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Set security configuration
RUN sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs

8.4 Network Security

8.4.1 Network Isolation

Custom Networks:

# Create isolated network
docker network create --driver bridge isolated-net

# Run container in isolated network
docker run --network isolated-net nginx

# Inter-network communication control
docker network create frontend
docker network create backend

# Frontend container
docker run --name web --network frontend nginx

# Backend container connecting to multiple networks
docker run --name api --network backend mysql
docker network connect frontend api

8.4.2 Port Security

Minimize Port Exposure:

# Bind only to local interface
docker run -p 127.0.0.1:8080:80 nginx

# Use non-standard ports
docker run -p 8443:443 nginx

# Dynamic port allocation
docker run -P nginx

Network Policies:

# docker-compose.yml
version: '3.8'
services:
  web:
    image: nginx
    networks:
      - frontend
    ports:
      - "127.0.0.1:8080:80"

  api:
    image: myapi
    networks:
      - frontend
      - backend
    expose:
      - "3000"

  db:
    image: postgres
    networks:
      - backend
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
    secrets:
      - db_password

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

secrets:
  db_password:
    file: ./db_password.txt

8.5 Secret Management

8.5.1 Docker Secrets

Secret Management:

# Create secret
echo "mypassword" | docker secret create db_password -

# View secrets
docker secret ls

# Use secret in service
docker service create --name web --secret db_password nginx

Using Secrets in Compose:

version: '3.8'
services:
  db:
    image: postgres
    secrets:
      - db_password
      - db_user
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
      - POSTGRES_USER_FILE=/run/secrets/db_user

secrets:
  db_password:
    external: true
  db_user:
    external: true

8.5.2 Environment Variable Security

Secure Environment Variable Handling:

# Use file instead of command line for passing sensitive information
docker run --env-file .env myapp

# .env file content
DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=secret_key_here

Runtime Secret Injection:

FROM alpine
RUN apk add --no-cache curl
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
# entrypoint.sh
# Retrieve secrets from external secret management system
export API_KEY=$(curl -s "$SECRET_SERVICE_URL/api-key")
exec "$@"

8.6 Security Monitoring and Auditing

8.6.1 Container Runtime Monitoring

Using Falco Monitoring:

# falco-rules.yaml
- rule: Shell in Container
  desc: Detect shell execution in container
  condition: spawned_process and container and shell_procs
  output: Shell spawned in container (user=%user.name container_id=%container.id image=%container.image.repository:%container.image.tag)
  priority: WARNING

- rule: Sensitive File Access
  desc: Detect access to sensitive files
  condition: open_read and fd.filename in (/etc/passwd, /etc/shadow)
  output: Sensitive file accessed (file=%fd.name user=%user.name container=%container.id)
  priority: HIGH

Docker Bench Security:

# Run security benchmark test
docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
  -v /etc:/etc:ro \
  -v /usr/bin/containerd:/usr/bin/containerd:ro \
  -v /usr/bin/runc:/usr/bin/runc:ro \
  -v /usr/lib/systemd:/usr/lib/systemd:ro \
  -v /var/lib:/var/lib:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  --label docker_bench_security \
  docker/docker-bench-security

8.6.2 Log Auditing

Enable Audit Logging:

# /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "experimental": true,
  "debug": true
}

Log Analysis Example:

# View container logs
docker logs --details container_name

# Monitor real-time logs
docker logs -f --tail 100 container_name

# Collect system logs using journald
journalctl -u docker.service -f

8.7 Best Practices Summary

8.7.1 Development Phase

Dockerfile Best Practices:

# 1. Use official base images
FROM node:16-alpine

# 2. Set non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

# 3. Minimize layers
RUN apk add --no-cache libc6-compat && \
    apk update

# 4. Set correct permissions when copying files
COPY --chown=nextjs:nodejs package*.json ./

# 5. Switch to non-privileged user
USER nextjs

# 6. Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 7. Use COPY instead of ADD
COPY . .

# 8. Explicitly expose ports
EXPOSE 3000

# 9. Use exec form
CMD ["node", "server.js"]

8.7.2 Deployment Phase

Security Deployment Checklist:

# security-checklist.yml
version: '3.8'
services:
  app:
    image: myapp:latest
    # 1. Use non-privileged user
    user: "1001:1001"

    # 2. Read-only root file system
    read_only: true

    # 3. Remove all capabilities
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

    # 4. Resource limits
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

    # 5. Health check
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

    # 6. Security options
    security_opt:
      - no-new-privileges:true

    # 7. Temporary file system
    tmpfs:
      - /tmp:noexec,nosuid,size=100m

    # 8. Read environment variables from file
    env_file:
      - .env

    # 9. Network isolation
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/16

8.7.3 Operations Phase

Continuous Security Monitoring:

#!/bin/bash
# security-monitor.sh

# 1. Regularly scan images
trivy image --format table myapp:latest

# 2. Check running container security status
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}" | while read line; do
    container_id=$(echo $line | awk '{print $1}')
    if [[ $container_id != "CONTAINER" ]]; then
        echo "Checking container: $container_id"
        docker inspect $container_id | jq '.[] | {Privileged: .HostConfig.Privileged, User: .Config.User}'
    fi
done

# 3. Monitor resource usage
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"

# 4. Check network connections
netstat -tulpn | grep docker

Practical Cases

Case 1: Web Application Security Hardening

Scenario Description: Perform comprehensive security hardening on a Node.js web application.

Implementation Steps:

  1. Secure Dockerfile:
# Multi-stage build
FROM node:16-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

FROM node:16-alpine AS runner
WORKDIR /app

# Create non-privileged user
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

# Copy dependencies
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .

# Set environment variables
ENV NODE_ENV production

# Switch user
USER nextjs

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "server.js"]
  1. Secure Deployment Configuration:
version: '3.8'
services:
  web:
    build: .
    ports:
      - "127.0.0.1:3000:3000"
    read_only: true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    tmpfs:
      - /tmp:noexec,nosuid,size=50m
    environment:
      - NODE_ENV=production
    secrets:
      - jwt_secret
      - db_password
    networks:
      - app-network
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

secrets:
  jwt_secret:
    external: true
  db_password:
    external: true

networks:
  app-network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.30.0.0/16
  1. Security Monitoring Script:
#!/bin/bash
# web-security-check.sh

echo "=== Web Application Security Check ==="

# Check container status
echo "1. Container Security Status:"
docker inspect web_container | jq '{
  Privileged: .HostConfig.Privileged,
  ReadonlyRootfs: .HostConfig.ReadonlyRootfs,
  User: .Config.User,
  Capabilities: .HostConfig.CapDrop
}'

# Check network connections
echo "2. Network Connections:"
docker exec web_container netstat -tulpn

# Check processes
echo "3. Running Processes:"
docker exec web_container ps aux

# Scan vulnerabilities
echo "4. Vulnerability Scan:"
trivy image --severity HIGH,CRITICAL web:latest

echo "Security check completed."

Case 2: Microservices Security Architecture

Scenario Description: Build a secure microservices architecture including frontend, API services, and databases.

Implementation Solution:

version: '3.8'
services:
  # Frontend service
  frontend:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    networks:
      - frontend-net
    deploy:
      resources:
        limits:
          memory: 128M
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /var/cache/nginx:noexec,nosuid,size=50m
      - /var/run:noexec,nosuid,size=10m

  # API gateway
  api-gateway:
    image: traefik:v2.9
    command:
      - --api.insecure=false
      - --providers.docker=true
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.tlschallenge=true
    ports:
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - frontend-net
      - api-net
    security_opt:
      - no-new-privileges:true

  # User service
  user-service:
    build: ./services/user
    networks:
      - api-net
      - db-net
    secrets:
      - user_db_password
      - jwt_secret
    deploy:
      resources:
        limits:
          memory: 256M
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp:noexec,nosuid,size=20m

  # Database
  database:
    image: postgres:14-alpine
    networks:
      - db-net
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_root_password
    secrets:
      - db_root_password
      - user_db_password
    security_opt:
      - no-new-privileges:true

networks:
  frontend-net:
    driver: bridge
  api-net:
    driver: bridge
  db-net:
    driver: bridge
    internal: true

secrets:
  jwt_secret:
    external: true
  db_root_password:
    external: true
  user_db_password:
    external: true

volumes:
  db_data:
    driver: local

Summary

Docker security is a multi-faceted issue that requires comprehensive protection from multiple angles including image building, container runtime, network isolation, and secret management. By following security best practices, the security risks of containerized applications can be significantly reduced:

  1. Principle of Least Privilege: Use non-privileged users, remove unnecessary capabilities
  2. Defense in Depth: Multi-layer security controls, network isolation, access control
  3. Continuous Monitoring: Real-time monitoring of container behavior, regular security scans
  4. Secret Management: Use dedicated secret management services, avoid hardcoding
  5. Image Security: Use trusted image sources, regular updates and scans

Remember, container security is not a one-time task, but a process that requires continuous attention and improvement throughout the application lifecycle.