乐竞体育

GitLab技术分享云原生时代保证容器镜像安全分几步?

发表时间:2022-08-11 07:58:16 | 作者:乐竞体育

  Docker 的出现改变了应用程序的运行方式与交付模式:应用程序运行在容器内而软件的交付变成了容器镜像的交付 。随着这几年云原生的火热,容器的采用率也是逐年上升。根Anchore发布的《Anchore 2021 年软件供应链安全报告》显示容器的采用成熟度已经非常高了,65% 的受访者表示已经在重度使用容器了,而其他 35% 表示也已经开始了对容器的使用:

  但是,容器的安全问题却不容乐观,由于容器是由容器镜像生成的,如何保证容器的安全,在很大程度上取决于如何保证容器镜像的安全。而对于容器镜像安全的保证,可以秉承预防为主,防治结合的理念来进行。所谓防,就是要在编写 Dockerfle 的时候,遵循最佳实践来编写安全的 Dockerfile;还要采用安全的方式来构建容器镜像;所谓治,即要使用容器镜像扫描,又要将扫描流程嵌入到 CI/CD 中,如果镜像扫描出漏洞,则应该立即终止 CI/CD Pipeline,并反馈至相关人员,进行修复后重新触发 CI/CD Pipeline。

  Dockerfile 的第一句通常都是 FROM some_image,也就是基于某一个基础镜像来构建自己所需的业务镜像,基础镜像通常是应用程序运行所需的语言环境,比如 Go、Java、PHP 等,对于某一种语言环境,一般是有多个版本的。以 Golang 为例,既有golang:1.12.9,也有golang:1.12.9-alpine3.9,不同版本除了有镜像体积大小的区别,也会有安全漏洞数量之别。上述两种镜像的体积大小以及所包含的漏洞数量(用 trivy 扫描)对比如下:

  可以看到 golang:1.12.9-alpine3.9 比 golang:1.12.9 有更小的镜像体积(351MB vs 814MB),更少的漏洞数量(24 vs 1306)。所以,在选取基础镜像的时候,要做出正确选择,不仅能够缩小容器镜像体积,节省镜像仓库的存储成本,还能够减少漏洞数量,缩小受攻击面,提高安全性。

  在 Linux 系统中,root 用户意味着超级权限,能够很方便地管理很多事情,但是同时带来的潜在威胁也是巨大的,用 root 身份执行的破坏行动,其后果是灾难性的。在容器中也是一样,需要以非root的身份运行容器,通过限制用户的操作权限来保证容器以及运行在其内的应用程序的安全性。在Dockerfile中可以通过添加如下的命令来以非 root 的身份启动并运行容器:

  sysdig发布的《Sysdig 2021 年容器安全和使用报告》中显示,58%的容器在以root 用户运行。足以看出,这一点并未得到广泛的重视。

  很多用户在编写 Dockerfile 的时候,习惯了直接写 apt-get&& apt-get install xxxx,网上也有很多这样的例子(包括 GitHub)。用户需要清楚 xxx 这个包是否真的要用,否则这种情况会造成镜像体积的变大以及受攻击面的增加。

  以 ubuntu:20.04为例来演示安装 vim curl telnet 这三个常用软件包,给镜像体积以及漏洞数量带来的影响:

  可以看出,因为安装了 vim curl telnet 这三个常见的软件包,导致镜像体积增加了一倍(从72.4MB到158MB),漏洞数量翻了接近一番(从60到119)。因此,在编写 Dockerfile 的时候,一定要搞清楚哪些包是必须安装的,而哪些包是非必需安装的。不要认为apt-get install 使用起来很爽就都安装。

  多阶段构建不仅能够对于容器镜像进行灵活的修改,还能够在很大程度上减小容器镜像体积,减少漏洞数量(这个第一点有异曲同工之妙)。

  由于镜像构建的灵活性和便捷性,任何人都可以构建容器镜像并推送至 Dockerhub供其他人使用。所以在搜索某一个镜像的时候,会出现很多类似的结果,这时候就需要仔细辨别:镜像是否有官方提供的,镜像是否一直有更新,镜像是否可以找到对应的Dockerfile 来查看到底是如何构建的。信息不全且长时间无更新的镜像,其安全性无法得到保证,不应该使用此类镜像,这时候可以选择自己使用这些规则来构建可用的安全镜像。

  当然,除此以外,还有很多编写Dockerfile的最佳时间,诸如不要把敏感信息编写在 Dockerfile并构建在镜像中,避免敏感信息造成泄漏;要用工具(如Hadolint)来对 Dockerfile进行扫描,以发现Dockerfile编写过程中的一些问题等等。遵循上述最佳实践编写的用于后续演示所用的Dockerfile 如下:

  良好的 Dockerfile 编写习惯是保证容器镜像安全的第一步,接下来还需要用安全的方式来构建容器镜像。

  常规构建容器镜像的方式就是 docker build,这种情况需要客户端要能和docker守护进程进行通信。对于云原生时代,容器镜像的构建是在 Kubernetes 集群内完成的,因此容器的构建也常用dind(docker in docker)的方式来进行。比如在前面所有文章的 Demo 演示中,镜像的构建通常用如下代码:

  Kaniko是谷歌发布的一款根据 Dockerfile 来构建容器镜像的工具。Kaniko无须依赖 docker守护进程即可完成镜像的构建。其和极狐GitLab CI/CD 的集成也是非常方便的,只需要在极狐GitLab CI/CD中嵌入如下代码即可:

  其中并没有 /var/run/docker.sock 相关的配置。这说明使用kaniko来构建容器镜像,并不需要与docker守护进程进行通信,所以是以一种更安全的方式完成了容器的构建。

  在遵从最佳实践编写 Dockerfile、用 Kaniko 构建容器之后,还需要对容器镜像做安全扫描,进一步确保容器镜像安全。而极狐GitLab有开箱即用的DevSecOps功能,其中就包含容器镜像扫描,可以很容易地在极狐GitLab CI/CD 中把容器镜像扫描集成进去,以下几行简单命令就可以实现:

  • include:极狐GitLab CI/CD 关键字,用来将一些“通用”的 CI/CD 代码以 template 的形式在 CI/CD Pipeline 中使用,对于用户来讲使用是非常方便的,而且还能够让整体 CI/CD 的代码行数得到有效控制,易读性也增加了。

  可以很容易的将镜像构建、镜像扫描集成到极狐GitLab CI/CD Pipeline中,代码如下:

  等研发人员根据 issue 进行相应的修复之后,再次提交MR会继续看到扫描结果,以查看修复是否成功,如果成功修复,则可合并此MR。

  这种将镜像安全扫描嵌入到CI/CD中,能够做到持续自动化;将安全与研发工作流结合起来,能够做到安全漏洞可视化,方便研发人员第一时间修复漏洞,做到了真正的“安全左移”。这也是极狐GitLab一体化DevSecOps的真正优势:助力用户在一个平台上高效、安全地交付软件。返回搜狐,查看更多