Kubernetes 身份与 RBAC:认证、授权、人、节点和 CI 的边界
把 Kubernetes API 的身份链路拆开,讲清用户、ServiceAccount、kubelet 与 CI 应该各拿什么凭据和权限。
很多集群的权限问题并不是没有 RBAC,而是所有调用者都被塞进了同一种身份模型:工程师共用一份 kubeconfig,CI 保存一段长期 token,Pod 使用默认 ServiceAccount,节点证书又被当成普通客户端证书管理。表面上都能访问 API,出问题时却很难回答三个问题:
- 这次请求到底是谁发的?
- 它为什么有这个权限?
- 凭据泄露后,影响能不能被限制在一个清楚的范围内?
Kubernetes API 的访问链路可以先压缩成四步:
TLS 建立连接
→ Authentication:你是谁
→ Authorization:你能做什么
→ Admission:这次对象变更是否符合额外规则
→ 写入存储
认证失败通常返回 401,已经识别身份但没有权限通常返回 403。RBAC 只负责授权,不负责登录,也不能替代准入控制。把这几层混在一起,是许多权限设计越做越乱的开端。
Kubernetes 不替你保存“人”
Kubernetes API 里没有通用的 User 对象。API Server 接受外部认证器给出的用户名、用户组和附加信息,再交给授权器判断。这意味着“人”的生命周期最好放在组织已有的身份系统里。
常见方案包括 OIDC、认证代理、Webhook 和客户端证书。对日常工程师,接入企业身份提供方并签发短期凭据通常比长期客户端证书更容易撤销、审计和管理。客户端证书依然可用,但证书里的 Common Name 会成为用户名,Organization 字段会映射成组;证书一旦签发,权限影响取决于有效期和组绑定。当前 Kubernetes 文档还特别提醒,客户端证书没有原生吊销机制,签发时不宜给过长有效期。
人的权限尽量绑定“组”,不要直接给几十个用户名分别创建 RoleBinding。人员入职、离职、转岗由身份系统调整组成员,Kubernetes 只维护“平台只读”“某命名空间开发者”“值班运维”这类角色映射。
system:masters 要单独看待。它不是“完全不做认证”,调用者仍然要先被认证成该组成员;危险在于认证成功后,这个内置高权限组会获得不受普通 RBAC 或 Webhook 约束的特权。官方建议不要把日常用户放进去。需要管理员权限时,也应使用可审计、可过期的临时提升,而不是长期携带超级用户证书。
Pod 应该使用 ServiceAccount
ServiceAccount 是 Kubernetes 内置的非人类身份,而且属于 namespace。Pod 没有显式指定时,会使用所在 namespace 的 default ServiceAccount。
这不等于默认 Pod 天生拥有大量权限。启用 RBAC 的正常集群里,default ServiceAccount 只有很有限的 API 发现能力。问题在于,凭据通常会自动挂载到 Pod 中。应用根本不访问 Kubernetes API,却带着一枚可用 token,容器一旦被攻破,就多出了一条没有必要的攻击路径。
对不访问 API 的工作负载,可以直接关闭自动挂载:
apiVersion: v1
kind: ServiceAccount
metadata:
name: web
automountServiceAccountToken: false
确实需要访问 API 时,应给每类控制器或应用单独建 ServiceAccount,再绑定它真正需要的资源和动作。现在推荐使用 TokenRequest 和 projected volume 产生有期限、带 audience、可绑定到 Pod 生命周期的 token,不要把历史遗留的长期 ServiceAccount Secret 当作通用 API key。
这里还有一个容易忽略的事实:namespace 内部并不是很强的安全边界。拥有创建或修改 Pod/Deployment 的权限,通常就可能让工作负载选择该 namespace 中的其他 ServiceAccount、挂载 Secret、ConfigMap 或存储卷。官方 RBAC 安全指南因此明确提醒,工作负载创建权限本身就是高权限。
节点不是普通应用
kubelet 也要访问 API Server,但它的权限模型不能照搬业务 Pod。正常的节点身份形如:
用户名:system:node:<nodeName>
用户组:system:nodes
节点可通过 TLS bootstrap 获取客户端证书。API Server 的 Node authorizer 会根据调度关系限制 kubelet,例如只读取绑定到本节点的 Pod,以及这些 Pod 相关的 Secret、ConfigMap 和存储信息。NodeRestriction 准入插件再限制 kubelet 修改 Node 和 Pod 对象的范围。
这套模型的重点是“节点知道运行在自己身上的工作负载”,而不是“节点属于集群,所以可以读整个集群”。如果所有 kubelet 共用一张过宽证书,或给 system:nodes 绑定通用集群管理员角色,一台节点失陷就可能放大成整个集群的身份问题。
节点名也不是装饰。Node authorizer 依赖证书中的 system:node:<nodeName> 与实际注册的 Node 名称精确对应。排查 kubelet 401/403 时,应同时核对证书身份、节点名、证书有效期、Node authorizer 和 NodeRestriction,而不是先加一个 cluster-admin 绑定“试试看”。
CI 是另一类非人身份
CI 不应冒充某位工程师,也不适合永久保存管理员 kubeconfig。它有自己的生命周期和风险:任务短、调用频繁、运行器可能是临时的,日志和缓存又容易意外带出凭据。
优先方案是让 CI 使用外部工作负载身份或 OIDC 联邦,在任务开始时换取短期集群凭据。做不到时,也应使用专用 ServiceAccount 和短期 TokenRequest,把权限限定到目标 namespace,并让 token 随任务过期。
下面这个 Role 只允许读取 Pod,并更新目标 namespace 里的 Deployment:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: release-deployer
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
再用 RoleBinding 把它授予专用身份:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: release-deployer
namespace: production
subjects:
- kind: ServiceAccount
name: release-bot
namespace: delivery
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: release-deployer
这比 cluster-admin 小得多,但仍不能把它称为“只能改镜像”。RBAC 能限制资源类型和 verb,通常不能精确表达“只允许修改 Deployment 的某个字段”。能更新 Deployment 的 CI,可能借此修改 Pod 模板、选择 ServiceAccount 或挂载资源。若要限制到“只能改镜像字段”,需要 ValidatingAdmissionPolicy 或其他准入策略;独立 namespace、受限 ServiceAccount 与 Pod Security 则分别用于收窄周边风险。GitOps 控制器也应按同样原则设计,它并不会因为“声明式”就自动变得低权限。
Role、ClusterRole 与 Binding 的真实关系
RBAC 主要有四种对象:
Role定义某个 namespace 内的权限。ClusterRole可以定义集群级资源权限,也可以定义一组可复用的 namespaced 权限。RoleBinding在一个 namespace 内授予 Role 或 ClusterRole。ClusterRoleBinding把 ClusterRole 授予整个集群范围的身份。
一个常见误会是“用了 ClusterRole 就一定是全局权限”。真正决定授权范围的还有 Binding。ClusterRole 可以被某个 namespace 的 RoleBinding 引用,此时权限只在该 namespace 生效。反过来,ClusterRoleBinding 才会把它扩展到集群范围。
Kubernetes RBAC 没有通用的 deny 规则。一个身份从多个 Binding 获得的允许权限会叠加,无法再创建一个更窄的 Role 抵消之前的宽权限。因此清理权限时,要找出所有身份、组和 Binding 的来源,不能只盯着最近新增的 YAML。
五类看起来“只读”的危险权限
get、list、watch Secret 都可能读到 Secret 数据,其中 list 和 watch 并不比 get 更安全。
get nodes/proxy 也不是普通只读。官方文档指出,它可以进入 kubelet API,甚至绕过常规 API 的审计与准入路径完成日志、执行或 attach 等操作。
create serviceaccounts/token 可以为现有 ServiceAccount 请求 token。
bind、escalate 和 impersonate 会绕过常规的权限提升保护,应只给真正的权限管理组件。
通配符 resources: ["*"]、verbs: ["*"] 会让未来新增的 API 资源也自动进入授权范围。短期省下几行 YAML,长期却很难知道这个身份究竟能做什么。
把权限验证放进日常流程
每个身份上线前,至少验证三组问题:
kubectl auth can-i get pods -n production
kubectl auth can-i update deployments -n production
kubectl auth can-i get secrets -n production
管理员审计时可以通过 --as 和 --as-group 模拟身份,但调用者自己必须拥有 impersonate 权限。更可靠的做法,是让 CI、ServiceAccount 和人工账号分别用自己的真实凭据跑一组允许与拒绝测试。
权限审查也不能只看“能不能部署”。还要检查凭据是否短期、是否有明确 audience、离职或任务结束后能否失效、审计日志能否还原到具体身份,以及创建工作负载后能否间接拿到更高权限。
一套可维护的 Kubernetes 身份体系,应该让人、Pod、节点和 CI 各用各的身份。RBAC 再围绕这些身份划范围,准入控制补上 RBAC 无法表达的对象级限制。这样遇到 403 时可以准确修权限,凭据泄露时也知道应该关哪一扇门,而不是给整个集群再加一个管理员。