如何检查 Docker 镜像是否存在漏洞

定期检查管道中的漏洞是非常重要的。执行步骤之一是对你的 Docker 镜像进行漏洞扫描。在本文中,你将学习如何执行漏洞扫描,如何修复漏洞,以及如何将其添加到你的 Jenkins 管道中。

在几年前的一篇博文中,描述了如何扫描 Docker 镜像的漏洞。后续的博文展示了如何将扫描添加到 Jenkins 管道中。然而,之前博文中使用的 Anchore Engine 已经不被支持了,我认为另一个解决方案是使用由 Anchore 提供的 grype

如今,我们必须保持最新的安全修复措施。许多安全漏洞是公开的,可以很容易地被利用。因此,为尽量减少被攻击,尽快修复安全漏洞是必须的。但如何跟上这个步伐呢?你主要关注的是业务,不希望有一个全职工作来修复安全漏洞。这就是为什么自动扫描你的应用程序和你的 Docker 镜像很重要。Grype 可以帮助扫描 Docker 镜像、检查操作系统的漏洞,也会检查特定语言的包,如 Java JAR 文件的漏洞,并会报告它们。它还可以扫描文件和目录,因此可以用来扫描你的源代码。

在本文中,我创建了一个包含 Spring Boot 应用程序的有漏洞的 Docker 镜像,并将安装和使用 grype,以便扫描镜像并修复漏洞。本文中使用的资源可以在 GitHub 上找到。

了解本文,所需的前提条件是:

  • 基本的 Linux 知识

  • 基本的 Docker 知识

  • 基本的 Java 和 Spring Boot 知识

易受攻击的应用程序

打开 Spring Initializr,选择 Maven 构建、Java 17、Spring Boot 2.7.6,以及 Spring Web 依赖。这不会是一个非常脆弱的应用程序,因为 Spring 已经确保你使用最新的 Spring Boot 版本。因此,将 Spring Boot 的版本改为 2.7.0。可以用以下命令构建 Spring Boot 应用程序,它将为你创建 jar 文件:

$ mvn clean verify

你要扫描一个 Docker 镜像,因此需要创建一个 Dockerfile。你将使用一个非常基本的 Dockerfile,它只包含创建镜像所需的最低指令。如果你想创建生产就绪的 Docker 镜像,请阅读《Docker 最佳实践》(Docker Best Practices)和《Spring Boot Docker 最佳实践》(Spring Boot Docker Best Practices)。

