容器镜像安全加固,先从一个可运行镜像开始

$ trivy image --severity HIGH,CRITICAL web-app:v1.4.2
web-app:v1.4.2 (debian 11.6)
============================
Total: 42 (HIGH: 35, CRITICAL: 7)

┌────────────────┬────────────────┬──────────┬───────────────────┬---------------+───────────────────────────────────────────┐
│    Library     │ Vulnerability  │ Severity │ Installed Version │ Fixed Version │                   Title                   │
│├────────────────┼────────────────┼──────────┼───────────────────┼---------------+───────────────────────────────────────────┤
│ libssl1.1      │ CVE-2024-2511  │ CRITICAL │ 1.1.1n-0+deb11u4  │ 1.1.1n-0+deb11u5│ OpenSSL session cache memory leak        │
│ bash           │ CVE-2024-38428 │ HIGH     │ 5.1-2+b3          │               │ Out-of-bounds read in parser              │
└────────────────┴────────────────┴──────────┴───────────────────┴---------------+───────────────────────────────────────────┤

上述 Trivy 扫描终端输出,展现了安全合规审计中常见的改进通知。镜像中打包了非必要的操作系统组件,包含未使用的 bash、curl、apt 等工具,同时引入了 High/Critical 级别的安全漏洞。

安全加固不应只把基础镜像从 ubuntu:latest 换成 ubuntu:22.04。是否采用精简镜像、非 root 账号和最小化 Capabilities,要结合应用依赖、运行权限和扫描结果逐项确认。

1. 从 Trivy 扫描报告入手:剥离无用软件包与多阶段构建精简。

多数标准基础镜像(如 python:3.11node:18)默认基于标准的 Debian 或 Ubuntu 构建,包含完整的包管理器、GCC 编译工具链以及数十个系统动态链接库。攻击者若利用 Web 漏洞(例如远程代码执行 RCE)注入指令,即可使用容器内的 curlwget 从外部下载恶意可执行文件。

加固的初始步骤是采用多阶段构建(Multi-stage Build),将“编译构建环境”与“运行时环境”进行完全隔离。在构建阶段使用功能完备的镜像,在最终交付阶段切换为 distroless 或极简 alpine 基础镜像。

改造前后的镜像对比指标:

  • 改造前:基于 python:3.11 基础镜像,体积为 1.02GB,检测出 68 个高危漏洞。
  • 改造后:基于 gcr.io/distroless/python3-debian12 镜像,体积降至 52MB,高危漏洞数降为 0。

多阶段构建除了清理非必要软件包外,关键在于移除了容器内的 Shell 执行环境(如 /bin/sh/bin/bash),削弱了反向 Shell(Reverse Shell)攻击的执行条件。

2. 配置 Non-root 用户运行:文件权限与 Capabilities 最小化剥离。

默认配置下,Docker 容器内部进程以 root(UID 0)身份运行。即使 Docker 提供了 Namespace 隔离机制,一旦出现 Linux 内核漏洞或 Docker Daemon 权限缺陷,容器内的 root 用户可能发起越权攻击并影响宿主机安全。

安全加固的关键在于 Dockerfile 中显式创建并指定非特权用户(Non-root User),同时收紧工作目录的文件读写权限。

以下为针对 Python Web 应用的生产加固 Dockerfile 示例:

# ==========================================
# 阶段 1: 依赖构建阶段 (Build Stage)
# ==========================================
FROM python:3.11-slim-bookworm AS builder

WORKDIR /build

# 安装编译依赖,防止某些 C 扩展编译失败
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    libc6-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .

# 将 Python 依赖安装至自定义 site-packages 目录
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ==========================================
# 阶段 2: 最终运行阶段 (Final Stage)
# ==========================================
FROM python:3.11-slim-bookworm AS final

# 1. 创建固定的非 root 用户组和用户 (UID 10001)
RUN groupadd -g 10001 appgroup && \
    useradd -u 10001 -g appgroup -s /sbin/nologin -M appuser

WORKDIR /app

