大家好,我是Edison。
最近在公司搭建CI流水線,涉及到容器鏡像安全的話題,形成了一個筆記,分享與你,也希望我們都能夠提高對安全的重視。
時代背景
近年來應用程序逐步廣泛運行在容器內,容器的采用率也是逐年上升。
根據 Anchore 發布的《Anchore 2021年軟件供應鏈安全報告》顯示容器的采用成熟度已經非常高了,65% 的受訪者表示已經在重度使用容器了,而其他 35% 表示也已經開始了對容器的使用:
因此,基于軟件的交付變成了基于容器鏡像的交付。
業界已經達成共識:云原生時代已經到來,如果說容器是云原生時代的核心,那么鏡像應該就是云原生時代的靈魂。鏡像的安全對于應用程序安全、系統安全乃至供應鏈安全都有著深刻的影響。
但是,容器的安全問題卻是大多數IT開發團隊所忽視的:
根據 snyk 發布的 2020年開源安全報告 中指出,在 dockerhub 上常用的熱門鏡像幾乎都存在安全漏洞,多的有上百個,少的也有數十個。具體數據如下圖所示:
不幸的是,很多應用程序的鏡像是以上述熱門鏡像作為基礎鏡像,進而將這些漏洞帶到了各自的應用程序中,增加了安全風險。
解決方式
GitLab建議我們:預防為主,防治結合的方式來提高鏡像的安全性。
所謂防,就是要在編寫 Dockerfle 的時候,遵循最佳實踐來編寫安全的Dockerfile;還要采用安全的方式來構建容器鏡像;
所謂治,即要使用容器鏡像掃描,又要將掃描流程嵌入到 CI/CD 中,如果鏡像掃描出漏洞,則應該立即終止CI/CD Pipeline,并反饋至相關人員,進行修復后重新觸發 CI/CD Pipeline。
防的最佳實踐
(1)以安全的方式構建容器鏡像
常規構建容器鏡像的方式就是 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-stageimage: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}"
(2)選擇合適且可靠的基礎鏡像
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的安全漏洞。
鏡像地址:https://catalog.redhat.com/software/containers/ubi8/dotnet-60-runtime/6182efaddd607bfc82e66343
docker pull registry.access.redhat.com/ubi8/dotnet-60-runtime:6.0-22
(3)不安裝非必要的安裝包
Dockerfile 中應該盡量避免安裝不必要的軟件包,除非是真的要用到。比如:我們習慣了直接寫?apt-get update && apt-get install xxxx。
因為,安裝非必要的軟件包除了會造成鏡像體積的增大 也會 增加受攻擊的風險程度。
(4)以非root用戶啟動容器
在 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 1001ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080CMD dotnet ${APPLICATION_DLL}
治的最佳實踐
在CI流水線中加入容器鏡像安全掃描任務
在 GitLab 中提供了容器鏡像分析器(Container-Scanning-Analyzer)來對生成的容器鏡像進行掃描,建議將其加入CI Pipeline中進行高頻率的檢查工作。
在我司的CI Pipeline中,集成了container-scanning-analyzer來掃描容器鏡像,如果掃描結果有Critical的漏洞,流水線會自動失敗,阻塞后續Job執行并發送Email提醒。下圖給出了一個簡單的示例(并非我司CI流水線完整流程):
只有當掃描結果不包含Critical的漏洞時,流水線才會被視為成功,進而允許后續操作,包括Merge開發分支到主干等。
參考資料
極狐:《GitLab DevSecOps七劍下天山之容器鏡像安全掃描》https://mp.weixin.qq.com/s/pnP0bjFdXlay42OGghUWNw
極狐:《云原生時代,如何保證容器鏡像安全?》https://blog.csdn.net/weixin_44749269/article/details/123077566
年終總結:Edison的2021年終總結
數字化轉型:我在傳統企業做數字化轉型
C#刷題:C#刷劍指Offer算法題系列文章目錄
.NET面試:.NET開發面試知識體系
.NET大會:2020年中國.NET開發者大會PDF資料