容器安全从入门到实践:镜像、运行时与供应链防护

引言

容器技术的普及极大地提高了应用交付效率,但容器在安全方面的特殊性往往被忽视。传统的边界安全模型在容器环境下失效——容器共享宿主机内核,隔离性不如虚拟机,攻击者一旦突破容器隔离就可能威胁整个宿主机。

本文从容器全生命周期的三个关键环节——构建、分发、运行——逐一讲解安全措施。

镜像安全:从源头控制风险

选择合适的基础镜像

基础镜像的选择直接决定了攻击面的大小。以下镜像大小对比可以直观地说明问题:

镜像 大小 包含内容 建议
ubuntu:22.04 ~80MB 完整包管理器、工具链 开发环境
alpine:3.19 ~7MB musl libc + BusyBox 通用生产
gcr.io/distroless/base ~15MB glibc + 核心库 推荐生产
scratch 0B 空镜像 静态编译语言

最小化原则:生产环境优先使用 distroless 或 scratch 基础镜像。这些镜像不含 Shell、包管理器、编译器,攻击者即使通过应用漏洞进入容器,也无法执行常见的横向移动操作。

多阶段构建

多阶段构建既能保证镜像最小化,又不影响编译过程:

# 阶段一:编译
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server

# 阶段二:运行(最小镜像)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]

这个 Dockerfile 的最终镜像只有可执行文件 + 必需的运行时库,不包含任何多余的组件。

镜像漏洞扫描

在 CI/CD 中集成镜像扫描是最基本的安全门禁。Trivy 是一个广泛使用的开源扫描器,支持多种语言和操作系统组件:

# GitHub Actions 示例
- name: Scan image for vulnerabilities
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: 'ghcr.io/secshu/app:${{ github.sha }}'
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'

exit-code 设置为 1 后,CI/CD 流水线会在扫描到高危漏洞时自动阻断构建。这个门禁策略应该保守一些——先阻挡 CRITICAL 和 HIGH 级别的漏洞,待团队成熟后再逐渐收紧。

镜像签名

使用 cosign 对镜像进行签名,可以在部署时验证镜像的完整性和来源:

# 生成密钥对
cosign generate-key-pair

# 签名镜像
cosign sign --key cosign.key ghcr.io/secshu/app:v1.0.0

# 在部署时验证
cosign verify --key cosign.pub ghcr.io/secshu/app:v1.0.0

运行时安全:限制容器能力

Capabilities 最小化

Docker 默认赋予容器一组 capabilities,其中很多是普通应用不需要的。通过 --cap-drop--cap-add 可以精细控制:

# Kubernetes Pod SecurityContext
securityContext:
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE  # 允许绑定特权端口(如果确实需要)
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 10001

推荐策略:先 drop ALL,再按需添加。大多数 Go 应用只需要 NET_BIND_SERVICE,有些甚至什么都不需要。

Seccomp 系统调用过滤

Seccomp 是 Linux 内核提供的系统调用过滤机制。Docker 的默认 seccomp 配置已经禁用了约 44 个高危系统调用,但在 K8s 中需要显式启用:

securityContext:
  seccompProfile:
    type: RuntimeDefault

如果需要更严格的策略,可以自定义 seccomp profile:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "openat", "close", "mmap", "munmap",
                "brk", "fstat", "nanosleep", "exit_group"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

上面的策略只放行了最基本的系统调用——如果应用只需要处理网络 I/O 和内存分配,这已经足够了。

文件系统只读

将根文件系统设置为只读是防止攻击者植入恶意文件的有效手段:

securityContext:
  readOnlyRootFilesystem: true

如果应用需要写入临时文件,可以单独挂载 emptyDir:

volumeMounts:
  - name: tmp
    mountPath: /tmp
volumes:
  - name: tmp
    emptyDir: {}

Rootless 容器

Rootless 模式让容器以普通用户身份运行,即使容器内发生逃逸,攻击者也拿不到宿主机的 root 权限。Podman 原生支持 Rootless 模式,Docker 在 20.10+ 版本中也提供了 Rootless 支持。

在 Kubernetes 中,可以通过 runAsUser: 10001 和 Pod Security Standard 的 Restricted 策略来近似实现 Rootless 的效果。

镜像仓库安全

镜像仓库是容器供应链的关键节点。以下是镜像仓库的安全配置建议:

  1. 镜像不可变标签:禁止覆盖已存在的标签,确保部署的可追溯性
  2. 访问控制:基于角色的访问控制,最小权限原则
  3. 镜像清理策略:自动清理超过一定时间未被拉取的镜像,减少攻击面
  4. TLS 加密:仓库通信全部使用 TLS 加密
  5. 镜像漏洞扫描:仓库侧自动扫描,阻止漏洞镜像被拉取

Harbor 是一个企业级的镜像仓库,内置了漏洞扫描、镜像复制、Robots 账号等功能,是生产环境的推荐选择。

运行时监控与异常检测

即使做了上述所有加固,仍然需要运行时监控作为最后一道防线。Falco 的常用规则包括:

# 容器内启动 Shell
- rule: Terminal Shell in Container
  desc: A shell was spawned in a container
  condition: >
    spawned_process and container
    and shell_procs
    and not user_expected_terminal_shell_in_container
  output: >
    Shell spawned in container (user=%user.name %container.info shell=%proc.name parent=%proc.pname)
  priority: WARNING

# 敏感路径读取
- rule: Read Sensitive File
  desc: An attempt to read sensitive file
  condition: >
    open_read and container
    and fd.name startswith /etc/kubernetes
  priority: CRITICAL

总结

容器安全的核心理念是"纵深防御"——不在任何一个环节建立完美防线,而是在每个环节设置合理的控制点,让攻击者付出的成本指数级上升。

三个最重要的实践:

  1. 镜像层面:distroless 基础镜像 + 多阶段构建 + 漏洞扫描门禁
  2. 运行层面:Capabilities 最小化 + 只读文件系统 + 非 root 运行
  3. 监控层面:Falco 运行时检测 + 审计日志

从"能跑就行"到"安全运行",容器安全的每一步改进都是风险概率的降低。按上述清单逐项检查,你的容器化应用就能达到一个不错的基线水平。