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 动词、PersistentVolume 的 create 访问权限或证书签名请求的 create 访问权限。如需查看风险列表,请参阅 Kubernetes RBAC - 提升权限风险。

请勿删除系统 RBAC 角色和绑定

Kubernetes 会创建多个具有 system: 前缀的 RBAC 资源,例如 system:basic-user、system:discovery 和 system:public-info-viewer ClusterRoleBinding。这些资源是确保集群正常运行所必需的。请避免删除系统角色和绑定,因为这可能会导致集群不稳定。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 群组

GKE 会将此群组分配给任何使用 Google 账号(包括所有 Gmail 账号)登录的用户发出的 API 服务器请求。实际上,这与 system:unauthenticated 没有什么区别,因为任何人都可以创建 Google 账号。

将角色绑定到该群组后,任何拥有 Google 账号(包括所有 Gmail 账号)的用户都会获得该角色授予的权限。

system:masters 群组

默认情况下,Kubernetes 会将 cluster-admin ClusterRole 分配给该群组,以启用系统功能。

向该群组添加您自己的主体后,这些主体将能够对集群中的任何资源执行任何操作。

如果可能,请避免创建涉及默认用户、角色和群组的绑定。这可能会导致集群的安全状况出现意外后果。例如:

  • 将默认的 cluster-admin ClusterRole 绑定到 system:unauthenticated 群组可让任何未经身份验证的用户访问集群中的所有资源(包括 Secret)。这些特权高的绑定由大规模恶意软件活动等攻击主动作为目标。
  • 将自定义角色绑定到 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 的名称。

RoleBinding
  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 的名称,则可以使用以下命令在所有命名空间中查找匹配的角色绑定:

    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。
  • 要授予对多个命名空间中资源的访问权限,请在每个命名空间中使用 ClusterRole 和 RoleBinding。
  • 要授予对每个命名空间中资源的访问权限,请使用 ClusterRole 和 ClusterRoleBinding。

在尽可能少的命名空间中授予权限。

请勿使用通配符

* 字符是一个适用于所有内容的通配符。避免在规则中使用通配符。在 RBAC 规则中明确指定 API 组、资源和动词。例如,在 verbs 字段中指定 * 将授予 get、list、watch、patch、update、deletecollection 和 delete 权限。下表举例说明了如何避免在规则中使用通配符:

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

专门向 apps 和 extensions API 组授予 get、list 和 watch 动词。

- 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 资源的权限,但需要 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 访问权限。 主体无法列出 Deployment。

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

将动词同时授予 Deployment 和 DaemonSet。可能对 deployments 对象不需要 list 访问权限的主体仍会获得该访问权限。

- 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 字段仅限访问资源的特定命名实例。例如,如果您正在编写仅需要 update seccomp-high ConfigMap 的 RBAC 角色,则可以使用 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"]

向命名空间中所有 ConfigMap 授予 list 访问权限,包括 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 工作负载(尤其是系统工作负载)有权执行自行修改。例如,某些工作负载会自行纵向自动扩缩。自行修改会使得已入侵节点的攻击者能够在集群中提升其访问权限。例如,攻击者可能会使命名空间中的工作负载自行更改,以将其作为在同一命名空间内具有更高权限的服务账号运行。

除非必要,否则请勿向 Pod 授予自我修改权限。如果某些 Pod 必须进行自我修改,请使用 Policy Controller 来限制工作负载可以更改的内容。例如,您可以使用 NoUpdateServiceAccount 限制条件模板来防止 Pod 更改其 ServiceAccount。创建政策时,请从限制中排除任何集群管理组件,如以下示例所示:

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

Kubernetes 服务账号

为每个工作负载创建 Kubernetes 服务账号

为每个工作负载创建单独的 Kubernetes 服务账号。将最小权限 Role 或 ClusterRole 绑定到该服务账号。

请勿使用默认服务账号

Kubernetes 会在每个命名空间中创建一个名为 default 的服务账号。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 Secret 存储令牌。基于 Secret 的服务账号令牌是不会过期且不会自动轮替的旧版凭据。如果您需要服务账号的凭证,请使用 TokenRequest API 获取可自动变换的短期令牌。

持续审核 RBAC 权限

定期审核 RBAC 角色和访问权限,以确定潜在的上报路径和冗余规则。例如,假设您没有删除将具有特殊权限的 Role 绑定到已删除用户的 RoleBinding。如果攻击者在该命名空间中创建一个与已删除用户同名的用户账号,则他们将绑定到该 Role 并继承相同的访问权限。定期审核可将此风险降至最低。

核对清单摘要

后续步骤