跳到主要内容

Docker 安全最佳实践

容器安全是生产环境部署的关键考量。本章将详细介绍 Docker 的安全机制和最佳实践,帮助你构建安全的容器化应用。

安全概述

Docker 安全涉及四个主要领域:

  1. 内核安全:Linux 内核的命名空间(Namespaces)和控制组(Cgroups)
  2. 守护进程安全:Docker Daemon 的攻击面
  3. 容器配置安全:容器的默认配置和自定义配置
  4. 内核加固特性:AppArmor、SELinux、Seccomp 等安全模块

容器安全架构

内核安全机制

命名空间(Namespaces)

命名空间提供了容器的第一层隔离。每个容器拥有独立的:

命名空间类型隔离内容说明
PID进程 ID容器内进程独立编号
NET网络栈独立网络接口、路由表
MNT挂载点独立文件系统视图
IPC进程间通信独立消息队列、信号量
UTS主机名和域名独立主机名
USER用户和用户组独立用户 ID 映射

命名空间隔离效果

# 在容器内查看进程
docker run -it alpine ps aux
# 只能看到容器内的进程

# 在主机上查看
ps aux | grep docker
# 可以看到容器进程,但属于不同的命名空间

网络隔离

# 每个容器拥有独立的网络栈
docker run -d --name web nginx
docker exec web ip addr
# 容器有独立的 IP 地址和网络接口

控制组(Cgroups)

Cgroups 实现资源限制和审计,防止单个容器耗尽系统资源:

# 限制容器内存
docker run -d --memory="512m" --memory-swap="1g" nginx

# 限制 CPU 使用
docker run -d --cpus="1.5" --cpuset-cpus="0,1" nginx

# 限制 IO
docker run -d --device-read-bps=/dev/sda:10mb nginx

# 查看容器资源使用
docker stats

Cgroups 的安全作用

  • 防止 DoS 攻击:限制单个容器的资源使用
  • 公平调度:确保多个容器公平分享资源
  • 资源审计:记录资源使用情况

Linux Capabilities

什么是 Capabilities?

传统的 Linux 权限模型是二元的:root(超级用户)或非 root。Capabilities 将 root 权限分解为细粒度的权限单元,实现了更精细的访问控制。

常用的 Capabilities

Capability说明风险级别
CAP_NET_BIND_SERVICE绑定 1024 以下端口
CAP_NET_RAW使用原始套接字
CAP_SYS_ADMIN系统管理操作
CAP_CHOWN修改文件所有者
CAP_DAC_OVERRIDE绕过文件权限检查
CAP_KILL发送信号给其他进程
CAP_SETUID/SETGID修改进程 UID/GID

Docker 默认 Capabilities

Docker 默认使用白名单方式,只保留必要的 Capabilities:

# 查看容器默认的 capabilities
docker run --rm alpine capsh --print

# 输出示例
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep

管理容器 Capabilities

删除不需要的 Capabilities

# 删除所有 capabilities
docker run --rm --cap-drop=ALL alpine capsh --print

# 删除特定 capability
docker run --rm --cap-drop=NET_RAW alpine capsh --print

# 只保留需要的 capabilities
docker run --rm --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

添加 Capabilities

# 添加特定 capability
docker run --cap-add=NET_ADMIN alpine

# 添加所有 capabilities(不推荐)
docker run --privileged alpine

最佳实践示例

# Web 服务器只需要绑定特权端口的能力
docker run -d \
--name web \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
-p 80:80 \
nginx

# 需要修改网络配置的容器
docker run -d \
--name network-tool \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
network-tool

非 Root 用户运行容器

为什么使用非 root 用户?

默认情况下,容器内的 root 用户与主机的 root 用户共享 UID 0。如果容器被攻破,攻击者可能获得主机的 root 权限。

在 Dockerfile 中配置非 root 用户

# 创建专用用户和组
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 或指定 UID/GID
RUN groupadd -g 1000 appuser && \
useradd -u 1000 -g appuser -m appuser

# 设置工作目录权限
WORKDIR /app
COPY --chown=appuser:appuser . .

# 切换到非 root 用户
USER appuser

# 启动应用
CMD ["node", "server.js"]

完整示例

FROM node:18-alpine

# 安装依赖阶段
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001

# 复制应用代码
COPY --chown=nextjs:nodejs . .

# 切换用户
USER nextjs

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# 启动命令
CMD ["node", "server.js"]

运行时指定用户

# 以特定用户运行
docker run -d --user 1000:1000 nginx

# 使用用户名
docker run -d --user appuser:appgroup myapp

# 以只读文件系统运行
docker run -d --read-only --user 1000:1000 myapp

用户命名空间

用户命名空间将容器内的 root 用户映射到主机上的非特权用户,提供额外的安全层。

启用用户命名空间

配置 Docker Daemon

# /etc/docker/daemon.json
{
"userns-remap": "default"
}

或指定特定用户:

{
"userns-remap": "dockremap:dockremap"
}

创建映射用户

# 创建用户和组
sudo useradd -r -s /bin/false dockremap

# 配置 subuid 和 subgid
echo "dockremap:100000:65536" | sudo tee -a /etc/subuid
echo "dockremap:100000:65536" | sudo tee -a /etc/subgid

# 重启 Docker
sudo systemctl restart docker

效果

  • 容器内的 root (UID 0) 映射到主机的 dockremap 用户 (UID 100000)
  • 容器内的进程在主机上以非特权用户身份运行
  • 即使容器被攻破,也无法获得主机 root 权限

Seccomp 安全配置

什么是 Seccomp?

Seccomp(Secure Computing Mode)限制容器可以使用的系统调用,减少内核攻击面。

Docker 默认 Seccomp 配置

Docker 默认应用 Seccomp 配置文件,阻止危险的系统调用:

# 查看默认配置
docker info | grep -i seccomp

# 输出示例
Security Options:
seccomp
Profile: builtin

默认阻止的系统调用

  • kexec_loadkexec_file_load:加载新内核
  • init_modulefinit_module:加载内核模块
  • acct:启用/禁用进程记账
  • swaponswapoff:管理交换空间
  • 等约 44 个危险系统调用

自定义 Seccomp 配置

禁用 Seccomp(不推荐)

docker run --security-opt seccomp=unconfined alpine

使用自定义配置文件

docker run --security-opt seccomp=/path/to/seccomp.json alpine

示例 Seccomp 配置

{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["kexec_load"],
"action": "SCMP_ACT_ERRNO"
}
]
}

AppArmor 和 SELinux

AppArmor(Ubuntu/Debian)

AppArmor 通过配置文件限制程序的访问权限:

查看 AppArmor 状态

# 检查 AppArmor 是否启用
docker info | grep -i apparmor

# 查看加载的配置文件
sudo aa-status

使用自定义 AppArmor 配置

# 创建配置文件
sudo aa-genprof /path/to/binary

# 运行容器时指定配置
docker run --security-opt apparmor=your-profile myapp

SELinux(CentOS/RHEL)

SELinux 提供强制访问控制:

查看 SELinux 状态

# 检查 SELinux 是否启用
getenforce

# 查看 Docker SELinux 配置
docker info | grep -i selinux

使用 SELinux 标签

# 使用 svirt 标签
docker run --security-opt label=level:s0:c100,c200 myapp

# 禁用 SELinux 标签(不推荐)
docker run --security-opt label=disable myapp

守护进程安全

Docker Daemon 攻击面

Docker Daemon 以 root 权限运行,具有以下安全风险:

  1. TCP 端口暴露:远程 API 访问可能导致权限提升
  2. 镜像加载:恶意镜像可能包含漏洞
  3. 特权容器:可以访问主机资源

安全配置

限制 Docker Daemon 访问

# 只允许特定用户组访问 Docker socket
sudo usermod -aG docker $USER

# 配置 daemon.json
{
"hosts": ["unix:///var/run/docker.sock"],
"tls": false
}

启用 TLS 加密

# 生成证书
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem

# 配置 daemon.json
{
"hosts": ["tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"],
"tls": true,
"tlscacert": "/etc/docker/ca.pem",
"tlscert": "/etc/docker/server-cert.pem",
"tlskey": "/etc/docker/server-key.pem",
"tlsverify": true
}

客户端配置

# 配置环境变量
export DOCKER_HOST=tcp://your-host:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=/path/to/certs

Rootless 模式

Rootless 模式允许非 root 用户运行 Docker Daemon,从根本上降低安全风险。在这种模式下,Docker Daemon 和容器都不以 root 权限运行。

Rootless 模式的优势

