为你的Node.js应用程序提供Docker安全最佳实践

243 阅读11分钟

Node.js应用程序的Docker安全最佳实践

本博客将介绍在使用Docker平台时需要考虑的12项Docker安全最佳实践。

有一些Docker安全最佳实践,在构建、共享和运行你的应用程序时,你需要考虑你的docker容器安全。Docker是一个开源的平台,用于构建、共享和运行你的容器化应用程序。你可以很容易地建立包含你的应用程序的Docker镜像,在你的团队内或团队外分享它们,只需一个命令就可以运行你的应用程序。这看起来非常容易,对吗?然而,它是。

你可能已经知道什么是Docker以及它是如何工作的。因此,我们不会深入了解它的细节。这篇博客将介绍使用Docker平台时需要考虑的12大Docker安全最佳实践

为什么你需要保护你的Docker文件和容器?

容器化由于其灵活的方式可以在任何地方部署应用程序,已经在世界范围内掀起了风暴。然而,这也引入了一些安全漏洞。Docker和Docker容器使得构建、共享和部署你的应用程序变得非常容易,这使得在部署后发现、检测、报告和修复安全问题变得非常困难。 这种开销的大部分可以通过将安全转移到左边来预防和减少。因此,从Dockerfile到Docker容器的一切安全都是至关重要的,以使Docker容器安全 到位。

12大Docker安全最佳实践

人们可以从一开始就采取各种措施来提高Docker容器的安全性,也就是说,从编写Docker文件到运行Docker容器。让我们继续看看12个Docker安全最佳实践,这些实践可以使Docker容器的安全性到位。

1.保持Docker主机和Docker引擎与你的Docker镜像同步更新

Docker容器在主机上可用的Docker引擎上运行。这些主机可以是Linux/Mac或Windows。Docker引擎可以是其中一个可用的版本。 使用最新的、稳定的版本是至关重要的,它可以更新以前版本中的已知问题。这同样适用于主机操作系统。

例如,截至2021年12月27日,如果你使用Ubuntu作为主机操作系统,那么你可以使用Ubuntu 20.04 LTS而不是Ubuntu 16.04 LTS,而对于Docker引擎,你可以使用20.10.11。查找Docker发行说明以获得更多信息,并查看Docker支持的操作系统平台

