技术博客

Docker 网络安全:daemon TLS 加密、网络隔离策略与 Secrets 管理

系统讲解 Docker 网络安全的完整方案:Docker daemon 的 TLS 双向认证配置(防止未授权的 Docker API 访问)、容器网络隔离策略(内部网络、网络分段、禁用容器间通信 ICC)、Swarm Secret 与 Compose Secret 的安全管理(密文不落磁盘)、敏感数据的安全传递(环境变量的陷阱与替代方案)、Nginx 反向代理 TLS 终止,以及 Docker Socket 访问控制。

Docker安全TLS网络隔离Secrets密钥管理网络安全

Docker 的网络安全涉及两个维度:控制平面安全(谁能访问 Docker daemon API)和数据平面安全(容器网络如何隔离、敏感数据如何传递)。两者任一疏漏都可能导致严重的安全问题。


一、Docker Daemon TLS 双向认证

1.1 为什么需要 TLS

# Docker daemon 默认只监听 Unix Socket(安全)
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker ...

# 如果开启 TCP(危险!)
# /etc/docker/daemon.json: "hosts": ["tcp://0.0.0.0:2375"]
# 任何人都可以访问 Docker API:
curl http://DOCKER_HOST:2375/v1.41/containers/json
# 相当于给任意人 root 权限!

# 正确方案:开启 TCP 但要求 TLS 双向认证
# 客户端需要有效证书才能连接

1.2 生成 TLS 证书

#!/bin/bash
# gen-docker-certs.sh

SERVER_IP=192.168.1.100    # Docker daemon 所在服务器 IP
CERT_DIR=/etc/docker/certs

mkdir -p ${CERT_DIR}
cd ${CERT_DIR}

# 1. 生成 CA 根证书
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 3650 -key ca-key.pem \
    -out ca.pem \
    -subj "/C=CN/ST=Hubei/L=Wuhan/O=Company/CN=Docker CA"

# 2. 生成服务端证书(daemon 使用)
openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem \
    -out server.csr \
    -subj "/CN=docker-server"

# SAN 扩展(包含服务器 IP 和域名)
cat > server-extfile.cnf << EOF
subjectAltName = IP:${SERVER_IP},IP:127.0.0.1,DNS:localhost,DNS:docker.company.com
extendedKeyUsage = serverAuth
EOF

openssl x509 -req -days 365 \
    -in server.csr \
    -CA ca.pem -CAkey ca-key.pem \
    -CAcreateserial \
    -out server-cert.pem \
    -extfile server-extfile.cnf

# 3. 生成客户端证书(docker CLI 使用)
openssl genrsa -out client-key.pem 4096
openssl req -new -key client-key.pem \
    -out client.csr \
    -subj "/CN=docker-client"

echo "extendedKeyUsage = clientAuth" > client-extfile.cnf

openssl x509 -req -days 365 \
    -in client.csr \
    -CA ca.pem -CAkey ca-key.pem \
    -CAcreateserial \
    -out client-cert.pem \
    -extfile client-extfile.cnf

# 设置权限
chmod 0600 ca-key.pem server-key.pem client-key.pem
chmod 0644 ca.pem server-cert.pem client-cert.pem

echo "证书生成完成:"
ls -la ${CERT_DIR}/

1.3 配置 daemon 使用 TLS

// /etc/docker/daemon.json
{
  "hosts": [
    "unix:///var/run/docker.sock",   // 保留 Unix socket
    "tcp://0.0.0.0:2376"             // TLS 端口(2376 是约定的 TLS 端口)
  ],
  "tls": true,
  "tlsverify": true,                 // 要求客户端证书(双向 TLS)
  "tlscacert": "/etc/docker/certs/ca.pem",
  "tlscert": "/etc/docker/certs/server-cert.pem",
  "tlskey": "/etc/docker/certs/server-key.pem"
}
# 需要修改 systemd 服务,避免与 daemon.json 中的 hosts 冲突
mkdir -p /etc/systemd/system/docker.service.d/
cat > /etc/systemd/system/docker.service.d/override.conf << 'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
EOF

systemctl daemon-reload
systemctl restart docker

