土法炼钢 · 系统与基础设施

【kube-apiserver】Authentication:SA、Bearer、OIDC 边界

文章导航

分类入口
kubernetesdistributed
标签入口
#kubernetes#apiserver#authentication#serviceaccount#oidc#bearer-token#v1.30.3

目录

一次 kubectl apply 失败可能返回 401,也可能返回 503。两者的原因完全不同:401 是 Authentication 轴的失败,503 通常是 Storage 轴或 Webhook 轴的失败。混淆的常见场景是:SRE 看到 503 后从 RBAC 入手,其实 etcd 已经超时;或者看到 401 后怀疑 webhook,其实是 SA token 过期。本篇钉 kube-apiserver 在 v1.30.3 里的认证链结构,以及各认证器的边界。

本篇在系列中的位置

篇目 核心内容
第 9 篇 · Webhook timeout、failurePolicy、可用性门
第 10 篇 · Authentication 认证链、SA bound token、OIDC flags 边界
第 11 篇 · Authorization 与 Audit RBAC、SAR、403 分列
系列目录 五轴、阅读路径

版本锚定:Kubernetes v1.30.3(tag v1.30.3)。认证链实现见 staging/src/k8s.io/apiserver/pkg/authentication/;SA token 见 pkg/serviceaccount/无真实集群则不粘贴伪造 kubectl 输出或 token decode 结果。


一、认证链架构

apiserver 启动时把所有启用的认证器组装成一条链,存放在 staging/src/k8s.io/apiserver/pkg/authentication/request/union/union.gounionAuthRequestHandler。处理逻辑是依次调用,直到某个认证器返回成功,或全部失败

flowchart LR
  req["HTTP Request"] --> x509["X509 cert"]
  x509 -- "no cert / fail" --> sa["SA token (JWT)"]
  sa -- "no match" --> bearer["Static Bearer"]
  bearer -- "no match" --> bootstrap["Bootstrap token"]
  bootstrap -- "no match" --> oidc["OIDC JWT"]
  oidc -- "no match" --> wh["Webhook token"]
  wh -- "no match / disabled" --> anon["Anonymous"]
  anon -- "disabled" --> 401["401 Unauthorized"]
  x509 -- "ok" --> identity["UserInfo: username, groups, extra"]
  sa -- "ok" --> identity
  oidc -- "ok" --> identity
  anon -- "ok" --> identity

认证成功的产出是 user.Infostaging/src/k8s.io/apiserver/pkg/authentication/user/user.go):包含 UsernameGroupsUIDExtra,传入后续 AuthZ 链与 Audit 层。认证本身不决定是否允许操作,那是 Authorization 的职责。


二、各认证器边界

X509 客户端证书

启用条件:--client-ca-file 指向 CA bundle。apiserver 在 TLS 握手阶段验证客户端证书;证书的 CN(Common Name)作为 Username,O(Organization)作为 Groups

Kubernetes 控制面组件(scheduler、controller-manager、kubelet)默认使用 X509 mTLS 认证,CA 由集群 bootstrap 阶段或 kubeadm 生成。客户端证书无法在不更换 CA 或重新颁发的情况下在线吊销——这是与 OIDC/Token 认证的重要区别;v1.30 中仍无内置 CRL/OCSP 机制。

ServiceAccount Token(SA token)

SA token 分两代:

Legacy SA token(v1.22 前默认):Kubernetes secret 中的长期 JWT,无过期时间,由 apiserver 私钥签名。BoundServiceAccountTokenVolume feature gate 在 v1.22 默认开启后,Legacy token 不再自动挂载到新 Pod,但 Secret 类型的 SA token 仍可手动创建。

Bound SA token(TokenRequest,v1.20 GA):通过 TokenRequest API(/apis/authentication.k8s.io/v1/serviceaccounts/{name}/token)颁发,带 expirationSeconds(默认 3600s)、audiences(受众)、boundObjectRef(绑定 Pod/Secret lifecycle)。v1.30 中 kubelet 挂载的 Projected Service Account Token 即此机制,token 过期后 kubelet 自动轮换。

SA token 验证器实现见 pkg/serviceaccount/jwt.goLegacyTokenValidation)与 staging/src/k8s.io/apiserver/pkg/authentication/token/cache/(token 验证缓存)。签名密钥由 --service-account-key-file 指定(验证)与 --service-account-signing-key-file 指定(颁发)。

Static Bearer Token

