本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

Kubernetes 准入控制器指南

Kubernetes極大地提高了當今生產環境中後端叢集的速度和可管理性。憑藉其靈活性、可伸縮性和易用性,Kubernetes已成為容器編排器的事實標準。Kubernetes還提供了一系列功能,以保護生產工作負載。安全功能中較新引入的是一組名為“准入控制器”的外掛。必須啟用准入控制器才能使用Kubernetes的一些更高階的安全功能,例如Pod安全策略,它在整個名稱空間中強制執行安全配置基線。以下必備的提示和技巧將幫助您利用准入控制器,充分利用Kubernetes中的這些安全功能。

什麼是Kubernetes准入控制器?

簡而言之,Kubernetes准入控制器是管理和強制叢集使用方式的外掛。它們可以被視為攔截(已認證的)API請求的看門人,並可以更改請求物件或完全拒絕請求。准入控制過程有兩個階段:首先執行_變更_階段,然後執行_驗證_階段。因此,准入控制器可以作為變更控制器或驗證控制器,或兩者的組合。例如,LimitRanger准入控制器可以為Pod新增預設的資源請求和限制(變更階段),還可以驗證具有明確設定的資源要求的Pod是否不超過在LimitRange物件中指定的每個名稱空間限制(驗證階段)。

Admission Controller Phases

准入控制器階段

值得注意的是,Kubernetes操作的某些方面,許多使用者可能認為是內建的,實際上是由准入控制器管理的。例如,當名稱空間被刪除並隨後進入Terminating狀態時,NamespaceLifecycle准入控制器可以阻止在此名稱空間中建立任何新物件。

在Kubernetes附帶的30多個准入控制器中,有兩個控制器因其近乎無限的靈活性而扮演著特殊角色——ValidatingAdmissionWebhooksMutatingAdmissionWebhooks,這兩個控制器在Kubernetes 1.13中都處於beta狀態。我們將仔細研究這兩個准入控制器,因為它們本身不實現任何策略決策邏輯。相反,相應的操作是從叢集內執行的服務(一個_webhook_)的REST端點獲取的。這種方法將准入控制器邏輯與Kubernetes API伺服器解耦,從而允許使用者在Kubernetes叢集中建立、更新或刪除資源時實現自定義邏輯。

這兩種准入控制器Webhook之間的區別不言而喻:變更准入Webhook可以更改物件,而驗證准入Webhook則不能。但是,即使是變更准入Webhook也可以拒絕請求,從而起到驗證作用。驗證准入Webhook比變更Webhook有兩個主要優勢:首先,出於安全原因,停用MutatingAdmissionWebhook准入控制器(或對誰可以建立MutatingWebhookConfiguration物件應用更嚴格的RBAC限制)可能是可取的,因為它可能產生令人困惑甚至危險的副作用。其次,如上圖所示,驗證准入控制器(以及Webhook)在任何變更准入控制器之後執行。因此,驗證Webhook看到的請求物件是最終版本,將被持久化到etcd

啟用准入控制器集是透過向Kubernetes API伺服器傳遞一個標誌來配置的。請注意,舊的--admission-control標誌在1.10中已棄用,並替換為--enable-admission-plugins

--enable-admission-plugins=ValidatingAdmissionWebhook,MutatingAdmissionWebhook

Kubernetes建議預設啟用以下准入控制器。

--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,Priority,ResourceQuota,PodSecurityPolicy

有關准入控制器及其描述的完整列表,請參閱官方Kubernetes參考。本討論將僅關注基於Webhook的准入控制器。