特性传统模式Rootless 模式
Daemon 权限root普通用户
容器进程权限root(容器内)用户映射
安全风险高(Daemon 被攻破可获得主机 root)低(仅普通用户权限)
适用场景生产环境开发环境、多租户环境

安装 Rootless Docker

前置条件

# 安装 uidmap(用于用户命名空间映射)
sudo apt-get install -y uidmap

# 确认内核支持用户命名空间
cat /proc/sys/kernel/unprivileged_userns_clone
# 应输出 1

安装步骤

# 方式一:使用官方安装脚本
curl -fsSL https://get.docker.com/rootless | sh

# 方式二:手动安装
# 下载 Docker 二进制文件
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz | tar xz

# 设置环境变量
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$UID/docker.sock

配置环境变量(持久化)

# 添加到 ~/.bashrc 或 ~/.profile
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$UID/docker.sock

启动 Rootless Docker

# 启动 daemon
dockerd-rootless.sh

# 或使用 systemd 服务
systemctl --user start docker
systemctl --user enable docker

Rootless 模式的限制

Rootless 模式有一些功能限制:

功能支持情况
镜像存储✅ 支持
容器运行✅ 支持
端口映射⚠️ 仅支持 > 1024 端口
ping 命令⚠️ 需要配置
卷挂载⚠️ 仅能访问用户有权限的文件
privileged 模式❌ 不支持
AppArmor❌ 不支持

解决端口限制

# 允许非特权端口(需要 root 执行一次)
sudo sysctl net.ipv4.ip_unprivileged_port_start=80

# 持久化配置
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee /etc/sysctl.d/50-unprivileged-ports.conf

启用 ping 支持

# 允许非特权用户使用 ping
sudo sysctl -w net.ipv4.ping_group_range="0 2000000"

验证 Rootless 模式

# 确认 Daemon 以非 root 运行
ps aux | grep dockerd
# 应显示普通用户而非 root

# 确认容器进程权限
docker run --rm alpine id
# 输出应为映射后的 UID,而非 0

镜像安全

使用可信镜像

镜像来源分类

类型说明安全级别
Docker Official ImagesDocker 官方维护最高
Verified Publisher验证发布者
Docker-Sponsored OSSDocker 赞助的开源项目中高
社区镜像社区贡献需评估

验证镜像来源

选择镜像时,优先考虑以下来源:

类型说明安全级别
Docker Official ImagesDocker 官方维护最高
Verified Publisher验证发布者
Docker-Sponsored OSSDocker 赞助的开源项目中高
社区镜像社区贡献需评估
Docker Content Trust 已移除

从 Docker Engine v29 开始,Docker Content Trust (DCT) 已从 Docker CLI 中移除。DCT 原用于验证镜像签名,但现在推荐使用以下替代方案:

  • Docker Scout:检测镜像漏洞和验证镜像来源
  • cosign:Sigstore 项目提供的镜像签名工具
  • Notary:CNCF 项目,用于镜像签名和验证

使用 Docker Scout 验证镜像

# 快速查看镜像安全信息
docker scout quickview nginx:latest

# 查看镜像详细信息(包括来源验证)
docker scout cves nginx:latest

# 查看镜像基础信息和建议
docker scout recommendations nginx:latest

# 比较两个镜像版本
docker scout compare nginx:1.25 nginx:1.26

# 只显示严重和高危漏洞
docker scout cves --only-severe nginx:latest

# 输出 SARIF 格式(用于 CI/CD)
docker scout cves --format sarif --output results.sarif nginx:latest

Docker Scout 订阅等级

等级功能适用场景
Docker Free基础扫描、快速查看个人开发者
Docker Pro高级 CVE 详情、策略检查专业开发者
Docker Team团队管理、策略配置开发团队
Docker Business企业级功能、SSO企业组织

安装 cosign

# macOS
brew install cosign

# Linux
curl -O https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign

# Windows (使用 winget)
winget install sigstore.cosign

# 验证安装
cosign version

生成密钥对

# 生成密钥对(会提示输入密码)
cosign generate-key-pair

# 输出文件:
# cosign.key - 私钥(妥善保管)
# cosign.pub - 公钥(用于验证)

# 使用 KMS 生成密钥
cosign generate-key-pair k8s://namespace/secret-name
cosign generate-key-pair aws-kms://alias/key-name
cosign generate-key-pair gcp-kms://projects/...

签名镜像

