大家好,我是Edison。
最近在公司搭建CI流水線,涉及到容器映象安全的話題,形成了一個筆記,分享與你,也希望我們都能夠提高對安全的重視。
近年來應用程式逐步廣泛執行在容器內,容器的採用率也是逐年上升。
根據 Anchore 釋出的《Anchore 2021年軟體供應鏈安全報告》顯示容器的採用成熟度已經非常高了,65% 的受訪者表示已經在重度使用容器了,而其他 35% 表示也已經開始了對容器的使用:
因此,基於軟體的交付變成了基於容器映象的交付。
業界已經達成共識:雲原生時代已經到來,如果說容器是雲原生時代的核心,那麼映象應該就是雲原生時代的靈魂。映象的安全對於應用程式安全、系統安全乃至供應鏈安全都有著深刻的影響。
但是,容器的安全問題卻是大多數IT開發團隊所忽視的:
根據 snyk 釋出的 2020年開源安全報告 中指出,在 dockerhub 上常用的熱門映象幾乎都存在安全漏洞,多的有上百個,少的也有數十個。具體資料如下圖所示:
不幸的是,很多應用程式的映象是以上述熱門映象作為基礎映象,進而將這些漏洞帶到了各自的應用程式中,增加了安全風險。
GitLab(極狐)建議我們:預防為主,防治結合的方式來提高映象的安全性。
所謂防,就是要在編寫 Dockerfle 的時候,遵循最佳實踐來編寫安全的Dockerfile;還要採用安全的方式來構建容器映象;所謂治,即要使用容器映象掃描,又要將掃描流程嵌入到 CI/CD 中,如果映象掃描出漏洞,則應該立即終止CI/CD Pipeline,並反饋至相關人員,進行修復後重新觸發 CI/CD Pipeline。
常規構建容器映象的方式就是 docker build,這種情況需要使用者端要能和 docker守護行程進行通訊。對於雲原生時代,容器映象的構建是在 Kubernetes 叢集內完成的,因此容器的構建也常用 dind(docker in docker)的方式來進行。
眾所周知,dind 需要以 privilege 模式來執行容器,需要將宿主機的 /var/run/docker.sock 檔案掛載到容器內部才可以,否則會在 CI/CD Pipeline構建時收到錯誤。
為了解決這個問題,可以使用一種更安全的方式來構建容器映象,也就是使用 kaniko。kaniko是谷歌釋出的一款根據 Dockerfile 來構建容器映象的工具。kaniko 無須依賴 docker 守護行程即可完成映象的構建。其和GitLab CI/CD的整合也是非常方便的,只需要在GitLab CI/CD 中嵌入即可,下面是在我司CI Pipeline中的實踐:
variables: EXECUTOR_IMAGE_NAME: "gcr.io/kaniko-project/executor" EXECUTOR_IMAGE_VERSION: "debug" docker-build-job: stage: docker-build-stage image: name: "$EXECUTOR_IMAGE_NAME:$EXECUTOR_IMAGE_VERSION" entrypoint: [""] rules: - if: '$IMAGE_SOURCE_BUILD != "" &&$BUILD_DOCKER_IMAGE == "true" && $CI_PIPELINE_SOURCE !="merge_request_event"' script: - |- KANIKO_CONFIG="{\"auths\":{\"$CI_REGISTRY_IMAGE\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" echo "${KANIKO_CONFIG}" >/kaniko/.docker/config.json - mkdir release - cp -r Build/* release/ - | /kaniko/executor \ --context "${CI_PROJECT_DIR}" \ --dockerfile "Dockerfile" \ --destination"${CI_REGISTRY_IMAGE}:${BUILD_TAG}"
Dockerfile 的第一句通常都是 FROM some_image,也就是基於某一個基礎映象來構建自己所需的業務映象,基礎映象通常是應用程式執行所需的語言環境,比如.NET、Go、Java、PHP等,對於某一種語言環境,一般是有多個版本的。
我司主要使用的是.NET,而原生微軟的ASP.NET 6.0映象(mcr.microsoft.com/dotnet/aspnet:6.0)有5個Critical的安全漏洞,一般不建議採用。根據Global專案組的實踐,建議採用RedHat提供的.NET 6.0執行時映象,該映象由RedHat維護,定期在更新(最新更新是一週前),目前無Critical的安全漏洞。
映象地址:點此瀏覽
docker pull registry.access.redhat.com/ubi8/dotnet-60-runtime:6.0-22
Dockerfile 中應該儘量避免安裝不必要的軟體包,除非是真的要用到。比如:我們習慣了直接寫 apt-get update && apt-get install xxxx。
因為,安裝非必要的軟體包除了會造成映象體積的增大 也會 增加受攻擊的風險程度。
在 Linux 系統中,root使用者意味著超級許可權,能夠很方便的管理很多事情,但是同時帶來的潛在威脅也是巨大的,用 root 身份執行的破壞行動,其後果是災難性的。在容器中也是一樣,需要以非root 的身份執行容器,通過限制使用者的操作許可權來保證容器以及執行在其內的應用程式的安全性。在 sysdig 釋出的《Sysdig 2021年容器安全和使用報告》中顯示,58% 的容器在以 root 使用者執行。足以看出,這一點並未得到廣泛的重視。
因此,建議在Dockerfile中新增命令來讓容器以非root使用者身份啟動,在我司的CI Pipeline中的實踐:
...... USER 0 RUN chown -R 1001:0/opt/app-root && fix-permissions /opt/app-root # No root should run USER 1001 ENV ASPNETCORE_URLS=http://+:8080 EXPOSE 8080 CMD dotnet ${APPLICATION_DLL}
備註:上面的${APPLICATION_DLL}是在映象打包階段由流水線通過引數傳遞給Dockerfile的。
治的最佳實踐就是:在CI流水線中加入容器映象安全掃描任務。
在 GitLab 中提供了容器映象分析器(Container-Scanning-Analyzer)來對生成的容器映象進行掃描,建議將其加入CI Pipeline中進行高頻率的檢查工作。在我司的CI Pipeline中,整合了container-scanning-analyzer來掃描容器映象,如果掃描結果有Critical的漏洞,流水線會自動失敗,阻塞後續Job執行並行送Email提醒。下圖給出了一個簡單的範例(並非我司CI流水線完整流程):
只有當掃描結果不包含Critical的漏洞時,流水線才會被視為成功,進而允許後續操作,包括Merge開發分支到主幹等。
極狐:《GitLab DevSecOps七劍下天山之容器映象安全掃描》
極狐:《雲原生時代,如何保證容器映象安全?》