Kubernetes 基础体系 · 第 44/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes RBAC:Role、ClusterRole、Binding、聚合和最小权限

Kubernetes RBAC(Role-Based Access Control,基于角色的访问控制)解决的是一个具体问题:已经通过认证的请求,是否允许执行某个动作

它不负责判断“你是谁”。证书、Bearer Token、OIDC、Webhook 等认证机制先把请求映射为用户名、用户组或 ServiceAccount;随后授权组件根据请求属性和 RBAC 对象决定允许或拒绝。一个简化的请求路径是:

客户端请求
    │
    ▼
认证 Authentication
    │  得到 user、groups、extra
    ▼
授权 Authorization
    │  RBAC、Node、Webhook 等
    ▼
准入 Admission
    │
    ▼
对象持久化或返回响应

因此,下面这个请求至少包含这些授权判断所需的信息:

主体:system:serviceaccount:team-a:reporter
动词:get
API Group:""                  # 核心 API 组
资源:pods
命名空间:team-a
资源名:report-api-7d8c6

如果认证失败,请求通常会得到 401 Unauthorized;如果认证成功但没有匹配的授权规则,请求通常会得到 403 Forbidden。RBAC 只处理后者。


一、RBAC 的基本模型:主体、动作、资源和作用域

RBAC 规则可以抽象为一组允许条件。设一次请求为:

q=(s,v,g,r,n,ρ)q=(s,v,g,r,n,\rho)

其中:

  • ss:subject,主体,例如用户、用户组或 ServiceAccount;
  • vv:verb,动词,例如 getlistcreate
  • gg:API Group,例如 apps,核心 API 组用空字符串表示;
  • rr:resource,例如 deploymentspods/log
  • nn:namespace,命名空间;对集群级资源为空;
  • ρ\rho:resourceName,具体对象名,可为空。

一条 RBAC 规则通常包含:

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

它表示:对 apps API 组中的 deployments 资源,允许执行 getlist。但这条规则仍然需要通过 Role 或 ClusterRole 被绑定到某个主体,才会生效。

对一个主体 ss,RBAC 的允许集合可以理解为:

Allow(s)=bBindings(s)Rules(Role(b))Allow(s)=\bigcup_{b\in Bindings(s)} Rules(Role(b))

也就是说,主体通过所有匹配的 Binding 获得的权限取并集。RBAC 本身没有“显式拒绝”规则:

  • 一条 Role 允许 get,另一条 Role 不能用 deny get 抵消它;
  • 没有任何匹配规则时,RBAC 返回拒绝;
  • 如果 Kubernetes 配置了多个授权器,整体行为还取决于授权器链,但 RBAC 对象本身仍是“允许型”模型。

这也是 RBAC 最容易被误解的地方:Role 是权限声明,Binding 才是权限授予;仅创建 Role 不会让任何主体获得权限。


二、Role:命名空间范围内的权限声明

Role 是命名空间级对象,API 版本为:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role

一个 Role 只能描述命名空间资源的权限,例如:

  • pods
  • services
  • configmaps
  • deployments
  • pods/log

它不能直接描述集群级资源,例如:

  • nodes
  • namespaces
  • persistentvolumes
  • clusterroles

下面的 Role 允许访问 team-a 命名空间中的 Pod 和特定 ConfigMap:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-observer
  namespace: team-a
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["public-config"]
    verbs: ["get"]

这里有几个容易忽略的细节。

1. 核心 API 组必须写成空字符串

Pod、Service、ConfigMap、Secret 等属于核心 API 组,不是名为 core 的 API 组:

apiGroups: [""]

Deployment 属于 apps API 组:

apiGroups: ["apps"]

写成下面这样不会匹配 Deployment:

apiGroups: [""]
resources: ["deployments"]

2. 资源名和子资源是不同的资源标识

日志、状态和扩缩容等接口通常通过子资源暴露:

resources:
  - pods/log

例如,允许查看 Pod 对象本身,不等于允许查看日志:

- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get"]

- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]

同样,Deployment 的扩缩容通常涉及:

resources: ["deployments/scale"]

而更新 Deployment 的 spec 是:

resources: ["deployments"]

如果只授予 deployments 的权限,不能假设它自动覆盖 deployments/scale

