GKE 角色型存取權控管 (RBAC) 的最佳做法

本文將說明在 Google Kubernetes Engine (GKE) 中規劃角色型存取權控管 (RBAC) 政策時,應遵循的最佳做法。本文假設您瞭解下列事項:

RBAC 是 Kubernetes 的核心安全功能,可讓您建立精細的權限,管理使用者和工作負載可對叢集中的資源執行的動作。您建立 RBAC 角色,並將這些角色繫結至主體,也就是經過驗證的使用者,例如服務帳戶或 Google 群組。

本文適用於負責為機構規劃及實作 RBAC 政策的安全專家和作業人員。如要進一步瞭解我們在 Google Cloud 內容中提及的常見角色和範例工作,請參閱「常見的 GKE 使用者角色和工作」。

如需本文件指導原則的檢查清單,請參閱「檢查清單摘要」。

如需所有 GKE 最佳做法的整合總覽,請參閱「GKE 最佳做法」。

如要瞭解如何在 Google Kubernetes Engine (GKE) 中導入 RBAC,請參閱「設定角色型存取權控管」。

RBAC 的運作方式

RBAC 支援下列類型的角色和繫結:

  • ClusterRole:一組可套用至任何命名空間或整個叢集的權限。
  • 角色:一組權限,僅限於單一命名空間。
  • ClusterRoleBinding:將 ClusterRole 繫結至使用者或群組,適用於叢集中的所有命名空間。
  • RoleBinding:將 Role 或 ClusterRole 繫結至特定命名空間中的使用者或群組。

您可以在 Role 或 ClusterRole 中將權限定義為 rules。角色中的每個 rules 欄位都包含 API 群組、該 API 群組中的 API 資源,以及允許對這些資源執行的動詞 (動作)。或者,您可以使用 resourceNames 欄位,將動詞範圍限定為 API 資源的具名執行個體。如需範例,請參閱「限制只能存取特定的資源執行個體」。

定義角色後,您可以使用 RoleBinding 或 ClusterRoleBinding 將角色繫結至主體。根據您要在單一命名空間或多個命名空間中授予權限,選擇繫結類型。

RBAC 角色設計

遵循最小權限原則

指派 RBAC 角色所含權限時,請採用最小權限原則,並授予執行工作所需的最低權限。採用最小權限原則可降低叢集遭駭時提權的可能性,以及因過度存取而引發安全事件的機率。

設計角色時,請仔細考量常見的提權風險,例如 escalate 或 bind 動詞、PersistentVolumes 的 create 存取權,或是 Certificate Signing Requests 的 create 存取權。如需風險清單,請參閱 Kubernetes RBAC - 提權風險。

請勿刪除系統 RBAC 角色和繫結

Kubernetes 會建立多個具有 system: 前置字元的 RBAC 資源,例如 system:basic-user、system:discovery 和 system:public-info-viewer ClusterRoleBindings。這些資源是叢集正常運作的必要條件。請勿刪除系統角色和繫結,否則可能會導致叢集不穩定。Kubernetes API 伺服器會在啟動時嘗試自動調解這些資源,但如果調解失敗,您可能就無法存取叢集。

為確保系統角色和繫結不會遭到刪除或修改,請檢查 RBAC 政策,並確認主體在具有 system: 前置字元的 RoleBinding 和 ClusterRoleBinding 上,沒有 delete 或 update 權限。

避免使用預設角色和群組

Kubernetes 會建立一組預設的 ClusterRole 和 ClusterRoleBinding,可用於探索 API 及啟用代管元件功能。視角色而定,這些預設角色授予的權限可能相當廣泛。Kubernetes 也有一組預設使用者和使用者群組,以 system: 前置字元識別。根據預設,Kubernetes 和 GKE 會自動將這些角色繫結至預設群組和各種主體。如需 Kubernetes 建立的預設角色和繫結完整清單,請參閱「預設角色和角色繫結」。

下表說明部分預設角色、使用者和群組。除非您已仔細評估這些角色、使用者和群組,否則建議您避免與這些項目互動,因為與這些資源互動可能會對叢集的安全性造成意想不到的後果。