FROM eclipse-temurin:17.0.1_12-jre-alpine
WORKDIR /opt/app
ARG JAR_FILE
COPY target/${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

在撰写本文时,Java 17 的最新 eclipse-temurin 基础镜像是 17.0.5_8 版本。同样,使用较旧的才能使其易受攻击。

为了构建 Docker 镜像,将使用 Spotify 的 dockerfile-maven-plugin 的分支。因此,将下面的代码片段添加到 pom 文件中。

<plugin>
  <groupId>com.xenoamess.docker</groupId>
  <artifactId>dockerfile-maven-plugin</artifactId>
  <version>1.4.25</version>
  <configuration>
    <repository>mydeveloperplanet/mygrypeplanet</repository>
    <tag>${project.version}</tag>
    <buildArgs>
      <JAR_FILE>${project.build.finalName}.jar</JAR_FILE>
    </buildArgs>
  </configuration>
</plugin>

使用该插件的好处是,你可以轻松地重复使用配置。创建 Docker 镜像只需一条 Maven 命令即可完成。

可以通过调用以下命令来构建 Docker 镜像:

mvn dockerfile:build

现在,你已经准备好开始使用 grype 了。

安装

可以通过执行以下脚本来安装 grype:

$ curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

通过执行以下命令验证安装:

$ grype version
Application:          grype
Version:              0.54.0
Syft Version:         v0.63.0
BuildDate:            2022-12-13T15:02:51Z
GitCommit:            93499eec7e3ce2704755e9f51457181b06b519c5
GitDescription:       v0.54.0
Platform:             linux/amd64
GoVersion:            go1.18.8
Compiler:             gc
Supported DB Schema:  5

扫描镜像

扫描 Docker 镜像的方法是调用 grype,然后是 docker:,表示你想从 Docker 守护程序、镜像和标签中扫描一个镜像。

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT
Application:          grype
Version:              0.54.0
Syft Version:         v0.63.0
 Vulnerability DB        [updated]
 Loaded image            
 Parsed image            
 Cataloged packages      [50 packages]
 Scanned image           [42 vulnerabilities]
NAME              INSTALLED  FIXED-IN   TYPE          VULNERABILITY        SEVERITY 
busybox           1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
jackson-databind  2.13.3                java-archive  CVE-2022-42003       High      
jackson-databind  2.13.3                java-archive  CVE-2022-42004       High      
jackson-databind  2.13.3     2.13.4     java-archive  GHSA-rgv9-q543-rqg4  High      
jackson-databind  2.13.3     2.13.4.1   java-archive  GHSA-jjjh-jjxp-wpff  High      
java              17.0.1+12             binary        CVE-2022-21248       Low       
java              17.0.1+12             binary        CVE-2022-21277       Medium    
java              17.0.1+12             binary        CVE-2022-21282       Medium    
java              17.0.1+12             binary        CVE-2022-21283       Medium    
java              17.0.1+12             binary        CVE-2022-21291       Medium    
java              17.0.1+12             binary        CVE-2022-21293       Medium    
java              17.0.1+12             binary        CVE-2022-21294       Medium    
java              17.0.1+12             binary        CVE-2022-21296       Medium    
java              17.0.1+12             binary        CVE-2022-21299       Medium    
java              17.0.1+12             binary        CVE-2022-21305       Medium    
java              17.0.1+12             binary        CVE-2022-21340       Medium    
java              17.0.1+12             binary        CVE-2022-21341       Medium    
java              17.0.1+12             binary        CVE-2022-21360       Medium    
java              17.0.1+12             binary        CVE-2022-21365       Medium    
java              17.0.1+12             binary        CVE-2022-21366       Medium    
libcrypto1.1      1.1.1l-r7             apk           CVE-2021-4160        Medium    
libcrypto1.1      1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libcrypto1.1      1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
libretls          3.3.4-r2   3.3.4-r3   apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7             apk           CVE-2021-4160        Medium    
libssl1.1         1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
snakeyaml         1.30                  java-archive  GHSA-mjmj-j48q-9wg2  High      
snakeyaml         1.30       1.31       java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml         1.30       1.31       java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-w37g-rhq8-7m4j  Medium    
spring-core       5.3.20                java-archive  CVE-2016-1000027     Critical  
ssl_client        1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
zlib              1.2.11-r3  1.2.12-r0  apk           CVE-2018-25032       High      
zlib              1.2.11-r3  1.2.12-r2  apk           CVE-2022-37434       Critical 

这个输出告诉你什么?

  • NAME:易受攻击的包的名称

  • INSTALLED:安装的是哪个版本

  • FIXED-IN:在哪个版本中修复了漏洞

  • TYPE:依赖项的类型,例如 JDK 的二进制等

  • VULNERABILITY:漏洞的标识符;通过此标识符,你可以获得有关 CVE 数据库中漏洞的更多信息

  • SEVERITY:不言自明,可以是可忽略、低、中、高或严重

当你仔细观察输出结果时,你会发现并非每个漏洞都有确认的修复方法。那么,在这种情况下,你该怎么办呢?Grype 提供了一个选项,以便只显示已经确认修复的漏洞。添加--only-fixed 标志就可以了。

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [50 packages]
 Scanned image           [42 vulnerabilities]
 
NAME              INSTALLED  FIXED-IN   TYPE          VULNERABILITY        SEVERITY 
busybox           1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
jackson-databind  2.13.3     2.13.4     java-archive  GHSA-rgv9-q543-rqg4  High      
jackson-databind  2.13.3     2.13.4.1   java-archive  GHSA-jjjh-jjxp-wpff  High      
libcrypto1.1      1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libcrypto1.1      1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
libretls          3.3.4-r2   3.3.4-r3   apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7  1.1.1n-r0  apk           CVE-2022-0778        High      
libssl1.1         1.1.1l-r7  1.1.1q-r0  apk           CVE-2022-2097        Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml         1.30       1.31       java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml         1.30       1.31       java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml         1.30       1.32       java-archive  GHSA-w37g-rhq8-7m4j  Medium    
ssl_client        1.34.1-r3  1.34.1-r5  apk           CVE-2022-28391       High      
zlib              1.2.11-r3  1.2.12-r0  apk           CVE-2018-25032       High      
zlib              1.2.11-r3  1.2.12-r2  apk           CVE-2022-37434       Critical 

请注意,Java JDK 的漏洞已经消失,尽管 Java 17 JDK 存在一个较新的更新。然而,这可能不是一个大问题,因为其他(非 java-archive)的漏洞显示基础镜像已经过时了。

修复漏洞

在这种情况下,修复漏洞是很容易的。首先,你需要更新 Docker 基础镜像。改变 Docker 镜像中的第一行:

FROM eclipse-temurin:17.0.1_12-jre-alpine

改成:

FROM eclipse-temurin:17.0.5_8-jre-alpine

构建镜像并再次运行扫描:

$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [14 vulnerabilities]
NAME              INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
jackson-databind  2.13.3     2.13.4    java-archive  GHSA-rgv9-q543-rqg4  High      
jackson-databind  2.13.3     2.13.4.1  java-archive  GHSA-jjjh-jjxp-wpff  High      
snakeyaml         1.30       1.31      java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml         1.30       1.31      java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml         1.30       1.31      java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml         1.30       1.31      java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml         1.30       1.32      java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml         1.30       1.32      java-archive  GHSA-w37g-rhq8-7m4j  Medium 

正如你在输出中所看到的,只有 java-archive 的漏洞仍然存在。其他漏洞已经被解决了。

接下来,修复 Spring Boot 依赖性漏洞,在 POM 中将 Spring Boot 的版本从 2.7.0 更改为 2.7.6。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.6</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

构建 JAR 文件,构建 Docker 镜像,然后再次运行扫描:

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [10 vulnerabilities]
NAME       INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
snakeyaml  1.30       1.31      java-archive  GHSA-3mc7-4q67-w48m  High      
snakeyaml  1.30       1.31      java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-w37g-rhq8-7m4j  Medium 

所以,你修复了 jackson-databind 的漏洞,但没有修复 snakeyaml 的漏洞。那么,snakeyaml 1.30 是在哪个依赖中被使用的?你可以通过 Maven 的 dependency:tree 命令来了解。为简洁起见,这里只显示部分输出:

$ mvnd dependency:tree
...
 com.mydeveloperplanet:mygrypeplanet:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.6:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.7.6:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.7.6:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.6:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.6:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.30:compile
...

输出显示,这个依赖是 spring-boot-starter-web 依赖的一部分。那么,你如何解决这个问题呢?严格来说,Spring 必须要解决这个问题。但如果你不想等待解决方案,你可以自己解决。

解决方案 1: 不需要依赖项。这是最简单的解决方案,风险也低。只需在 pom 中从 spring-boot-starter-web 依赖项中排除该依赖项即可。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
    </exclusion>
  </exclusions>
</dependency>

构建 JAR 文件,构建 Docker 镜像,然后再次运行扫描:

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [61 packages]
 Scanned image           [3 vulnerabilities]
No vulnerabilities found

再也没发现任何漏洞了。

解决方案 2:你确实需要这个依赖关系。你可以通过 pom 中的 dependencyManagement 来替换这个过渡性依赖。这就有点麻烦了,因为更新后的横向依赖没有经过 spring-boot-starter-web 依赖的测试。你要不要这样做,需要权衡一下。在 pom 中添加以下部分。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.32</version>
    </dependency>
  </dependencies>
</dependencyManagement>

构建 JAR 文件,构建 Docker 镜像,然后再次运行扫描:

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [3 vulnerabilities]
No vulnerabilities found

同样,再也没发现漏洞了。

解决方案 3:这是在你不想做任何事情或是否有误报通知时的解决方案。创建一个 .grype.yaml 文件,在其中排除高严重性的漏洞,然后用 --config 标志执行扫描,后面是包含排除项的 .grype.yaml 文件。

.grype.yaml 文件如下所示:

ignore:
  - vulnerability: GHSA-3mc7-4q67-w48m

再次运行扫描:

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed
 Vulnerability DB        [no update available]
 Loaded image            
 Parsed image            
 Cataloged packages      [62 packages]
 Scanned image           [10 vulnerabilities]
NAME       INSTALLED  FIXED-IN  TYPE          VULNERABILITY        SEVERITY 
snakeyaml  1.30       1.31      java-archive  GHSA-98wm-3w3q-mw94  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-c4r9-r8fh-9vj2  Medium    
snakeyaml  1.30       1.31      java-archive  GHSA-hhhw-99gj-p3c3  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-9w3m-gqgf-c4p9  Medium    
snakeyaml  1.30       1.32      java-archive  GHSA-w37g-rhq8-7m4j  Medium 

High 漏洞就不再显示了。

持续集成

现在你知道如何手动扫描你的 Docker 镜像了。然而,你可能想把扫描镜像作为持续集成管道的一部分。在本节中,将提供一个使用 Jenkins 作为 CI 平台时的解决方案。

要回答的第一个问题是,当发现漏洞时,你将如何得到通知。到现在为止,你只能通过查看标准输出来注意到这些漏洞。这不是一个 CI 管道的解决方案。你想得到通知,这可以通过失败的构建来实现。Grype 有 --fail-on <severity>标志来达到这个目的。你可能不想在发现严重程度为 negligible 的漏洞时让管道失败。

让我们看看当你手动执行这个时,会发生什么。首先,在 Spring Boot 应用程序和 Docker 镜像中再次引入漏洞。

构建 JAR 文件,构建 Docker 镜像,用标志 --fail-on 运行扫描。

$ mvn clean verify
...
$ mvn dockerfile:build
...
$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOT --only-fixed --fail-on high
...
1 error occurred:
        * discovered vulnerabilities at or above the severity threshold

这里没有显示所有的输出,只显示了重要的部分。而且,正如你所看到的,在输出的最后,显示了一条信息,表明扫描产生了一个错误。这将导致你的 Jenkins 管道失败,因此,开发人员会被通知出了问题。

为了将其添加到您的 Jenkins 管道中,有几个选项。这里选择创建 Docker 镜像,并从 Maven 中执行 grype Docker 扫描。grype 没有单独的 Maven 插件,但你可以使用 exec-maven-plugin 来实现这一目的。在 POM 的 build-plugins 部分添加以下内容。

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <version>3.1.0</version>
      <configuration>
        <executable>grype</executable>
          <arguments>
            <argument>docker:mydeveloperplanet/mygrypeplanet:${project.version}</argument>
            <argument>--scope</argument>
            <argument>all-layers</argument>
            <argument>--fail-on</argument>
            <argument>high</argument>
            <argument>--only-fixed</argument>
            <argument>-q</argument>
          </arguments>
      </configuration>
    </plugin>
  </plugins>
</build>

这里添加了两个额外的标志:

  • --scope all-layers。这将扫描 Docker 镜像中涉及的所有层。

  • --q:这将使用安静的日志,只显示漏洞和可能出现的故障。

你可以用下面的命令来调用它:

$ mvnd exec:exec

你可以把它添加到你的 Jenkinsfile 中的 withMaven 包装器中:

withMaven() {
  sh 'mvn dockerfile:build dockerfile:push exec:exec'
}

结论

在本文中,你了解了如何通过 grype 来扫描你的 Docker 镜像。Grype 有一些有趣的、用户友好的特性,允许你有效地将它们添加到你的 Jenkins 管道中,另外安装 grype 也很容易。与 Anchor Engine 相比,Grype 绝对是一个很大的改进。

作者简介:

Gunter Rotsaert,系统工程师,居住在荷兰的比利时人。

原文链接:

https://mydeveloperplanet.com/2023/01/18/how-to-check-docker-images-for-vulnerabilities/

本文文字及图片出自 InfoQ

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注