首页 / Kubernetes (k8s) 入门教程 / RBAC 基于角色的访问控制

Kubernetes (k8s) 入门教程

RBAC 基于角色的访问控制

本教程共 65 篇 · 第 44 篇 · 更新于 2026-08-14 · 约 22 分钟阅读

KubernetesRBACRoleClusterRoleRoleBinding权限管理安全

本节目标:掌握 Role、ClusterRole、RoleBinding、ClusterRoleBinding 这四个对象,学会写出精确到某个资源某个动词的权限规则。

上一章说到鉴权有六种模式,RBAC 是现在唯一值得你花时间学的那一种。它用 rbac.authorization.k8s.io API 组驱动决策,规则本身就是 Kubernetes 对象,可以 kubectl apply 动态改,不用重启 API 服务器。

RBAC 的思路很朴素:不直接给人授权,而是先定义角色,再把角色绑给人。 人事变动只需要改绑定,不用重写规则。

44-1 四个对象,两个维度

RBAC 只有四种对象,先把它们的关系记牢:

命名空间级集群级
定义权限RoleClusterRole
授予权限RoleBindingClusterRoleBinding

左边一列是”角色”,说明能做什么。右边一列是”绑定”,说明谁拿到了这些能力

上下两行的区别是作用域:Role 必须属于某个命名空间,ClusterRole 是集群作用域的资源。

Kubernetes 之所以拆成两套名字,是因为一个对象要么是命名空间级的,要么是集群级的,不可能两者兼具。

Note

RBAC 的权限是纯累加的,不存在”拒绝”规则。没有明确授予的动作,默认就是不允许。所以你永远不需要写”禁止某人删除 Pod”,只需要不给他 delete 权限。

44-2 Role:命名空间内的权限清单

看一个最经典的例子——只读 Pod:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
  - apiGroups: [""]           # "" 代表核心 API 组
    resources: ["pods"]
    verbs: ["get", "watch", "list"]

三个字段撑起了整条规则:

  • apiGroups——资源属于哪个 API 组。空字符串 "" 是核心组(Pod、Service、ConfigMap 等都在这里)。DeploymentappsIngressnetworking.k8s.io
  • resources——资源类型,用 URL 里出现的那个复数小写名字,比如 pods 而不是 Pod
  • verbs——允许的动作,就是上一章讲的那套请求动词。

rules 是个列表,可以写多条。每条独立生效,取并集。

不确定某个资源属于哪个 API 组?查一下:

# 列出全部资源类型及其 API 组、是否命名空间级
kubectl api-resources

# 只看某一个
kubectl api-resources | grep deployments

这个命令我几乎每次写 RBAC 都要敲一遍,比翻文档快。

子资源怎么写

有些 API 有子资源,比如取 Pod 日志的请求路径是:

GET /api/v1/namespaces/{namespace}/pods/{name}/log

这里 pods 是资源,log 是子资源。RBAC 里用斜杠分隔:

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

常见子资源还有 pods/exec(进容器执行命令)、pods/portforwarddeployments/scalepods/status

Warning

pods/exec 权限威力极大。给了它,等于让人能钻进任何 Pod 里,拿到容器内的一切——包括挂载进去的 Secret。给权限时想清楚。

精确到某个对象名

resourceNames 可以把权限锁到具体某个实例上:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: configmap-updater
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["my-configmap"]
    verbs: ["update", "get"]

这条规则只能改 my-configmap 这一个,别的碰不了。

Note

resourceNamescreatedeletecollection 无效create 是因为鉴权时对象名字还不确定。另外,如果你用 resourceNames 限制 listwatch,客户端必须在请求里带上匹配的字段选择器才能通过,比如 kubectl get configmaps --field-selector=metadata.name=my-configmap。这个细节挺容易让人困惑——明明给了权限,kubectl get configmaps 却还是 403。

44-3 ClusterRole:三种用法

ClusterRole 能干 Role 能干的一切,此外还能授权:

  1. 集群作用域的资源——NodePersistentVolumeNamespaceClusterRole 自己。
  2. 非资源端点——/healthz/metrics/version 这类路径。
  3. 跨全部命名空间的命名空间级资源——比如让某人能跑 kubectl get pods --all-namespaces

一个 Secret 读取角色:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # ClusterRole 不写 namespace
  name: secret-reader
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "watch", "list"]

非资源端点这样写:

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

注意 nonResourceURLsresources 不能出现在同一条规则里,得分开写。

44-4 绑定:把角色发给主体

