寫在前面
確保容器中服務與應用安全是容器化演進的關鍵點。容器安全涉及到應用開發與維護的整個生命周期,本文主要從鏡像構建的視角來看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容器減小鏡像尺寸實踐
希望小作文對你有些許幫助,如果內容有誤請指正。
您可以隨意轉載、修改、發布本文章,無需經過本人同意。