--token-auth-file 指向一个 CSV 文件,每行格式 token,user,uid[,group...]生产中不推荐:token 明文存在文件,修改须重启 apiserver,无过期机制。不在现代集群新建配置中出现。

Bootstrap Token

kubeadm 使用的引导 token(kube-system 命名空间下 bootstrap.kubernetes.io/ 前缀的 Secret)。Usernamesystem:bootstrap:<token-id>,仅用于节点首次加入流程,与日常运维无关。

OIDC JWT

启用条件:--oidc-issuer-url--oidc-client-id 两个必填 flag,以及以下可选 flag:

Flag 说明
--oidc-username-claim JWT claim 映射为 Username(默认 sub
--oidc-username-prefix 防止与 K8s 内置用户名冲突(推荐设置,如 oidc:
--oidc-groups-claim JWT claim 映射为 Groups(如 groups
--oidc-groups-prefix 防止 group 与内置冲突
--oidc-ca-file IdP TLS CA bundle
--oidc-required-claim 必须存在的 claim key=value(可多次)

apiserver 在收到请求时本地验证 JWT 签名(通过 OIDC discovery endpoint 获取 JWKS,缓存后本地校验),不向 IdP 发起实时认证请求。这与 Webhook token 认证不同,也意味着 IdP 吊销 token 后,apiserver 在 JWKS 缓存未过期期间仍可能接受。

OIDC 配置仅是 apiserver 的 flags 边界;OAuth2/OIDC 流程全书(授权码流、PKCE、SAML federation、refresh token)见 iam/。本篇只关心 apiserver 收到 Bearer token 后如何验证。

Webhook Token Authenticator

--authentication-token-webhook-config-file 指向一个 kubeconfig 格式文件,apiserver 向配置的 URL 发送 TokenReview 对象(authentication.k8s.io/v1)。外部服务返回 status.authenticated 与用户信息。此路径与 Webhook Admission 类似,外部服务不可用 = 认证失败;无 failurePolicy 可选(始终 Fail)。

Anonymous 认证

--anonymous-auth(默认 true):未被任何认证器识别的请求被赋予 system:anonymous user 和 system:unauthenticated group,仍进入 AuthZ 链——默认 RBAC 中 system:unauthenticated 只有极少 ClusterRoleBinding(例如 discovery)。关闭匿名认证 (--anonymous-auth=false) 后,未认证请求直接 401。


三、401 与存储层故障分列

返回码 落格 典型原因
401 Authentication 轴 Token 过期、签名密钥不匹配、OIDC issuer 不符
403 Authorization 轴 RBAC 无规则,见第 11 篇
503 Storage / Admission 轴 etcd 不可达、webhook 故障
504 Storage / APF 轴 etcd 超时、APF 排队超时,见第 12 篇

etcd 不可达不会直接产生 401。apiserver 与 etcd 之间用 --etcd-cafile/--etcd-certfile/--etcd-keyfile 建立 mTLS,该层 TLS 故障表现为 apiserver 写/读失败(503/504),不影响 Authentication 链的 JWT 验证逻辑。

SA token 过期排障:kubectl get secret -n kube-system 查 bootstrap token;对 Bound SA token,kubelet 轮换日志可看到 token expired, refreshing;直接 kubectl create token <sa-name> 颁发临时 token 验证签名密钥是否一致。


四、谱系与开放问题

K8s 早期:X509 + static token(无在线吊销)
  → v1.20:TokenRequest Bound SA token GA(生命周期绑定 + expiry)
  → v1.22:BoundServiceAccountTokenVolume 默认开启
  → v1.30:Legacy SA token 不再自动生成;Projected token 为默认
  → 仍开放:X509 吊销(无内置 CRL/OCSP);OIDC token 吊销与 JWKS 缓存时间 trade-off

开放问题:apiserver OIDC JWKS 缓存刷新间隔在 v1.30 没有可配置 flag(由实现决定);IdP 吊销 token 后,缓存内的 JWKS 仍可验证未过期 JWT。SA token 轮换间隔(默认 expirationSeconds: 3600)与 token projection 更新延迟之间的窗口,在节点时钟漂移时的行为未有正式 SLA。

本篇不写什么:OIDC/OAuth2 全书(见 iam/);kubeadm 证书轮换全书;Dex/Keycloak 配置教程;伪造 jwt.io 解码截图。


参考资料

规范 / 源码(A)

论文 / 规范(A)

站内对照

实验台账


上一篇Mutating / Validating Webhook

下一篇Authorization 与 Audit:RBAC、SAR 与 403 分列

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .