容器安全的第一原则:永远不要在容器内以 root 身份运行进程。Docker Rootless 模式彻底移除了 Docker daemon 的 root 依赖,配合 Linux内核安全机制,能构建纵深防御体系。
Linux Capabilities 详解
传统 Unix 权限模型只有 root 和普通用户两档。Linux capabilities 将 root 的特权拆分为 40+ 个独立单元,容器可以按需开启最小权限集:
CAP_CHOWN # 修改文件属主
CAP_SETUID # 修改进程 UID
CAP_NET_BIND_SERVICE # 绑定 <1024 端口
CAP_SYS_ADMIN # 系统管理(危险,容器应避免)
CAP_SYS_PTRACE # 进程调试(容器逃逸常见入口)
查看容器进程拥有的 capabilities:
# 在宿主机上查看
pid=$(docker inspect -f '{{.State.Pid}}' mycontainer)
cat /proc/$pid/status | grep Cap
# Debian 容器内用 capsh 查看
docker run --rm -it debian:bookworm bash -c "apt-get update && apt-get install -y libcap2-bin && capsh --print"
Docker 默认使用 --cap-drop 策略,移除了大部分危险 capabilities,仅保留一个白名单。
User Namespaces 与 Rootless
User Namespace 将容器内的 UID/GID 映射到宿主机上的不同 UID/GID:
容器内: UID 0 (root) → 宿主机: UID 100000
容器内: UID 1 → 宿主机: UID 100001
容器内: UID 1000 → 宿主机: UID 100999
这个映射意味着:即使容器内进程以 root 运行,在宿主机看来它只是一个普通用户。一旦容器逃逸,攻击者获得的权限极低。
Docker Rootless 的安装方式:
# 方式一:官方脚本安装
curl -fsSL https://get.docker.com/rootless | sh
# 方式二:手动安装(系统已有 dockerd 时)
dockerd-rootless-setuptool.sh install
# 配置 systemd 用户级服务
systemctl --user enable docker
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
Rootless 模式的核心限制:
| 功能 | Rootless 支持状态 | 原因 |
|---|---|---|
| Privileged 容器 | 不支持 | 需要宿主机 root |
Host networking (--net=host) | 不支持 | 需要 net admin |
| Overlay2 on XFS | 受限 | 需要 xfs project quota |
| AppArmor | 受限 | 非 root 无法加载 profile |
| 设备挂载 | 受限 | 需要 CAP_MKNOD |
seccomp / AppArmor / SELinux 对比
这三者是 Linux 的强制访问控制(MAC)层,从不同维度限制容器行为:
| 机制 | 作用域 | 粒度 | 配置方式 |
|---|---|---|---|
| seccomp | 系统调用 | 精确到 syscall | JSON profile |
| AppArmor | 文件/网络 | 路径和 capability | profile 文件 |
| SELinux | 进程/文件标签 | 类型强制(TE) | policy 模块 |
Docker 默认启用 seccomp 过滤,禁用了 44 个危险系统调用:
# 查看默认 seccomp profile
cat /etc/docker/seccomp/default.json | jq '.syscalls[].names'
# 禁用 seccomp(绝不在生产使用)
docker run --security-opt seccomp=unconfined nginx
# 使用自定义 seccomp
docker run --security-opt seccomp=my-seccomp.json nginx
在 Kubernetes 中,seccomp 通过 Pod Security 标准管理:
apiVersion: v1
kind: Pod
metadata:
annotations:
seccomp.security.alpha.kubernetes.io/pod: runtime/default
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
运行时安全工具:Falco 与 sysdig
静态漏洞扫描无法捕捉运行时异常行为。Falco 和 sysdig 通过 eBPF 内核探针监控系统调用:
# Falco 规则示例:检测意外 shell 启动
- rule: Unexpected Shell Spawn
desc: Detect shell execution inside a container
condition: spawned_process and shell_procs and container
output: >
Shell spawned in container
user=%user.name parent=%proc.pname cmdline=%proc.cmdline
priority: WARNING
Falco 检测的典型威胁场景:
| 事件类型 | 示例 | 严重程度 |
|---|---|---|
| 权限提升 | 容器内执行 sudo/su | Critical |
| 后门通信 | 容器内启动反向 shell | Critical |
| 横向移动 | 容器内扫描内网端口 | High |
| 数据外泄 | 容器内读取 /etc/shadow | High |
| 异常进程 | 容器内运行矿工程序 | High |
部署 Falco 到 Kubernetes:
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
--set driver.kind=ebpf \
--set collectors.containerd.socket=/run/containerd/containerd.sock
CVE 扫描与 SBOM
供应链安全要求精确知道每个镜像包含的软件组成。软件物料清单(SBOM)是最佳实践:
# Syft 生成 SBOM
syft myapp:latest -o spdx-json > sbom.spdx.json
# Grype 基于 SBOM 扫描漏洞
grype sbom.spdx.json
# 将 SBOM 附加到镜像签名
syft myapp:latest -o cyclonedx-json | cosign attach sbom --sbom - myapp:latest
将 SBOM 和漏洞扫描纳入 CI/CD 门禁:建立不允许存在 Critical 漏洞的分支保护策略,同时启用 Dependabot 或 Renovate 自动追踪基础镜像和依赖包的更新。
运行时隔离增强:gVisor 与 Kata Containers
Rootless 模式虽然降低了特权风险,但仍共享宿主机内核。对于高安全要求的场景,需要更强的运行时隔离:
| 方案 | 架构 | 内核隔离 | 性能开销 | 适用场景 |
|---|---|---|---|---|
| runc (默认) | 共享宿主机内核 | 无 | 最低 | 通用场景 |
| gVisor | 用户态内核 (Sentry) | 独立系统调用拦截 | 中等 (syscall 翻译) | 不受信代码、SaaS |
| Kata Containers | 轻量级 VM (microVM) | 独立内核 | 较大 (启动 ~100ms) | 金融、政府合规 |
| Firecracker | AWS microVM | 独立内核 | 中低 | Serverless、边缘 |
# gVisor 安装与运行
curl -fsSL https://gvisor.dev/releases/release/20240701/x86_64/runsc \
-o /usr/local/bin/runsc && chmod +x /usr/local/bin/runsc
# 配置 Docker 使用 gVisor
runsc install
# 添加自签名镜像支持时指定 `runtimeArgs`
docker run --runtime=runsc --rm alpine echo "Hello from gVisor"
# Kata Containers 使用
kata-runtime install
docker run --runtime=kata-runtime --rm alpine uname -r
gVisor 通过 Sentry 中间层拦截容器发起的系统调用,以 Go 语言重写内核逻辑,即使容器内 root 逃逸,也只能接触到 Sentry 的模拟内核。Kata Containers 则为每个 Pod 启动一个轻量级虚拟机(如 QEMU microVM),真正的内核隔离级别。两者可以组合使用: Rootless Docker + gVisor/Kata 运行时,构建"纵深防御 + 物理隔离"的双保险。
镜像签名与内容信任
镜像分发链条上的篡改风险不容忽视。Docker Content Trust (DCT) 和 Notary v2 提供端到端签名验证:
# 启用 Docker Content Trust
export DOCKER_CONTENT_TRUST=1
docker push registry.mycompany.com/app:v1.2
# 自动签名,生成清单信任元数据
# Cosign (Sigstore) — 无密钥签名,基于 OIDC 身份
cosign sign --yes registry.mycompany.com/app:v1.2
cosign verify --certificate-identity=developer@company.com \
--certificate-oidc-issuer=https://accounts.google.com \
registry.mycompany.com/app:v1.2
# 签名镜像 + SBOM + SLSA 出处绑定
cosign attach sbom --sbom sbom.spdx.json registry.mycompany.com/app:v1.2
cosign attest --predicate slsa-provenance.json \
--type slsaprovenance registry.mycompany.com/app:v1.2
安全分发流程: 开发者签名 → CI 系统添加 SBOM/SLSA → 注册表验证签名 → 生产节点拉取时校验。任何中间人篡改都会在签名验证阶段暴露。
容器安全加固检查清单
| 层级 | 加固项 | 检查命令 / 方法 |
|---|---|---|
| 构建 | 最小基础镜像(scratch/distroless/alpine) | dive 分析镜像层 |
| 构建 | 多阶段构建分离编译与运行 | Dockerfile Lint |
| 镜像 | 无 CRITICAL/HIGH 漏洞 | trivy image、grype |
| 镜像 | 签名验证已启用 | cosign verify |
| 运行时 | 非 root 用户运行 | docker run --rm app id |
| 运行时 | User Namespace / Rootless 已启用 | `docker info |
| 运行时 | seccomp / AppArmor / SELinux 生效 | `/proc/self/status |
| 运行时 | capabilities 最小白名单 | capsh --print |
| 运行时 | 无特权容器 | docker ps --filter "label=privileged" |
| 网络 | 不暴露不必要的端口 | docker ps --format "{{.Ports}}" |
| 存储 | 敏感数据使用 tmpfs / Secrets Manager | docker inspect |
| 审计 | Falco 规则覆盖异常行为 | kubectl logs -l app=falco |
| 审计 | 镜像 SBOM 可溯源 | cosign download sbom |
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。