名稱 類型 說明
cluster-admin ClusterRole 授予主體權限,對叢集中的任何資源執行任何操作。
system:anonymous 使用者

Kubernetes 會將這個使用者指派給未提供驗證資訊的 API 伺服器要求。

將角色繫結至這個使用者後,任何未通過驗證的使用者都會獲得該角色授予的權限。

system:unauthenticated 群組

Kubernetes 會將這個群組指派給未提供驗證資訊的 API 伺服器要求。

將角色繫結至這個群組後,任何未經驗證的使用者都會獲得該角色授予的權限。

system:authenticated 群組

如果使用者登入 Google 帳戶 (包括所有 Gmail 帳戶),GKE 會將這個群組指派給 API 伺服器要求。實際上,這與 system:unauthenticated 沒有顯著差異,因為任何人都能建立 Google 帳戶。

將角色繫結至這個群組後,任何擁有 Google 帳戶的使用者 (包括所有 Gmail 帳戶) 都會獲得該角色授予的權限。

system:masters 群組

Kubernetes 預設會將 cluster-admin ClusterRole 指派給這個群組,以啟用系統功能。

如果將自有主體新增至這個群組,這類主體就能對叢集內的各項資源執行任何操作。

請盡可能避免建立涉及預設使用者、角色和群組的繫結。這可能會對叢集的安全性狀態造成意想不到的後果。例如:

  • 將預設 cluster-admin ClusterRole 繫結至 system:unauthenticated 群組,可讓任何未經驗證的使用者存取叢集中的所有資源 (包括密鑰)。這些高權限繫結是攻擊 (例如大規模惡意軟體活動) 的目標。
  • 將自訂角色繫結至 system:unauthenticated 群組,即可授予未經驗證的使用者該角色提供的權限。

請盡可能遵循下列準則:

  • 請勿將自己的主體新增至 system:masters 群組。
  • 請勿將 system:unauthenticated 群組繫結至任何 RBAC 角色。
  • 請勿將 system:authenticated 群組繫結至任何 RBAC 角色。
  • 請勿將 system:anonymous 使用者繫結至任何 RBAC 角色。
  • 請勿將 cluster-admin ClusterRole 繫結至您自己的主體,或任何預設使用者和群組。如果應用程式需要多項權限,請判斷確切需要的權限,並為此建立特定角色。
  • 繫結主體前,請先評估其他預設角色授予的權限。
  • 修改預設群組的成員之前,請先評估繫結至這些群組的角色。

禁止使用預設群組

您可以使用 gcloud CLI,在參照 system:unauthenticated 和 system:authenticated 群組或 system:anonymous 使用者的叢集中,停用非預設的 RBAC 繫結。建立新的 GKE 叢集或更新現有叢集時,請使用下列一或兩個標記:使用這些標記不會停用參照這些群組的預設 Kubernetes 繫結。這些標記需要 GKE 1.30.1-gke.1283000 以上版本。

偵測及移除預設角色和群組的使用情形

如要檢查叢集是否在 RBAC 繫結中參照這些使用者和群組,請為叢集或機群啟用 Kubernetes 資安態勢掃描標準層級,這樣 GKE 就能在 Google Cloud 控制台的資安態勢資訊主頁中顯示結果。如需操作說明,請參閱「啟用工作負載設定稽核」。

以下各節說明如何找出參照預設使用者和群組的特定 RoleBinding 或 ClusterRoleBinding,以及如何刪除這些資源。

