背景
近年来,在与DevOps相关的技术讨论中,DevSecOps
和Shift Left Testing
经常被提及。其中,容器扫描是一个非常重要的主题。一般来说,我们会在CI/CD流水线中添加容器镜像扫描的步骤。当容器镜像构建完成后,立即执行安全漏洞扫描。如果扫描到高风险的安全漏洞,就将CI/CD流水线标记为失败;如果没有发现具有威胁性的安全漏洞,就允许部署到正式环境中。然而,从信息安全防御的角度来看,这样做真的足够吗?
答案当然是否定的,那么要怎么做会相对更安全,并且让 DevSecOps 融入在开发流程的基因中,就是这篇文章想要探讨的,文章会从什么是 Container Scanning 开始谈起,然后讨论 Container 的生命周期,借此了解在每一个阶段如何确保 Container 的安全无虞,最后提到当在 Container 中发现到安全弱点时该如何处置
容器扫描是什么?
所谓的容器扫描,就是利用扫描工具来发现容器中隐藏的安全漏洞的过程。那么具体的做法和安全弱点通常会隐藏在哪里呢?
机制
相关扫描工具的具体做法是通过逐层分析容器镜像来找出其中隐藏的安全弱点。大部分的扫描工具会将常见漏洞和披露的公开信息与找到的安全弱点进行关联,例如国家漏洞数据库(NVD)和各大常见漏洞与公开漏洞(CVE)数据库。这样一来,扫描工具的使用者可以方便地进行后续的信息安全弱点管理工作
冷知识
💡️ 通用漏洞和公开漏洞(CVE):用于定义在软件/固件中发现的信息安全漏洞的标准,它本身并不是一个直接可用的信息安全漏洞数据库
💡️ CVE编号机构(CNA):是授予发现的CVE一个ID的机构,这个ID也被称为CVE标识符,例如我们经常看到的Log4j 2 CVE-2021–44228
💡️通用漏洞评分系统(CVSS):是一套用于定义CVE严重程度分级的标准。该系统的分数范围从0.0到10.0,分数越高标识漏洞越严重。
📚 容器漏洞来源
安全漏洞可以通过以下几种方式进入到容器中:
🐛从基础镜像以及安装和使用的软件和配置开始
🐛从运行机器的操作系统
🐛来自容器的网络或存储设备
🐛Container本身指的是在同一台主机上运行的容器之间的交互作用所产生的操作系统
不同的容器存在不同的安全漏洞来源,因此需要采用不同的方法来发现。例如,常用的容器镜像扫描工具就是针对基础镜像和所使用的软件中的安全漏洞进行扫描。它也是容器安全的关键点,因为它可以让开发、运维和安全团队在将容器化应用部署到正式环境之前排除威胁
容器镜像生命周期
那么,要怎么样才能用扫描工具彻底检查和修复容器呢?当然是将其嵌入到容器镜像的生命周期中,不仅仅限于CI/CD。为什么要这样做呢?难道只要扫描一下就可以了吗?原因在于高风险的CVE随时可能出现,因此在容器生命周期的每个环节都必须具备排除和监控安全漏洞的能力,而且越早越好,以避免恶意人员在正式环境中利用任何安全漏洞
📚 发展
瞎咪!在开发的时候就可以开始扫描吗?没错,当然就是利用开发人员所使用的 IDE,以目前广受欢迎的 Visual Studio Code 来说,已经有一些安全弱点扫描Extension
在市面上推出,所以在写程序的当下要是引用到具有有安全弱点的函式库时,开发人员马上就会知晓;Docker Desktop 的 Command 也可以在本机端就先扫描建置出来的 Container Image
🛠️jFrog Xray
🛠️Snyk Code
🛠️Trivy漏洞扫描器
🛠️Docker Desktop(docker scan)与Snyk集成
🛠 Haskell Dockerfile Linter
️ 🛠 … ️ 🛠 …
当代码被推送到版本控制系统时,实际上可以自动进行安全漏洞扫描。换句话说,在代码构建成容器镜像之前,就可以发现安全漏洞
🛠️ GitHub
🛠️ GitLab
🛠️ …
📚 构建
DevSecOps经常被提及(正如文章一开始所提到的),它指的是将容器扫描添加到CI/CD流程中。根据目前的主流做法,这个扫描通常会在容器镜像注册表上执行。也就是说,当CI工具构建出容器镜像后,会将其推送到容器镜像注册表进行安全漏洞扫描,然后根据扫描结果进行相应的处理
一种相对严谨的做法是将同一个容器镜像的注册表拆分为测试环境和正式环境两个注册表。CI工具首先将构建完成的容器镜像推送到测试环境的容器镜像注册表进行安全漏洞扫描,待确认扫描结果出来并确认没有高威胁性的安全漏洞后,CI工具再将容器镜像推送到正式环境的容器镜像注册表
部署
CI工具完成Container Image的构建后,下一步是通过CD工具将其部署到Kubernetes或其他容器编排平台(这里以最常用的K8s为例)。在K8s将Pod启动之前,有没有可能排除掉存在安全漏洞的Container Image,防止其成功运行?对于K8s来说,可以利用Admission Controller来实现,因为它可以拦截发送到K8s API的任何请求。因此,可以开发一个Admission Controller来与Image Scanner进行确认,当K8s准备运行的Container Image存在高风险的安全漏洞时,就不允许其成功运行
然而,需要注意的是,不是等待 Admission Controller 通知 Container Image Scanner 才进行扫描,这里只是确认扫描结果是否符合预期。建议整合类似 Open Policy Agent (OPA) 的角色来实现,使得 K8s 运行容器镜像的策略更加灵活,而不仅仅遵循容器镜像扫描器的结果。例如,可以针对开发环境和正式环境的 K8s 集群给予不同的策略,开发环境可以采用相对宽松的政策,而正式环境则需要相对严格的政策
不过这样不对啊,在上一个容器镜像构建步骤时不是已经针对具有高风险安全弱点的容器镜像做过相应的处理了,为什么这边还需要再确认一次?因为在某些紧急情况下,手动部署有可能还是免不了,所以当没有通过CD工具部署容器镜像到正式环境的K8s集群时,就会需要通过此种方式才能够阻挡安全弱点跑到正式环境中
📚 运行
通过前面层层关卡开始运行的容器还是有可能具有安全弱点,只是还没有被发现而已。例如,原本使用的某些函数库突然爆发出高风险的安全弱点,这时就必须要赶紧去修复它,因为在正式环境上存在高风险弱点是可以被恶意人士利用的
所以即使已经部署到K8s集群内的容器镜像,也需要随时进行扫描。也就是说,不仅仅是将容器扫描放入CI/CD流程中的Shift Left Testing,还需要达到持续的容器扫描/监控的境界。实际上,许多网络安全公司已经推出了类似的商业解决方案。如果可以接受开源软件的话,可以尝试一下Trivy Operator
漏洞管理
在讨论如何发现容器镜像的安全漏洞之后,我们应该如何处理这些漏洞呢?而且如果同时爆发多个CVE,我们应该先修复哪一个呢?这时候就需要有人来进行安全漏洞管理的工作,也就是所谓的漏洞管理。通常,漏洞管理分为四个阶段来处理安全漏洞,它们分别是..
📚 证件识别
其实就是上面提到的容器扫描,不过组织内可能不止使用容器解决方案,或是具有不同的运行环境(本地部署 vs. 云端),所以会在不同的地方部署许多不同种类的扫描器。这个阶段主要就是通过这些扫描器将安全漏洞给找出来。
📚 优先级排序
在组织内部拥有众多系统的情况下,同时存在多个安全漏洞在不同环境中是很常见的现象。每个漏洞的威胁程度也不同,因此需要根据安全漏洞对组织的风险进行优先级排序并进行修复。简单来说,可以通过CVSS分数来确定优先级,但仍需基于组织内部的实际需求做出决策。例如,某个CVE的CVSS分数为10.0,但只存在于内部使用的次要系统中,而不是在正式环境的机器中。这种情况下,修复的优先级当然会被排在后面一些。
📚 治疗
在确定了优先权之后,接下来就是开始进行安全漏洞的修复工作。通常有以下几种做法:
🛠️ 升级/补丁:如果不是大版本升级的话,这应该是最轻松的修复方式,所以可以按照一般软件的升级政策,先从测试环境开始升级,确保没有问题后再将正式环境升级
🛠️ 解决方案:有时候可能没有可供升级的新版本,这时候就会通过一些绕过安全漏洞的方法,比如修改配置、关闭某个端口等方式,来防止安全漏洞被利用
🛠️ 未修复:在某些情况下,修复的方式是接受该安全漏洞的存在,前提是大家都了解并接受该安全漏洞不会对组织造成威胁
📚 验证与报告
就像程序需要测试一样,修复好之后当时需要验证安全弱点是不是真的消失了,这个步骤其实没有想象中的容易,因为不同公司不同团队所提供的服务都有其自有的生态系,所有都要先了解其如何运行与使用才能够进行有的验证
容器构建最佳实践
除了在容器的每个生命周期中发现其中隐藏的安全漏洞之外,最后当然也要提一下,如何减少引入容器中的安全漏洞的一些做法(容器构建最佳实践实际上无法用一篇文章来详尽阐述,这里主要专注于如何减少从第三方引入安全漏洞的做法)
📚️正确的基础镜像
首先当然要选择正确的基础镜像,不过什么叫做正确的?
🛠️ 小伙伴们,如果不需要的东西就别让它存在于容器镜像中了。所以,请尽量选择更小的容器镜像作为基础镜像,这样会更好哦
🛠️ 可信赖:选择官方发布的容器镜像作为基础镜像,而不是来源不明的容器镜像,以避免安全漏洞在 Dockerfile 的第一行就被引入
🛠️ Distroless: 考虑使用 Distroless 容器镜像作为基础镜像,这种类型的容器镜像不包含软件包管理器、Shell 或任何其他程序,换句话说,对人类来说使用起来很不方便XD
🛠️ 分类:测试和正式环境使用不同的基础镜像,理论上测试环境的容器镜像会需要更多的开发工具,但正式环境并不需要,因此正式环境的基础镜像可以将这些开发工具移除掉
📚 使用多阶段构建
Docker有一个名为Multi-Stage Builds的功能,它允许开发者同时使用多个容器镜像来构建构件,并且可以选择性地从特定的容器镜像中复制所需的构件。在构建过程中使用完所需的工具后即可丢弃,以确保你的最终容器镜像保持最小的容量
📚 图层排序
最后一点与减少安全弱点无关,但可以加快安全弱点的扫描效率。就是尽量将会对Container Image造成巨大改变的指令移动到Dockerfile后面才执行,例如编译完的最终执行文件,然后不会改变的部分放置在Dockerfile前面执行。这样可以让Container Image Cache发挥最大效用,加快Container Image的建置速度,同时也会提升Container Scanning的效率
结论
自己觉得管理信息安全弱点就是将风险控制在公司可接受的合理范围内,所以尽量利用所拥有的资源去做最大努力,优先将对公司威胁性高的安全弱点给排除掉;然后一定要将Container Scanning的工作给自动化,不然就愧对DevSecOps这个Buzzword了,其实是假如没有自动化的话,这一堆事情要花的人力太多,而且也无法运行的精确与长久!