為什麼我需要准入控制器?

  • 安全性:准入控制器可以透過在整個名稱空間或叢集中強制執行合理的安全基線來提高安全性。內建的PodSecurityPolicy准入控制器可能是最突出的例子;例如,它可用於禁止容器以root身份執行或確保容器的根檔案系統始終以只讀方式掛載。透過自定義、基於Webhook的准入控制器可以實現的其他用例包括:
  • 僅允許從企業已知的特定登錄檔拉取映象,拒絕未知映象登錄檔。
  • 拒絕不符合安全標準的部署。例如,使用privileged標誌的容器可以繞過許多安全檢查。可以透過基於Webhook的准入控制器來緩解此風險,該控制器要麼拒絕此類部署(驗證),要麼覆蓋privileged標誌,將其設定為false
  • 治理:准入控制器允許您強制遵守某些做法,例如擁有良好的標籤、註解、資源限制或其他設定。一些常見場景包括:
  • 對不同物件強制執行標籤驗證,以確保為各種物件使用正確的標籤,例如每個物件都分配給一個團隊或專案,或者每個部署都指定一個應用程式標籤。
  • 自動向物件添加註解,例如為“dev”部署資源分配正確的成本中心。
  • 配置管理:准入控制器允許您驗證叢集中執行的物件的配置,並防止任何明顯的錯誤配置進入叢集。准入控制器有助於檢測和修復未部署語義標籤的映象,例如透過:
  • 自動新增資源限制或驗證資源限制,
  • 確保為Pod新增合理的標籤,或
  • 確保生產部署中使用的映象引用不使用latest標籤或帶有-dev字尾的標籤。

透過這種方式,准入控制器和策略管理有助於確保應用程式在不斷變化的控制環境中保持合規。

示例:編寫和部署准入控制器Webhook

為了說明如何利用准入控制器Webhook建立自定義安全策略,讓我們考慮一個解決Kubernetes缺點之一的示例:它的許多預設設定都以易用性和減少摩擦為最佳化目標,有時以犧牲安全性為代價。其中一個設定是,容器預設允許以root身份執行(並且,在沒有進一步配置和Dockerfile中沒有USER指令的情況下,也會這樣做)。儘管容器在一定程度上與底層主機隔離,但以root身份執行容器確實增加了部署的風險——並且應作為眾多安全最佳實踐之一加以避免。例如,最近暴露的runC漏洞CVE-2019-5736)只有在容器以root身份執行時才可能被利用。

您可以使用自定義的變更准入控制器Webhook來應用更安全的預設值:除非明確請求,否則我們的Webhook將確保Pod以非root使用者身份執行(如果未明確指定,我們分配使用者ID 1234)。請注意,此設定不會阻止您在叢集中部署任何工作負載,包括那些合法需要以root身份執行的工作負載。它只要求您在部署配置中明確啟用這種風險更高的操作模式,同時所有其他工作負載都預設為非root模式。

完整的程式碼和部署說明可以在我們隨附的GitHub倉庫中找到。在這裡,我們將重點介紹Webhook工作原理的一些更微妙的方面。

變更Webhook配置

變更准入控制器Webhook是透過在Kubernetes中建立MutatingWebhookConfiguration物件來定義的。在我們的示例中,我們使用以下配置:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  name: demo-webhook
webhooks:
  - name: webhook-server.webhook-demo.svc
    clientConfig:
      service:
        name: webhook-server
        namespace: webhook-demo
        path: "/mutate"
      caBundle: ${CA_PEM_B64}
    rules:
      - operations: [ "CREATE" ]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]

此配置定義了一個webhook webhook-server.webhook-demo.svc,並指示Kubernetes API伺服器在建立Pod時,透過向/mutate URL發出HTTP POST請求,諮詢webhook-demo名稱空間中的webhook-server服務。為了使此配置生效,必須滿足幾個先決條件。

Webhook REST API

Kubernetes API伺服器向給定服務和URL路徑發出HTTPS POST請求,請求體中包含JSON編碼的AdmissionReview(已設定Request欄位)。響應也應該是一個JSON編碼的AdmissionReview,這次已設定Response欄位。

我們的演示倉庫包含一個函式,它處理序列化/反序列化的樣板程式碼,讓您可以專注於實現對Kubernetes API物件進行操作的邏輯。在我們的示例中,實現准入控制器邏輯的函式名為applySecurityDefaults,可以透過以下方式設定一個在/mutate URL下提供此函式的HTTPS伺服器:

mux := http.NewServeMux()
mux.Handle("/mutate", admitFuncHandler(applySecurityDefaults))
server := &http.Server{
  Addr:    ":8443",
  Handler: mux,
}
log.Fatal(server.ListenAndServeTLS(certPath, keyPath))

