天天看點

12 個優化 Docker 鏡像安全性的技巧

作者 | Marius

譯者 | 王強

策劃 | 闫園園

本文介紹了 12 個優化 Docker 鏡像安全性的技巧。每個技巧都解釋了底層的攻擊載體,以及一個或多個緩解方法。這些技巧包括了避免洩露建構密鑰、以非 root 使用者身份運作,或如何確定使用最新的依賴和更新等。

1

前言

當你是剛開始使用 Docker 的新手時,你很可能會建立不安全的 Docker 鏡像,使攻擊者很容易借此接管容器,甚至可能接管整個主機,然後滲透到你公司的其他基礎設施中。

可以被濫用來接管你的系統的攻擊向量有很多,例如:

啟動的應用程式(在你 Dockerfile 的 ENTRYPOINT 中指定)以 root 使用者身份運作。這樣以來,一旦攻擊者利用了一個漏洞并獲得了 shell 權限,他們就可以接管 Docker 守護程式所運作的主機。

你的鏡像是基于一個過時的和 / 或不安全的基礎鏡像,其中包含(現在)衆所周知的安全漏洞。

你的鏡像包含了一些工具(如 curl、apt 等),一旦攻擊者獲得了某種通路權,就可以通過這些工具将惡意軟體加載到容器中。

下面的各個章節講解了能夠優化你的鏡像安全性的各種方法。它們是按重要性 / 影響程度排序的,也就是說排名靠前的方法更重要。

2

避免洩露建構密鑰

建構密鑰是隻在建構 Docker 鏡像時需要的憑證(不是在運作時)。例如,你可能想在你的鏡像中包含某個應用程式的一個編譯版本,這個應用的源代碼是閉源的,并且其 Git 存儲庫是有通路保護的。在建構鏡像時,你需要克隆 Git 存儲庫(這需要建構密鑰,例如該存儲庫的 SSH 通路密鑰),從源代碼建構應用程式,然後再删除源代碼(和密鑰)。

“洩露“建構密鑰是說你不小心把這種密鑰烘焙到了你的鏡像的某個層中。這種情況很嚴重,因為拉取你的鏡像的所有人都可以檢索到這些機密。這個問題源于這樣一個事實,即 Docker 鏡像是以純粹的加法方式逐層建構的。你在一個層中删除的檔案隻是被“标記”為已删除,但拉取你鏡像的人們仍然可以使用進階工具通路它們。

可以使用以下兩種方法之一來避免洩露建構密鑰。

多階段建構

Docker 多階段建構(官方文檔)有許多用例,例如加快你的鏡像建構速度,或減少鏡像大小。本系列的其他文章會詳細介紹其他用例。總之,你也可以通過多階段建構來避免洩露建構密鑰,如下所示:

建立一個階段 #A,将憑證複制到其中,并使用它們來檢索其他工件(例如上述例子中的 Git 存儲庫)和執行進一步的步驟(例如編譯一個應用程式)。階段 #A 的建構确實包含了建構的密鑰!

建立一個 #B 階段,其中你隻從 #A 階段複制非加密的工件,例如一個已編譯的應用程式。

隻釋出 / 推送階段 #B 的鏡像

BuildKit 的密鑰

背景知識

如果你使用 docker build 進行建構,可以實際執行建構的後端選項不止一個。其中較新和較快的後端是 BuildKit,你需要在 Linux 上設定環境變量 DOCKER_BUILDKIT=1 來顯式啟用它。注意,BuildKit 在 Windows/MacOS 的 Docker for Desktop 上是預設啟用的。

正如這裡的文檔所解釋的(閱讀它們以了解更多細節),BuildKit 建構引擎支援 Dockerfile 中的額外文法。要使用建構密鑰,請在你的 Dockerfile 中放入類似下面這樣的内容:

當 RUN 語句被執行時,密鑰将對這個建構容器可用,但不會将密鑰本身(這裡是:/foobar 檔案夾)放入建構的鏡像中。你需要在運作 docker build 指令時指定密鑰的源檔案 / 檔案夾(位于主機上)的路徑,例如:

不過有一點需要注意:你不能通過 docker-compose up --build 來建構需要密鑰的鏡像,因為 Docker-compose 還不支援用于建構的 --secret 參數,見 GitHub 問題。如果你依賴 docker-compose 的建構,請使用方法 1(多階段建構)。

題外話:不要推送在開發機上建構的鏡像

你應該一直在一個幹淨的環境中建構和推送鏡像(例如 CI/CD 管道),其中建構代理會将你的存儲庫克隆到一個新目錄。

使用本地開發機器進行建構的問題是,你的本地 Git 存儲庫的“工作樹“可能是髒的。例如,它可能包含有開發過程中需要的密鑰檔案,例如對中轉甚至生産伺服器的通路密鑰。如果沒有通過.dockerignore 排除這些檔案,那麼 Dockerfile 中的“COPY . .“等語句可能會意外導緻這些密鑰洩露到最終鏡像中。

3

以非 root 使用者身份運作

預設情況下,當有人通過“docker runyourImage:yourTag“運作你的鏡像時,這個容器(以及你在 ENTRYPOINT/CMD 中的程式)會以 root 使用者身份運作(在容器和主機上)。這給了一個使用某種漏洞在你的運作容器中獲得 shell 權限的攻擊者以下權力:

