GKE RBAC 的最佳做法


本页面介绍了规划基于角色的访问权限控制 (RBAC) 政策的最佳做法。如需了解如何在 Google Kubernetes Engine (GKE) 中实现 RBAC,请参阅配置基于角色的访问权限控制

RBAC 是 Kubernetes 中的一个核心安全功能,可让您创建精细的权限,以管理用户和工作负载可以对集群中的资源执行的操作。作为平台管理员,您可以创建 RBAC 角色并将这些角色绑定到主体,这些主体是经过身份验证的用户,例如服务账号或 Google 群组。

准备工作

确保熟悉以下概念:

如需查看本指南的核对清单,请参阅核对清单摘要

RBAC 的工作原理

RBAC 支持以下类型的角色和绑定:

  • ClusterRole:一组可以应用于任何命名空间或整个集群的权限。
  • 角色:一组仅限于单个命名空间的权限。
  • ClusterRoleBinding:将 ClusterRole 绑定到集群中所有命名空间的用户或群组。
  • RoleBinding:将 RoleClusterRole 绑定到特定命名空间中的用户或群组。

您可以在 RoleClusterRole 中将权限定义为 rules。角色中的每个 rules 字段都包含 API 组、该 API 组中的 API 资源以及允许对这些资源使用的动词(操作)。(可选)您可以使用 resourceNames 字段将动词的范围限定为已命名的 API 资源实例。如需查看示例,请参阅仅限访问特定资源实例

定义角色后,使用 RoleBindingClusterRoleBinding 将该角色绑定到主体。根据您要在单个命名空间中还是在多个命名空间中授予权限,选择绑定类型。

RBAC 角色设计

遵循最小权限原则

在 RBAC 角色中分配权限时,请遵循最小权限原则并授予执行任务所需的最低权限。使用最小权限原则可以降低集群被盗用时提升权限的可能性,并降低过度访问导致安全事件的可能性。

设计角色时,请仔细考虑常见的提升权限风险,例如 escalatebind 动词、PersistentVolume 的 create 访问权限或证书签名请求的 create 访问权限。如需查看风险列表,请参阅 Kubernetes RBAC - 提升权限风险

避免使用默认角色和群组

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 绑定到您自己的主体或任何默认用户和群组。如果您的应用需要许多权限,请确定所需的确切权限,然后为此创建一个特定角色。
  • 在绑定主体之前评估其他默认角色授予的权限。
  • 在修改这些群组的成员之前,请评估绑定到默认群组的角色。

检测并阻止使用默认角色和群组

您应评估集群,以确定是否已使用 ClusterRoleBinding 和 RoleBinding 绑定 system:anonymous 用户或者绑定 system:unauthenticatedsystem:authenticated 群组。

ClusterRoleBinding
  1. 列出主体为 system:anonymoussystem:unauthenticatedsystem: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:anonymoussystem:unauthenticatedsystem: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 的命名空间。

将权限限制到命名空间级别

根据工作负载或用户的需求,使用如下绑定和角色:

  • 要授予对一个命名空间中的资源的访问权限,请使用 RoleRoleBinding
  • 要授予对多个命名空间中资源的访问权限,请在每个命名空间中使用 ClusterRoleRoleBinding
  • 要授予对每个命名空间中资源的访问权限,请使用 ClusterRoleClusterRoleBinding

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

请勿使用通配符

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

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

专门向 appsextensions API 组授予 getlistwatch 动词。

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

将动词授予任何 API 组中的 deployments

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

仅将 getlistwatch 动词授予 appsextensions API 组中的部署。

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

授予所有动词,包括 patchdelete

使用单独规则对特定资源授予最小访问权限

规划规则时,请尝试以下简要步骤,在每个角色中采用更高效的最小权限规则设计:

  1. 为主体需要访问的每项资源上的动词草拟单独的 RBAC 规则。
  2. 草拟规则后,分析规则,以检查多条规则是否具有相同的 verbs 列表。将这些规则合并为一条规则。
  3. 请将其余的所有规则彼此分散。

这种方法可实现更有条理的规则设计,将对多个资源授予相同动词的规则组合起来,将为资源授予不同动词的规则彼此分散。

例如,如果您的工作负载需要获取 deployments 资源的权限,但需要 daemonsets 资源的 listwatch 权限,则您应该在创建角色是使用单独规则。当您将 RBAC 角色绑定到工作负载时,该角色将无法对 deployments 使用 watch

再举一例,如果您的工作负载需要 pods 资源和 daemonsets 资源的 getwatch 权限,您可以将它们组合成一条规则,因为工作负载需要在这两个资源上使用相同的动词。

在下表中,这两种规则设计均有效,但拆分规则会根据需要更精细地限制资源访问权限:

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

为 Deployment 授予 get 访问权限,为 DaemonSet 授予 watchlist 访问权限。 主体无法列出 Deployment。

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

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

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

组合两条规则,因为主体需要对 daemonsetsdeployments 资源使用相同的动词。

- 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 组具有 bindescalatecreateupdatepatch 权限的 RoleClusterRole 资源绑定到任何命名空间中的服务账号。特别是 escalatebind 可让攻击者绕过 RBAC 中内置的升级预防机制

Kubernetes 服务账号

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

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

请勿使用默认服务账号

Kubernetes 会在每个命名空间中创建一个名为 default 的服务账号。default 服务账号会自动分配给未在清单中明确指定服务账号的 Pod。避免将 RoleClusterRole 绑定到 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 并继承相同的访问权限。定期审核可将此风险降至最低。

核对清单摘要

后续步骤