# 验证 TLS 连接
docker --tlsverify \
    --tlscacert=/etc/docker/certs/ca.pem \
    --tlscert=/etc/docker/certs/client-cert.pem \
    --tlskey=/etc/docker/certs/client-key.pem \
    -H tcp://192.168.1.100:2376 \
    info

# 客户端默认证书位置(放这里就不用每次指定)
mkdir -p ~/.docker
cp /etc/docker/certs/{ca,client-cert,client-key}.pem ~/.docker/
export DOCKER_HOST=tcp://192.168.1.100:2376
export DOCKER_TLS_VERIFY=1
docker info   # 自动使用 TLS 证书

二、容器网络隔离策略

2.1 禁用容器间通信(ICC)

// /etc/docker/daemon.json
{
  "icc": false    // 禁用默认 bridge 网络上的容器间通信
}
# compose.yml — 精细化网络隔离
# 前端不能直接访问数据库;API 才能访问数据库
services:
  nginx:
    networks:
      - frontend   # 只在前端网络

  api:
    networks:
      - frontend   # 可接收 nginx 的请求
      - backend    # 可访问数据库

  db:
    networks:
      - backend    # 只在后端网络

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true   # 内部网络,容器无法访问外网

2.2 网络分段(Zero Trust 容器网络)

# 多层网络隔离模型
services:
  # DMZ 层:只有 nginx 暴露到公网
  nginx:
    networks:
      - dmz
    ports:
      - "80:80"
      - "443:443"

  # 应用层:api 在 dmz 和 app 两个网络
  api:
    networks:
      - dmz     # 接收 nginx 转发的请求
      - app     # 调用 worker 和 cache

  # 服务层
  worker:
    networks:
      - app     # 只和 api 通信
  
  cache:
    image: redis:7-alpine
    networks:
      - app     # 只有 api 能访问
  
  # 数据层:数据库只在 data 网络,与 app 隔离
  db:
    networks:
      - data    # 只和 api 通信(api 也要在 data 网络)

  # api 需要额外加入 data 网络
  # api: networks: [dmz, app, data]

networks:
  dmz:
    # 允许外网访问(不设 internal)
  app:
    internal: true    # 禁止外网
  data:
    internal: true    # 禁止外网

2.3 iptables 额外隔离规则

# 限制特定容器访问宿主机敏感端口
# 场景:容器不应该能访问宿主机的 metadata API(云环境)

