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