1 copa 介绍

copa 是一个使用 Go 编写的 CLI 工具,基于 buildkit,可以根据像 Trivy 这样的流行工具的漏洞扫描结果直接修补容器镜像。

1.1 为什么?

我们需要能够快速修补容器,而无需上游进行完整重建。随着漏洞披露和主动利用之间的时间窗口不断缩小,迫切需要修补容器镜像中的关键安全漏洞,以便能够快速重新部署到生产环境中。特别是当这些漏洞是

  • 从基础镜像继承而来,有多个层级,并且不能通过更新版本在供应链中传播的方式来解决
  • 存在于你不维护的第三方应用镜像中,其更新频率无法满足你的安全服务级别协议。

💫[Docker] 无需重建镜像,直接修补容器漏洞 - 图1

除了填补左移安全实践和工具未满足的运营空白之外,copa 能够在不需要重建容器镜像的情况下修补容器的能力还提供了其他好处

  • 允许除镜像发布者外的用户也能够修补容器镜像,比如 DevSecOps 工程师。
  • 通过仅创建额外的修补层而不是重建整个镜像来减少重新分发修补后镜像的存储和传输成本,重建整个镜像通常导致不同的层哈希值,破坏了层缓存。
  • 通过无需等待基础镜像更新并且比完整镜像重建操作更快的方式,减少修补容器镜像的时间。
  • 通过从运行重建流水线到运行镜像上的单个工具来减少修补镜像的复杂性。

1.2 如何实现?

copa 工具是一个可扩展的引擎,具有以下功能:

  1. 从像 Trivy 这样的扫描器生成的容器镜像漏洞报告中解析所需的更新包。可以编写新的适配器以支持更多报告格式。
  2. 使用适当的软件包管理工具(如 apt、apk 等)获取和处理所需的更新包。可以编写新的适配器以支持更多软件包管理器。
  3. 使用 buildkit 将生成的更新二进制文件应用于容器镜像

💫[Docker] 无需重建镜像,直接修补容器漏洞 - 图2

这种方法的动机是基于以下核心原则,使直接容器修补具有广泛适用性和可访问性

  • Copa 支持修补现有的容器镜像
    • 开发人员不需要使用特定工具构建镜像或以某种方式修改它们以支持容器修补。
  • Copa 与现有的漏洞扫描和缓解生态系统配合工作
    • 镜像发布者不需要为容器修补创建新的工作流程,因为 Copa 支持使用已经发布的安全更新包修补容器镜像。
    • 消费者不需要迁移到新的、可能更受限制的支持生态系统来处理自定义发行版,也不需要更改其容器漏洞扫描流程以包含修复措施,因为 Copa 可以无缝集成为额外的步骤,根据这些扫描报告来修补容器。
  • Copa 减少了修补镜像所需的技术专长和等待依赖项
    • 对于操作系统软件包的漏洞,不需要对特定镜像有专门的知识来修补它,因为 Copa 依赖于今天流行的容器扫描工具生成的报告中已嵌入的漏洞修复知识。

2 安装

2.1 Homebrew

在 macOS 和 Linux 上,可以通过Homebrew工具[2]安装copa:

  1. brew install copa

2.2 GitHub

你可以从Copa GitHub 仓库[3]下载最新版本和早期版本copa。

以下说明适用于Ubuntu 22.04,其依赖版本是我们用于构建和测试的开发容器环境的一部分。对于其他发行版和操作系统,请参阅每个组件的相应安装说明。

  1. git clone https://github.com/project-copacetic/copacetic
  2. cd copacetic
  3. make
  4. # OPTIONAL: install copa to a pathed folder
  5. sudo mv dist/linux_amd64/release/copa /usr/local/bin/

3 快速开始

此示例说明了如何使用漏洞报告和copa来修补容器。

3.1 先决条件

  • copa工具已安装。
  • buildkit[4]守护进程已安装。
  • docker[5]守护进程正在运行,CLI 已安装。
  • trivy CLI[6]已安装并。

3.2 示例

  1. 下载目标容器进行扫描和打补丁:
  1. docker pull mcr.microsoft.com/oss/nginx/nginx:1.21.6
  1. 扫描容器镜像中是否存在可修补的操作系统漏洞,并将结果输出到 JSON 文件:
  1. trivy image --vuln-type os --ignore-unfixed -f json -o nginx.1.21.6.json mcr.microsoft.com/oss/nginx/nginx:1.21.6

你还可以在 shell 上以表格形式查看现有的可修补漏洞:

  1. trivy image --vuln-type os --ignore-unfixed mcr.microsoft.com/oss/nginx/nginx:1.21.6
  1. 要修补镜像,请使用 Trivy 报告并指定要连接到的 buildkit 实例:默认情况下,copa 将尝试按顺序自动连接到实例:如果实例不存在或该实例不支持 copa 所需的所有功能,则将尝试下一个实例。你可能需要使用—addr标志指定自定义地址。以下是支持的格式:

