为 Java 应用程序创建安全 Docker 镜像的 5 个技巧

Docker 是容器化应用程序最广泛使用的方法。使用 Docker Hub,可以轻松创建和提取预先创建的映像。这非常方便,因为您可以使用 Docker Hub 中的这些映像快速为 Java 应用程序构建映像。

然而,为 Java 应用程序创建自定义 Docker 镜像的简单方法会带来许多安全问题。那么,我们如何使安全性成为 Java Docker 镜像的重要组成部分呢?

1. 为你的 Java 应用程序选择正确的 Docker 基础镜像

创建 Docker 镜像时,我们会根据从 Docker Hub 中提取的某个镜像来制作该镜像。这就是我们所说的基础镜像。基础镜像是您将为 Java 应用程序构建的新镜像的基础。您选择的基础镜像至关重要,因为它允许您利用此镜像中可用的所有内容。然而,这是有代价的。当基础镜像存在漏洞时,您将在新创建的镜像中继承此漏洞。 

让我们看一下 Adoptopenjdk 提供的一组流行的 Docker Java 基础镜像openjdk11。使用其默认标记,此镜像基于 ubuntu 发行版构建。但是,我们也可以选择特定版本的标记,例如基于 Debian、Centos 或 Alpine(请注意,alpine 不是基于 glibc,可能与进行本机 JNI 调用的应用程序不兼容)。


我们可以得出结论,从安全角度来看,选择正确的基础镜像至关重要。您可能不需要完整操作系统附带的所有二进制文件。最好基于最小基础镜像为您的应用程序构建新的 Docker Java 镜像。您没有的二进制文件不会对您造成伤害。

除了安全性方面,最小化基础镜像还会减少新创建镜像的大小。较小的 Docker 镜像还意味着占用空间更小,而且很可能启动时间更快。

2. 使用 JRE,而不是 JDK

创建 Docker 镜像时,我们应仅分配正常运行所需的资源。这意味着我们应该首先为生产镜像使用适当的 Java 运行时环境 (JRE),而不是完整的 Java 开发工具包 (JDK)。此外,生产镜像不应包含 Maven 或 Gradle 等构建系统。构建的产品(例如 jar 文件)就足够了。

即使您想在 Docker 容器内构建应用程序,您也可以使用多阶段构建轻松地将构建映像与生产映像分离。

例如:
我想为我的 java-code-workshop 应用程序创建一个 Docker Java 映像。这是一个基于 spring-boot 的应用程序,使用 maven 构建,需要 Java 版本 8。 

创建此 Docker Java 镜像的简单方法如下:

我选择了一个包含 maven 和 openjdk8 的基础镜像,将源代码复制到镜像中,然后调用 maven 来构建和运行我的应用程序。此示例运行正常。我的应用程序将顺利启动和运行。但是,我刚刚创建的 Docker 镜像的大小为 631 MB。

让我们改变这个 Dockerfile 并使用多阶段构建:

现在发生的事情是,我仍然使用 maven-openjdk8 镜像来构建我的项目。但是,这不会是输出。我基于一个明显较小的 java 8 JRE 镜像创建一个新镜像,并仅复制可执行的 spring-boot jar。现在我只需执行jar-file,就完成了!结果是一个不包含 JDK 或 maven 而仅包含 JRE 的 Docker 镜像。镜像大小急剧减小到 132 MB。 

较小的镜像不仅更容易上传和节省启动时间,而且更安全。你能想象如果攻击者出于某种原因访问了包含 JDK、源代码和可用构建工具的正在运行的容器会发生什么吗? 

当您必须包含访问私有存储库的机密时,也可以使用此方法。您不希望这些机密存在于生产映像的缓存中。您不会在生产中使用构建映像,因此在那里使用机密是完全可以接受的。使用此技术,您可以从其他映像中挑选所需的内容,并仅使用其所需的资源创建产品 Docker 映像。

3. 不要以 root 身份运行 Docker 容器

创建 Docker 容器时,默认情况下,您将以 root 身份运行它。虽然这对于开发来说很方便,但您不希望在生产映像中出现这种情况。假设出于某种原因,攻击者可以访问终端或执行代码。在这种情况下,它对该正在运行的容器具有很大的权限,并且可能通过具有不适当高访问权限的文件系统绑定挂载访问主机文件系统。

防止这种情况的最简单方法是创建一个特定用户,如下所示:

在第三行,我创建了一个新组并添加了一个用户。此用户是系统用户 (-r),没有密码和主目录。我还将其添加到新创建的组中。

接下来,我在第 6 行授予用户对应用程序文件夹的权限。别忘了第 7 行。在这里,我设置要使用的用户。这样,新创建的受限用户就会执行最后一行的命令。 

4. 在开发过程中扫描 Docker 镜像和 Java 应用程序

从 Dockerfile 创建 Docker 镜像甚至重建镜像都可能在系统中引入新的漏洞。在开发过程中扫描 Docker 镜像应该成为工作流程的一部分,以尽早发现漏洞。

使用Snyk CLI可以轻松扫描 Docker 镜像。在本地计算机上使用它,作为管道的一部分,或两者兼而有之。安装并验证 Snyk CLI 后,扫描镜像唯一需要做的就是

$ snyk container test <imageName>

如果我想扫描adoptopenjdk图像,正如我在第一部分中提到的那样,命令将是这样的。

$ docker pull adoptopenjdk/opendjdk11:latest
$ snyk container test adoptopenjdk/opendjdk11:latest

输出:

您可以测试和监控 Docker 映像。对于监控,您可以使用snyk container monitor <image>。监控会拍摄快照并监控您的映像是否随着时间的推移出现新的漏洞或修复。

当您扫描映像并拥有 Dockerfile(您创建了一个新的 Docker Java 映像)时,您应该将标志添加--Dockerfile=<dockerfile>snyk container testsnyk container monitor。现在您可以获得更好的补救建议。例如,如果有可用的基础映像可以减少可用的漏洞数量,您就会知道。

例子:

$ snyk container test myImage:mytag --Dockerfile=path/Dockerfile
$ snyk container monitor myImage:mytag --Dockerfile=path/Dockerfile
扫描您的 Java 应用程序

您正在构建的 Docker Java 镜像还包含您的应用程序。显然,这也是一个可能的攻击点。您必须确保您的 Java 应用程序没有安全漏洞,从一开始就做出安全的决定。想象一下,您的应用程序包含一个允许在调用 REST 端点时执行远程代码的库。即使您的镜像的其余部分没有任何漏洞,这也可能是灾难性的。

您放入 Docker 镜像中的 Java 二进制文件的大部分可能是您导入的代码。您可以将应用程序所具有的库和框架视为依赖项。使用 Snyk CLI 可以轻松检查依赖项。这与我们之前用于扫描镜像的 CLI 相同。在您的根文件夹中调用snyk testsnyk monitor ,您将扫描或监控您的应用程序是否存在库中的安全漏洞。 

对于您编写的代码,使用代码分析工具或 Linter(如 SonarLint、PMD 或 spotbugs)是明智之举。这些工具是用于创建更好代码的通用工具,同时也可以帮助您避免犯下明显的安全错误。

5. 重建

为 Docker 镜像构建 Java 应用程序,以便您可以随时将其丢弃并重建。假设您注意到正在运行的容器出了问题。如果您可以简单地将其杀死并启动一个新实例,那就太好了。这意味着您必须设计无状态 Java 应用程序,以便数据存储在容器外部。您可以想到的几件事是:

  • 不要在容器中运行数据库的数据存储。
  • 不要在容器中存储(日志)文件
  • 确保缓存自动恢复(如果适用)

如果您构建应用程序以便可以随时丢弃它并启动新实例,那么您也可以安全地重建整个 Docker 映像。您是否知道对于20% 的易受攻击的 Docker 映像,只需重建映像即可修复一个或多个安全问题?在许多情况下,Docker 映像基于基础映像的“最新”标签。这些“最新”会随着时间而变化,并被更新的改进版本所取代。使用 apt 或 yum 等包管理器安装在容器中的关键二进制文件也是如此。当然,从安全角度来看,使用最新版本是好的,因为您将自动获取最新的安全修复程序,但是,您需要考虑到基础映像会随着时间而变化,因此很难在特定时间快照重新创建映像。 

即使您的应用程序没有变化,也要定期重建 Docker 映像,最好使用较新或最新的基础映像版本标记。操作系统层等底层的改进可以提高映像质量并减少安全漏洞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值