Repository Reading Site
第六课:身份、认证、授权、准入与 ServiceAccount / RBAC 原理
前两轮网络课,我们解决的是: 但你要成为能搭架构、能做平台、能排障的人,不能只停在“流量通不通”。 因为在 Kubernetes 里,还有另外一条同样关键的链路: 这条链路解决的是: 所以这节课,本质上是在补 Kubernetes 的“政治学”和“法治体系”。 如果网络课是“道路系统”,这一课就是: --- 很多人把下面四个词混成一句话: 这会导致后面看报错
第六课:身份、认证、授权、准入与 ServiceAccount / RBAC 原理
为什么这节课必须放在网络课后面
前两轮网络课,我们解决的是:
- 数据包能不能到达
- Service 怎么转发
- DNS 怎么解析
- NetworkPolicy 该不该放行
但你要成为能搭架构、能做平台、能排障的人,不能只停在“流量通不通”。
因为在 Kubernetes 里,还有另外一条同样关键的链路:
即使你已经能打到 API Server,也不代表你有资格操作资源。
这条链路解决的是:
- 你是谁
- 你凭什么有这个身份
- 你可以做什么
- 你的请求在写入 etcd 之前还会不会被系统再次修改或拦截
所以这节课,本质上是在补 Kubernetes 的“政治学”和“法治体系”。
如果网络课是“道路系统”,这一课就是:
- 身份证
- 门禁
- 权限表
- 审批流
先把四个最容易混淆的概念分开
很多人把下面四个词混成一句话:
- 身份
- 认证
- 授权
- 准入
这会导致后面看报错时完全分不清问题出在哪一层。
你必须把它们拆开。
1. Identity:你是谁
这是“身份本体”。
在 Kubernetes 里,常见身份有:
- 人类用户
User - 用户组
Group - 程序身份
ServiceAccount - 节点身份
system:node:<node-name> - 匿名身份
system:anonymous
身份本身不等于权限。
“我是 pod-reader”只回答:
你是谁。
它没有回答:
你能不能读 Pod。
2. Authentication:系统如何确认你是谁
认证回答的问题是:
你宣称自己是谁,系统凭什么信?
常见认证材料有:
- TLS 客户端证书
- Bearer Token
- OIDC
- Webhook 认证
- Bootstrap Token
在这套集群里,我们这次重点观察的是:
- Pod 挂载进去的 ServiceAccount Token
也就是:
Pod 带着 token 去访问 kube-apiserver,API Server 校验签名、校验发行者、校验时间窗口,然后认出这个 Pod 代表哪个 ServiceAccount。
3. Authorization:你能做什么
授权回答的是:
就算我知道你是谁,你有没有权限做这件事?
这套集群里,真实配置显示:
--authorization-mode=Node,RBAC
也就是说,授权决策由两部分组成:
Node:给 kubelet / 节点身份使用RBAC:给人和程序做角色授权
这也是你以后平台治理里最常见的一套组合。
4. Admission:即使授权通过了,请求还要不要被改、被卡
准入是最容易被忽略的一层。
它发生在:
- 认证之后
- 授权之后
- 对象真正持久化到 etcd 之前
准入关心的是:
- 这个对象是否需要被默认补字段
- 这个对象是否违反策略
- 这个对象是否需要经过 webhook 二次校验
你可以把它理解成:
你人到了门口,身份也是真的,权限表也允许,但真正录入系统前,还要经过规则引擎和审批系统。
用一条请求,把整条主链路串起来
你以后排障时,脑子里要有下面这条链路:
kubectl / Pod / 控制器
|
v
HTTPS 请求 kube-apiserver
|
v
Authentication
认证:识别请求者是谁
|
v
Authorization
授权:判断这个身份能不能做这个动作
|
v
Admission
准入:对写请求做默认化、变更、校验、拒绝
|
v
etcd
你要牢记:
curl能连上kubernetes.default.svc只说明网络层没问题- API Server 返回
403 Forbidden说明 HTTP 和 TLS 大概率都已经通了,问题在授权或策略 - 返回里出现
system:anonymous说明请求没带有效身份材料,或者系统把它当匿名身份处理 - 返回里出现
system:serviceaccount:<ns>:<name>说明认证成功了,系统已经识别出它是谁
这就是:
网络通,不等于认证成功;认证成功,也不等于授权成功。
我们先看真实集群证据,而不是只讲教科书
这节课不是空讲原理,我直接去控制平面节点和真实集群对象里取了证。
证据 1:你的 kube-apiserver 真这么配置
我通过控制平面节点上的静态 Pod 清单,确认了这套集群的关键参数:
--authorization-mode=Node,RBAC--enable-admission-plugins=NodeRestriction--service-account-issuer=https://kubernetes.default.svc.cluster.local--service-account-key-file=/etc/kubernetes/pki/sa.pub--service-account-signing-key-file=/etc/kubernetes/pki/sa.key
这几项非常关键。
它们分别意味着:
--authorization-mode=Node,RBAC
表示 API Server 在做授权决策时,会用:
- Node Authorizer
- RBAC Authorizer
节点身份主要给 kubelet 用,普通业务程序最常打交道的是 RBAC。
--enable-admission-plugins=NodeRestriction
这表示集群显式启用了 NodeRestriction。
它的核心目的,是防止节点身份乱改不该改的对象,例如:
- 节点标签伪造
- 节点越权操作别的节点对象
这属于“节点最小权限”的防线。
--service-account-issuer
这定义了:
ServiceAccount Token 是谁签发的。
我们后面解码 token 时,确实看到了:
iss = https://kubernetes.default.svc.cluster.local
和控制面配置完全一致。
--service-account-signing-key-file
这表示 API Server 会用专门的私钥来签 ServiceAccount Token。
所以 Pod 里那个 token 不是随便拼出来的字符串,而是:
- 有签名
- 有发行者
- 有过期时间
- 有 subject
- 可被 API Server 验证
集群里真的存在准入链,而不是“理论上有”
我还检查了当前集群里的 Admission 相关资源。
结果看到:
- 存在
ValidatingWebhookConfiguration - 存在
MutatingWebhookConfiguration - 当前没有
ValidatingAdmissionPolicy
其中一条真实配置是:
monitoring-kube-prometheus-admission
它包含:
- 对
PrometheusRule的 mutate webhook - 对
PrometheusRule的 validate webhook - 对
AlertmanagerConfig的 validate webhook
这件事非常重要,因为它告诉你:
在这套集群里,请求即使通过了 RBAC,也不代表一定能落库。
如果你以后碰到:
kubectl apply失败- 提示 webhook reject
- PrometheusRule 提交后报校验错误
就要想到问题不一定是 RBAC,也可能已经进入 Admission 阶段了。
Kubernetes 里最重要的程序身份:ServiceAccount
为什么程序不能直接复用人类用户身份
因为程序是:
- 自动运行
- 长时间运行
- 需要最小权限
- 需要可追踪
如果你让程序直接拿人的 kubeconfig 干活,会有几个严重问题:
- 权限通常过大
- 很难审计是谁的机器在代替程序调用
- 人离职、证书变更、凭据轮换都会影响系统
所以 Kubernetes 的标准做法是:
给程序分配 ServiceAccount。
ServiceAccount 的作用域
ServiceAccount 是命名空间级对象。
也就是说:
auth-lab/pod-readerlearn-k8s/pod-reader
虽然名字一样,但它们是两个完全不同的身份。
这点会直接反映到 token 的 sub 字段里。
我们这次创建的 token 解码后,真实结果是:
sub = system:serviceaccount:auth-lab:pod-reader
这就是它在 Kubernetes 世界里的完整身份字符串。
现代 Kubernetes 里的 ServiceAccount Token,已经不是“自动生成 Secret”了
这是很多老文章、老视频最容易误导你的地方。
真实现象
我在 auth-lab 里执行:
kubectl -n auth-lab get sa
结果看到:
NAME SECRETS AGE
default 0 ...
pod-reader 0 ...
也就是说:
- ServiceAccount 的
SECRETS列是0
这说明什么
说明这套集群使用的是现代的:
Bound ServiceAccount Token Volume
而不是老式的:
自动生成长期
kubernetes.io/service-account-tokenSecret,再挂进 Pod。
为什么新模型更好
因为老模型的问题很明显:
- Token 生命周期长
- 容易泄漏后长期可用
- Secret 到处复制,管理成本高
新模型的特点是:
- Pod 启动时,由 kubelet / API 机制投影短期 token
- token 带过期时间
- audience 明确
- 不再默认长期存成独立 Secret
这在安全上是明显进步。
Pod 里到底挂进去了什么
我查看了 default-api-client 和 reader-api-client 的真实 Pod YAML,看到卷定义是:
projectedserviceAccountTokenconfigMapdownwardAPI
这三个来源会组合成一个目录:
/var/run/secrets/kubernetes.io/serviceaccount
目录里真实存在三个文件:
tokenca.crtnamespace
token
这是访问 API Server 时拿来做 Bearer Token 认证的核心凭据。
ca.crt
这不是业务证书,而是用来校验 kube-apiserver 服务器证书的根 CA。
它来自每个 namespace 自动注入的:
kube-root-ca.crtConfigMap
也就是说,Pod 之所以能用:
curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt ...
去安全访问 https://kubernetes.default.svc,是因为 Kubernetes 已经把 API Server 的 CA 链给它准备好了。
namespace
这个文件来自 downwardAPI。
它告诉容器自己当前所在的 namespace 是什么。
这类信息看似不起眼,但在很多控制器、sidecar、通用工具镜像里非常有用。
token 里到底写了什么
我没有把完整 token 写进文档,这是基本安全意识。
但我生成了一个临时 token,并且只解码 payload 字段,看到的关键信息是:
aud = ["https://kubernetes.default.svc.cluster.local"]iss = "https://kubernetes.default.svc.cluster.local"sub = "system:serviceaccount:auth-lab:pod-reader"iat = 2026-04-10 02:20:20 CSTexp = 2026-04-10 03:20:20 CSTkubernetes.io.namespace = auth-labkubernetes.io.serviceaccount.name = pod-reader
这里每一项都有意义。
aud
表示这个 token 的目标受众是谁。
如果一个 token 的 audience 和接收方不匹配,就不应该被接受。
iss
表示谁签发了它。
它必须能和 API Server 的签发配置对上。
sub
这是最终身份字符串。
很多日志、告警、RBAC 错误、审计日志里,你看到的就是这个值。
iat / exp
说明 token 是时间受限的,不是永久有效。
这和老式长期 Secret token 的思路完全不同。
automountServiceAccountToken: false 是非常重要的安全开关
我专门做了一个对照 Pod:
no-token-api-client
它的 spec 中明确写了:
automountServiceAccountToken: false
结果非常直观:
- Pod 仍然有
serviceAccountName: default - 但
/var/run/secrets/kubernetes.io/serviceaccount目录根本不存在
这说明一个很重要的事实:
Pod 绑定了哪个 ServiceAccount,和是否自动把 token 投影进容器,是两件相关但不完全相同的事。
这个开关的典型使用场景是:
- 你的 Pod 根本不需要访问 Kubernetes API
- 你想减少 token 暴露面
- 你想降低容器逃逸或应用漏洞导致的 API 凭据泄漏风险
这是很实用的硬化手段。
本轮实验设计:三个 Pod,三种状态
为了把认证和授权彻底分开,我在 auth-lab 中做了三组对照:
default-api-client- 使用默认
defaultServiceAccount
- 使用默认
no-token-api-client- 仍然是默认
defaultServiceAccount - 但
automountServiceAccountToken: false
- 仍然是默认
reader-api-client- 使用自定义
pod-readerServiceAccount - 绑定最小权限 Role,只允许
get/list/watch pods
- 使用自定义
另外,我还放了一个演示用 Secret:
demo-secret
这样可以同时验证三件事:
- 没带 token 时,系统如何识别你
- 带了 token 但没权限时,会报什么
- 给了最小权限后,为什么仍然不能读 Secret
实验文件在:
实验一:没有 token,不代表完全不能访问 API Server
这是很多人第一次看到会意外的现象。
真实结果
在 no-token-api-client 中:
- 请求
GET /version返回HTTP 200 - 请求
GET /api/v1/namespaces/auth-lab/pods返回HTTP 403 - 错误里明确显示用户是:
system:anonymous
这说明什么
这说明在这套集群里:
- 匿名身份是存在的
- 某些非资源 URL,例如
/version,可以匿名读取 - 但真正的资源访问,例如
list pods,匿名身份没有权限
这也是为什么你不能看到:
- 某个接口能返回内容
就误判为:
- “我已经有 Kubernetes 权限了”
不是的。
你可能只是访问到了一个允许匿名读取的非资源端点。
为什么这件事对安全很重要
因为很多平台故障排查时会有人说:
- “我从容器里都能 curl 到 API Server 了”
这句话的信息量非常有限。
它最多说明:
- DNS 正常
- Service 正常
- TLS 端口可达
但不能说明:
- 你有 token
- token 有效
- RBAC 放行了业务资源
实验二:默认 ServiceAccount 有身份,但默认没业务权限
default-api-client 是第二个非常典型的案例。
真实现象
这个 Pod 里:
token文件存在ca.crt文件存在namespace文件存在
它带着挂载进去的 token 去访问 API:
GET /version返回200GET /api/v1/namespaces/auth-lab/pods返回403
错误消息明确写着:
User "system:serviceaccount:auth-lab:default" cannot list resource "pods"
你必须从这条报错里读出什么
这条报错说明:
- 网络没问题
- TLS 没问题
- token 被接受了
- API Server 已经识别出请求者是谁
- 失败发生在授权阶段
也就是说:
这不是认证失败,而是授权失败。
这是你以后看 Forbidden 时最重要的判断能力。
实验三:最小授权后,能读 Pod,但仍然不能读 Secret
我创建了:
- ServiceAccount:
pod-reader - Role:只允许
pods的get/list/watch - RoleBinding:把这条 Role 绑定到
auth-lab/pod-reader
然后用它启动:
reader-api-client
真实结果
这个 Pod 去调用 API:
GET /api/v1/namespaces/auth-lab/pods返回HTTP 200GET /api/v1/namespaces/auth-lab/secrets/demo-secret返回HTTP 403
此外,外部权限检查也证明了:
reader可以在auth-lab里list podsreader不能在auth-lab里get secretsreader不能到learn-k8s里list pods
这说明什么
说明 RBAC 真正控制的是:
- 哪个身份
- 在哪个作用域
- 对哪类资源
- 能做哪些动词
不是一句泛泛的:
- “读权限”
- “管理员权限”
而是非常精确的矩阵。
RBAC 的正确认知模型
你以后脑子里要始终是这个公式:
Subject + Role + Binding + Scope = 最终权限
Subject:谁
可以是:
- User
- Group
- ServiceAccount
Role / ClusterRole:能做什么
Role 是命名空间级。
ClusterRole 是集群级定义。
但要注意:
ClusterRole不等于“全局生效”
因为它也可以通过 RoleBinding 只在某个 namespace 里使用。
RoleBinding / ClusterRoleBinding:把谁和什么绑起来
RoleBinding:
- 绑定作用域是某个 namespace
ClusterRoleBinding:
- 整个集群范围生效
Scope:作用域
这一步最容易被忽略。
比如我们这次的 pod-reader:
- 在
auth-lab能list pods - 到
learn-k8s就不行
不是因为它突然“身份失效”了。
而是因为:
- 权限绑定只在
auth-lab生效
为什么 Secret 默认要更敏感
你会看到很多内置角色和团队规范里,都对 secrets 非常谨慎。
原因很简单:
- Secret 往往包含数据库密码
- Token
- 证书私钥
- 外部系统凭据
一旦有人能 get secret,很多时候就等于:
- 拿到了业务系统真正的控制权
这也是为什么:
- 很多“只读角色”不包含 Secret 读取
- 生产 RBAC 设计里,Secret 权限应该单独审视
不要把“我只是给开发同学看一眼”想得太轻。
在 Kubernetes 里:
get secrets通常不是“小权限”,而是高敏感权限。
准入控制到底管什么
很多人学到 RBAC 就以为权限体系学完了,其实没有。
什么时候会经过 Admission
最典型的是写请求,例如:
- CREATE
- UPDATE
- PATCH
- DELETE
读请求更多是认证 + 授权,不一定进入这类对象变更链路。
Admission 的两类核心动作
1. Mutating
先修改对象。
例如:
- 自动补默认字段
- 给对象加 label / annotation
- 调整某些字段
2. Validating
再校验对象。
例如:
- 字段是否合法
- 规则是否冲突
- 配置是否违反平台策略
真实集群证据
你的集群里真实存在:
MutatingWebhookConfiguration/monitoring-kube-prometheus-admission- 路径是
/admission-prometheusrules/mutate
- 路径是
ValidatingWebhookConfiguration/monitoring-kube-prometheus-admission- 路径包括
/admission-prometheusrules/validate - 也包括
alertmanagerconfigs的 validate
- 路径包括
这说明你以后如果在监控栈里 apply 某些 CRD 对象失败,不一定是 YAML 语法错,也不一定是 RBAC 错,而可能是:
- webhook 认为对象不合法
这就是:
认证回答“你是谁”,授权回答“你能不能做”,准入回答“即使能做,这个对象是否允许这样落地”。
你必须建立的排障顺序
以后遇到 Kubernetes 权限问题,不要一上来就瞎改 ClusterRoleBinding。
正确顺序应该是:
第一步:先确认访问链是否通
看的是:
- DNS 是否能解析
kubernetes.default.svc - 是否能连到
10.96.0.1:443 - TLS 是否通过
第二步:确认请求者到底是谁
看错误里出现的是:
system:anonymoussystem:serviceaccount:<ns>:<name>- 某个用户证书里的用户名
第三步:确认失败在哪一层
401 Unauthorized- 通常是认证问题
403 Forbidden- 通常是授权问题
- webhook reject / invalid
- 多半是 Admission 问题
第四步:再回头检查 RBAC 对象
看的是:
- Role / ClusterRole
- RoleBinding / ClusterRoleBinding
- namespace 作用域
- verbs
- resources
- apiGroups
- subresources
这时你才会改得准。
这节课最有价值的几个调试命令
看某个身份到底能做什么
kubectl auth can-i --list \
--as system:serviceaccount:auth-lab:pod-reader \
-n auth-lab
检查某个具体动作
kubectl auth can-i list pods \
--as system:serviceaccount:auth-lab:pod-reader \
-n auth-lab
看 Pod 里是否真的挂了 token
kubectl -n auth-lab exec default-api-client -- \
ls -l /var/run/secrets/kubernetes.io/serviceaccount
看 Pod 最终 YAML,确认 projected volume
kubectl -n auth-lab get pod reader-api-client -o yaml
生成临时 token 做检查
kubectl -n auth-lab create token pod-reader --duration=1h
看当前集群里的 webhook
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
看控制平面的真实授权配置
ssh root@107.148.176.193 \
'sudo sed -n "1,260p" /etc/kubernetes/manifests/kube-apiserver.yaml'
初学者最容易犯的错误
错误 1:把 ServiceAccount 当成权限本身
不是。
ServiceAccount 只是身份。
真正的权限来自:
- Role / ClusterRole
- Binding
错误 2:看到 Pod 里有 token,就以为它能管集群
不是。
默认 ServiceAccount 往往只能做少量发现类操作,业务资源经常仍然是 Forbidden。
错误 3:把 403 当成“网络不通”
正相反。
403 往往说明:
- 网络通了
- TLS 也基本通了
- API Server 已经回应你了
只是权限不够。
错误 4:为了省事直接绑 cluster-admin
这是很多实验环境最容易养成的坏习惯。
一旦养成,到了生产环境你会:
- 不会做最小权限设计
- 排障时只会加权,不会定位
- 审计和安全边界会非常差
这节课你现在应该真正掌握什么
学完这一课,你至少要能独立说清以下几件事:
ServiceAccount是程序身份,不等于权限。- Pod 访问 API Server 依赖的是挂载进来的 token、CA 和 namespace 信息。
- 现代 Kubernetes 已经默认使用短期投影 token,而不是老式长期 Secret token。
401、403、webhook reject分别大致对应不同层次的问题。Role、RoleBinding是命名空间级边界,跨 namespace 不会自动继承。- Secret 读取权限要比普通只读权限敏感得多。
- Admission 发生在授权之后、落库之前,是真实存在并且会影响业务交付的。
如果这些点你能真正说透,那么你对 Kubernetes 的理解已经开始从“会用命令”迈向“能解释系统”。
下一节我会顺着这条线继续往下推进:
- ConfigMap / Secret 的配置注入模型
- 环境变量、volume、subPath 的差异
- base64 不是加密
- Secret 真正的安全边界和常见误区