大家好,我是張晉濤。
Kubernetes 作為雲原生的基石,為我們帶來了極大的便利性,越來越多的公司也都將 Kubernetes 應用到了生產環境中。然而,在享受其帶來的便利性的同時,我們也需要關注其中的一些安全隱患。
本篇,我將為你重點介紹容器映象安全相關的內容。
通常情況下,我們提到容器映象安全,主要是指以下兩個方面:
- 映象自身內容的安全;
- 映象分發過程的安全;
映象自身內容的安全
要聊映象自身內容的安全,那我們就需要知道映象到底是什麼,以及它其中的內容是什麼。
映象是什麼
我們以 debian
映象為例,pull 最新的映象,並將其儲存為 tar 檔案,之後進行解壓:
➜ ~ mkdir -p debian-image
➜ ~ docker pull debian
Using default tag: latest
latest: Pulling from library/debian
647acf3d48c2: Pull complete
Digest: sha256:e8c184b56a94db0947a9d51ec68f42ef5584442f20547fa3bd8cbd00203b2e7a
Status: Downloaded newer image for debian:latest
docker.io/library/debian:latest
➜ ~ docker image save -o debian-image/debian.tar debian
➜ ~ ls debian-image
debian.tar
➜ ~ tar -C debian-image -xf debian-image/debian.tar
➜ ~ tree -I debian.tar debian-image
debian-image
├── 827e5611389abf13dad1057e92f163b771febc0bcdb19fa2d634a7eb0641e0cc.json
├── b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda
│ ├── json
│ ├── layer.tar
│ └── VERSION
├── manifest.json
└── repositories
1 directory, 6 files
解壓完成後,我們看到它是一堆 json 檔案和 layer.tar
檔案的組合,我們再次對其中的 layer.tar
進行解壓:
➜ ~ tar -C debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda -xf debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda/layer.tar
➜ ~ tree -I 'layer.tar|json|VERSION' -L 1 debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda
debian-image/b331057b5d32f835ac4b051f6a08af6e9beedb99ec9aba5c029105abe360bbda
├── bin
├── boot
├── dev
├── etc
├── home
├── lib
├── lib64
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
19 directories, 0 files
解壓後的目錄結構想必你已經很熟悉了,是的,這是 rootfs
的目錄結構。
如果我們使用的是自己構建的一些應用映象的話,經過幾次解壓,你也會在其中找到應用程式相對應的檔案。
映象自身內容安全如何保證
前面我們已經看到了容器映象就是 rootfs
和應用程式,以及一些配置檔案的組合。所以要保證它自身內容的安全性,主要從以下幾個方面來考慮:
rootfs
安全
對應到我們的實際情況,rootfs
通常是由我們使用的基礎(系統)映象提供的,或者也可以認為是我們構建映象時,Dockerfile
的 FROM
欄位所配置的映象提供的。
在這個方面想要做到安全性就需要我們:
- 使用可信來源的映象,比如 Docker 官方維護的映象;
- 對基礎映象持續的進行漏洞掃描和升級;
- 也可以考慮使用
Distroless
映象,這樣也可以一定程度上免受攻擊;
應用程式
應用程式其實是我們自己提供的,在這方面想要做到安全性,那麼就需要我們:
- 持續的進行軟體的漏洞掃描;
- 對依賴及時的進行更新;
- 可以考慮從 SDL(Security Development Lifecycle)過渡到 DevSecOps ;
配置檔案
映象中所包含的那些配置檔案是由映象構建工具所提供的,一般情況下,只要我們保證使用的映象構建工具未被篡改或者留下什麼漏洞,那麼這裡基本上不會有什麼大的問題。
綜合來看,我們可以直接使用類似 Trivy 或者 Anchore Engine 等映象漏洞掃描工具來幫助我們保障映象內容的安全。此外,一些映象倉庫,比如 Harbor 等都已經內建了映象安全的掃描工具,或者可以使用 docker scan
命令進行映象的安全掃描。
映象分發安全
映象如何分發
我們首先來看看,容器映象是怎麼樣從構建到部署到我們的 Kubernetes 環境中的。
圖 1 ,容器映象自建立到釋出部署的簡要過程示意圖
開發者在編寫完程式碼後,推送程式碼到程式碼倉庫。由此來觸發 CI 進行構建,在此過程中會進行映象的構建,以及將映象推送至映象倉庫中。
在 CD 的環節中,則會使用映象倉庫中的映象,部署至目標 Kubernetes 叢集中。
那麼在此過程中,攻擊者如何進行攻擊呢?
映象分發中的安全問題
圖 2 ,映象分發部署安全示例
如圖,在映象分發部署的環節中其上游是映象倉庫,下游是 Kubernetes 叢集。對於映象倉庫而言,即使是內網的自建環境,由於我們的觀念已從基於邊界的安全轉變為零信任安全,所以,我們統一以公共倉庫為例來講解。
攻擊者可以通過一些手段進行劫持、替換成惡意的映象,包括直接攻擊映象倉庫等。
要保證部署到 Kubernetes 叢集中映象的安全性來源以及完整性,其實是需要在兩個主要的環節上進行:
- 構建映象時進行映象的簽名;
- 映象分發部署時進行簽名的校驗;(下一篇內容繼續)
我們來分別看一下。
映象的標籤和摘要
我們通常在使用容器映象時有兩種選擇:
- 標籤,比如
alpine:3.14.3
- 摘要,比如
alpine@sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2
大多數場景下,我們會直接使用標籤,因為它的可讀性更好。但是映象內容可能會隨著時間的推移而變化,因為我們可能會為不同內容的映象使用相同的標籤,最常見的就是 :latest
標籤,每次新版本釋出的時候,新版本的映象都會繼續沿用 :latest
標籤,但其中的應用程式版本已經升級到了最新。
使用摘要的主要弊端是它的可讀性不好,但是,每個映象的摘要都是唯一的,摘要是映象內容的 SHA256 的雜湊值。所以我們可以通過摘要來保證映象的唯一性。
通過以下示例可以直接看到標籤和摘要資訊:
➜ ~ docker pull alpine:3.14.3
3.14.3: Pulling from library/alpine
Digest: sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2
Status: Image is up to date for alpine:3.14.3
docker.io/library/alpine:3.14.3
➜ ~ docker image inspect alpine:3.14.3 | jq -r '.[] | {RepoTags: .RepoTags, RepoDigests: .RepoDigests}'
{
"RepoTags": [
"alpine:3.14.3"
],
"RepoDigests": [
"alpine@sha256:635f0aa53d99017b38d1a0aa5b2082f7812b03e3cdb299103fe77b5c8a07f1d2"
]
}
那麼如何來保證映象的正確性/安全性呢?這就是映象簽名解決的主要問題了。
映象簽名解決方案
數字簽名是一種眾所周知的方法,用於維護在網路上傳輸的任何資料的完整性。對於容器映象簽名,我們有幾種比較通用的方案。
Docker Content Trust (DCT)
在傳輸一般檔案時,可能有過類似的經歷,比如因為網路原因導致下載的檔案不完整;或是遭遇中間人的攻擊導致檔案被篡改、替換等。
映象在分發過程中其實也可能會遇到類似的問題,這就是此處我們要討論的重點,也就是 Docker Content Trust(DCT)主要解決的問題。
Docker Content Trust 使用數字簽名,並且允許客戶端或執行時驗證特定映象標籤的完整性和釋出者。對於使用而言也就是 docker trust
命令所提供的相關功能。注意:這需要 Docker CE 17.12 及以上版本。
前面我們提到了,映象記錄可以有一些標籤,格式如下:
[REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]
以標籤為例,DCT 會與標籤的一部分相關聯。每個映象倉庫都有一組金鑰,映象釋出者使用這些金鑰對映象標籤進行簽名。(映象釋出者可以自行決定要簽署哪些標籤)映象倉庫可以同時包含多個帶有已簽名標籤和未簽名標籤的映象。
這裡需要說明下,如果映象釋出者先推送簽名的 latest 映象,再推送未簽名的 latest 映象,那麼後一個映象不會影響前一個映象的內容(區別於上文中標籤覆蓋的地方)。
圖 4 ,DCT 映象簽名示例(圖中簡略了登入映象倉庫的認證過程)
在生產中,我們可以啟用 DCT 確保使用的映象都已簽名。如果啟用了 DCT,那麼只能對受信任的映象(已簽名並可驗證的映象)進行拉取、執行或構建。
啟用 DCT 有點像對映象倉庫應用“過濾器”,即,只能看到已簽名的映象標籤,看不到未簽名的映象標籤。如果客戶端沒有啟用 DCT ,那麼它可以看到所有的映象。
這裡我們來快速的看一下 DCT 的工作過程
它對映象標籤的信任是通過使用簽名金鑰來管理的。在我們首次開啟 DCT 並使用的時候會建立金鑰集。一個金鑰集由以下幾類金鑰組成:
- 一個離線金鑰 offline key ,它是映象標籤 DCT 的根 (丟失根金鑰很難恢復)
- 對標籤進行簽名的儲存庫或標記金鑰 tag key
- 伺服器管理的金鑰,例如時間戳金鑰
圖 5 , 映象簽名金鑰示例
剛從我們提到客戶端使用 DCT 也就是我們的 docker trust
命令,它是建立在 Notary v1 上的。預設情況下,Docker 客戶端中禁用 DCT 。要啟用需要設定 DOCKER_CONTENT_TRUST=1
環境變數 。
效果如下:
➜ ~ DOCKER_CONTENT_TRUST=1 docker pull alpine:3.12
Pull (1 of 1): alpine:3.12@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
docker.io/library/alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a: Pulling from library/alpine
188c0c94c7c5: Already exists
Digest: sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
Status: Downloaded newer image for alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a
Tagging alpine@sha256:c0e9560cda118f9ec63ddefb4a173a2b2a0347082d7dff7dc14272e7841a5b5a as alpine:3.12
docker.io/library/alpine:3.12
Notary v1
前面我們提到 DCT 是基於 Notary v1 實現的,不過這不是我們本篇的重點,所以這裡僅對 Notary v1 做個簡單的介紹。Notary 專案地址: https://github.com/notaryproj...
圖 6 ,Notary 客戶端、伺服器和簽名相關的互動流程
過程1 - 身份認證,任何沒有令牌的連線將被重定向到授權伺服器(Docker Registry v2 身份認證);
過程2 - 客戶端將通過 HTTPS 上的身份驗證登入到授權伺服器,獲取令牌;
過程3 - 當客戶端上傳新的後設資料檔案時,伺服器會根據以前的版本檢查它們是否存在衝突,並驗證上傳的後設資料的簽名、校驗和和有效性;
過程4 - 一旦所有上傳的後設資料都經過驗證,伺服器會生成時間戳(可能還有快照),然後將它們傳送給 sign 進行簽名;
過程5 - sign 從其資料庫中檢索加密私鑰,解密金鑰,並使用它們進行簽名,併傳送回伺服器;
過程6 - 伺服器將客戶端上傳和伺服器生成的後設資料儲存在 TUF 庫中。生成的時間戳和快照後設資料證明客戶端上傳的後設資料是該可信集合的最新版本。之後,伺服器會通知客戶端--上傳成功;
過程7 - 客戶端現在可以立即從伺服器下載最新的後設資料了。在時間戳過期的情況下,伺服器將遍歷整個序列,生成新的時間戳,請求 sign 簽名,將新簽名的時間戳儲存在資料庫中。然後,它將這個新的時間戳連同其他儲存的後設資料一起傳送給請求客戶端;
這個專案由於是一個安全專案,雖然用途很大,但整體並不活躍。現在正在進行 v2 版本的開發,有興趣的小夥伴歡迎加入。
sigstore 和 Cosign
這裡介紹另一個來自 Linux 基金會的專案,叫做 sigstore 它主要是為了提供一些標準的庫/工具,便於更好的進行簽名和校驗。當然,目前 sigstore 已經匯聚了包括 Cosign,Fulcio 和 Rekor 等開源專案,涉及到映象映象簽名校驗和供應鏈等方面。
圖 7 ,sigstore 簡介
Cosign 是 sigstore 的工具之一,用於 OCI registry 中建立、儲存和驗證容器映象簽名。Cosign v1.0 已於今年下半年釋出,是否能穩定用於生產環境,還有待考驗。 截止目前,Cosign 已經發布了 v1.3.1 版本,詳細變更請參考其 ReleaseNote: https://github.com/sigstore/c...
我們這裡看下它如何進行映象的簽名
➜ cosign cosign generate-key-pair
Enter password for private key:
Enter password for private key again:
Private key written to cosign.key
Public key written to cosign.pub
➜ cosign cosign sign --key cosign.key ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo:fa5714f419b3d11dee6ac795e38356e9c3c439cb
Enter password for private key: %
➜ cosign cosign verify --key cosign.pub ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo:fa5714f419b3d11dee6ac795e38356e9c3c439cb
Verification for ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo:fa5714f419b3d11dee6ac795e38356e9c3c439cb --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- The signatures were verified against the specified public key
- Any certificates were verified against the Fulcio roots.
[{"critical":{"identity":{"docker-reference":"ghcr.io/tao12345666333/argo-cd-demo/argo-cd-demo"},"image":{"docker-manifest-digest":"sha256:768845efa2a32bc5c5d83a6f7ec668b98f5db46585dd1918afc9695a9e653d2d"},"type":"cosign container image signature"},"optional":null}]
看起來還是比較簡單的。
總結
以上就是關於映象自身內容安全,以及映象分發安全中的映象簽名校驗部分的內容。
下一篇我將為大家介紹如何在映象分發及部署時進行簽名的校驗,以及如何保護 Kubernetes 叢集免受未簽名或不可信來源映象的攻擊,敬請期待!
歡迎訂閱我的文章公眾號【MoeLove】