ClusterRoleBindings
  1. 列出主體為 system:anonymous、system:unauthenticated 或 system:authenticated 的所有 ClusterRoleBinding 名稱:

    kubectl get clusterrolebindings -o json \
      | jq -r '["Name"], ["-----"], (.items[] | select((.subjects | length) > 0) | select(any(.subjects[]; .name == "system:anonymous" or .name == "system:unauthenticated" or .name == "system:authenticated")) | [.metadata.namespace, .metadata.name]) | @tsv'
    

    輸出內容應只列出下列 ClusterRoleBinding:

    Name
    ----
    "system:basic-user"
    "system:discovery"
    "system:public-info-viewer"
    

    如果輸出內容包含其他非預設繫結,請為每個額外繫結執行下列步驟。如果輸出內容不包含非預設繫結,請略過下列步驟。

  2. 列出與繫結相關聯的角色權限:

    kubectl get clusterrolebinding CLUSTER_ROLE_BINDING_NAME -o json \
        | jq ' .roleRef.name +" " + .roleRef.kind' \
        | sed -e 's/"//g' \
        | xargs -l bash -c 'kubectl get $1 $0 -o yaml'
    

    將 CLUSTER_ROLE_BINDING_NAME 替換為非預設 ClusterRoleBinding 的名稱。

    輸出結果會與下列內容相似:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
    ...
    rules:
    - apiGroups:
      - ""
      resources:
      - secrets
      verbs:
      - get
      - watch
      - list
    

    如果判斷輸出內容中的權限可安全授予預設使用者或群組,則無須採取進一步行動。如果您判斷繫結授予的權限不安全,請繼續下一個步驟。

  3. 從叢集刪除不安全的繫結:

    kubectl delete clusterrolebinding CLUSTER_ROLE_BINDING_NAME
    

    將 CLUSTER_ROLE_BINDING_NAME 替換為要刪除的 ClusterRoleBinding 名稱。

RoleBindings
  1. 列出主體為 system:anonymous、system:unauthenticated 或 system:authenticated 的任何 RoleBinding 的命名空間和名稱:

    kubectl get rolebindings -A -o json \
      | jq -r '["Namespace", "Name"], ["---------", "-----"], (.items[] | select((.subjects | length) > 0) | select(any(.subjects[]; .name == "system:anonymous" or .name == "system:unauthenticated" or .name == "system:authenticated")) | [.metadata.namespace, .metadata.name]) | @tsv'
    

    如果叢集設定正確,輸出內容應為空白。 如果輸出內容包含任何非預設繫結,請針對每個額外繫結執行下列步驟。如果輸出結果空白,請略過後續步驟。

    如果您只知道 RoleBinding 的名稱,可以使用下列指令在所有命名空間中尋找相符的 RoleBinding:

    kubectl get rolebindings -A -o json \
      | jq -r '["Namespace", "Name"], ["---------", "-----"], (.items[] | select((.subjects | length) > 0) | select(.metadata.name == "ROLE_BINDING_NAME") | [.metadata.namespace, .metadata.name]) | @tsv'
    

    將 ROLE_BINDING_NAME 替換為非預設 RoleBinding 的名稱。

  2. 列出與繫結相關聯的角色權限:

    kubectl get rolebinding ROLE_BINDING_NAME --namespace ROLE_BINDING_NAMESPACE -o json \
        | jq ' .roleRef.name +" " + .roleRef.kind' \
        | sed -e 's/"//g' \
        | xargs -l bash -c 'kubectl get $1 $0 -o yaml --namespace ROLE_BINDING_NAMESPACE'
    

    更改下列內容:

    • ROLE_BINDING_NAME:非預設 RoleBinding 的名稱。
    • ROLE_BINDING_NAMESPACE:非預設 RoleBinding 的命名空間。

    輸出結果會與下列內容相似:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    ...
    rules:
    - apiGroups:
      - ""
      resources:
      - secrets
      verbs:
      - get
      - watch
      - list
    

    如果判斷輸出內容中的權限可安全授予預設使用者或群組,則無須採取進一步行動。如果您判斷繫結授予的權限不安全,請繼續下一個步驟。

  3. 從叢集刪除不安全的繫結:

    kubectl delete rolebinding ROLE_BINDING_NAME --namespace ROLE_BINDING_NAMESPACE
    

    更改下列內容:

    • ROLE_BINDING_NAME:要刪除的 RoleBinding 名稱。
    • ROLE_BINDING_NAMESPACE:要刪除的 RoleBinding 命名空間。

將權限範圍設為命名空間層級