3. getlistwatch 语义不同

  • get:读取单个对象;
  • list:列出对象;
  • watch:持续接收对象变化;
  • create:创建对象;
  • update:整体更新对象;
  • patch:部分更新对象;
  • delete:删除单个对象;
  • deletecollection:批量删除对象。

例如,只给:

verbs: ["get"]

不能让控制器正常工作。控制器通常需要:

verbs: ["get", "list", "watch"]

因为它先要列出现有对象,再通过 watch 感知变化。将 watch 授予敏感资源还意味着主体可以持续观察资源变化,不能把它当作普通读取权限完全等价处理。

4. resourceNames 是精确对象名限制,不是通配符

resourceNames: ["public-config"]

表示只允许名为 public-config 的对象。它不是正则表达式,也不是前缀匹配。

resourceNames 对不同动作存在重要边界:

  • getupdatepatchdelete 等可以按对象名限制;
  • create 无法通过 resourceNames 限制,因为授权检查时规则不能可靠地把“将要创建的对象名”作为这种通用约束;
  • deletecollection 不能按单个 resourceName 限制;
  • listwatch 使用 resourceNames 时,客户端通常需要提供名称字段选择器,例如 metadata.name=public-config,否则请求可能不匹配规则。

因此,下面的规则不能表达“只允许创建名为 public-config 的 ConfigMap”:

- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["public-config"]
  verbs: ["create"]

如果必须限制创建出的对象名称,需要结合准入控制策略,而不是依赖 RBAC 的 resourceNames


三、ClusterRole:集群级权限声明和可复用权限模板

ClusterRole 也是权限声明对象,但它不是简单的“更大版本 Role”:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole

ClusterRole 可以描述三类权限:

  1. 集群级资源权限;
  2. 跨命名空间的命名空间资源权限;
  3. 非资源 URL 权限。

例如:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-observer
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]

  - nonResourceURLs: ["/version", "/healthz"]
    verbs: ["get"]

1. ClusterRoleBinding 使权限作用于整个集群

如果通过 ClusterRoleBinding 绑定 ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: node-observer-binding
subjects:
  - kind: Group
    name: platform-observers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: node-observer
  apiGroup: rbac.authorization.k8s.io

那么 platform-observers 获得:

  • 所有命名空间中的命名空间资源权限;
  • 集群级资源权限;
  • 规则中声明的非资源 URL 权限。

例如,如果 ClusterRole 里同时有:

resources: ["pods"]
verbs: ["get", "list"]

那么该 ClusterRoleBinding 会让主体读取所有命名空间的 Pod。

2. RoleBinding 也可以引用 ClusterRole

这是 ClusterRole 的一个关键用途。RoleBinding 位于某个命名空间,但其 roleRef 可以指向 ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-observer-in-team-a
  namespace: team-a
subjects:
  - kind: ServiceAccount
    name: reporter
    namespace: team-a
roleRef:
  kind: ClusterRole
  name: pod-observer-template
  apiGroup: rbac.authorization.k8s.io

此时,ClusterRole 中的命名空间资源权限只在 team-a 生效,而不是所有命名空间。

因此:

绑定方式 命名空间资源的作用范围 集群级资源的作用
Role + RoleBinding RoleBinding 所在命名空间 不适用
ClusterRole + RoleBinding RoleBinding 所在命名空间 不能通过该 RoleBinding 获得集群级资源权限
ClusterRole + ClusterRoleBinding 所有命名空间 集群级资源也生效

这使 ClusterRole 可以作为“权限模板”复用,而不必为每个命名空间复制一份 Role。

3. Role 和 ClusterRole 不是继承关系

一个 Role 不会自动继承某个 ClusterRole;一个 ClusterRole 也不会因为名称相似而继承 Role。权限来源只能是:

  1. 直接绑定的 Role 或 ClusterRole;
  2. ClusterRole 聚合得到的规则;
  3. 其他授权器或特殊系统机制。

四、Binding:把权限声明授予主体

Binding 是连接“谁”和“哪些规则”的对象。Kubernetes 有两种:

  • RoleBinding:命名空间级绑定;
  • ClusterRoleBinding:集群级绑定。

Binding 主要包含三部分:

subjects:
  - ...
roleRef:
  kind: Role 或 ClusterRole
  name: ...

1. RoleBinding 的主体类型

ServiceAccount

subjects:
  - kind: ServiceAccount
    name: reporter
    namespace: team-a

ServiceAccount 的完整身份通常是:

system:serviceaccount:team-a:reporter

