將Docker鏡像安全掃描步驟添加到CI/CD管道
使用GitlabCI和Trivy
介紹
如今,鏡像安全掃描變得越來越流行。這個想法是分析一個Docker鏡像并基于CVE數(shù)據(jù)庫尋找漏洞。這樣,我們可以在使用鏡像之前知道其包含哪些漏洞,因此我們只能在生產(chǎn)中使用“安全”鏡像。
有多種分析Docker鏡像的方法(取決于您使用的工具)??梢詮腃LI執(zhí)行安全掃描,也可以將其直接集成到Container Registry中,或者更好(在我看來),您可以將安全掃描集成到CI/CD管道中。最后一種方法很酷,因為它使我們能夠自動化流程并不斷分析所生成的圖像,從而符合DevOps的理念。
這是一個簡單的例子:

因此,今天我將向您展示如何設(shè)置集成到CI/CD管道中的鏡像安全掃描。
工具類
有多種工具可以執(zhí)行鏡像安全掃描:
- Trivy:由AquaSecurity開發(fā)。
- Anchore:由Anchore Inc.開發(fā)。
- Clair:由Quay開發(fā)。
- Docker Trusted Registry:如果您使用Docker Enterprise,尤其是Docker Trusted Registry,則可以使用直接集成在注冊表中的即用型安全掃描程序。
- Azure/AWS/GCP:如果您使用這些云提供程序之一,則可以輕松設(shè)置安全掃描。實際上,您不需要進行任何設(shè)置,只需要您的信用卡即可。:)
當(dāng)然,還有更多開放源代碼或?qū)S泄ぞ呖梢詫崿F(xiàn)該目標(biāo)。對于本教程,我將在GitlabCI管道上使用Trivy。
Trivy快速概述
Trivy是一種易于使用但準(zhǔn)確的圖像安全掃描儀。安裝非常簡單:
- $ 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
及其用法:
- $ trivy image nginx:alpine
給我們這樣的輸出:

就如此容易。
有關(guān)更多信息:Trivy的Github
添加一個簡單的Docker鏡像
為了說明將安全掃描包含在CI/CD管道中,我們需要一個Docker鏡像作為示例。我將使用該簡單的Dockerfile:
- FROM debian:buster
- RUN apt-get update && apt-get install nginx -y
這個Dockerfile非常簡單。它從正式的debian buster映像開始,并添加了nginx的安裝。
我們稍后將在CI/CD管道中構(gòu)建該映像,但是我們可以如下構(gòu)建它:
- $ docker build -t security_scan_example:latest。
現(xiàn)在,我們只需要創(chuàng)建一個Gitlab項目并將Dockerfile推送到該項目中即可。
創(chuàng)建一個簡單的CI/CD管道
現(xiàn)在,我們已經(jīng)為示例鏡像創(chuàng)建了Dockerfile,我們可以創(chuàng)建CI/CD管道來構(gòu)建鏡像并使用Trivy對其進行掃描。
毫不奇怪,由于我們正在使用Gitlab,因此我們將在我們的CI/CD管道中使用GitlabCI。首先,讓我們添加構(gòu)建部分:
- 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
該作業(yè)在基于docker:stable映像的容器上運行。它基于我們之前推送的Dockerfile構(gòu)建項目的映像,然后將映像推送到Gitlab容器注冊表中。
現(xiàn)在讓我們添加有趣的部分:
- 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管道,該管道應(yīng)該在推送后自動運行。我們可以看到我們的兩個作業(yè)都成功運行了:

讓我們看一下安全掃描作業(yè):

images
報告在哪里?
如您在掃描作業(yè)的結(jié)果中看到的,我們有多個漏洞,更確切地說是114個“低”和8個“中”,24個“高”和1個“嚴(yán)重”漏洞。
我們希望獲得有關(guān)這些漏洞的更多詳細(xì)信息。默認(rèn)情況下,Trivy在標(biāo)準(zhǔn)輸出中打印報告。在此示例中,我們告訴trivy將報告輸出到文件中,并根據(jù)該文件創(chuàng)建了作業(yè)工件。因此,該報告可按以下方式下載:

images
下載后,我們可以查看報告以獲取更多詳細(xì)信息:

images
我們可以看到我們有更多有關(guān)掃描程序發(fā)現(xiàn)的漏洞的信息,例如受影響的庫/二進制文件,CVE ID,嚴(yán)重性,可能的修復(fù)程序等。
現(xiàn)在怎么辦 ?
好的,現(xiàn)在我們已經(jīng)將鏡像掃描集成到CI / CD管道中,現(xiàn)在的問題是如何處理這些信息?
當(dāng)前,安全掃描作業(yè)永遠(yuǎn)不會失敗,因為trivy命令默認(rèn)情況下返回0。如果鏡像“不安全”,則使工作失敗,否則,則可以使工作成功,從而改善這種情況。
問題是,什么時候失敗?顯然,我們不能簡單地說“每當(dāng)發(fā)現(xiàn)一個漏洞時就會失敗”,因為我們的映像很可能至少會存在一些漏洞。答案很難說,因為它取決于您要實現(xiàn)的安全級別。通常,我們希望盡可能避免嚴(yán)重漏洞。答案還取決于您獲得的漏洞。您能忽略其中一些嗎?這取決于您。這就是為什么與安全團隊持續(xù)合作可以從這些掃描中受益匪淺的原因。
對于此示例,如果我們只有一個嚴(yán)重漏洞,我們將使我們的CI/CD管道失敗,否則將成功。
幸運的是,trivy允許我們使用“嚴(yán)重性”選項僅查找特定嚴(yán)重性的漏洞。我們還可以借助“退出代碼”選項來處理退出代碼,告訴trivy如果發(fā)現(xiàn)一個漏洞,則返回1,否則返回0。
因此,如果發(fā)現(xiàn)一個或多個“關(guān)鍵”漏洞,我們將更改掃描作業(yè)以使其失敗,例如:
- script:
- - trivy --no-progress --output scanning-report.json $CI_REGISTRY_IMAGE:latest
- - trivy --exit-code 1 --no-progress --severity CRITICAL $CI_REGISTR_IMAGE:latest
因此,當(dāng)執(zhí)行我們的作業(yè)時,我們?nèi)匀豢梢韵螺d完整的報告,但是這次,CI/CD作業(yè)將成功還是失敗,這取決于trivy是否發(fā)現(xiàn)了嚴(yán)重漏洞:
最后一步……
好的,我們的CI/CD管道看起來很棒!我們需要處理最后一件事……
目前,僅在構(gòu)建/推送圖像時才對其進行分析。這很酷,但不足。確實,我們的掃描工具使用的CVE數(shù)據(jù)庫每天都有新的漏洞在發(fā)展。今天的“安全”鏡像明天可能(而且很可能)不安全。因此,我們需要在第一次推送圖像后繼續(xù)對其進行掃描。
好吧,讓我們添加一個計劃的管道,比如說每晚2AM掃描鏡像。我們需要進入CI/CD->時間表->新時間表:
注意:我們使用“ security_scan”值定義了一個名為SCHEDULED_PIPELINE的變量。稍后我們將看到此變量的目的。
這樣做,我們的管道將被完全執(zhí)行,包括構(gòu)建部分。這不是我們真正想要的。因此,我們將修改gitlabCI文件,以使計劃的管道僅執(zhí)行掃描作業(yè)。
我們將添加一個額外的掃描作業(yè),其中包含與上一個作業(yè)完全相同的定義,并帶有一個額外的“only”選項,使其僅在變量SCHEDULED_PIPELINE(我們先前在計劃的管道中定義)等于“ scanning_scan”時才可執(zhí)行。為了避免代碼冗余,我們將使用作業(yè)模板。
因此,我們最終的gitlabCI文件如下所示:
- .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"
這樣,當(dāng)我們推送一些代碼時,我們的標(biāo)準(zhǔn)管道(構(gòu)建+掃描)將正常執(zhí)行,而調(diào)度的管道將每天凌晨2點執(zhí)行安全掃描作業(yè)。
我們?nèi)绾谓鉀Q這些漏洞?
通常,通過升級映像。在我們的情況下,我們可能會升級基礎(chǔ)映像(或者可能使用另一個鏡像,例如Alpine)或升級我們安裝的nginx。
另一個答案可能是通過刪除映像中不必要的內(nèi)容,無論如何構(gòu)建docker映像都是一個好習(xí)慣。安全掃描可以幫助您檢測實際未使用的組件。
在我們的情況下,讓我們更改基本圖像并改為使用Alpine:
- FROM alpine:3.12RUN apk update && apk add nginx -y
這次,我們的管道成功了……:
……沒有一個漏洞。
結(jié)論
因此,我們已經(jīng)看到了如何將安全掃描作業(yè)集成到GitlabCI管道中,這非常簡單(至少使用Trivy)。當(dāng)然,在我的示例中,我在單個master分支中完成了所有操作。在現(xiàn)實世界中,我們將進行多分支項目,這需要進行一些調(diào)整。