寫在前面
確保容器中服務與應用安全是容器化演進的關鍵點。容器安全涉及到應用開發與維護的整個生命週期,本文主要從映象構建的視角來看docker容器的一些安全問題及應對措施。
一、許可權管理
1.避免以容器以root身份執行
在Openshift與k8s環境中預設容器需要以非root身份執行,使用root身份執行的情況很少,所以不要忘記在dockerfile中包含USER指令,以將啟動容器時預設有效 的UID 更改為非 root 使用者。
以非 root 身份執行需要在 Dockerfile 中做的兩個步驟:
- 確保USER指令中指定的使用者存在於容器內。
- 在程式將要讀取或寫入的位置提供適當的檔案系統許可權。
FROM alpine
#建立目錄,新增myuser使用者,目錄所有作為myuser
RUN mkdir /server && adduser -D myuser && chown -R myuser /server
USER myuser
WORKDIR /server
COPY myapp ./
CMD ["./myapp"]
2.可執行檔案許可權應為root使用者擁有但不可寫
容器中的每個可執行檔案都應該由 root 使用者擁有,即使它由非 root 使用者執行,並且不應該是全域性可寫的。
通過阻止執行使用者修改現有的二進位制檔案或指令碼,可以有效降低攻擊,保證容器不變性。不可變容器不會在執行時自動更新其程式碼,通過這種方式,我們可以防止正在執行的應用程式被意外或惡意修改。
我們在使用COPY時
COPY --chown=myuser:myuser myapp ./
#應改為
COPY myapp ./
二、減少攻擊面
避免載入不必要的包、第三方應用或暴露埠以減少攻擊面。我們在映象中包含的元件內容越多,容器暴露的就越多,維護起來就越困難。
1.採用多階段構建
我們在《Dockerfile 多階段構建實踐》中說到採用多階段構建,可以此降低構建複雜度,同時有效減小映象尺寸。
在多階段構建中,我們建立一箇中間容器(階段),其中包含編譯工具及生成最終可執行檔案。然後,我們只將生成的工件複製到最終映象中,而無需額外的開發依賴項、臨時構建檔案等等。
精心設計的多階段構建僅包含最終映像中所需的最少二進位制檔案和依賴項,而不包含構建工具或中間檔案。它更為安全,並且還減小了映象大小。可以有效減少了攻擊面,減少了漏洞。
多階段構建的實現請參考上篇文章《Dockerfile 多階段構建實踐》
2.使用可信賴的映象
假如我們不是從頭開始構建映象,基映象建立在不受信任或不受維護的映象之上會將所有問題和漏洞從該映象繼承到您的容器中。
基礎映象選擇的參考:
- 我們應該選擇來自受信任倉庫和經過驗證的官方映象。
- 使用自定義映象時,我們應該檢查映象源和構建的 Dockerfile。更進一步,我們甚至應該以這個Dockerfile來構建自己的基礎映象。因為我們無法保證在dockerhub等公共倉庫中釋出的映像確實是從指定的 Dockerfile 構建的。也不能保證它是最新的。
- 有時候在安全性和極簡主義方面考慮,官方映象可能並不非合適的,最優解是我們自己從頭構建屬於自己的映象。
2.從頭開始構建映象
假如如果你是從centos映象開始構建,那麼你建立的容器可能將會包含幾十個或者上百個漏洞。所以構建一個安全的映象我們最好需要知道我們的基映象存在哪些威脅。在生產中通常會從Scratch空映象或distroless開始。
distroless映象僅包含應用程式及其執行時依賴項。它們不包括在標準 Linux 發行版中釋出應用如包管理器、shell 或任何其他程式。Distroless 映象非常小。最小的 distroless 影像gcr.io/distroless/static
大約為 650 kB。只有alpine
(約2.5 MB)大小的 四分之一 ,不到debian
(50 MB)大小的 1.5% 。
FROM golang:1.13-buster as build
WORKDIR /go/src/app
ADD . /go/src/app
RUN go get -d -v ./...
RUN go build -o /go/bin/app
# 引用Distroless映象
FROM gcr.io/distroless/base-debian10
COPY --from=build /go/bin/app /
CMD ["/app"]
gcr.io/distroless/base-debian10
只包含一組基本的包,如包括只需要的庫,如glibc、libssl和openssl 當然對於像 Go 這樣不需要libc 的靜態編譯應用程式我們就可以替換為如下基映象
FROM gcr.io/distroless/static-debian10
關於distroless基映象的更多資訊可以參考https://github.com/GoogleContainerTools/distroless
3.及時更新映象
使用經常更新的基礎映象,在需要時重構你的映象。隨著新的安全漏洞不斷被發現,堅持使用最新的安全補丁是一種通用的安全最佳實踐。
版本控制策略:
- 堅持使用穩定或長期支援版本,這些版本會迅速提供安全修復程式。
- 提前計劃。準備好在基本映象版本達到生命週期結束或停止接收更新之前刪除舊版本並遷移。
- 定期重建自己的映象,從基礎發行版、Node、Golang、Python 等獲取最新的包。 大多數包或依賴項管理器,如npm或go mod,將提供指定版本最新的安全更新。
4.埠暴露
容器中每個開啟的埠都是通往系統的大門。我們應該僅公開應用程式需要的埠,並且避免公開 SSH (22) 等埠。
我們知道 Dockerfile 提供了EXPOSE
命令有暴露埠,但是該命令僅用於提供資訊和用於文件目的。執行容器時,容器不會自動允許所有 EXPOSE 埠的連線(除非在啟動容器時使用docker run --publish-all
)。
啟動容器時,通過-P
暴露的埠應與dockerfile中EXPOSE命令指定的埠一致,這樣更便於維護。
三、敏感資料管理
1.憑證和金鑰
禁止在 Dockerfile 指令(環境變數、引數或其他任何命令中)中放入憑據和金鑰。
在複製檔案到映象時,即使檔案在 Dockerfile 的後續指令中被刪除,它仍然可以在之前的層上訪問。因為映象分層原理,你的檔案並沒有真正被刪除,只是“隱藏”在最終檔案系統中。因此在構建映象時,我們應該遵循以下做法:
- 如果應用程式支援通過環境變數進行配置,我們可以通過docker run 中的
-e
選項配置,或者使用Docker secrets、Kubernetes secrets提供值作為環境變數。 - 使用配置檔案並在docker 中繫結掛載配置檔案,或者使用Kubernetes secret 掛載。
關於secrets
的使用會在後面文章中詳細介紹。
2.ADD、COPY
ADD 和 COPY 指令在 Dockerfile 中提供類似的功能。但是COPY 更為明確。
除非我們確實需要 使用ADD 功能,例如從 URL 或從 tar 檔案新增檔案。不然最好使用 COPY,COPY 的結果更具可預測性且不易出錯。
在某些情況下,最好使用 RUN 指令而不是 ADD 來下載使用curl或wget的包,解壓縮然後刪除原始檔案,減少層數。
3.構建上下文與dockerignore
在構建時我們通常使用.
作為上下文
#docker build -t images:v1 .
使用 .
作為上下文時我們需要謹慎些,因為docker CLI會將上下文中機密或不必要的檔案新增到守護程式,甚至到容器中,例如配置檔案、憑據、備份、鎖定檔案、臨時檔案、源、子資料夾、點檔案等等。
在比如:
COPY . /server
此時會將目錄下所有內容都新增到映象中,包括Dockfile本身。
所以正確做法是建立一個包含需要在容器內複製檔案的資料夾,將其用作構建上下文,並在可能的情況下明確 COPY 指令(避免使用萬用字元)。例如:
#docker build -t images:v1 build_files/
為了排除不必要的檔案,我們也可以建立一個.dockerignore
檔案,在其中明確排除的檔案和目錄。
以上是容器構建時常見安全問題與相關處理措施,容器安全涉及面廣,遍佈整個devops流程中。有興趣的同學可以另外一個位面介入深究。
NEXT
- Docker容器secrets詳解
- Docker容器減小映象尺寸實踐
希望小作文對你有些許幫助,如果內容有誤請指正。
您可以隨意轉載、修改、釋出本文章,無需經過本人同意。