工程实践 Docker

Docker 容器安全加固:银行生产环境实战

从非 root 用户、Linux capabilities、seccomp 到镜像安全扫描,详解银行生产环境 Docker 容器安全加固的完整方案与落地实践。

发布于 2026/03/21 更新于 2026/03/21 1 分钟

“容器安全不是部署之后才考虑的事——从 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 银行级部署实战