不要把 ServiceAccount 名称当作集群范围唯一名称;命名空间是身份的一部分。

User

subjects:
  - kind: User
    name: alice@example.com
    apiGroup: rbac.authorization.k8s.io

User 通常来自证书的 CN、OIDC 的用户名声明或外部认证 Webhook。Kubernetes 不会自动创建一个名为 alice@example.com 的 User 对象。

Group

subjects:
  - kind: Group
    name: platform-readers
    apiGroup: rbac.authorization.k8s.io

Group 的名称必须与认证阶段提供的组名精确匹配。比如 OIDC 的组声明到底映射成 platform-readers 还是 groups:platform-readers,取决于认证配置和身份提供商,RBAC 本身不会替你规范化。

2. RoleBinding 的作用域由 Binding 决定

下面的 Binding 只在 team-a 生效:

metadata:
  namespace: team-a

即使它引用了一个 ClusterRole:

roleRef:
  kind: ClusterRole
  name: pod-observer-template

也不会自动扩展到 team-b。如果要让同一个主体访问多个命名空间,通常需要在每个命名空间分别创建 RoleBinding,或者使用 ClusterRoleBinding 授予全局范围。

3. roleRef 不可修改

RoleBinding 和 ClusterRoleBinding 的 roleRef 是不可变的。要把绑定从 reader 改成 writer,不能直接修改 roleRef.name,而应删除并重新创建 Binding:

kubectl delete rolebinding pod-observer-in-team-a -n team-a
kubectl apply -f new-binding.yaml

这是为了避免通过修改现有绑定而绕过对“绑定新权限”的审计和控制。

Binding 的 subjects 可以修改,但修改主体本身就是高风险操作:能修改某个高权限 Binding 的主体,通常等价于能把该权限转交给其他身份。


五、一个可运行的最小权限示例

下面创建一个报告程序使用的 ServiceAccount。它只能在 team-a 中:

  • 读取 Pod;
  • 观察 Pod 变化;
  • 读取名为 public-config 的 ConfigMap;
  • 不能读取 Secret;
  • 不能读取其他命名空间;
  • 不能创建、删除或修改资源。

1. 创建命名空间、主体和 Role

apiVersion: v1
kind: Namespace
metadata:
  name: team-a
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: reporter
  namespace: team-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: reporter-read
  namespace: team-a
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["public-config"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: reporter-read
  namespace: team-a
subjects:
  - kind: ServiceAccount
    name: reporter
    namespace: team-a
roleRef:
  kind: Role
  name: reporter-read
  apiGroup: rbac.authorization.k8s.io

保存为 rbac-example.yaml 后执行:

kubectl apply -f rbac-example.yaml

预期结果类似:

namespace/team-a created
serviceaccount/reporter created
role.rbac.authorization.k8s.io/reporter-read created
rolebinding.rbac.authorization.k8s.io/reporter-read created

2. 验证允许和拒绝的请求

使用 --as 模拟该 ServiceAccount:

kubectl auth can-i get pods \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

预期:

yes

跨命名空间访问:

kubectl auth can-i get pods \
  --as=system:serviceaccount:team-a:reporter \
  -n team-b

预期:

no

读取指定 ConfigMap:

kubectl auth can-i get configmap/public-config \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

预期:

yes

读取其他 ConfigMap:

kubectl auth can-i get configmap/private-config \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

预期:

no

尝试读取 Secret:

kubectl auth can-i list secrets \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

预期:

no

查看主体在该命名空间下的权限列表:

kubectl auth can-i --list \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

输出会列出匹配的资源和动词,但不同 Kubernetes 版本、客户端和授权配置下的展示格式可能不同。--list 适合发现明显的过宽权限,不能替代对关键路径逐项测试。

3. 用实际客户端访问时,Token 不是 RBAC 对象

在现代 Kubernetes 中,ServiceAccount 通常通过短期、投影的 Token 访问 API,而不是依赖长期 Secret Token。可以让 Kubernetes 创建一个短期 Token:

TOKEN=$(kubectl create token reporter -n team-a)

然后使用它访问 API:

kubectl --server="$KUBE_SERVER" \
  --token="$TOKEN" \
  --certificate-authority="$CA_FILE" \
  get pods -n team-a

其中:

  • KUBE_SERVER 是 API Server 地址;
  • CA_FILE 是验证 API Server 证书的 CA 文件;
  • Token 只证明请求主体是 team-a 中的 reporter
  • 是否能读取 Pod,仍由 RoleBinding 决定。

Token 的签发、过期和轮换属于认证与凭据生命周期问题,不会因为修改 Role 而改变 Token 的身份。修改 RBAC 后,已有 Token 通常仍代表同一个主体,但后续请求会按最新的授权对象重新判断。


六、权限规则的匹配边界

1. 通配符会跨越未来新增能力

下面的规则非常宽:

- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]