# 使用本地密钥签名
cosign sign --key cosign.key docker.io/myorg/myapp:latest

# Keyless 签名(使用 OIDC 身份验证,推荐)
cosign sign docker.io/myorg/myapp:latest
# 会打开浏览器进行身份验证

# 添加注释
cosign sign -a author=devops -a version=1.0 docker.io/myorg/myapp:latest

# 使用 KMS 密钥签名
cosign sign --key aws-kms://alias/my-key docker.io/myorg/myapp:latest
cosign sign --key gcp-kms://projects/my-project/locations/global/keyRings/my-ring/cryptoKeys/my-key docker.io/myorg/myapp:latest

验证镜像

# 使用公钥验证
cosign verify --key cosign.pub docker.io/myorg/myapp:latest

# Keyless 验证(验证 Sigstore 签名)
cosign verify docker.io/myorg/myapp:latest

# 验证特定注释
cosign verify -a author=devops --key cosign.pub docker.io/myorg/myapp:latest

# 验证多个签名者
cosign verify --key cosign.pub --key another.pub docker.io/myorg/myapp:latest

在 CI/CD 中使用

# GitHub Actions 示例
name: Sign and Push Image

on:
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and Push
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:latest

- name: Install cosign
uses: sigstore/cosign-installer@v3

- name: Sign Image
run: |
cosign sign --yes ghcr.io/${{ github.repository }}:latest

- name: Verify Signature
run: |
cosign verify ghcr.io/${{ github.repository }}:latest

策略执行

在 Kubernetes 中可以使用策略控制器强制验证镜像签名:

# Kyverno 策略示例
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: enforce
background: false
rules:
- name: verify-signature
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "docker.io/myorg/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

Notary 项目

Notary 是 Docker Content Trust (DCT) 的底层实现,基于 The Update Framework (TUF) 规范。虽然 DCT 已从 Docker CLI 移除,但 Notary 项目仍在活跃开发中:

# 安装 Notary CLI
# 从 https://github.com/theupdateframework/notary/releases 下载

# 初始化信任(首次使用)
notary init docker.io/myorg/myapp

# 添加签名密钥
notary key generate docker.io/myorg/myapp

# 发布签名
notary publish docker.io/myorg/myapp

# 验证镜像
notary verify docker.io/myorg/myapp latest

cosign vs Notary 对比

特性cosignNotary
签名存储镜像仓库(OCI Artifact)独立服务器
Keyless 签名支持(Sigstore OIDC)不支持
密钥管理本地文件、KMS、K8s Secret本地文件、YubiKey
透明日志内置 Rekor
易用性简单较复杂
推荐场景新项目、CI/CD已有 DCT 遗留系统

镜像扫描

使用 Docker Scout 扫描漏洞

# 快速扫描
docker scout quickview nginx:latest

# 详细扫描
docker scout cves nginx:latest

# 查看修复建议
docker scout recommendations nginx:latest

使用 Trivy 扫描

# 安装 Trivy
brew install trivy # macOS

# 扫描镜像
trivy image nginx:latest

# 输出到文件
trivy image --output results.json nginx:latest

最小化基础镜像

# 不推荐:使用完整镜像
FROM ubuntu:22.04

# 推荐:使用精简镜像
FROM ubuntu:22.04-minimal

# 更推荐:使用 Alpine
FROM alpine:3.18

# 生产环境:使用 distroless
FROM gcr.io/distroless/static-debian12

镜像大小对比

基础镜像大小安全性
ubuntu:22.04~77MB
debian:slim~74MB
alpine:3.18~5MB
distroless/static~2MB最高

网络安全

网络隔离

# 使用内部网络(无法访问外网)
docker network create --internal internal-net

# 运行容器
docker run -d --network internal-net myapp

# 分离前端和后端网络
docker network create frontend
docker network create --internal backend

docker run -d --name web --network frontend nginx
docker run -d --name api --network frontend --network backend myapi
docker run -d --name db --network backend mysql

端口安全

# 仅绑定到本地接口
docker run -d -p 127.0.0.1:8080:80 nginx

# 使用反向代理暴露服务
# nginx.conf
server {
listen 80;
location / {
proxy_pass http://127.0.0.1:8080;
}
}

限制网络能力

# 禁止修改网络配置
docker run --cap-drop=NET_ADMIN --cap-drop=NET_RAW myapp

