深色模式
容器安全基础:镜像与运行时加固
摘要:容器不是天然安全的边界。本文从镜像(瘦身、扫描、签名)和运行时(非 root、只读根、降权、资源限制)两个方向给出可复制的加固项。
适用环境
bash
cat /etc/os-release
docker --version || podman --version
command -v trivy && trivy --version
command -v docker && docker info --format '{{.SecurityOptions}}'1
2
3
4
2
3
4
操作步骤
1. 镜像瘦身:减小攻击面
dockerfile
# 用 distroless 或 alpine 作为运行时基础镜像
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/bin/app /app
USER 65532:65532
ENTRYPOINT ["/app"]1
2
3
4
5
2
3
4
5
多阶段构建避免把编译工具链打进运行时镜像:
dockerfile
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/app
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app /app
USER 65532:65532
ENTRYPOINT ["/app"]1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
bash
docker images --format '{{.Repository}}:{{.Tag}}\t{{.Size}}' | sort -k2 -h | tail -101
2. 非 root 运行
bash
# 构建时指定
docker build --build-arg USERID=10001 -t myapp:1.0 .
# 运行时覆盖
docker run --user 10001:10001 myapp:1.0 id
docker run --user 10001:10001 myapp:1.0 sh -c 'id -u'1
2
3
4
5
2
3
4
5
验证进程身份:
bash
docker exec <cid> id
ps -o user,pid,cmd -p $(docker inspect -f '{{.State.Pid}}' <cid>)1
2
2
3. 只读根文件系统 + 可写目录单独挂
bash
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \
-v appdata:/var/lib/myapp myapp:1.01
2
2
--read-only 会让写临时文件的程序直接崩
先观察应用写了哪些路径,用 --tmpfs 或 volume 单独放行,不要直接放弃只读根。
4. 能力降权与禁止提权
bash
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
--security-opt seccomp=/etc/docker/seccomp-default.json \
myapp:1.01
2
3
4
2
3
4
需要检查的能力白名单(默认 Docker 已给一批,逐个确认):
bash
capsh --print | grep "Current:"
docker run --rm myapp:1.0 sh -c 'grep Cap /proc/self/status'1
2
2
5. 资源限制:防 DoS 与挖矿
bash
docker run --memory=512m --memory-swap=512m --cpus=1 \
--pids-limit=200 --ulimit nofile=1024:1024 myapp:1.01
2
2
6. 镜像扫描与签名
bash
trivy image --severity HIGH,CRITICAL myapp:1.0
trivy image --exit-code 1 --severity CRITICAL myapp:1.0 # 流水线门禁1
2
2
签名(cosign):
bash
cosign generate-key-pair
cosign sign --key cosign.key registry.example.com/myapp:1.0
cosign verify --key cosign.pub registry.example.com/myapp:1.01
2
3
2
3
7. 不要用特权容器与宿主挂载
bash
# 排查现存危险容器
docker ps --format '{{.Names}}\t{{.Image}}' | while read -r n i; do
docker inspect "$n" --format '{{.Name}} privileged={{.HostConfig.Privileged}} pid={{.HostConfig.PidMode}}' 2>/dev/null
done
docker ps -q | xargs -r docker inspect --format '{{.Name}}: {{range .Mounts}}{{.Source}} {{end}}' \
| grep -E ' /(etc|var/run/docker.sock|proc|sys) '1
2
3
4
5
6
2
3
4
5
6
挂载 /var/run/docker.sock 等于把宿主机 root 交给容器
容器内可创建特权容器逃逸。任何情况下都不要挂载 docker.sock,改用 Docker Socket Proxy 或 rootless 方案。
8. 运行时检测
bash
# Falco 检测容器内 shell / 敏感文件访问
falco --validate /etc/falco/falco_rules.yaml && systemctl restart falco
journalctl -u falco -f | grep -i container1
2
3
2
3
验证
bash
docker run --rm --user 10001:10001 myapp:1.0 id -u # 应非 0
docker inspect myapp:1.0 --format '{{.Config.User}}'
docker run --rm --read-only myapp:1.0 sh -c 'touch /x' 2>&1 | head -1 # 应失败
trivy image --severity CRITICAL --exit-code 1 myapp:1.0; echo "exit=$?"
docker ps -q | xargs -r docker inspect --format '{{.HostConfig.Privileged}}' | sort -u # 应只有 false1
2
3
4
5
2
3
4
5
判定标准:无 root 容器;无 privileged 容器;无 docker.sock 挂载;CRITICAL 漏洞为 0;镜像已签名。
常见坑
为了方便直接 --privileged
特权容器几乎等于宿主机 root,容器逃逸一步到位。需要设备时用 --device 精确授予。
以 root 构建并以 root 运行
镜像里 UID 0 且宿主机未开 userns-remap,容器逃逸后直接拿到宿主机 root。
镜像 tag 用 latest,无法追溯与回滚
必须打不可变 tag(git sha 或语义化版本),并开启镜像摘要(digest)校验。
只构建时扫描,运行时不管
容器运行后被 docker exec 装了包、下了挖矿程序,构建期扫描看不到。需要运行时扫描 + Falco。
环境变量传密钥
docker inspect 和 /proc/<pid>/environ 都能看到。改用 secret 挂载或密钥中心注入。