視工作負載或使用者的需求,使用下列繫結和角色:

  • 如要授予一個命名空間中資源的存取權,請使用 Role 和 RoleBinding。
  • 如要授予多個命名空間中資源的存取權,請為每個命名空間使用 RoleBinding 的 ClusterRole。
  • 如要授予每個命名空間中資源的存取權,請使用 ClusterRole,並搭配 ClusterRoleBinding。

盡可能在最少的命名空間中授予權限。

請勿使用萬用字元

* 字元是適用於所有內容的萬用字元。請勿在規則中使用萬用字元。在 RBAC 規則中明確指定 API 群組、資源和動詞。舉例來說,在 verbs 欄位中指定 *,會授予資源 get、list、watch、patch、update、deletecollection 和 delete 權限。下表列出在規則中避免使用萬用字元的範例:

建議 不建議使用
- rules:
    apiGroups: ["apps","extensions"]
    resources: ["deployments"]
    verbs: ["get","list","watch"]

專門授予 get、list 和 watch 動詞給 apps 和 extensions API 群組。

- rules:
    apiGroups: ["*"]
    resources: ["deployments"]
    verbs: ["get","list","watch"]

將動詞授予任何 API 群組中的 deployments。

- rules:
    apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch"]

只會將 get、list 和 watch 動詞授予 apps 和 extensions API 群組中的部署作業。

- rules:
    apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    verbs: ["*"]

授予所有動詞,包括 patch 或 delete。

使用個別規則,授予特定資源的最低權限存取權

規劃規則時,請嘗試下列高階步驟,為每個角色設計更有效率的最低權限規則:

  1. 針對主體需要存取的每個資源,為每個動詞草擬個別的 RBAC 規則。
  2. 草擬規則後,請分析規則,檢查是否有相同 verbs 清單的多項規則。將這些規則合併為單一規則。
  3. 其餘規則則維持各自獨立。

這種做法可讓規則設計更有條理,將授予多個資源相同動詞的規則合併,並將授予資源不同動詞的規則分開。

舉例來說,如果工作負載需要 deployments 資源的 get 權限,但需要 daemonsets 資源的 list 和 watch,則建立角色時應使用個別規則。將 RBAC 角色繫結至工作負載後,工作負載就無法在 deployments 上使用 watch。

舉例來說,如果工作負載需要 pods 資源和 daemonsets 資源的 get 和 watch,您可以將這些項目合併為單一規則,因為工作負載需要這兩個資源的相同動詞。

下表顯示兩種規則設計都可行,但分割規則可根據您的需求,更精細地限制資源存取權:

建議 不建議使用
- rules:
    apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get"]
- rules:
    apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["list", "watch"]

授予 Deployment 的 get 存取權,以及 DaemonSet 的 watch 和 list 存取權。受試者無法列出部署作業。

- rules:
    apiGroups: ["apps"]
    resources: ["deployments", "daemonsets"]
    verbs: ["get","list","watch"]

將動詞授予 Deployment 和 DaemonSet。主體可能不需要物件的 list 存取權,但仍會取得該存取權。deployments

- rules:
    apiGroups: ["apps"]
    resources: ["daemonsets", "deployments"]
    verbs: ["list", "watch"]

合併兩項規則,因為主體需要 daemonsets 和 deployments 資源的相同動詞。

- rules:
    apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["list", "watch"]
- rules:
    apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["list", "watch"]

這些分割規則與合併規則的結果相同,但會在角色資訊清單中產生不必要的雜亂內容。

限制特定資源例項的存取權

RBAC 可讓您在規則中使用 resourceNames 欄位,限制對特定具名資源執行個體的存取權。舉例來說,如果您要編寫 RBAC 角色,該角色只需要 update seccomp-high ConfigMap,您可以使用 resourceNames 僅指定該 ConfigMap。請盡可能使用 resourceNames。

建議 不建議使用
- rules:
    apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["seccomp-high"]
    verbs: ["update"]

將主體限制為僅更新 seccomp-high ConfigMap。主體無法更新命名空間中的任何其他 ConfigMap。

- rules:
    apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["update"]

主體可以更新 seccomp-high ConfigMap 和命名空間中的任何其他 ConfigMap。

- rules:
    apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["list"]