它不仅覆盖当前已知资源,还可能覆盖未来安装的 API 资源。CRD、聚合 API Server 或 Kubernetes 新版本增加资源后,这类通配符可能自动获得新的权限。

最小权限设计通常应明确写出:

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

但明确列表也不能消除所有风险,因为对 Deployment 的 updatepatch 可能间接影响 Pod、Secret 挂载和容器权限。

2. list 不是“只能知道数量”

允许 list pods 通常意味着可以读取返回结果中的 Pod 元数据,往往包括:

  • Pod 名称;
  • 标签;
  • 镜像;
  • 节点;
  • 状态;
  • IP;
  • 部分配置字段。

如果规则还允许读取 Secret、ConfigMap 或 Pod 详情,泄露范围会扩大。RBAC 按 API 操作控制,不会根据调用者是否“只打算读取一个字段”自动进行字段级脱敏。

3. 对象权限不等于字段权限

RBAC 通常控制资源和动词,不控制对象内部字段。例如:

resources: ["deployments"]
verbs: ["patch"]

意味着可以对匹配的 Deployment 执行补丁操作,而不是“只能修改副本数”。要限制可写字段,通常需要准入策略、专用控制器或 API 设计,而不是单靠 RBAC。

4. 非资源 URL 只适用于 ClusterRole

某些 API Server 端点不是 Kubernetes 资源,例如:

/version
/healthz
/readyz

ClusterRole 可以声明:

rules:
  - nonResourceURLs: ["/version", "/healthz"]
    verbs: ["get"]