請注意,為了使伺服器在沒有提升許可權的情況下執行,我們將HTTP伺服器監聽在埠8443。Kubernetes不允許在Webhook配置中指定埠;它總是假定HTTPS埠為443。但是,由於無論如何都需要一個服務物件,我們可以輕鬆地將服務的埠443對映到容器上的埠8443:

apiVersion: v1
kind: Service
metadata:
  name: webhook-server
  namespace: webhook-demo
spec:
  selector:
    app: webhook-server  # specified by the deployment/pod
  ports:
    - port: 443
      targetPort: webhook-api  # name of port 8443 of the container

物件修改邏輯

在變更准入控制器Webhook中,變更透過JSON補丁執行。雖然JSON補丁標準包含許多超出本次討論範圍的複雜細節,但我們示例中的Go資料結構及其用法應該能讓使用者對JSON補丁的工作原理有一個良好的初步瞭解:

type patchOperation struct {
  Op    string      `json:"op"`
  Path  string      `json:"path"`
  Value interface{} `json:"value,omitempty"`
}

為了將Pod的.spec.securityContext.runAsNonRoot欄位設定為true,我們構造了以下patchOperation物件:

patches = append(patches, patchOperation{
  Op:    "add",
  Path:  "/spec/securityContext/runAsNonRoot",
  Value: true,
})

TLS證書

由於Webhook必須透過HTTPS提供服務,我們需要為伺服器提供適當的證書。這些證書可以是自簽名證書(更確切地說:由自簽名CA簽名),但我們需要Kubernetes在與Webhook伺服器通訊時指定相應的CA證書。此外,證書的通用名稱(CN)必須與Kubernetes API伺服器使用的伺服器名稱匹配,對於內部服務來說,該名稱是<service-name>.<namespace>.svc,即我們示例中的webhook-server.webhook-demo.svc。由於自簽名TLS證書的生成在網際網路上已有多篇文件,我們在此僅參考我們示例中的相關shell指令碼

前面所示的Webhook配置包含一個佔位符${CA_PEM_B64}。在建立此配置之前,我們需要用CA的Base64編碼PEM證書替換此部分。可以使用openssl base64 -A命令實現此目的。

測試Webhook

部署並配置Webhook伺服器後(可以透過呼叫倉庫中的./deploy.sh指令碼來完成),現在是時候測試和驗證Webhook是否確實完成了它的工作了。該倉庫包含三個示例

  • 一個未指定安全上下文的Pod(pod-with-defaults)。我們預計這個Pod將以非root使用者(使用者ID 1234)執行。
  • 一個指定了安全上下文的Pod,明確允許它以root身份執行(pod-with-override)。
  • 一個配置衝突的Pod,指定它必須以非root使用者執行,但使用者ID為0(pod-with-conflict)。為了展示拒絕物件建立請求,我們增強了准入控制器邏輯以拒絕此類明顯的錯誤配置。

透過執行kubectl create -f examples/<name>.yaml建立這些Pod中的一個。在前兩個示例中,您可以透過檢查日誌來驗證Pod執行的使用者ID,例如:

$ kubectl create -f examples/pod-with-defaults.yaml
$ kubectl logs pod-with-defaults
I am running as user 1234

在第三個示例中,物件建立將被拒絕並顯示適當的錯誤訊息:

$ kubectl create -f examples/pod-with-conflict.yaml
Error from server (InternalError): error when creating "examples/pod-with-conflict.yaml": Internal error occurred: admission webhook "webhook-server.webhook-demo.svc" denied the request: runAsNonRoot specified, but runAsUser set to 0 (the root user)

也歡迎您使用自己的工作負載進行測試。當然,您還可以透過更改Webhook的邏輯來進一步實驗,看看這些更改如何影響物件建立。有關如何進行此類更改的更多資訊,請參閱倉庫的README

總結

Kubernetes准入控制器為安全性提供了顯著優勢。深入研究兩個強大的示例,並附帶可用的程式碼,將幫助您開始利用這些強大的功能。

參考資料