對主機上所有顯式挂載到容器中的目錄的無限制寫權限(因為是 root)。

能夠在容器中做 Linux 根使用者可以做的一切事情。例如,攻擊者可以安裝他們需要的額外工具來加載更多的惡意軟體,比如說通過 apt-get install(非 root 使用者無法做到這一點)。

如果你的鏡像容器是用 docker run --privileged 啟動的,攻擊者甚至可以接管整個主機。

為了避免這種情況,你應該以非 root 使用者(你在 docker build 過程中建立的一些使用者)的身份運作你的應用程式。在你的 Dockerfile 中的某個地方(通常是在結尾處)放置以下語句:

Dockerfile 中所有在 USER appuser 語句之後的指令(如 RUN、CMD 或 ENTRYPOINT)都将以這個使用者運作。這裡有一些需要注意的地方:

在切換到非 root 使用者之前,你通過 COPY 複制到鏡像中的檔案(或由某些 RUN 指令建立的檔案)是由 root 使用者擁有的,是以以非 root 使用者身份運作的應用程式無法寫入。為了解決這個問題,請把建立和切換到非 root 使用者的代碼移到 Dockerfile 的開頭。

如果這些檔案是在 Dockerfile 的開頭以根使用者身份建立的(存儲在 /root/ 下面,而不是 /home/appuser/ 下面),那麼你的程式期望在使用者的主目錄中的某個地方(例如~/.cache)的檔案,現在從應用程式的視角來看可能突然消失了。

如果你的應用程式監聽一個 TCP/UDP 端口,就必須使用大于 1024 的端口。小于等于 1024 的端口隻能以 root 使用者身份使用,或者以一些進階 Linux 能力來使用,但你不應該僅僅為了這個目的而給你的容器這些能力。

4

使用最新的基礎鏡像建構和更新系統包

如果你使用的基礎鏡像包含了某個真正的 Linux 發行版(如 Debian、Ubuntu 或 alpine 鏡像)的全部工具集,其中包括一個軟體包管理器,建議使用該軟體包管理器來安裝所有可用的軟體包更新。

基礎鏡像是由某人維護的,他配置了 CI/CD 管道計劃來建構基礎鏡像,并定期推送到 Docker Hub。你無法控制這個時間間隔,而且經常發生的情況是,在該管道将更新的 Docker 鏡像推送到 Docker Hub 之前,Linux 發行版的包系統資料庫(例如通過 apt)中已經有了安全更新檔。例如,即使基礎鏡像每周推送一次,也有可能在最近的鏡像釋出幾小時或幾天後出現安全更新。

是以,最好總是運作更新本地軟體包資料庫和安裝更新的包管理器指令,采用無人值守模式(不需要使用者确認)。每個 Linux 發行版的這個指令都不一樣。

例如,對于 Ubuntu、Debian 或衍生的發行版,使用 RUN apt-get update && apt-get -y upgrade

另一個重要的細節是,你需要告訴 Docker(或你使用的任何鏡像建構工具)來重新整理基礎鏡像。否則,如果你引用一個基礎鏡像,比如 python:3(而 Docker 在其本地鏡像緩存中已經有了這樣一個鏡像),Docker 甚至不會檢查 Docker Hub 上是否存在更新的 python:3 版本。為了擺脫這種行為,你應該使用這個指令:

這可以確定 Docker 在建構鏡像之前拉取你的 Dockerfile 中 FROM 語句中提到的鏡像的更新。

你還應該注意 Docker 的層緩存機制,它會讓你的鏡像變得陳舊,因為 RUN 指令的層是緩存的,直到基礎鏡像維護者釋出新版本的基礎鏡像才重新整理。如果你發現基礎鏡像的釋出頻率相當低(比如少于一周一次),那麼定期(比如每周一次)重建你的鏡像并禁用層緩存是個好主意。你可以運作以下指令來做到這一點:

5

定期更新第三方依賴

你編寫的軟體是基于第三方的依賴,也就是由其他人制作的軟體。這包括了:

你的鏡像下面的基礎 Docker 鏡像,或

你作為自己應用程式的一部分使用的第三方軟體元件,例如通過 pip/npm/gradle/apt/……安裝的元件。

如果你的鏡像中的這些依賴過時了,就會增加攻擊面,因為過時的依賴往往有可利用的安全漏洞。

你可以定期使用 SCA(軟體元件分析)工具來解決這個問題,比如 Renovate Bot。這些工具(半)自動将你聲明的第三方依賴更新為最新版本,例如在你的 Dockerfile、Python 的 requirements.txt、NPM 的 packages.json 等檔案中聲明的清單。你需要設計你的 CI 管道,使 SCA 工具所做的更改自動觸發你的鏡像的 re-build。

這種自動觸發的鏡像重建對于處在隻維護模式,但代碼仍将被客戶在生産環境中使用(客戶希望它是安全的)的項目特别有用。在維護期間,你不再開發新的特性,也不會建構新的鏡像,因為沒有新的送出(由你做出)來觸發新的建構。然而,由 SCA 工具做出的送出确實會再次觸發鏡像建構。

繼續閱讀