# 使用只读文件系统防止修改
docker run --read-only --tmpfs /tmp myapp

敏感数据管理

Docker Secrets

Docker Secrets 用于安全地管理敏感数据:

在 Swarm 中使用

# 创建 secret
echo "my_secret_password" | docker secret create db_password -

# 在服务中使用
docker service create \
--name app \
--secret db_password \
-e DB_PASSWORD_FILE=/run/secrets/db_password \
myapp

在 Docker Compose 中使用

services:
app:
image: myapp
secrets:
- db_password
environment:
DB_PASSWORD_FILE: /run/secrets/db_password

secrets:
db_password:
file: ./secrets/db_password.txt

避免硬编码敏感信息

不推荐

# 不要这样做
services:
app:
environment:
- DATABASE_PASSWORD=secret123 # 明文密码

推荐方式

# 使用环境变量文件
services:
app:
env_file:
- .env # 添加到 .gitignore

# 或使用 Docker Secrets
services:
app:
secrets:
- db_password

安全配置清单

Dockerfile 安全清单

# ✅ 使用最小化基础镜像
FROM alpine:3.18

# ✅ 指定镜像版本和摘要
# FROM alpine:3.18@sha256:...

# ✅ 创建非 root 用户
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup

# ✅ 只安装必要的包
RUN apk add --no-cache nodejs npm

# ✅ 设置适当的文件权限
COPY --chown=appuser:appgroup . /app

# ✅ 切换到非 root 用户
USER appuser

# ✅ 声明健康检查
HEALTHCHECK --interval=30s CMD wget -q --spider http://localhost:3000/health

# ✅ 不暴露不必要的端口
EXPOSE 3000

# ✅ 使用 exec 格式
CMD ["node", "server.js"]

运行时安全清单

docker run -d \
--name myapp \
--user 1001:1001 \ # 非 root 用户
--read-only \ # 只读文件系统
--cap-drop=ALL \ # 删除所有能力
--cap-add=NET_BIND_SERVICE \ # 只添加需要的能力
--security-opt=no-new-privileges \ # 禁止提权
--security-opt seccomp=unconfined \ # 使用 seccomp
--memory="512m" \ # 内存限制
--cpus="0.5" \ # CPU 限制
--pids-limit=100 \ # 进程数限制
--tmpfs /tmp \ # 临时目录
myapp:latest

Docker Compose 安全配置

services:
app:
image: myapp:latest
user: "1001:1001"
read_only: true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
tmpfs:
- /tmp
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3

安全监控

日志审计

# 启用 Docker 审计日志
# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}

# 查看容器日志
docker logs myapp

# 使用 journald 日志驱动
docker run -d --log-driver=journald myapp

运行时监控

# 查看容器资源使用
docker stats

# 查看容器进程
docker top myapp

# 查看容器事件
docker events --filter container=myapp

# 使用 Falco 监控异常行为
falco -r /etc/falco/falco_rules.yaml

安全检查命令汇总

# 检查 Docker 版本和配置
docker version
docker info

# 检查容器安全配置
docker inspect --format='{{.HostConfig.SecurityOpt}}' myapp
docker inspect --format='{{.HostConfig.CapAdd}}' myapp
docker inspect --format='{{.HostConfig.CapDrop}}' myapp

# 检查容器用户
docker inspect --format='{{.Config.User}}' myapp

# 检查镜像漏洞
docker scout quickview myapp:latest

# 检查容器进程
docker exec myapp ps aux

# 检查容器网络
docker network inspect bridge

参考资源

小结

Docker 安全涉及多个层面:

  • 内核安全机制:命名空间隔离、Cgroups 资源限制、Capabilities 权限控制
  • 非 root 用户运行:Dockerfile 中创建用户,运行时指定用户
  • Seccomp 和 AppArmor/SELinux:系统调用过滤和强制访问控制
  • 守护进程安全:TLS 加密通信、Rootless 模式
  • 镜像安全:使用可信镜像、漏洞扫描、最小化基础镜像
  • 网络安全:网络隔离、端口绑定限制
  • 敏感数据管理:Docker Secrets 安全存储

练习

  1. 创建一个使用非 root 用户的 Dockerfile
  2. 配置容器只保留必要的 Capabilities
  3. 使用 Docker Scout 扫描镜像漏洞
  4. 使用 cosign 验证镜像签名
  5. 设计一个包含前后端分离的安全网络架构