# 2. 从 builder 阶段复制编译依赖与源代码
COPY --from=builder /install /usr/local
COPY --chown=appuser:appgroup ./app /app/app

# 3. 严格限制目录读写权限,禁止运行时修改代码目录
RUN chown -R root:root /app && \
    chmod -R 755 /app && \
    chown -R appuser:appgroup /app/app

# 4. 切换为非 root 用户运行
USER 10001:10001

# 5. 暴露业务端口与健康检查指令
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health').read()"]

ENTRYPOINT ["python", "-m", "app.main"]

在该 Dockerfile 方案中,通过 chown -R root:root /app 确保应用进程在运行时无法篡改底层源码文件;通过 USER 10001 将运行身份与 root 权限解除绑定。

3. 编写 Dockerfile 最佳安全模板与自动化校验脚本。

依赖人工审核 Dockerfile 容易遗漏安全细节。工程实践中应当在 CI/CD 流水线中引入自动化审计工具(如 Hadolint),或编写 Go 语言脚本对构建产物开展安全基线校验。

以下为使用 Go 实现的镜像安全基线检查程序,用于验证镜像运行用户与安全配置:

package imageverifier

import (
	"context"
	"fmt"

	"github.com/docker/docker/api/types/image"
	"github.com/docker/docker/client"
)

// VerifyImageSecurity Inspect 镜像配置,确保非 root 用户且无高危配置
func VerifyImageSecurity(ctx context.Context, imageName string) error {
	if imageName == "" {
		return fmt.Errorf("invalid parameter: imageName cannot be empty")
	}

	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.43"))
	if err != nil {
		return fmt.Errorf("failed to create docker client: %w", err)
	}
	defer cli.Close()

	inspect, _, err := cli.ImageInspectWithRaw(ctx, imageName)
	if err != nil {
		return fmt.Errorf("failed to inspect image [%s]: %w", imageName, err)
	}

	// 1. 校验 USER 字段配置
	configUser := inspect.Config.User
	if configUser == "" || configUser == "root" || configUser == "0" {
		return fmt.Errorf("security violation: image [%s] runs as ROOT (User field is '%s')", imageName, configUser)
	}

	// 2. 校验健康检查配置
	if inspect.Config.Healthcheck != nil && len(inspect.Config.Healthcheck.Test) > 0 && inspect.Config.Healthcheck.Test[0] == "NONE" {
		return fmt.Errorf("security violation: image [%s] disabled HEALTHCHECK", imageName)
	}

	fmt.Printf("[PASS] Image [%s] passed security baseline check. User: %s\n", imageName, configUser)
	return nil
}

4. 运行时防护与 Seccomp 配置文件在 Docker 容器中的挂载实战。

在镜像去除了 root 权限后,若在运行容器时赋予过高的 Linux Capabilities,攻击者仍有可能利用未受限的 syscall 系统调用影响宿主机内核。

在 Docker CLI 启动指令或 Kubernetes Deployment 配置中,需显式移除默认 Capabilities,仅按需保留必要的权限(如 NET_BIND_SERVICE):

# Docker 运行时安全加固启动命令示例
docker run -d \
  --name web-app-secure \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt no-new-privileges:true \
  --security-opt seccomp=/etc/docker/seccomp-default.json \
  -p 80:8080 \
  web-app:v2.0.0

关键加固参数的作用解析如下:

  1. --read-only:将容器根文件系统挂载为只读模式,阻止恶意脚本的写入。
  2. --tmpfs /tmp:挂载内存中的临时文件目录,并加上 noexec(禁止执行二进制文件)与 nosuid 标志。
  3. --cap-drop=ALL:剥离所有 Linux Capabilities(如 CAP_SYS_ADMINCAP_NET_RAW 等)。
  4. --security-opt no-new-privileges:true:阻止容器内部子进程通过 setuid 提升权限。

多阶段构建、非特权 UID、只读根目录和 Capabilities 剥离可以减少暴露面;是否满足上线要求仍应以镜像扫描、运行时策略和实际业务验证的结果为准。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