新浪新闻客户端

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

GitLab技术分享|云原生时代,保证容器镜像安全分几步?
2022年03月09日 21:04 新浪网 作者 车疾无疆

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

  

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

  下面我们就从即防又治的角度来讲述如何确保容器镜像安全。

  一、遵从最佳实践,编写 Dockerfile

  1、选择合适的基础镜像

  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)。所以,在选取基础镜像的时候,要做出正确选择,不仅能够缩小容器镜像体积,节省镜像仓库的存储成本,还能够减少漏洞数量,缩小受攻击面,提高安全性。

  2、以非root用户启动容器

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

  RUN addgroup -S jh && adduser -S devsecops -G jhUSER devsecops

  上述命令创建了一个名为jh的Group,一个名为devsecops的用户,并将用户 devsecops添加到了jh Group下,最后以devsecops启动容器。

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

  3、不安装非必要的软件包

  很多用户在编写 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 使用起来很爽就都安装。

  针对其他操作系统的包管理器存在同样的问题,诸如apk add,yum install等。

  4、采用多阶段构建

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

  5、选择来源可靠且经常更新的镜像

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

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

  FROM golang:1.12.9-alpine3.9 as builderWORKDIR /tmpCOPY main.go /tmpRUN go build main.goFROM alpine:latestWORKDIR /usr/src/app/RUN addgroup -S jh && adduser -S devsecops -G jhCOPY --from=builder --chown=devsecops:devsecops /tmp/main /usr/src/app/USER devsecopsCMD ["./main"]

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

  二、用安全的方式构建容器镜像

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

  build:image: docker:lateststage: buildservices:-docker:20.10.7-dindscript:-docker login -u"$CI_REGISTRY_USER"-p"$CI_REGISTRY_PASSWORD"$CI_REGISTRY-docker build -t$CI_REGISTRY_IMAGE:1.0.0.-docker push$CI_REGISTRY_IMAGE:1.0.0

  众所周知,dind 需要以 privilege 模式来运行容器,需要将宿主机的/var/run/docker.sock 文件挂载到容器内部才可以,否则会在 CI/CD Pipeline 构建时收到如下错误:

  

  因此在使用自建 Runner 的时候,往往都需要挂在/var/run/docker.sock,诸如在使用 K3s 来运行极狐GitLab Runner 的时候,就需要在 Runner 的配置文件中添加以下内容:

  [[runners.kubernetes.volumes.host_path]]name="docker"mount_path="/var/run/docker.sock"host_path="/var/run/docker.sock"

  为了解决这个问题,可以使用一种更安全的方式来构建容器镜像,也就是使用kaniko。

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

  代码块说明:

  •image.name:kaniko 的镜像名称,官方镜像为 gcr.io/kaniko-project/executor:debug,本文为了加速构建过程,将此镜像托管在极狐GitLab SaaS上,地址如上述代码块所示;

  •script:首先需要创建一个/kaniko/.docker 目录,用来存放登录容器镜像仓库所需的凭证,接下来就是将镜像仓库的登录凭证以 config.json 的格式存放在/kaniko/.docker 目录下,最后使用/kaniko/exector 命令来构建容器镜像。

  CI/CD Pipeline 的构建日志如下:

  

  上述整个过程是在用 K3s 拉起的极狐GitLab Runner 实例上面运行的此次构建,Runner 的信息可以在 Project --> Settings --> CI/CD --> Runners 里面看到:

  

  在构建日志中也可以看到,此次构建是在 K3s 上运行的 Runner 上进行的:

  

  而用 K3s 来安装极狐GitLab Runner 的配置文件如下:

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

  三、使用容器镜像扫描

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

  include:- template: Security/Container-Scanning.gitlab-ci.ymlcontainer_scanning:stage:testtags:-k3svariables:DOCKER_IMAGE:$CI_REGISTRY_IMAGE:1.0.0

  参数说明:

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

  •tags:指定此次构建需要在哪个 Runner 上执行;

  •variables.DOCKER_IMAGE:指定需要扫描的容器镜像;

  •触发 CI/CD Pipeline 之后,可以看到构建日志:

  

  •最后可以在极狐GitLab的 Security Dashboard 中看到扫描报告:

  

  四、和极狐GitLab workflow 的结合

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

  接下来只要提交 MR,就会触发构建流程,扫描的结果会显示在 MR 中:

  

  点击相应的 CVE 就能够直接创建 issue 来对此问题进行跟踪:

  

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

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

特别声明:以上文章内容仅代表作者本人观点,不代表新浪网观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与新浪网联系。
权利保护声明页/Notice to Right Holders

举报邮箱:jubao@vip.sina.com

Copyright © 1996-2024 SINA Corporation

All Rights Reserved 新浪公司 版权所有