非资源 URL 通常使用路径匹配规则,不能把它们写进 resources。这类权限应明确列出,避免使用过宽的 /*


七、ClusterRole 聚合:把多个权限片段组合起来

ClusterRole 聚合允许一个 ClusterRole 根据标签自动收集其他 ClusterRole 的规则。聚合目标通常包含:

aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.example.com/aggregate-to-reporting: "true"

例如先创建一个作为“目标角色”的 ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: reporting
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.example.com/aggregate-to-reporting: "true"

再创建一个带匹配标签的权限片段:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: reporting-pods
  labels:
    rbac.example.com/aggregate-to-reporting: "true"
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

ClusterRole 聚合控制器观察这些对象,并把 reporting-pods 的规则写入 reportingrules。之后只需要绑定 reporting

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: reporting-binding
subjects:
  - kind: Group
    name: reporting-users
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: reporting
  apiGroup: rbac.authorization.k8s.io

数据流可以表示为:

flowchart LR
    A[权限片段 ClusterRole] -->|标签匹配| C[聚合控制器]
    B[另一个权限片段 ClusterRole] -->|标签匹配| C
    C -->|写入规则| D[目标 ClusterRole]
    D --> E[RoleBinding 或 ClusterRoleBinding]
    E --> F[主体获得权限]

聚合有三个重要边界:

  1. 聚合不是 Binding
    聚合只修改目标 ClusterRole 的规则;目标 ClusterRole 仍需通过 Binding 授予主体。

  2. 聚合不是静态复制
    当匹配标签的 ClusterRole 增加、修改或删除时,控制器会重新计算目标角色。权限可能因此动态增加或减少。

  3. 目标角色的 rules 由控制器管理
    对包含 aggregationRule 的 ClusterRole 手工编辑 rules,可能被控制器覆盖。应修改被聚合的权限片段或聚合选择器。

Kubernetes 内置的 admineditview 等默认 ClusterRole 也使用聚合机制向角色提供扩展资源权限。自定义资源的安装者可以通过约定标签把 CRD 相关权限聚合到这些默认角色中。常见标签包括:

rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"

这很方便,但也是安全边界:安装一个带有聚合标签的 ClusterRole,可能让已有的 admineditview 用户获得新资源的权限。 因此,检查 CRD 或扩展组件安装包中的 RBAC 清单非常重要。

此外,标签本身也是权限输入。能够创建或修改 ClusterRole,并能给它添加聚合标签的主体,可能影响多个现有角色。不能把“只是修改标签”视为低风险操作。


八、默认角色、自动修复和系统角色

Kubernetes API Server 会创建一些默认 ClusterRole 和 ClusterRoleBinding,例如:

  • cluster-admin
  • admin
  • edit
  • view
  • system:discovery
  • system:node
  • 各类 system: 前缀的控制器角色

带有 kubernetes.io/bootstrapping=rbac-defaults 标签的默认 RBAC 对象可能被 Kubernetes 的自动协调机制恢复缺失的规则或绑定。例如,直接从默认角色中删除一条规则,控制器可能在之后重新添加。

生产环境中不应直接修改默认角色来实现业务权限。更可控的方式是:

  1. 创建自己的 ClusterRole;
  2. 通过 RoleBinding 限制到指定命名空间;
  3. 如果确实需要复用默认角色的语义,创建独立角色或使用明确的聚合标签;
  4. 对升级和扩展组件安装后的实际权限重新验证。

cluster-admin 是全局高权限角色。尤其要注意,system:masters 组在 Kubernetes 的授权语义中具有特殊地位,通常被视为绕过普通授权检查的超级管理员组。不要把普通运维人员、应用 ServiceAccount 或自动化系统加入该组来“解决权限问题”。


九、最小权限:从形式化目标到实际规则

最小权限不是“规则行数最少”,而是主体在任务约束下获得的权限集合尽可能小。

设任务要求集合为:

T={(g,r,v,n,ρ)}T=\{(g,r,v,n,\rho)\}

实际授予权限集合为 PP。安全上首先要求:

TPT \subseteq P

如果存在某个权限:

pP,pTp\in P,\quad p\notin T

那么它是潜在的额外权限。最小权限的目标是在满足任务的前提下,尽量减少:

PTP-T

完整推导一个报告程序的权限:

第一步:列出实际 API 调用

假设程序需要:

GET    /api/v1/namespaces/team-a/pods
GET    /api/v1/namespaces/team-a/pods/{name}
WATCH  /api/v1/namespaces/team-a/pods
GET    /api/v1/namespaces/team-a/configmaps/public-config

对应为:

("", "pods", "list",  "team-a")
("", "pods", "get",   "team-a")
("", "pods", "watch", "team-a")
("", "configmaps", "get", "team-a", "public-config")

第二步:删除没有必要的动作

程序不创建、不修改、不删除 Pod,因此不授予:

create、update、patch、delete

程序只读取一个 ConfigMap,因此不授予:

list configmaps
get configmaps/*

第三步:限定命名空间

如果程序只服务 team-a,使用 RoleBinding,而不是 ClusterRoleBinding:

RoleBinding(team-a) ⟹ 规则只在 team-a 生效

第四步:限定对象名

对 ConfigMap 使用 resourceNames

resourceNames: ["public-config"]

但不对 Pod 使用对象名限制,因为程序需要观察一组动态 Pod。

第五步:验证反例

如果误用 ClusterRoleBinding:

roleRef:
  kind: ClusterRole
  name: reporter-read

即使 ClusterRole 中只有 get/list/watch pods,它也会把这些命名空间资源权限扩展到所有命名空间。规则本身看起来不宽,绑定范围却改变了权限集合

再例如,如果把:

resources: ["pods"]
verbs: ["*"]

授予报告程序,它将获得创建、修改和删除 Pod 的权限。即使程序代码当前只调用 list,凭据泄露后攻击者仍可使用全部已授予权限。


十、RBAC 与“权限升级”风险

允许一个主体管理 RBAC 对象,通常比表面上的资源权限更危险。

例如,主体拥有:

- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["roles", "rolebindings"]
  verbs: ["create", "update", "patch"]

它可能尝试创建一个包含 secrets/get 的 Role,再把 RoleBinding 指向自己。若没有额外限制,这相当于把“管理 RBAC”变成“自我授权”。

Kubernetes 的 RBAC 授权实现包含权限升级保护:

  • 创建或更新 Role、ClusterRole 时,主体通常不能声明自己本身没有的权限;
  • 把 RoleBinding 或 ClusterRoleBinding 绑定到超出自身权限的角色时,通常需要相应的 bind 权限;
  • escalatebind 是针对 RBAC 对象的特殊动词;
  • 拥有 escalatebind 或特殊超级管理员身份的主体可以绕过部分保护,因此这些权限必须单独审查。

一个典型的高风险规则是:

- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["roles", "clusterroles", "rolebindings", "clusterrolebindings"]
  verbs: ["*"]

它不只是“能查看权限配置”,而是可能控制整个集群的授权关系。

同理,以下权限虽然不是 RBAC API 本身,也常常可以导致间接提权:

  • 修改可执行工作负载的 Deployment;
  • 创建能挂载高权限 ServiceAccount 的 Pod;
  • 读取 Secret;
  • 修改 Webhook、准入配置或控制器配置;
  • 修改命名空间中的资源,使其使用高权限身份。

所以最小权限分析必须考虑权限的可达效果,不能只按资源名称判断危险程度。


十一、常见误解和失败表现

误解一:创建 Role 后权限自动生效

错误示例:

kind: Role
metadata:
  name: reader

没有任何 RoleBinding 时,所有主体仍然可能得到:

no

检查方法:

kubectl get role reader -n team-a
kubectl get rolebinding -n team-a
kubectl auth can-i get pods -n team-a \
  --as=system:serviceaccount:team-a:reporter

误解二:RoleBinding 绑定 ClusterRole 就变成集群权限

RoleBinding 的命名空间决定命名空间资源的作用范围。引用 ClusterRole 只复用规则,不改变 RoleBinding 的范围。

误解三:给了 get pods 就能读日志

日志是 pods/log 子资源,需要单独授权:

- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]

误解四:允许 get secrets 不危险,因为没有 list

Secret 的 get 仍然可以读取已知名称的 Secret。名称可从 Pod、Deployment、配置或错误信息中推断,不能把“没有 list”当作足够的保密措施。

误解五:ServiceAccount 是命名空间级,所以它不能拥有集群权限

ServiceAccount 对象位于命名空间,但它可以被 ClusterRoleBinding 绑定。最终权限取决于 Binding,而不是 ServiceAccount 对象的存放位置。

误解六:认证成功就应该能访问 API

认证只产生身份。下面的错误通常属于授权问题:

Error from server (Forbidden): pods is forbidden:
User "system:serviceaccount:team-a:reporter" cannot list resource "pods"

应检查:

kubectl auth can-i list pods \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

如果实际客户端使用的是 OIDC 或 Webhook 用户,还要确认 API Server 看到的用户名和组名,而不是只看身份提供商界面中的显示名称。


十二、诊断 RBAC 问题的顺序

发生 403 Forbidden 时,可以按请求属性逐项核对。

1. 确认实际主体

对于 ServiceAccount:

system:serviceaccount:<namespace>:<name>

对于 OIDC、证书或 Webhook 用户,要确认:

  • 用户名;
  • 组;
  • 是否使用了前缀;
  • 是否存在大小写或空格差异。

2. 确认资源的 API Group

kubectl api-resources

该命令可以帮助确认资源名称、短名称、API Group 和是否命名空间级。

例如:

kubectl api-resources | grep -E 'pods|deployments|roles'

3. 确认子资源

命令行中的:

kubectl logs pod/example -n team-a

实际对应的授权资源通常是:

pods/log

而不是普通 pods

4. 分别测试具体权限

kubectl auth can-i get pods -n team-a --as=...
kubectl auth can-i list pods -n team-a --as=...
kubectl auth can-i watch pods -n team-a --as=...
kubectl auth can-i get pods/log -n team-a --as=...

不要只测试 --list 后就认为所有客户端操作都正确,因为客户端可能还会调用 watchpatchupdate/status 等接口。

5. 检查绑定范围和聚合状态

kubectl get role,rolebinding -n team-a
kubectl get clusterrole,clusterrolebinding
kubectl describe rolebinding reporter-read -n team-a
kubectl describe clusterrole reporting

如果使用了聚合角色,还要检查:

kubectl get clusterrole reporting -o yaml
kubectl get clusterrole --show-labels

确认匹配标签的权限片段是否存在,以及目标 ClusterRole 的 rules 是否已由控制器更新。

6. 区分 RBAC 拒绝和其他授权器拒绝

Kubernetes 可以配置 RBAC、Node、Webhook 等授权器。kubectl auth can-i 能帮助模拟授权,但最终拒绝原因仍可能来自:

  • Node 授权器;
  • 外部 Webhook 授权器;
  • 准入控制器;
  • API 资源本身的业务校验。

例如,请求得到 403 不一定意味着某个 Role 缺少规则,也可能是外部授权 Webhook 返回了拒绝。审计日志可以记录请求中的用户、动词、资源、命名空间和响应状态,用来将实际失败请求与 RBAC 配置对应起来。


十三、生产环境中的边界和取舍

1. 命名空间隔离不是完整的多租户隔离

RoleBinding 可以把命名空间资源权限限制在租户命名空间,但以下对象仍是集群级的:

  • Node;
  • PersistentVolume;
  • ClusterRole;
  • ClusterRoleBinding;
  • StorageClass;
  • CustomResourceDefinition;
  • ValidatingWebhookConfiguration、MutatingWebhookConfiguration。

如果租户能创建 Pod,它还可能通过节点、主机路径、网络策略、运行时配置等其他机制影响隔离边界。因此,RBAC 是多租户的一部分,不是完整的租户隔离方案。

2. “只读角色”也可能泄露敏感数据

内置 view 角色通常不包含 Secret 读取权限,这是有意设计的。因为读取 Secret 的价值通常接近读取凭据本身。扩展内置角色时,应明确判断新资源是否包含:

  • Token;
  • 密钥;
  • 数据库密码;
  • 配置中的内部地址;
  • 其他租户信息。

3. 自动化组件需要的不是“所有权限”

控制器常见的权限是:

get/list/watch:观察输入资源
create/update/patch/delete:管理它负责的输出资源

但不同控制器的输入和输出应分别列出。直接使用:

apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]

会把控制器的故障、漏洞或凭据泄露影响面扩大到整个集群。

4. 权限变更应有验证和回滚路径

修改 RBAC 前,先导出相关对象:

kubectl get role,rolebinding -n team-a -o yaml > team-a-rbac-backup.yaml
kubectl get clusterrole,clusterrolebinding -o yaml > cluster-rbac-backup.yaml

应用后验证:

kubectl auth can-i --list \
  --as=system:serviceaccount:team-a:reporter \
  -n team-a

如果错误地创建了高权限 ClusterRoleBinding,应立即删除错误绑定,而不是只删除 Role:

kubectl delete clusterrolebinding <错误绑定名>

删除 Role 不一定能撤销其他 Binding 通过同名或其他角色授予的权限;撤销时必须定位实际的权限来源。


十四、设计 RBAC 时的检查框架

一个可验证的 RBAC 设计至少应回答以下问题:

  1. 主体是谁:用户、组还是 ServiceAccount?
  2. 主体来自哪种认证机制,API Server 最终看到的用户名和组是什么?
  3. 需要访问哪些 API Group、资源和子资源?
  4. 每个资源需要哪些动词,而不是笼统地使用 *
  5. 权限只需要一个命名空间,还是确实需要跨命名空间?
  6. 是否可以用 RoleBinding 引用 ClusterRole,而不是 ClusterRoleBinding?
  7. 是否能用 resourceNames 缩小到特定对象?
  8. 是否包含 Secret、Token、Webhook 配置或 RBAC 对象等高风险资源?
  9. 修改资源的权限是否会间接修改 Pod、身份或凭据?
  10. 是否会通过聚合标签影响内置角色?
  11. 是否存在 bindescalateimpersonatesystem:masters 等提权路径?
  12. 是否使用 kubectl auth can-i 和实际审计日志验证了允许与拒绝两类结果?

其中 impersonate 也需要特别审查。允许主体模拟用户、组或 ServiceAccount 后,它可能以另一个身份发起请求;这种权限本质上是在控制授权输入,而不是普通的业务资源读取权限。

RBAC 的核心关系可以最后压缩为一句形式化描述:

有效权限=所有匹配 Binding 引用的规则并集\text{有效权限} = \text{所有匹配 Binding 引用的规则并集}

而最小权限的工程目标是:

任务所需权限实际授予权限\text{任务所需权限} \subseteq \text{实际授予权限}

并尽量让两者之间的差集最小。Role 与 ClusterRole 决定“允许什么”,RoleBinding 与 ClusterRoleBinding 决定“授予谁、在哪个范围授予”,聚合决定“多个权限片段如何动态组合”。理解这三层关系,才能在命名空间隔离、扩展资源和自动化控制器之间建立可审计、可验证的授权边界。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。