- rules:
    apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["seccomp-high"]
    verbs: ["update"]

授予 list 存取命名空間中所有 ConfigMap 的權限,包括 seccomp-high。限制 update存取權,僅限 seccomp-high ConfigMap。由於您無法為具名資源授予 list,因此規則會拆開。

- rules:
    apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["update", "list"]

授予所有 ConfigMap 的 update 存取權,以及 list 存取權。

禁止服務帳戶修改 RBAC 資源

請勿將在 rbac.authorization.k8s.io API 群組中具有 bind、escalate、create、update 或 patch 權限的 Role 或 ClusterRole 資源,繫結至任何命名空間中的服務帳戶。escalate 和 bind 特別容易讓攻擊者規避 RBAC 內建的權限提升防範機制。

限制工作負載自行修改的能力

特定 Kubernetes 工作負載 (尤其是系統工作負載) 具有自我修改的權限。舉例來說,部分工作負載會自動垂直調整自身大小。如果節點遭到入侵,攻擊者可能會透過自我修改功能,在叢集中提升存取權。舉例來說,攻擊者可能會讓命名空間中的工作負載變更自身,以在同一命名空間中以權限較高的 ServiceAccount 執行。

除非必要,否則請勿授予 Pod 自行修改的權限。如果某些 Pod 必須自行修改,請使用 Policy Controller 限制工作負載可變更的內容。舉例來說,您可以使用 NoUpdateServiceAccount 限制範本,防止 Pod 變更其 ServiceAccount。建立政策時,請從限制中排除任何叢集管理元件,如下例所示:

parameters:
  allowedGroups:
  - system:masters
  allowedUsers:
  - system:addon-manager

Kubernetes 服務帳戶

為各項工作負載建立 Kubernetes 服務帳戶

為每個工作負載建立個別的 Kubernetes 服務帳戶。將最低權限 Role 或 ClusterRole 繫結至該服務帳戶。

請勿使用預設服務帳戶

Kubernetes 會在每個命名空間中建立名為 default 的服務帳戶。如果 Pod 未在資訊清單中明確指定服務帳戶,系統會自動將 default 服務帳戶指派給 Pod。請勿將 Role 或 ClusterRole 繫結至 default 服務帳戶。Kubernetes 可能會將 default 服務帳戶指派給不需要這些角色授予存取權的 Pod。

不要自動掛接服務帳戶權杖

Pod 規格中的 automountServiceAccountToken 欄位會告知 Kubernetes 將 Kubernetes 服務帳戶的憑證權杖插入 Pod。Pod 可以使用這個權杖,向 Kubernetes API 伺服器發出已驗證的要求。這個欄位的預設值為 true。

在所有 GKE 版本中,如果 Pod 不需要與 API 伺服器通訊,請在 Pod 規格中設定 automountServiceAccountToken=false。

偏好使用臨時權杖,而非以 Secret 為基礎的權杖

根據預設,節點上的 kubelet 程序會為每個 Pod 擷取短期服務帳戶權杖,並自動輪替。除非您在 Pod 規格中將 automountServiceAccountToken 欄位設為 false,否則 kubelet 會將這個權杖掛接至 Pod,做為投射磁碟區。Pod 對 Kubernetes API 的任何呼叫都會使用這個權杖,向 API 伺服器進行驗證。

如果您是手動擷取服務帳戶權杖,請避免使用 Kubernetes Secrets 儲存權杖。以密碼為準的服務帳戶權杖是舊版憑證,不會過期,也不會自動輪替。如需服務帳戶的憑證,請使用 TokenRequest API 取得會自動輪替的短期權杖。

持續檢查 RBAC 權限

定期檢查 RBAC 角色和存取權,找出潛在的權限提升路徑和多餘規則。舉例來說,假設您未刪除將具有特殊權限的 Role 繫結至已刪除使用者的 RoleBinding,如果攻擊者在該命名空間中建立與已刪除使用者同名的使用者帳戶,該帳戶就會繫結至 Role,並沿用相同的存取權。定期審查可將這類風險降至最低。

檢查清單摘要

後續步驟