使用GitlabCI和Trivy
介绍
如今,镜像安全扫描变得越来越流行。这个想法是分析一个Docker镜像并基于CVE数据库寻找漏洞。这样,我们可以在使用镜像之前知道其包含哪些漏洞,因此我们只能在生产中使用“安全”镜像。
有多种分析Docker镜像的方法(取决于您使用的工具)。可以从CLI执行安全扫描,也可以将其直接集成到Container Registry中,或者更好(在我看来),您可以将安全扫描集成到CI/CD管道中。最后一种方法很酷,因为它使我们能够自动化流程并不断分析所生成的图像,从而符合DevOps的理念。
这是一个简单的例子:
因此,今天我将向您展示如何设置集成到CI/CD管道中的镜像安全扫描。
工具类
有多种工具可以执行镜像安全扫描:
- Trivy:由AquaSecurity开发。
- Anchore:由Anchore Inc.开发。
- Clair:由Quay开发。
- Docker Trusted Registry:如果您使用Docker Enterprise,尤其是Docker Trusted Registry,则可以使用直接集成在注册表中的即用型安全扫描程序。
- Azure/AWS/GCP:如果您使用这些云提供程序之一,则可以轻松设置安全扫描。实际上,您不需要进行任何设置,只需要您的信用卡即可。:)
当然,还有更多开放源代码或专有工具可以实现该目标。对于本教程,我将在GitlabCI管道上使用Trivy。
Trivy快速概述
Trivy是一种易于使用但准确的图像安全扫描仪。安装非常简单:
代码语言:javascript复制$ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s--b / usr / local / bin $ sudo mv ./bin/trivy / usr / local / bin / trivy $ trivy --version
及其用法:
代码语言:javascript复制$ trivy image nginx:alpine
给我们这样的输出:
就如此容易。
有关更多信息:Trivy的Github
添加一个简单的Docker镜像
为了说明将安全扫描包含在CI/CD管道中,我们需要一个Docker镜像作为示例。我将使用该简单的Dockerfile:
代码语言:javascript复制FROM debian:buster
RUN apt-get update && apt-get install nginx -y
这个Dockerfile非常简单。它从正式的debian buster映像开始,并添加了nginx的安装。
我们稍后将在CI/CD管道中构建该映像,但是我们可以如下构建它:
代码语言:javascript复制$ docker build -t security_scan_example:latest。
现在,我们只需要创建一个Gitlab项目并将Dockerfile推送到该项目中即可。
创建一个简单的CI/CD管道
现在,我们已经为示例镜像创建了Dockerfile,我们可以创建CI/CD管道来构建镜像并使用Trivy对其进行扫描。
毫不奇怪,由于我们正在使用Gitlab,因此我们将在我们的CI/CD管道中使用GitlabCI。首先,让我们添加构建部分:
代码语言:javascript复制build:
stage: build
image: docker:stable
services:
- docker:dind
tags:
- docker
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest
该作业在基于docker:stable
映像的容器上运行。它基于我们之前推送的Dockerfile构建项目的映像,然后将映像推送到Gitlab容器注册表中。
现在让我们添加有趣的部分:
代码语言:javascript复制security_scan:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
services:
- docker:dind
tags:
- docker
script:
- trivy --no-progress --output scanning-report.txt $CI_REGISTRY_IMAGE:latest
artifacts:
reports:
container_scanning: scanning-report.txt
这项工作是我们的安全扫描工作。这次,它在基于Trivy官方图像的容器上运行。它基于trivy命令扫描镜像,并将报告输出到名为scanning-report.txt
的文件中
太好了!让我们看一下我们的GitlabCI管道,该管道应该在推送后自动运行。我们可以看到我们的两个作业都成功运行了:
让我们看一下安全扫描作业:
images
报告在哪里?
如您在扫描作业的结果中看到的,我们有多个漏洞,更确切地说是114个“低”和8个“中”,24个“高”和1个“严重”漏洞。
我们希望获得有关这些漏洞的更多详细信息。默认情况下,Trivy在标准输出中打印报告。在此示例中,我们告诉trivy将报告输出到文件中,并根据该文件创建了作业工件。因此,该报告可按以下方式下载:
images
下载后,我们可以查看报告以获取更多详细信息:
images
我们可以看到我们有更多有关扫描程序发现的漏洞的信息,例如受影响的库/二进制文件,CVE ID,严重性,可能的修复程序等。
现在怎么办 ?
好的,现在我们已经将镜像扫描集成到CI / CD管道中,现在的问题是如何处理这些信息?
当前,安全扫描作业永远不会失败,因为trivy命令默认情况下返回0。如果镜像“不安全”,则使工作失败,否则,则可以使工作成功,从而改善这种情况。
问题是,什么时候失败?显然,我们不能简单地说“每当发现一个漏洞时就会失败”,因为我们的映像很可能至少会存在一些漏洞。答案很难说,因为它取决于您要实现的安全级别。通常,我们希望尽可能避免严重漏洞。答案还取决于您获得的漏洞。您能忽略其中一些吗?这取决于您。这就是为什么与安全团队持续合作可以从这些扫描中受益匪浅的原因。
对于此示例,如果我们只有一个严重漏洞,我们将使我们的CI/CD管道失败,否则将成功。
幸运的是,trivy允许我们使用“严重性”选项仅查找特定严重性的漏洞。我们还可以借助“退出代码”选项来处理退出代码,告诉trivy如果发现一个漏洞,则返回1,否则返回0。
因此,如果发现一个或多个“关键”漏洞,我们将更改扫描作业以使其失败,例如:
代码语言:javascript复制script:
- trivy --no-progress --output scanning-report.json $CI_REGISTRY_IMAGE:latest
- trivy --exit-code 1 --no-progress --severity CRITICAL $CI_REGISTR_IMAGE:latest
因此,当执行我们的作业时,我们仍然可以下载完整的报告,但是这次,CI/CD作业将成功还是失败,这取决于trivy是否发现了严重漏洞:
最后一步……
好的,我们的CI/CD管道看起来很棒!我们需要处理最后一件事……
目前,仅在构建/推送图像时才对其进行分析。这很酷,但不足。确实,我们的扫描工具使用的CVE数据库每天都有新的漏洞在发展。今天的“安全”镜像明天可能(而且很可能)不安全。因此,我们需要在第一次推送图像后继续对其进行扫描。
好吧,让我们添加一个计划的管道,比如说每晚2AM扫描镜像。我们需要进入CI/CD->时间表->新时间表:
注意:我们使用“ security_scan”值定义了一个名为SCHEDULED_PIPELINE的变量。稍后我们将看到此变量的目的。
这样做,我们的管道将被完全执行,包括构建部分。这不是我们真正想要的。因此,我们将修改gitlabCI文件,以使计划的管道仅执行扫描作业。
我们将添加一个额外的扫描作业,其中包含与上一个作业完全相同的定义,并带有一个额外的“only”选项,使其仅在变量SCHEDULED_PIPELINE(我们先前在计划的管道中定义)等于“ scanning_scan”时才可执行。为了避免代码冗余,我们将使用作业模板。
因此,我们最终的gitlabCI文件如下所示:
代码语言:javascript复制.scanning-template: &scanning-template
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
services:
- docker:dind
tags:
- docker
script:
- trivy --no-progress --output scanning-report.json $CI_REGISTRY_IMAGE:latest
- trivy --exit-code 1 --no-progress --severity CRITICAL $CI_REGISTR_IMAGE:latest
artifacts:
reports:
container_scanning: scanning-report.json
build:
stage: build
image: docker:stable
services:
- docker:dind
tags:
- docker
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest
except:
variables:
- $SCHEDULED_PIPELINE
security_scan:
<<: *scanning-template
except:
variables:
- $SCHEDULED_PIPELINE
security_scan:on-schedule:
<<: *scanning-template
only:
variables:
- $SCHEDULED_PIPELINE == "security_scan"
这样,当我们推送一些代码时,我们的标准管道(构建 扫描)将正常执行,而调度的管道将每天凌晨2点执行安全扫描作业。
我们如何解决这些漏洞?
通常,通过升级映像。在我们的情况下,我们可能会升级基础映像(或者可能使用另一个镜像,例如Alpine)或升级我们安装的nginx。
另一个答案可能是通过删除映像中不必要的内容,无论如何构建docker映像都是一个好习惯。安全扫描可以帮助您检测实际未使用的组件。
在我们的情况下,让我们更改基本图像并改为使用Alpine:
代码语言:javascript复制FROM alpine:3.12RUN apk update && apk add nginx -y
这次,我们的管道成功了……:
……没有一个漏洞。
结论
因此,我们已经看到了如何将安全扫描作业集成到GitlabCI管道中,这非常简单(至少使用Trivy)。当然,在我的示例中,我在单个master分支中完成了所有操作。在现实世界中,我们将进行多分支项目,这需要进行一些调整。