將Docker鏡像安全掃描步驟添加到CI/CD管道
使用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是一種易于使用但準確的圖像安全掃描儀。安裝非常簡單:
- $ 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
給我們這樣的輸出:

就如此容易。
有關更多信息: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管道中構建該映像,但是我們可以如下構建它:
- $ docker build -t security_scan_example:latest。
現在,我們只需要創建一個Gitlab項目并將Dockerfile推送到該項目中即可。
創建一個簡單的CI/CD管道
現在,我們已經為示例鏡像創建了Dockerfile,我們可以創建CI/CD管道來構建鏡像并使用Trivy對其進行掃描。
毫不奇怪,由于我們正在使用Gitlab,因此我們將在我們的CI/CD管道中使用GitlabCI。首先,讓我們添加構建部分:
- 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容器注冊表中。
現在讓我們添加有趣的部分:
- 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。
因此,如果發現一個或多個“關鍵”漏洞,我們將更改掃描作業以使其失敗,例如:
- 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文件如下所示:
- .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:
- FROM alpine:3.12RUN apk update && apk add nginx -y
這次,我們的管道成功了……:
……沒有一個漏洞。
結論
因此,我們已經看到了如何將安全掃描作業集成到GitlabCI管道中,這非常簡單(至少使用Trivy)。當然,在我的示例中,我在單個master分支中完成了所有操作。在現實世界中,我們將進行多分支項目,這需要進行一些調整。