3.3 Buildkit 连接示例

  1. 示例:使用默认值连接:
    • unix:///path/to/buildkit.sock- 通过 unix 套接字连接到 buildkit。
    • tcp://$BUILDKIT_ADDR:$PORT- 通过 TCP 连接到 buildkit。(出于安全原因不推荐)
    • docker://- 连接到 docker,目前仅支持 unix 套接字,例如docker://unix:///var/run/docker.sock(或仅支持docker://)。
    • docker-container://my-buildkit-container- 连接到在 docker 容器中运行的 buildkitd。
    • buildx://my-builder- 连接到 buildx 构建器(或buildx://当前选择的构建器)。注意:当前仅支持容器支持的 buildx 实例
    • nerdctl-container://my-container-name- 类似于docker-container但使用nerdctl。
    • podman-container://my-container-name- 类似于docker-container但使用podman。
    • ssh://myhost- 通过 SSH 连接到 buildkit 实例。主机规范的格式应模仿 SSH 命令。
    • kubepod://mypod- 连接到在 Kubernetes Pod 中运行的 buildkit。还可以指定 kubectl 上下文和 pod 命名空间 ( kubepod://mypod?context=foo&namespace=notdefault)。
    1. 默认 docker buildkit 端点(至少需要 docker v24.0 且启用了containerd snapshotter[7]支持)
      1. 当前选择的 buildx 构建器(参见docker buildx —help:)
      1. 默认地址的 buildkit 守护进程/run/buildkit/buildkitd.sock
  1. copa patch -i mcr.microsoft.com/oss/nginx/nginx:1.21.6 -r nginx.1.21.6.json -t 1.21.6-patched

示例:连接到 buildx

  1. docker buildx create --name demo
  2. copa patch -i mcr.microsoft.com/oss/nginx/nginx:1.21.6 -r nginx.1.21.6.json -t 1.21.6-patched --addr buildx://demo

示例:容器中的 Buildkit

  1. export BUILDKIT_VERSION=v0.12.0
  2. docker run \
  3. --detach \
  4. --rm \
  5. --privileged \
  6. --name buildkitd \
  7. --entrypoint buildkitd \
  8. "moby/buildkit:$BUILDKIT_VERSION"
  9. copa patch -i mcr.microsoft.com/oss/nginx/nginx:1.21.6 -r nginx.1.21.6.json -t 1.21.6-patched --addr docker-container://buildkitd

示例:基于 TCP 的 Buildkit

  1. $ export BUILDKIT_VERSION=v0.12.0
  2. $ export BUILDKIT_PORT=8888
  3. $ docker run \
  4. --detach \
  5. --rm \
  6. --privileged \
  7. -p 127.0.0.1:$BUILDKIT_PORT:$BUILDKIT_PORT/tcp \
  8. --name buildkitd \
  9. --entrypoint buildkitd \
  10. "moby/buildkit:$BUILDKIT_VERSION" \
  11. --addr tcp://0.0.0.0:$BUILDKIT_PORT
  12. $ copa patch \
  13. -i mcr.microsoft.com/oss/nginx/nginx:1.21.6 \
  14. -r nginx.1.21.6.json \
  15. -t 1.21.6-patched \
  16. -a tcp://0.0.0.0:$BUILDKIT_PORT

无论哪种情况,copa都是非破坏性的,并将标签为1.21.6-patched的新镜像导出到本地。

注意:如果你针对私有镜像仓库中的镜像运行此示例,请确保在运行之前在默认 Docker的config.json 中配置了凭据,例如通过sudo docker login -u -p

  1. 扫描修补后的镜像并验证漏洞是否已被修补:
  1. trivy image --vuln-type os --ignore-unfixed mcr.microsoft.com/oss/nginx/nginx:1.21.6-patched

