“容器安全不是部署之后才考虑的事——从 Dockerfile 编写开始,安全就已经在每一步被决定或破坏。“
前言
银行系统的 Docker 容器安全比普通互联网应用更严格:
- 监管要求:PCI-DSS、SWIFT CSP、当地金融监管机构对容器运行环境有明确要求
- 数据敏感性:客户账户信息、交易数据不能有任何泄露风险
- 审计要求:所有容器操作必须可追溯
- 最小权限原则:容器只应获得完成其功能所需的最小权限
1. 容器逃逸风险矩阵
容器安全事件分类:
┌────────────────┬──────────────────────────────────────┐
│ 风险类型 │ 说明 │
├────────────────┼──────────────────────────────────────┤
│ 特权容器 │ 以 --privileged 运行,可访问宿主机 │
│ 共享宿主机内核 │ 容器共享宿主机内核,内核漏洞可逃逸 │
│ 挂载敏感目录 │ 挂载 /var/run/docker.sock、/ 等 │
│ 不安全的能力 │ CAP_SYS_ADMIN、CAP_NET_RAW 等 │
│ 明文密钥存储 │ 密钥写在 Dockerfile 或环境变量中 │
│ 镜像漏洞 │ 使用含有已知 CVE 的基础镜像 │
│ 横向移动 │ 攻破一个容器后攻入其他容器 │
└────────────────┴──────────────────────────────────────┘
2. Dockerfile 安全编写规范
2.1 禁止使用 root 用户运行
# ❌ 危险:默认以 root 用户运行
FROM openjdk:21-slim
CMD ["java", "-jar", "/app.jar"]
# ✅ 安全:创建非 root 用户并切换
FROM eclipse-temurin:21-jre-alpine
# 创建应用用户(UID 固定,便于运维审计)
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# 只 COPY 文件到 appuser 可读的目录
COPY --chown=appuser:appgroup /target/app.jar /app/app.jar
WORKDIR /app
# 切换到非 root 用户
USER appuser
# 如果必须绑定特权端口(<1024),用 sysctl 或跳过检查
# 业务容器永远不需要绑定 <1024 端口
CMD ["java", "-jar", "/app/app.jar"]
2.2 最小化基础镜像
# ❌ 不推荐:系统完整镜像(~800MB,包含大量不必要的工具)
FROM ubuntu:22.04
RUN apt-get install -y curl wget vim net-tools strace ...
# 攻击面巨大
# ✅ 推荐:专门的应用运行时镜像
FROM eclipse-temurin:21-jre-alpine # ~180MB,仅含 JRE
# 或者 distroless(Google 出品,无 shell,无包管理器)
FROM gcr.io/distroless/java17-debian11 # ~120MB
# ❌ 不推荐:latest 标签
FROM openjdk:latest # latest = 未知版本 = 无法审计 CVE
# ✅ 推荐:固定版本,优先使用 Alpine 或 distroless
FROM eclipse-temurin:21.0.2_12-jre-alpine@sha256:abc123...
2.3 保护敏感信息
# ❌ 危险:密钥、密码写在 Dockerfile 中
ENV DB_PASSWORD=prod_secret_password
ARG API_KEY=sk-prod-key
# ❌ 危险:敏感文件被 COPY 进镜像(即使后来删除,层历史中仍存在)
COPY . /app
RUN rm -f /app/.env # 层历史中仍可见
# ✅ 安全:使用 Kubernetes Secret / Vault 挂载,不进镜像
# 运行时通过 volume 挂载或环境变量注入(K8s 自动管理)
# docker-compose 中用 ${DB_PASSWORD} 从 .env 文件读取(.env 不进 git)
# ✅ 推荐:多阶段构建,敏感文件不进最终镜像
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests
# Stage 2: Runtime(干净的运行时镜像,无源码,无构建工具)
FROM eclipse-temurin:21-jre-alpine AS runtime
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
COPY --from=builder /build/target/app.jar /app/app.jar
WORKDIR /app
USER appuser
CMD ["java", "-jar", "/app/app.jar"]
3. Linux Capabilities:最小权限原则
3.1 理解 Capabilities
Linux 的 root 权限被拆分成多个 capabilities,容器默认只拥有部分 capabilities:
# 查看容器当前 capabilities
docker run --rm alpine:latest capsh --print
# 关键 capabilities 说明:
# CAP_NET_BIND_SERVICE:绑定 <1024 端口(业务容器不需要)
# CAP_SYS_ADMIN:系统管理权限(危险!等价于 root)
# CAP_SYS_MODULE:加载内核模块(危险!)
# CAP_SYS_RAW_IO:裸磁盘读写(危险!)
# CAP_NET_RAW:原始数据包(可被用于网络攻击)
# CAP_NET_ADMIN:网络管理(仅网络组件需要)
3.2 生产环境推荐配置
# ✅ 推荐:移除所有危险 capabilities
docker run --rm \
--cap-drop ALL \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
mybank/payment-service:latest
# 如果确实需要某些 capabilities(仅网络/日志等)
docker run --rm \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--cap-add CHOWN \
--cap-add SETGID \
--cap-add SETUID \
mybank/audit-service:latest
# 禁止新特权(默认开启,防止容器升级为特权容器)
docker run --rm --security-opt=no-new-privileges:true ...
# 禁止特权容器(防止 --privileged 标志被滥用)
# 在 /etc/docker/daemon.json 中配置:
{
"icc": false,
"live-restore": true,
"no-new-privileges": true,
"default-ulimits": {
"nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 }
},
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
4. Seccomp:限制系统调用
Seccomp(Secure Computing Mode)限制容器可执行的系统调用——这是防止容器逃逸的最后一道防线:
// seccomp-profile.json(自定义配置文件)
{
"defaultAction": "SCMP_ACT_ERRNO",
" architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_AARCH64"
],
"syscalls": [
{
"names": [
"accept", "accept4", "bind", "connect",
"epoll_create", "epoll_create1", "epoll_ctl",
"epoll_wait", "eventfd2", "listen",
"read", "readv", "recvfrom", "recvmsg",
"send", "sendmsg", "sendto", "shutdown",
"socket", "write", "writev"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": ["brk", "mmap", "mprotect", "munmap"],
"action": "SCMP_ACT_ALLOW"
}
]
}
# 应用自定义 seccomp 配置
docker run --rm \
--security-opt seccomp=seccomp-profile.json \
mybank/payment-service:latest
# 或者使用 Docker 默认的 seccomp 配置(禁止约 44 个危险系统调用)
docker run --rm \
--security-opt seccomp=docker/default \
mybank/payment-service:latest
# 不使用 seccomp(最不安全,但兼容性好)
docker run --rm --security-opt seccomp=unconfined ...
银行 PCI-DSS 要求:生产环境的支付容器必须启用 seccomp 配置,限制能访问的文件描述符和系统调用。
5. 非 root 文件系统与只读根目录
# ✅ 推荐:只读根文件系统 + 可写 tmpfs
docker run --rm \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=64m \
--tmpfs /var/log:rw,noexec,nosuid,size=32m \
--tmpfs /app/logs:rw,noexec,nosuid,size=128m \
-v /var/data:/var/data:rw \
mybank/payment-service:latest
# 说明:
# --read-only:根文件系统只读,防止恶意写入
# --tmpfs /tmp:临时文件在内存中,不落盘,不持久化
# -v /var/data:需要持久化的数据通过 volume 挂载
6. 镜像安全扫描
6.1 构建时扫描(CI/CD 集成)
# .gitlab-ci.yml 或 GitHub Actions
stages:
- build
- scan
- push
docker_scan:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--ignore-unfixed \
--security-checks vuln \
$IMAGE_NAME:$CI_COMMIT_SHA
variables:
TRIVY_SEVERITY: "HIGH,CRITICAL"
TRIVY_EXIT_CODE: "1" # 发现高危漏洞则失败
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
6.2 Docker Scout(Docker Desktop 内置)
# 安装 Docker Scout
docker scout install
# 分析镜像漏洞
docker scout cves mybank/payment-service:latest
docker scout recommendations mybank/payment-service:latest
# 在 CI 中生成报告
docker scout compare \
--to mybank/payment-service:previous-tag \
mybank/payment-service:latest
6.3 Trivy 定期扫描 CronJob
# Kubernetes 中部署定时镜像扫描
apiVersion: batch/v1
kind: CronJob
metadata:
name: image-vulnerability-scan
namespace: security
spec:
schedule: "0 3 * * *" # 每天凌晨 3 点
jobTemplate:
spec:
template:
spec:
serviceAccountName: image-scanner
containers:
- name: trivy
image: aquasec/trivy:latest
args:
- "--cache-dir"
- "/var/lib/trivy"
- "image"
- "--severity"
- "CRITICAL,HIGH"
- "--exit-code"
- "1"
- "--ignore-unfixed"
- "mybank/payment-service:latest"
env:
- name: TRIVY_DB_REPOSITORY
value: "ghcr.io/aquasecurity/trivy-db:multiarch"
7. Docker Bench Security:自动合规检查
# 运行 Docker CIS Benchmark 检查
docker run --rm -it \
--label=label_type=Docker_Benchmark \
--cap-drop ALL \
-v /var/lib/docker:/var/lib/docker \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
aquasec/docker-bench-for-security:latest
# 常见检查项:
# [PASS] 1.1 - 宿主机上的 Docker 版本是否为最新
# [PASS] 1.2 - Docker socket 不应挂载进容器
# [FAIL] 2.1 - 容器的 Linux capabilities 应该被限制
# [PASS] 4.1 - 镜像应定期扫描漏洞
# [WARN] 5.1 - 应使用 USER 指令切换到非 root 用户
8. 完整 Dockerfile 安全检查清单
Dockerfile 安全检查清单(部署前必查):
基础镜像
[ ] 使用最小化镜像(Alpine/distroless/jlink)
[ ] 固定镜像版本(不用 latest)
[ ] 基于镜像摘要(@sha256:xxx)而非标签
[ ] 镜像通过漏洞扫描(无 HIGH/CRITICAL CVE)
用户权限
[ ] 使用 USER 切换到非 root 用户
[ ] 用户 UID 固定(1001),便于审计
[ ] 不使用 --privileged 标志
敏感信息
[ ] 不在 Dockerfile 中硬编码密钥/密码
[ ] 不 COPY .env 文件
[ ] 使用多阶段构建,源码不进最终镜像
运行时安全
[ ] 根文件系统只读(--read-only)
[ ] 所有 capabilities 被移除(--cap-drop ALL)
[ ] 启用 seccomp 限制
[ ] tmpfs 用于临时文件
[ ] 日志通过标准输出(Docker 日志驱动收集)
审计
[ ] 记录谁在何时构建/部署了哪个镜像
[ ] 镜像标签包含 Git commit hash
[ ] CI/CD 中集成镜像扫描
相关阅读:Docker 快速入门完全指南 · Kubernetes 快速入门完全指南 · GitOps ArgoCD 银行级部署实战