绑定对象有两个关键字段:subjects(发给谁)和 roleRef(发什么)。

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
  - kind: User
    name: jane                    # 大小写敏感
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role                      # 只能是 Role 或 ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

subjects 支持三种 kind

subjects:
  # 人类用户
  - kind: User
    name: "alice@example.com"
    apiGroup: rbac.authorization.k8s.io
  # 用户组
  - kind: Group
    name: "frontend-admins"
    apiGroup: rbac.authorization.k8s.io
  # 服务账号(注意这个不写 apiGroup,要写 namespace)
  - kind: ServiceAccount
    name: default
    namespace: kube-system

UserGroup 只是字符串,Kubernetes 不存储它们——上一章说过。ServiceAccount 是真实对象,所以要指定它所在的命名空间。

Tip

前缀 system: 是 Kubernetes 系统保留的,别给自己的用户或组起这种名字。ServiceAccount 在认证层的用户名前缀是 system:serviceaccount:(单数),所属组的前缀是 system:serviceaccounts:(复数)。差一个字母,含义完全不同,我第一次配的时候就写错过。

一个容易忽略的组合技

RoleBinding 可以引用 ClusterRole。这时候 ClusterRole 里的权限只在这个 RoleBinding 所在的命名空间生效

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: development           # 只在 development 生效
subjects:
  - kind: User
    name: dave
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole                # 引用集群角色
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

上面这个 secret-reader 是集群角色,但 dave 只能读 development 命名空间的 Secret。

这个技巧特别实用:定义一次通用角色,在不同命名空间反复绑定。不然你得在每个命名空间复制一份一模一样的 Role

要真正跨全集群生效,就换成 ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
  - kind: Group
    name: manager
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
Warning

roleRef 创建后不可修改。想换角色只能删掉绑定重建。这个限制是故意的:换角色本质上是一次全新的授权,强制重建能确保你重新确认过主体列表里每个人都该拿到新权限。kubectl auth reconcile 命令可以自动处理这种删除重建。

44-5 通配符:能用但要克制

apiGroupsresourcesverbs 都支持 *

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: example.com-superuser      # 此角色仅作示范,请勿使用
rules:
  - apiGroups: ["example.com"]
    resources: ["*"]
    verbs: ["*"]

nonResourceURLs* 可以做后缀通配。resourceNames 为空集表示不限制。

问题在哪?通配符会自动吞掉未来新增的东西。 集群升级后多了个新资源类型、新子资源、新自定义动词,你的通配符规则立刻把它们也授权了出去。你不知道,也没审批过。

按最小特权原则,把 resourcesverbs 一个个列清楚。麻烦一点,但麻烦的是你,不是攻击者。

44-6 内置角色:先别急着自己写

Kubernetes 自带一批 ClusterRole。带 system: 前缀的是给组件用的,不带前缀的是给人用的:

角色默认绑定说明
cluster-adminsystem:masters超级用户,任何资源任何操作
admin用 RoleBinding 授予,命名空间内近乎全权,能建 Role/RoleBinding
edit命名空间内大多数对象读写,但不能看改 Role/RoleBinding
view命名空间内只读,看不到 Secret

日常配权限,viewedit 两个覆盖了绝大多数场景。一行命令就搞定:

# 让 zhangsan 在 dev 命名空间有读写权限
kubectl create rolebinding zhangsan-edit \
  --clusterrole=edit \
  --user=zhangsan \
  --namespace=dev

# 让某个 ServiceAccount 有全集群只读
kubectl create clusterrolebinding monitor-view \
  --clusterrole=view \
  --serviceaccount=monitoring:prometheus

几个必须知道的细节:

  • view 故意排除了 Secret。因为读 Secret 等于读走凭据。
  • edit 能访问 Secret,还能以命名空间里任意 ServiceAccount 的身份运行 Pod。也就是说,拿到 edit 就能拿到该命名空间里任何 ServiceAccount 的 API 权限。这是一条真实的提权路径,别把 edit 当成”安全的中等权限”。
  • admin 不能写 ResourceQuota,也不能改命名空间本身。
  • v1.22 及以后创建的集群里,adminedit 都不能写 EndpointSlices
Warning

cluster-adminClusterRoleBinding 就是完全的集群 root。它甚至能改 RBAC 规则本身,也就是能给自己和别人再授任何权。生产环境里应该只有极少数账号拥有它。

44-7 聚合 ClusterRole:给内置角色打补丁