你还可以检查修补镜像的结构,以docker history查看附加到镜像的新修补层:

  1. docker history mcr.microsoft.com/oss/nginx/nginx:1.21.6-patched
  2. IMAGE CREATED CREATED BY SIZE COMMENT
  3. a372df41e06d 1 minute ago mount / from exec sh -c apt install --no-ins 26.1MB buildkit.exporter.image.v0
  4. <missing> 3 months ago CMD ["nginx" "-g" "daemon off;"] 0B buildkit.dockerfile.v0
  5. <missing> 3 months ago STOPSIGNAL SIGQUIT 0B buildkit.dockerfile.v0
  6. <missing> 3 months ago EXPOSE map[80/tcp:{}] 0B buildkit.dockerfile.v0
  7. <missing> 3 months ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B buildkit.dockerfile.v0
  8. <missing> 3 months ago COPY 30-tune-worker-processes.sh /docker-ent 4.61kB buildkit.dockerfile.v0
  9. <missing> 3 months ago COPY 20-envsubst-on-templates.sh /docker-ent 1.04kB buildkit.dockerfile.v0
  10. <missing> 3 months ago COPY 10-listen-on-ipv6-by-default.sh /docker 1.96kB buildkit.dockerfile.v0
  11. <missing> 3 months ago COPY docker-entrypoint.sh / # buildkit 1.2kB buildkit.dockerfile.v0
  12. <missing> 3 months ago RUN /bin/sh -c set -x && addgroup --syst 61.1MB buildkit.dockerfile.v0
  13. <missing> 3 months ago ENV PKG_RELEASE=1~bullseye 0B buildkit.dockerfile.v0
  14. <missing> 3 months ago ENV NJS_VERSION=0.7.0 0B buildkit.dockerfile.v0
  15. <missing> 3 months ago ENV NGINX_VERSION=1.20.2 0B buildkit.dockerfile.v0
  16. <missing> 3 months ago LABEL maintainer=NGINX Docker Maintainers <d 0B buildkit.dockerfile.v0
  17. <missing> 4 months ago /bin/sh -c #(nop) CMD ["bash"] 0B
  18. <missing> 4 months ago /bin/sh -c #(nop) ADD file:09675d11695f65c55… 80.4MB
  1. 运行容器以验证镜像没有回退:
  1. docker run -it --rm --name nginx-test
  2. mcr.microsoft.com/oss/nginx/nginx:1.21.6-patched
  3. /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
  4. /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
  5. /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
  6. 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
  7. 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
  8. /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
  9. /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
  10. /docker-entrypoint.sh: Configuration complete; ready for start up
  11. 2022/05/16 18:00:17 [notice] 1#1: using the "epoll" event method
  12. 2022/05/16 18:00:17 [notice] 1#1: nginx/1.20.2
  13. 2022/05/16 18:00:17 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
  14. 2022/05/16 18:00:17 [notice] 1#1: OS: Linux 5.10.102.1-microsoft-standard-WSL2
  15. 2022/05/16 18:00:17 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
  16. 2022/05/16 18:00:17 [notice] 1#1: start worker processes
  17. 2022/05/16 18:00:17 [notice] 1#1: start worker process 31
  18. 2022/05/16 18:00:17 [notice] 1#1: start worker process 32
  19. 2022/05/16 18:00:17 [notice] 1#1: start worker process 33
  20. 2022/05/16 18:00:17 [notice] 1#1: start worker process 34
  21. 2022/05/16 18:00:17 [notice] 1#1: start worker process 35
  22. 2022/05/16 18:00:17 [notice] 1#1: start worker process 36
  23. 2022/05/16 18:00:17 [notice] 1#1: start worker process 37
  24. 2022/05/16 18:00:17 [notice] 1#1: start worker process 38
  25. 2022/05/16 18:00:17 [notice] 38#38: signal 28 (SIGWINCH) received
  26. 2022/05/16 18:00:17 [notice] 36#36: signal 28 (SIGWINCH) received
  27. 2022/05/16 18:00:17 [notice] 33#33: signal 28 (SIGWINCH) received
  28. 2022/05/16 18:00:17 [notice] 32#32: signal 28 (SIGWINCH) received
  29. 2022/05/16 18:00:17 [notice] 34#34: signal 28 (SIGWINCH) received
  30. 2022/05/16 18:00:17 [notice] 35#35: signal 28 (SIGWINCH) received
  31. 2022/05/16 18:00:17 [notice] 37#37: signal 28 (SIGWINCH) received
  32. 2022/05/16 18:00:17 [notice] 1#1: signal 28 (SIGWINCH) received
  33. 2022/05/16 18:00:17 [notice] 31#31: signal 28 (SIGWINCH) received

你可以通过打开一个新的 shell 实例并运行以下命令来停止容器:docker stop nginx-test

4 参考资料

引用链接

[1] Copa 设计: https://project-copacetic.github.io/copacetic/website/design
[2] Homebrew工具: https://brew.sh/
[3] Copa GitHub 仓库: https://github.com/project-copacetic/copacetic/releases
[4] buildkit: https://github.com/moby/buildkit/#quick-start
[5] docker: https://docs.docker.com/desktop/linux/install/#generic-installation-steps
[6] trivy CLI: https://aquasecurity.github.io/trivy/latest/getting-started/installation/
[7] containerd snapshotter: https://docs.docker.com/storage/containerd/#enable-containerd-image-store-on-docker-engine