# 获取容器的 IP
CONTAINER_IP=$(docker inspect myapp \
    --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')

# 阻止访问宿主机的 169.254.169.254(云实例 metadata API)
iptables -I DOCKER-USER -s ${CONTAINER_IP} \
    -d 169.254.169.254 \
    -j DROP

# 永久化(重启后 iptables 规则会消失,用 iptables-persistent)
apt-get install iptables-persistent
netfilter-persistent save

三、Docker Secrets 管理

3.1 环境变量的安全陷阱

# ❌ 错误:通过环境变量传递密码
docker run -d \
    -e MYSQL_PASSWORD=supersecret123 \
    myapp

# 风险:
# 1. docker inspect 可以看到所有环境变量(任何有 docker 权限的人)
docker inspect myapp --format '{{.Config.Env}}'
# [MYSQL_PASSWORD=supersecret123 ...]

# 2. 容器内 /proc/1/environ 可以读取
docker exec myapp cat /proc/1/environ | tr '\0' '\n' | grep MYSQL

# 3. docker history 看到 Dockerfile 中的 ENV 值
docker history --no-trunc myapp

# ✅ 更安全的替代方案(下面讲)

3.2 Docker Swarm Secret

# Docker Swarm Secret 特点:
# - 存储在 Raft 日志中(加密)
# - 只注入到需要的 service,挂载为 tmpfs(内存文件)
# - 容器停止后自动消失
# - docker inspect service 不会显示 secret 内容

# 创建 Secret
echo "super-secret-password" | docker secret create db_password -
# 或从文件创建
docker secret create ssl_cert ./server.crt

# 查看已有 Secret(不显示内容)
docker secret ls
# ID             NAME           CREATED        UPDATED
# xxx...         db_password    2 minutes ago  2 minutes ago

# 在 Swarm Service 中使用
docker service create \
    --name api \
    --secret db_password \      # 挂载到 /run/secrets/db_password
    myapp

# 应用读取方式(在容器内)
docker exec api cat /run/secrets/db_password
# super-secret-password

# 代码中读取 Secret
# Python
with open('/run/secrets/db_password') as f:
    DB_PASSWORD = f.read().strip()

# Stack 文件中使用
# stack.yml
services:
  api:
    image: myapp:v1
    secrets:
      - db_password
      - ssl_cert

secrets:
  db_password:
    external: true      # 使用已创建的 secret
  ssl_cert:
    file: ./server.crt  # 从文件创建(stack deploy 时自动创建)

3.3 Docker Compose Secret(本地开发)

# compose.yml
services:
  api:
    image: myapp:v1
    secrets:
      - db_password
      - jwt_secret
    environment:
      # 告诉应用从哪里读 secret(而不是直接传值)
      DB_PASSWORD_FILE: /run/secrets/db_password
      JWT_SECRET_FILE: /run/secrets/jwt_secret

secrets:
  db_password:
    file: ./secrets/db_password.txt   # 本地文件(.gitignore 排除)
  jwt_secret:
    environment: JWT_SECRET           # 从环境变量读取
# 为 Compose 创建 secrets 文件(不提交 git)
mkdir -p secrets
echo "localdevpassword" > secrets/db_password.txt
echo "secrets/" >> .gitignore

3.4 外部 Secret 管理工具集成

# 生产级别:使用 HashiCorp Vault 或云厂商 Secret Manager

# 方案1:Vault Agent Sidecar(Vault 注入 secret 到文件)
# 容器启动时,Vault Agent 先运行,将 secret 写入 tmpfs
# 主容器从文件读取 secret

# 方案2:云厂商 Secret Manager(阿里云/AWS/GCP)
# 使用 SDK 在应用启动时从 Secret Manager 获取
# import aliyunsdkcore
# secret = client.get_secret_value("prod/db/password")

# 方案3:entrypoint 脚本获取
# entrypoint.sh
#!/bin/sh
# 从 Vault 获取并写入环境变量(不落磁盘)
export DB_PASSWORD=$(vault kv get -field=password secret/db)
exec "$@"

四、Docker Socket 访问控制

# Docker Socket 是最危险的攻击面之一
# 有 socket 访问权限 = 有 root 权限

# 检查谁有 socket 访问权限
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker
getent group docker
# docker:x:999:ubuntu,jenkins,gitlab-runner

# 最小化 docker 组成员(只保留必需的)
# 移除不需要的用户
deluser jenkins docker
deluser someuser docker

# 监控 docker socket 的访问(auditd)
cat >> /etc/audit/rules.d/docker.rules << 'EOF'
-w /var/run/docker.sock -p rwxa -k docker-socket
EOF
auditctl -R /etc/audit/rules.d/docker.rules
ausearch -k docker-socket | tail -20

# Docker Socket Proxy(推荐 CI 场景)
# 只暴露 CI 需要的 API(只读)
docker run -d \
    --name docker-proxy \
    --restart unless-stopped \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -e CONTAINERS=1 \     # 允许读容器信息
    -e IMAGES=1 \         # 允许读镜像信息
    -e INFO=1 \           # 允许 docker info
    -e PING=1 \           # 允许 ping
    -e POST=0 \           # 禁止 POST(创建/删除操作)
    -e BUILD=0 \          # 禁止构建
    -e COMMIT=0 \         # 禁止 commit
    -e DELETE=0 \         # 禁止删除
    -p 127.0.0.1:2375:2375 \
    tecnativa/docker-socket-proxy

小结

Docker 网络安全的核心是最小暴露原则:daemon API 不对外暴露(或必须用 TLS)、容器网络按业务分层隔离(DMZ/App/Data)、敏感数据用 Secret 而不是环境变量传递。

最容易被忽视的问题是Docker Socket 的滥用:给 CI runner 挂载 /var/run/docker.sock 方便构建,实际上给了它宿主机 root 权限。解决方案是使用 Docker Socket Proxy 只暴露需要的 API,或者改用 DinD 完全隔离。

Secrets 管理是生产安全的基础工程。Swarm Secret 提供了简单可靠的秘密注入机制(tmpfs 挂载,进程退出即消失),大幅优于明文环境变量。规模较大时建议引入 Vault 等专业工具统一管理。