写点什么

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

  • 2023-04-14
    北京
  • 本文字数:9281 字

    阅读完需:约 30 分钟

如何检查 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-alpineWORKDIR /opt/appARG JAR_FILECOPY target/${JAR_FILE} app.jarENTRYPOINT ["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 versionApplication:          grypeVersion:              0.54.0Syft Version:         v0.63.0BuildDate:            2022-12-13T15:02:51ZGitCommit:            93499eec7e3ce2704755e9f51457181b06b519c5GitDescription:       v0.54.0Platform:             linux/amd64GoVersion:            go1.18.8Compiler:             gcSupported DB Schema:  5
复制代码

 

扫描镜像

 

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

 

$ grype docker:mydeveloperplanet/mygrypeplanet:0.0.1-SNAPSHOTApplication:          grypeVersion:              0.54.0Syft 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/

2023-04-14 14:223983

评论

发布
暂无评论
发现更多内容
如何检查 Docker 镜像是否存在漏洞_云原生_Gunter Rotsaert_InfoQ精选文章