无论您是在生产中运行容器和Kubernetes,还是想在当前的DevOps工作流程中嵌入更多安全性,都不要错过这12种镜像扫描最佳实践。
您的团队面临的主要挑战之一是如何在不减慢应用交付速度的情况下管理容器安全风险。解决此问题的一种方法是采用安全DevOps工作流程。
安全DevOps(也称为DevSecOps)在从开发到生产的整个应用程序生命周期中提供安全性和监视功能。这使您可以交付安全,稳定和高性能的应用程序。该工作流程将插入您现有的工具链中,并为DevOps,开发人员和安全团队提供单一的事实来源,以最大程度地提高效率。
镜像扫描是嵌入到Secure DevOps工作流程中的一项关键功能。作为您的第一道防线之一,它还可以帮助您在漏洞被利用之前检测并阻止它们。
幸运的是,镜像扫描易于实现和自动化。在此博客中,我们将介绍许多镜像扫描最佳实践和技巧,以帮助您采用有效的容器镜像扫描策略。
什么是镜像扫描?
镜像扫描是指分析容器镜像的内容和构建过程以检测安全问题,漏洞或不良做法的过程。
工具通常会从多个Feed(NVD,Alpine,Canonical等)中收集“常见漏洞和披露(CVE)”信息,以检查镜像是否容易受到攻击。有些还提供了开箱即用的扫描规则,以查找最常见的安全问题和不良做法。
镜像扫描可以轻松地集成到安全DevOps工作流程的多个步骤中。例如,您可以将其集成到CI /CD管道中以阻止漏洞到达注册表,也可以集成在注册表中以防止第三方镜像中的漏洞,或者在运行时集成以防止新发现的CVE。
当自动执行并遵循最佳实践时,镜像扫描可确保您的团队不会因部署应用程序而变慢。
让我们深入研究一下您今天可以实施的12种镜像扫描最佳实践。
1: 将镜像扫描嵌入到CI/CD管道中
构建容器镜像时,应格外小心并在发布之前对其进行扫描。
您可以利用已经为DevOps工作流构建的CI/CD管道,并增加一个额外的步骤来执行扫描。
测试和构建代码后,您可以将镜像推送到暂存库,而不必将镜像推送到生产库。然后,您可以运行镜像扫描工具。这些工具通常会返回一份报告,列出发现的不同问题,并为每个问题分配不同的严重性。在您的CI/CD管道中,您可以检查这些镜像扫描结果,并在出现任何严重问题时使构建失败。
请记住,自动化是关键。这是DevOps的核心概念,对吗?这同样适用于保护DevOps。
通过使CI/CD管道中的安全性自动化,您可以在漏洞进入注册表之前捕获漏洞,而不会给人们带来受这些问题或生产问题影响的机会。
2: 采用内联扫描以控制您的隐私
在上一步中,我们看到了CI/CD管道中的镜像扫描传统上是如何涉及登台注册表的。但是,如果您的镜像错误地包含一些凭据怎么办?它们可能会接触到错误的人并最终被泄漏。
更进一步,您可以实施内联镜像扫描,直接从CI/CD管道扫描镜像,而无需该暂存库。
使用内联镜像扫描时,仅扫描元数据会发送到您的扫描工具,从而帮助您控制隐私。
我们已经准备了一些指南,说明如何使用最常见的CI/CD工具(例如Gitlab,Github Actions,AWS Codepipeline,Azure Pipelines,CircleCI,Jenkins,Atlassian Bamboo和Tekton)实施内嵌镜像扫描。
3: 在注册表执行扫描
开始实施镜像扫描时,应将其包含在注册表中,这是第一步。
您部署的所有镜像都将从注册表中提取。通过在那里扫描镜像,您至少知道它们在运行之前已被扫描。
4: 利用Kubernetes准入控制器
即使您在CI/CD管道中阻止了易受攻击的镜像,也没有阻止它在生产中部署的功能。而且,即使在注册表中对其进行了扫描,谁还会阻止来自外部开发人员的镜像?
理想情况下,您希望Kubernetes在计划镜像之前先对其进行检查,以阻止将未扫描或易受攻击的镜像部署到集群上。
您可以利用准入控制器来实施此策略。
Kubernetes准入控制器是Kubernetes的强大功能,可帮助您定义和自定义允许在集群上运行的功能。在对请求进行身份验证和授权之后,但在对象持久之前,准入控制器会拦截并处理对Kubernetes API的请求。
扫描工具通常提供一个验证Webhook,该Webhook可以按需触发镜像扫描,然后返回验证决定。
准入控制器可以在计划镜像之前调用此Webhook。 Webhook返回的安全性验证决策将传播回API服务器,该API服务器将答复原始请求者,并且仅在镜像通过检查后才将对象持久保存在etcd数据库中。
但是,该决定是由镜像扫描仪做出的,没有任何有关集群中正在发生的情况的上下文。您可以使用OPA改进此解决方案。
开放策略代理(OPA)是一种开源通用策略引擎,它使用称为rego的高级声明性语言。 OPA背后的关键思想之一是将决策与政策执行脱钩。
使用OPA,您可以在Kubernetes集群而不是镜像扫描仪中做出准入决定。这样,您可以在决策过程中使用群集信息,例如名称空间,pod元数据等。一个示例是为“ dev”名称空间使用一个策略,并使用更多的允许规则,然后对“生产”使用另一个非常严格的策略。
5: 固定镜像版本
有时,您扫描的镜像与您在Kubernetes集群中部署的镜像不同。当您使用可变标签(例如“latest”或“staging”)时,可能会发生这种情况。此类标签会不断更新,以使最新的扫描结果仍然有效。
使用可变标签会导致从同一镜像部署具有不同版本的容器。除了扫描结果带来的安全问题外,这还可能导致难以调试的问题。
例如,应尽可能使用ubuntu:focal-20200423
之类的不可变标签,而不是使用ubuntu:focal
。
请记住,版本标记(对于某些镜像)往往会进行细微的,不间断的更新。因此,尽管看起来有些冗长,但确保可重复性的唯一选择是使用实际的图像ID:
ubuntu:@sha256:d5a6519d9f048100123c568eb83f7ef5bfcad69b01424f420f17c932b00dea76
您可能开始认为这是超出镜像扫描最佳实践的问题。您需要一套Dockerfile最佳实践。这不仅会影响Dockerfiles中的FROM命令,还会影响Kubernetes部署文件以及几乎所有放置镜像名称的地方。
从镜像扫描的角度来看,您能做什么?
您可以通过结合使用Kubernetes准入控制器,镜像扫描仪和OPA引擎(上一点中所述的系统)来实施此策略。
6: 扫描操作系统漏洞
作为一般的镜像扫描最佳实践,请牢记以下思想:“镜像越小越好。”较小的镜像意味着更快的构建,更快的扫描以及更少的潜在漏洞依赖性。
新的Docker镜像通常是在现有基础镜像的基础上构建的,或在现有基础镜像之上添加一层。该基本镜像由镜像Dockerfile中的FROM语句定义。结果是分层的体系结构设计,在最常见的任务中节省了大量时间。例如,对于镜像扫描,您只需要扫描一次基本镜像。如果父镜像易受攻击,则在该父镜像之上构建的任何其他镜像也将易受攻击。
即使您没有在镜像中引入新漏洞,基本镜像中的漏洞也很容易受到攻击。
因此,您的扫描工具应主动跟踪已知漏洞文件的漏洞源,并在您使用其中之一时通知您。
7: 利用distroless镜像
向您介绍标准发行版镜像,不包含程序包管理器,shell或您希望在标准Linux发行版中找到的任何其他程序的基本镜像。非发行版镜像允许您仅将应用程序及其依赖项打包在轻量级容器镜像中。
将运行时容器中的内容严格限制为必要内容,以最大程度地减少攻击面。它还可以改善扫描仪的信噪比(例如CVE),并减轻根据您的需要建立物源的负担。
以下示例显示了用于"Hello world"应用程序的Dockerfile,该应用程序在Ubuntu和Distroless上运行。
FROM ubuntu:focal
COPY main /
ENTRYPOINT ["/main"]
对其进行扫描后,我们发现了24个操作系统漏洞,其中两个严重程度为中。同样,对于这样一个简单的应用程序,镜像大小相当大,为77.98MB。
现在,基于相同版本镜像的同一应用程序:
FROM gcr.io/distroless/static-debian10
COPY main /
ENTRYPOINT ["/main"]
现在,我们只发现了两个可以忽略不计的严重性OS漏洞。此外,镜像大小减小到只有6.93MB,这更适合此应用程序。
这表明,这种Distroless容器没有任何不必要的程序包,这些程序包可能导致更多的漏洞,因此被识别出来。
8: 扫描第三方库中的漏洞
应用程序使用了大量的库,以至于这些库最终提供的代码行比团队编写的实际代码多。这意味着您不仅需要意识到代码中的漏洞,还需要意识到其所有依赖项中的漏洞。
幸运的是,在扫描仪用来警告您有关操作系统漏洞的相同漏洞源中,很好地跟踪了这些漏洞。并非所有工具都能像扫描镜像中的库一样深入,因此请确保镜像扫描仪已深入挖掘并警告您这些漏洞。
9: 优化层顺序
如果您谨慎使用Dockerfile中的RUN命令,则可以进一步优化镜像。 RUN命令的顺序可能会对最终镜像产生很大影响,因为它决定了符合该镜像的图层的顺序。
您可以通过首先放置较大的层(通常是不变的),最后放置变化最大的文件(即,已编译的应用程序)来优化Docker缓存的使用。这将有利于重用现有图层,加快构建镜像的速度,并间接地也加快镜像扫描的速度。
10: 扫描Dockerfile中的配置错误
正如我们已经看到的那样,Docker镜像构建过程遵循清单Dockerfile中的指令。
您可以遵循几种Dockerfile最佳实践来检测常见的安全性错误配置:
- 以特权(root)用户身份运行,可以访问比所需更多的资源。
- 暴露不安全的端口,例如不应在容器上打开的ssh端口22。
- 通过错误的“ COPY”或“ ADD”命令意外地包含了私人文件。
- 包括(泄露)机密或凭据,而不是通过环境变量或安装注入它们。
- 允许用户将选项传递给Entrypoint和CMD也是一种很好的做法。
- 您的特定策略定义的许多其他内容,例如被阻止的软件包,允许的基本镜像,是否已添加SUID文件等。
在这样的Dockerfile中:
FROM ubuntu:focal-20200423
USER root
RUN curl http://my.service/?auth=ABD54F0356FA0054
EXPOSE 80/tcp
EXPOSE 22/tcp
ENTRYPOINT ["/main"]
我们的镜像扫描可以自动检测到以下问题:
USER root
我们以root身份运行。
EXPOSE 22/tcp
在这里,我们将公开通常用于ssh的端口22,这是容器不应该包含的工具。我们还将公开端口80,但这应该没问题,就像HTTP服务器的通用端口一样。
RUN curl http://my.service/?auth=ABD54F0356FA005432D45D0056AF5400
此命令使用一个auth密钥,任何人都可以使用它来给我们造成一些伤害。我们应该改用某种变量。这样的键不仅可以在Dockerfile上而且可以在镜像中存在的任何文件中使用正则表达式进行检测。作为一项额外措施,您还可以检查已知可存储凭据的文件名。
有很多事情要牢记,这可能会令人难以承受。幸运的是,这些最佳实践中的大多数已被NIST或PCI等安全标准所涵盖,许多镜像扫描工具提供了已映射到特定合规性控制的即用型策略。
11: 快速标记Kubernetes部署中的漏洞
通过扫描的镜像并不完全安全。想象一下,您扫描并部署了镜像,然后立即发现了一个新漏洞。还是假设您在给定的时刻加强了安全策略,但是那些已经运行的镜像会发生什么呢?
连续扫描镜像以达到以下目的是镜像扫描的最佳做法:
- 检测新漏洞并适应您的策略更改。
- 向适当的团队报告这些发现,以便他们尽快修复镜像。
当然,实施运行时扫描可以帮助您减轻这些漏洞的影响。让我们以CVE-2019-14287为例您可以轻松编写一些Falco规则来检测该漏洞是否已被利用。但是,必须为每个漏洞编写特定规则是一项耗时的工作,应作为最后一道防线。
因此,关于连续扫描群集中正在运行的镜像。
安全工具使用不同的策略对此进行存档,最简单的方法是每隔几个小时重新扫描一次所有镜像。理想情况下,您希望在漏洞摘要更新后立即重新扫描受影响的镜像。此外,某些工具能够存储镜像元数据,并且无需完全重新扫描即可检测新漏洞。
然后,一旦在运行中的容器中发现漏洞,则应尽快修复。这里的关键是有效报告漏洞,因此每个人都可以专注于与他们相关的信息。
实现此目标的一种方法是使用可查询的漏洞数据库,该数据库允许DevOps和安全团队在其庞大的镜像,程序包和CVE目录中进行一些排序。他们将要搜索诸如CVE年龄,是否有可用的修复程序,软件版本等参数。最后,如果可以下载这些报告并与漏洞管理团队,CISO共享(PDF /CSV),则将非常有用。
让我们用一个例子来说明。想象一下这样的查询:向我显示prod名称空间中的严重性大于高,CVE> 30天并且可以使用修复程序的所有漏洞。
借助此类漏洞报告功能,团队可以轻松地识别出他们可以实际修复的易受攻击的镜像,并可以在利用漏洞之前开始着手解决方案。
12: 选择基于SaaS的扫描解决方案
在本地解决方案上选择基于SaaS的扫描服务有很多好处:
按需和可扩展的资源:您可以首先扫描一些镜像,然后随着容器应用程序的扩展而增长;无需担心后端数据管理。
快速实施:您可以将扫描嵌入到CI/CD管道中,并在几分钟内启动并运行,而本地应用程序则需要更多时间来安装和设置。
轻松升级和维护:SaaS提供商处理补丁程序并推出不需要您手动升级的新功能更新。
无需基础设施或人员成本:您避免为拥有永久所有权的内部硬件和软件许可证付费。您也不需要现场维护和支持该应用程序。
总结
镜像扫描是Secure DevOps工作流程中的第一道防线。通过使其自动化,您可以最大程度地发挥其潜力,并在问题有机会成为问题之前发现问题。遵循镜像扫描最佳实践将帮助您将安全性嵌入其中,而不会影响您的速度。
同样,镜像扫描不是您一次应用的对象,而是工作流程中某些时刻的连续检查点,包括在构建时,在注册表上,在部署之前以及一旦容器已经运行时。
PS:本文属于翻译,原文