有更新或补丁是很重要的,因为每一个补丁都能解决CVE-2021-41092这样的漏洞。这个特别的补丁修复了一个错误,即从CLI登录docker注册表的凭证被发送到`registry-1.docker.io`,而不是预定的私有注册表;这曾经是由于配置错误的配置文件(通常是`~/.docker/config.json`)列出`credsStore`或`credHelpers而发生。

参考下面的截图,了解Ubuntu操作系统的发布周期。

2.避免在Dockerfile指令中存储机密数据

Dockerfile包含用于创建Docker镜像的指令。COPY, RUN, ADD, CMD, ENTRYPOINT等。这些是可能成为Dockerfile一部分的一些命令指令。我们还可以使用ENV和ARGS来在运行时或构建时设置变量,这取决于需求。你可以使用ENV或ARGS指令在Docker容器内使用环境变量。在设置环境变量时,你应该注意,千万不要在Docker文件中为变量分配秘密、凭证、密码等,也不要在任何命令中硬编码。将此视为Dockerfile安全的最佳实践之一,因为忽视它可能会导致潜在的安全威胁,并可能使你损失惨重。

**例如,**如果你在Dockerfile中硬编码数据库的密码或连接细节,并将其分配给环境变量,然后将Dockerfile上传到公共资源库,任何能够访问该资源库的人都可以得到这些凭证,并可以访问细节或连接到数据库。

FROM mysql:5.7    ENV DATABASE_USERNAME=admin # < - Setting username is acceptable ENV DATABASE_PASSWORD=mypassword@!@#$ # < - Avoid setting password at any cost

3.避免使用不受信任的图像注册处

图像注册处是存储Docker图像的地方。它可以是私人的或公共的。Docker Hub是Docker官方基于云的Docker镜像注册处,当你安装它时,它是默认注册处。如果你不配置任何注册表,图像就会从这个公共存储库中提取。

当你从任何公共注册库拉取Docker镜像时,要注意其来源,因为你可能不知道是谁构建的,如何构建的,或者它们是否值得信赖。每当你从一个不受信任的发布者那里提取镜像时,不要忘记验证源注册表和用于构建镜像的Docker文件。另外,你的Dockerfile的基础镜像,也就是FROM指令。

FROM <registry/image:tag>  # < - Verify the base image before you use it 

4.谨防递归复制

在为需要将文件从本地机器复制到Docker镜像的应用程序编写Dockerfile时,你应该注意使用COPY指令复制的内容。你的本地机器上可能有一些文件,可能包含机密数据或秘密。因此,如果这些文件被复制到Docker Image中,任何能够访问该容器的人都可以从容器中获得这些文件。因此,为了提高Docker容器的安全性,只复制容器中需要的文件而不是复制所有的文件是非常重要的,如下面的指令所示。

你可以尝试一下.dockerignor.它可以帮助排除符合模式的文件和目录,避免不必要地使用ADD或COPY向镜像发送大的或敏感的文件。

**例如,**在下面的COPY指令中,当前位置的所有文件都将被复制到Docker镜像中。所以,你应该总是避免 "COPY . ./",应该在COPY指令中明确指定文件名为 "COPY package.json ./"

FROM node:12.18.4-alpine WORKDIR /app ENV PATH /app/node_modules/.bin:$PATH COPY . ./              # < - Avoid such kind of COPY instruction

5.在开发过程中扫描镜像

Docker镜像 是由Dockerfiles构建的,Dockerfiles包含使用基础镜像、安装包、启动应用程序等指令。Dockerfile也可能错误地包含硬编码的凭证。那么,如果你使用的基础镜像有一些漏洞,并且Dockerfile中硬编码的凭证从使用该Dockerfile构建的镜像所创建的容器中泄露出来,该怎么办?

扫描镜像是识别Docker镜像中已知安全漏洞的过程,这样你就可以在将其推送到Docker Hub或其他注册中心之前修复它们。

现在你可以直接从Docker桌面CLI运行Snyk漏洞扫描,因为Snyk和Docker已经合作了。

例如
你可以通过提供用于创建Docker镜像的Dockerfile来获得关于Docker镜像的详细报告。

docker scan --file PATH_TO_DOCKERFILE DOCKER_IMAGE

6.6.使用固定的标签以实现不变性

在Docker中,镜像上有标签。Docker镜像最常见和默认的标签是 "最新"。因此,如果你没有给你的镜像指定一个标签,它将默认拥有 "最新 "标签。人们可以用同一个标签发布多个镜像,也就是说,Docker镜像的标签不是不可更改的。因此,非常重要的一点是 -

  1. 为你的镜像选择一个更具体的标签,这样你每次都会得到相同的镜像。
  2. 倾向于在你的私人仓库中保留一份镜像的副本。
  3. 使用Docker内容信任(DCT)来使用数字签名,对特定图像标签的完整性和发布者进行客户端或运行时验证。请访问官方文档以了解更多相关信息。

例如
比方说:

  1. Dev1拉出myimage:latest imag,在他/她的电脑上运行1.1版本的应用程序,并发现该应用程序运行顺利。
  2. 稍后,Dev2拉出同样的图像myimage:fresh,在他/她的计算机上运行应用程序,发现应用程序出了问题。
    在这里,你确定他们两个人都在运行同一个版本的应用程序吗?
  3. 答案是否定的,因为在Dev2拉出镜像myimage:fresh 之前**,** Dev3进行了修改,并推送了不稳定的1.2版本的应用程序,名称也是myimage:fresh。
  4. 现在,当他们部署myimage:fresh 镜像时**,** 它将指向应用程序的不稳定版本1.2,然后这将破坏生产环境。

7.对Dockerfile进行润色

市场上有各种Dockerfile提示器,用于确保Dockerfile遵循Dockerfile安全最佳实践。Linters可以在构建过程产生警告的情况下帮助你停止构建过程;它们会遍历你的Dockerfile,并在Dockerfile不遵循最佳实践时提出警告。你可以利用Hadolint,它是一个开源项目,用于验证内联bash和linting Dockerfiles。

8.限制容器资源

默认情况下,在主机上运行的Docker容器可以利用所有的内存和CPU。在Docker容器被破坏的情况下,攻击者可能会试图使用主机资源来执行恶意活动。此外,如果一个特定的容器开始利用主机的所有资源,那么驻扎在同一地点的其他容器可能会因为资源不可用而受到影响。为了避免这种情况,建议对Docker容器设置资源限制,这样它们使用的资源就不会超过分配给它们的资源,同时也有助于提供Docker容器的安全性。

例如, 如果主机有1个CPU,下面的第一条命令可以保证容器每秒最多使用50%的CPU,第二条命令可以将容器的内存使用限制在1GB。

CPU限制

docker run -it --cpus=".5" ubuntu /bin/bash

内存限制

docker run -it --memory="1g" ubuntu /bin/bash

9.不要暴露Docker Daemon的套接字

Docker与一个名为/var/run/docker.sock的UNIX域套接字进行通信,这也是Docker API的主要入口点。如果任何人有访问Docker守护神套接字的权限,也会获得root权限。因此,允许任何用户写入/var/run/docker.sock或将套接字暴露给容器,对系统的其他部分来说是一个很大的Docker容器安全风险,因为这将给它带来root权限。因此,永远不要在Docker容器内挂载/var/run/docker.sock。

**例如,**永远不要用"-v /var/run/docker.sock:/var/run/docker.sock "这样的选项来运行你的docker运行命令,并将此作为Docker安全最佳实践之一,以帮助你保持你的系统受到保护。

10.以非Root用户身份运行容器

根据sysdig.com的 "2021年容器安全和使用报告",大多数镜像都是过度许可的,有58%的容器以root身份运行。这不被认为是Dockerfile的最佳实践;人们应该避免这样做,因为很少有情况下你真的需要在容器中使用root用户来运行你的进程。为了确保你不使用根用户,总是在用户名中指定一个 "USER "指令。

在Docker容器中使用非root用户可以确保减轻容器运行时的潜在漏洞,实现Docker容器安全。

例如
始终尝试创建并使用一个非root用户来在容器中运行你的应用进程。你需要的非root用户可能不存在,因此你首先要在使用它之前创建一个。

FROM alpine:3.12 RUN adduser -D non-root-user && chown -R non-root-user /myapp-data # < - Create a user USER non-root-user           # < - use a non-root user

11.保持Docker镜像尽可能小

在整个Dockerfile构建过程中会创建许多工件,这些工件只在构建时才需要。这些包是编译所需的,或者是运行单元测试、临时文件、秘密等的依赖。在基础镜像中保留这些工件会增加Docker镜像的大小,这可能会减慢下载时间并扩大攻击面,因为要加载更多的包,因此,是Docker支持多阶段构建的原因之一。这个功能允许你在构建过程中使用许多临时镜像,同时只保留最新的镜像和你复制到其中的数据。

结果是,你有两个镜像。

  1. 镜像1:第一个镜像是一个大的镜像,它带有许多依赖性,你需要创建你的应用程序并运行测试。
  2. 镜像2:就大小和库的数量而言,是一个非常小和轻量级的镜像,只有在生产中运行程序所需的工件的副本。

通过这种方式,你可以采用多阶段构建来构建你的Docker镜像,可以减少镜像的大小,避免安装不需要的库,增加潜在安全风险的机会。

12.使用最新的Docker镜像

由于新的安全漏洞经常被发现,保持最新的安全补丁是一般Docker安全最佳实践。因此,使用经常更新的基础镜像并在此基础上建立自己的镜像是很重要的。没有必要总是使用最新的版本,因为它可能包含破坏性的变化,但你应该有一个版本策略。以下是你的基础镜像需要考虑的几个要点。

  1. 坚持使用稳定或长期支持的版本,这些版本更有可能获得安全更新。
  2. 在你的基本镜像版本达到其生命周期的终点,不再收到更新之前,准备放弃旧版本并进行迁移。
  3. 定期重建你的镜像,并使用类似的技术从基础版本中抓取最新的软件包。

结论

我们都知道 "预防胜于治疗",这同样适用于Docker的情况。人们不能简单地忽视其安全性,因为这可能导致巨大的灾难。我们现在知道了为什么Docker容器安全是至关重要的,以及为什么它必须要到位。将安全转移到左边是改善你的Docker环境和减少管理开销的关键。

上面提到的12条建议,主要集中在Dockerfile安全最佳实践和Docker容器安全最佳实践,将帮助你在保护Docker容器和Docker环境的过程中。