引言
容器技术的普及极大地提高了应用交付效率,但容器在安全方面的特殊性往往被忽视。传统的边界安全模型在容器环境下失效——容器共享宿主机内核,隔离性不如虚拟机,攻击者一旦突破容器隔离就可能威胁整个宿主机。
本文从容器全生命周期的三个关键环节——构建、分发、运行——逐一讲解安全措施。
镜像安全:从源头控制风险
选择合适的基础镜像
基础镜像的选择直接决定了攻击面的大小。以下镜像大小对比可以直观地说明问题:
| 镜像 | 大小 | 包含内容 | 建议 |
|---|---|---|---|
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 的效果。
镜像仓库安全
镜像仓库是容器供应链的关键节点。以下是镜像仓库的安全配置建议:
- 镜像不可变标签:禁止覆盖已存在的标签,确保部署的可追溯性
- 访问控制:基于角色的访问控制,最小权限原则
- 镜像清理策略:自动清理超过一定时间未被拉取的镜像,减少攻击面
- TLS 加密:仓库通信全部使用 TLS 加密
- 镜像漏洞扫描:仓库侧自动扫描,阻止漏洞镜像被拉取
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
总结
容器安全的核心理念是"纵深防御"——不在任何一个环节建立完美防线,而是在每个环节设置合理的控制点,让攻击者付出的成本指数级上升。
三个最重要的实践:
- 镜像层面:distroless 基础镜像 + 多阶段构建 + 漏洞扫描门禁
- 运行层面:Capabilities 最小化 + 只读文件系统 + 非 root 运行
- 监控层面:Falco 运行时检测 + 审计日志
从"能跑就行"到"安全运行",容器安全的每一步改进都是风险概率的降低。按上述清单逐项检查,你的容器化应用就能达到一个不错的基线水平。