装了 CRD(自定义资源)之后,你会发现 edit 角色管不了它——内置角色不认识你的新资源类型。

不用去改 edit 本身(改了升级会被覆盖)。正确做法是新建一个带特定标签的 ClusterRole,控制平面会自动把它聚合进去:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: my-crd-edit
  labels:
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
rules:
  - apiGroups: ["example.com"]
    resources: ["mycrds"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

可用的标签有三个:aggregate-to-adminaggregate-to-editaggregate-to-view

聚合的原理是:目标 ClusterRole 上有个 aggregationRule 字段定义标签选择器,控制器持续监视匹配的 ClusterRole,把它们的规则合并进目标的 rules

Warning

聚合角色的 rules 字段由控制平面接管,你手写的值会被覆盖。写聚合 ClusterRole 的清单时干脆别写 rules 字段——哪怕设为空列表也不行。服务端应用(Server-Side Apply)会认为你声明了对该字段的所有权,之后每次 apply 要么因字段管理器冲突失败,要么反复清空聚合规则再被控制平面填回来,来回打架。GitOps 环境里这个坑尤其常见。

44-8 防提权:为什么你建不了那个角色

RBAC 有个保护机制,新手常在这里卡住:你不能凭空创建出超过自己权限的角色。

具体规则:

  • 创建/更新角色,必须满足其一——你已经拥有该角色里包含的所有权限(作用域相同),或者你被显式授予了 roles/clusterroles 上的 escalate 动词。
  • 创建/更新绑定,必须满足其一——你已经拥有被引用角色的全部权限,或者你被授予了该角色上的 bind 动词。

举例:user-1 没有集群范围列举 Secret 的权限,那他就建不出一个包含该权限的 ClusterRole,API 请求会直接 403。

这个设计挡住了一条经典攻击路径:拿到一个能创建 RBAC 对象的低权限账号,然后给自己写一个 cluster-admin 绑定。

如果确实要让某人代为授权(比如命名空间自治),给他 bind 动词,并用 resourceNames 限定他只能绑哪几个角色:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: role-grantor
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["create"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterroles"]
    resourceNames: ["admin", "edit", "view"]   # 只允许绑这三个
    verbs: ["bind"]

这样他能在自己命名空间发 admin/edit/view,但发不出 cluster-admin

44-9 排查与常用命令

快速建对象:

# 建 Role
kubectl create role pod-reader \
  --verb=get,list,watch --resource=pods -n dev

# 建 ClusterRole
kubectl create clusterrole node-reader \
  --verb=get,list,watch --resource=nodes

# 加 --dry-run=client -o yaml 可以先看生成的 YAML
kubectl create role pod-reader --verb=get --resource=pods \
  --dry-run=client -o yaml

排查 403 的三步法:

  1. 先确认到底缺哪个动词:
kubectl auth can-i list secrets -n dev \
  --as=system:serviceaccount:dev:my-app
  1. 看这个主体现在有什么:
kubectl describe rolebinding -n dev
kubectl describe clusterrolebinding | grep -A5 my-app
  1. 看角色具体给了什么:
kubectl describe role pod-reader -n dev
kubectl describe clusterrole view

kubectl describe clusterrole view 输出的那张表值得你认真读一遍,能学到很多规则怎么组织。

44-10 三个高频错误

资源名写成单数或大写。 Podpod 都不对,必须是 pods。RBAC 用的是 URL 里的那个名字。

API 组填错。 Deploymentapps 组,不在核心组。写成 apiGroups: [""] 规则不会报错,但也不会生效——静默失效最难查。拿不准就 kubectl api-resources 确认。

忘了 ServiceAccount 主体不需要 apiGroup UserGroup 要写 apiGroup: rbac.authorization.k8s.ioServiceAccount 要写 namespace 而不写 apiGroup。写反了会被 API 校验挡下来。

小结

  • 四个对象两个维度:Role/ClusterRole 定义权限,RoleBinding/ClusterRoleBinding 授予权限。
  • 权限纯累加,没有拒绝规则,默认全禁。
  • RoleBinding 引用 ClusterRole 是最实用的组合——通用角色定义一次,多命名空间复用。
  • 优先用内置的 view/edit/admin,别一上手就自己造。但要清楚 edit 隐含的提权路径。
  • 通配符会自动包含未来新增资源,能不用就不用。
  • 防提权机制会拦住”给自己发超权角色”,需要代授权时用 bindresourceNames

下一章讲 ServiceAccount——工作负载自己的身份是怎么来的。