K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

第六课:身份、认证、授权、准入与 ServiceAccount / RBAC 原理

前两轮网络课,我们解决的是: 但你要成为能搭架构、能做平台、能排障的人,不能只停在“流量通不通”。 因为在 Kubernetes 里,还有另外一条同样关键的链路: 这条链路解决的是: 所以这节课,本质上是在补 Kubernetes 的“政治学”和“法治体系”。 如果网络课是“道路系统”,这一课就是: --- 很多人把下面四个词混成一句话: 这会导致后面看报错

Markdown06-第六课-身份认证授权准入与ServiceAccount-RBAC原理.md2026年4月9日 18:27

第六课:身份、认证、授权、准入与 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-reader
  • learn-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-token Secret,再挂进 Pod。

为什么新模型更好

因为老模型的问题很明显:

  • Token 生命周期长
  • 容易泄漏后长期可用
  • Secret 到处复制,管理成本高

新模型的特点是:

  • Pod 启动时,由 kubelet / API 机制投影短期 token
  • token 带过期时间
  • audience 明确
  • 不再默认长期存成独立 Secret

这在安全上是明显进步。


Pod 里到底挂进去了什么

我查看了 default-api-clientreader-api-client 的真实 Pod YAML,看到卷定义是:

  • projected
    • serviceAccountToken
    • configMap
    • downwardAPI

这三个来源会组合成一个目录:

  • /var/run/secrets/kubernetes.io/serviceaccount

目录里真实存在三个文件:

  • token
  • ca.crt
  • namespace

token

这是访问 API Server 时拿来做 Bearer Token 认证的核心凭据。

ca.crt

这不是业务证书,而是用来校验 kube-apiserver 服务器证书的根 CA。

它来自每个 namespace 自动注入的:

  • kube-root-ca.crt ConfigMap

也就是说,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 CST
  • exp = 2026-04-10 03:20:20 CST
  • kubernetes.io.namespace = auth-lab
  • kubernetes.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
    • 使用默认 default ServiceAccount
  • no-token-api-client
    • 仍然是默认 default ServiceAccount
    • automountServiceAccountToken: false
  • reader-api-client
    • 使用自定义 pod-reader ServiceAccount
    • 绑定最小权限 Role,只允许 get/list/watch pods

另外,我还放了一个演示用 Secret:

  • demo-secret

这样可以同时验证三件事:

  1. 没带 token 时,系统如何识别你
  2. 带了 token 但没权限时,会报什么
  3. 给了最小权限后,为什么仍然不能读 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 返回 200
  • GET /api/v1/namespaces/auth-lab/pods 返回 403

错误消息明确写着:

User "system:serviceaccount:auth-lab:default" cannot list resource "pods"

你必须从这条报错里读出什么

这条报错说明:

  1. 网络没问题
  2. TLS 没问题
  3. token 被接受了
  4. API Server 已经识别出请求者是谁
  5. 失败发生在授权阶段

也就是说:

这不是认证失败,而是授权失败。

这是你以后看 Forbidden 时最重要的判断能力。


实验三:最小授权后,能读 Pod,但仍然不能读 Secret

我创建了:

  • ServiceAccount:pod-reader
  • Role:只允许 podsget/list/watch
  • RoleBinding:把这条 Role 绑定到 auth-lab/pod-reader

然后用它启动:

  • reader-api-client

真实结果

这个 Pod 去调用 API:

  • GET /api/v1/namespaces/auth-lab/pods 返回 HTTP 200
  • GET /api/v1/namespaces/auth-lab/secrets/demo-secret 返回 HTTP 403

此外,外部权限检查也证明了:

  • reader 可以在 auth-lablist pods
  • reader 不能在 auth-labget secrets
  • reader 不能到 learn-k8slist 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-lablist 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:anonymous
  • system: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

这是很多实验环境最容易养成的坏习惯。

一旦养成,到了生产环境你会:

  • 不会做最小权限设计
  • 排障时只会加权,不会定位
  • 审计和安全边界会非常差

这节课你现在应该真正掌握什么

学完这一课,你至少要能独立说清以下几件事:

  1. ServiceAccount 是程序身份,不等于权限。
  2. Pod 访问 API Server 依赖的是挂载进来的 token、CA 和 namespace 信息。
  3. 现代 Kubernetes 已经默认使用短期投影 token,而不是老式长期 Secret token。
  4. 401403webhook reject 分别大致对应不同层次的问题。
  5. RoleRoleBinding 是命名空间级边界,跨 namespace 不会自动继承。
  6. Secret 读取权限要比普通只读权限敏感得多。
  7. Admission 发生在授权之后、落库之前,是真实存在并且会影响业务交付的。

如果这些点你能真正说透,那么你对 Kubernetes 的理解已经开始从“会用命令”迈向“能解释系统”。

下一节我会顺着这条线继续往下推进:

  • ConfigMap / Secret 的配置注入模型
  • 环境变量、volume、subPath 的差异
  • base64 不是加密
  • Secret 真正的安全边界和常见误区