在 Kubernetes 早期的版本中,每个 Pod 创建时都会自动挂载一个基于 Secret 的 ServiceAccount Token,这个 Token 长期有效、不可撤销,一旦泄露就意味着攻击者可以永久访问 API Server。为了解决这个问题,社区从 1.21 版本开始将这种传统 Token 标记为废弃,并引入了 TokenRequest API 作为替代方案。它允许按需签发短期、绑定到特定对象的 Token,从根本上降低了令牌泄露带来的风险。本文将详细讲解 TokenRequest 的原理和具体使用方法。

一、为什么需要 TokenRequest API:传统 Token 的安全隐患
要理解 TokenRequest 的价值,得先看看老的 Token 机制有什么问题。传统方式下,创建 ServiceAccount 时 Kubernetes 会自动生成一个类型为 kubernetes.io/service-account-token 的 Secret,里面包含 Token、CA 证书和命名空间信息。Pod 启动时这个 Secret 会被自动挂载到 /var/run/secrets/kubernetes.io/serviceaccount/ 目录,应用就能读取 Token 去访问 API Server。
问题在于这种 Token 有三个致命缺陷。第一,它是长期有效的,默认情况下永不过期,即使 Pod 被删除,Token 依然可以继续使用。第二,Token 与具体的使用场景没有绑定,任何一个拿到它的进程都能用它做任何事情,权限完全取决于 ServiceAccount 被赋予的 RBAC 角色。第三,如果怀疑 Token 泄露,唯一的处理办法是删除整个 ServiceAccount 或者轮换 ServiceAccount 的密钥,这会影响所有使用它的 Pod,操作成本很高。
TokenRequest API 针对这些问题给出了系统性方案:签发的 Token 是短期的,有明确的有效期;Token 会绑定到指定的 Pod 或 Secret 对象上,如果绑定的对象被删除,Token 立即失效;Token 还可以指定受众(Audience),只有目标服务会接受它。这样即使 Token 被窃取,攻击窗口和可用范围都被大幅压缩。
二、TokenRequest 的核心概念与工作原理
TokenRequest 是一个资源对象,向 API Server 请求为某个 ServiceAccount 签发一个 Token。签发出来的 Token 实际上是一个 JWT 格式的字符串,签发方是 API Server 自身,验证方也是 API Server。下面先看一个手动创建 TokenRequest 的 YAML 示例:
apiVersion: authentication.k8s.io/v1
kind: TokenRequest
metadata:
name: my-token-request
namespace: default
spec:
# 指定为哪个 ServiceAccount 签发 Token
serviceAccountName: my-sa
# Token 有效期,超过后 Token 失效
expirationSeconds: 3600
# 受众,指定 Token 的接收方标识
audiences:
- my-api-server
# 可选:绑定到某个 Pod,Pod 删除后 Token 失效
boundObjectRef:
kind: Pod
apiVersion: v1
name: my-pod
uid: 9f2c8b7e-xxxx-xxxx-xxxx-e5a1b2c3d4f5
其中几个字段值得重点关注。audiences 定义了 Token 的预期接收方,JWT 的 aud 声明会被设置成这个值,验证方如果发现自己的标识不在 aud 里就会拒绝,这能防止 Token 被跨服务重放。expirationSeconds 设置有效期,最小值是 10 分钟,也就是 600 秒,生产环境建议设置在 1 小时以内。boundObjectRef 是绑定引用,可以绑定到 Pod 或者 Secret,kubelet 会周期性地检查绑定对象是否还存在,一旦对象被删除,对应的 Token 会被立即加入失效列表。
另一个重要机制是Projected ServiceAccount Volume,也就是投射卷。这是 TokenRequest 在 Pod 中最典型的使用方式:Pod 声明一个 projected 类型的 Volume,Kubelet 会调用 TokenRequest API 为该 Pod 的 ServiceAccount 签发一个短期 Token,挂载到容器内,并且在 Token 过期前自动轮换刷新,应用完全不用关心刷新逻辑。这也是新版本集群中默认的 Token 挂载方式。
三、用 kubectl 快速签发和调试 Token
日常调试场景下,不需要写 YAML,直接用 kubectl 的内置命令就能签发 Token。基本用法如下:
# 为 default 命名空间的 my-sa 签发一个默认有效期(1小时)的 Token kubectl create token my-sa -n default # 指定 10 分钟有效期 kubectl create token my-sa -n default --duration=10m # 指定受众 kubectl create token my-sa -n default --audience=my-api-server # 输出 Token 的 JWT 详情,方便调试 kubectl create token my-sa -n default -o json
拿到 Token 之后,可以用它直接调用 API Server 验证是否可用。把 Token 值存到环境变量里,再用 curl 请求:
export TOKEN=$(kubectl create token my-sa -n default) curl -s -k -H "Authorization: Bearer $TOKEN" \ https://192.168.0.1:6443/api/v1/namespaces/default/pods
这里需要注意,如果请求返回 403,说明 Token 认证通过了,但 ServiceAccount 没有 RBAC 权限,这和 Token 本身是否有效是两回事。排查时可以先给 ServiceAccount 绑定一个有权限的 Role,再验证 Token 是否能正常工作。如果返回 401,则要检查 Token 是否已过期或者受众是否匹配。把 JWT 的 payload 部分 Base64 解码一下,可以直接看到 exp、aud、kubernetes.io 等声明字段,是排查认证问题的好办法。
四、在 Pod 中通过 Projected Volume 自动使用短期 Token
生产环境中推荐的做法是让应用通过投射卷自动获取 Token,下面是一个完整的 Pod 定义示例:
apiVersion: v1
kind: Pod
metadata:
name: token-demo
namespace: default
spec:
serviceAccountName: my-sa
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: token-volume
mountPath: /var/run/secrets/tokens
volumes:
- name: token-volume
projected:
sources:
- serviceAccountToken:
path: my-token
expirationSeconds: 3600
audience: my-api-server
这个 Pod 启动后,Kubelet 会在容器的 /var/run/secrets/tokens/my-token 路径放置一个短期 Token。关键优势在于刷新机制:当 Token 有效期过去大约 80% 时,Kubelet 会自动调用 TokenRequest API 签发新 Token 并更新文件内容。应用只需要在每次发请求前重新读取文件即可拿到最新 Token,无需自己实现刷新逻辑,也不需要引入额外的客户端库。
如果是使用 client-go 的 Go 应用,直接用 kubernetes.NewForConfig 配合 rest.InClusterConfig 就能自动读取挂载的 Token 并处理刷新,代码非常简洁:
package main
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func main() {
// 自动读取 Pod 内挂载的 Token 和 CA 证书
config, err := rest.InClusterConfig()
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
pods, err := clientset.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic(err)
}
for _, pod := range pods.Items {
fmt.Println(pod.Name)
}
}
五、迁移注意事项与最佳实践
从传统 Token 迁移到 TokenRequest 时,有几点需要留意。首先,如果 Pod 使用了自定义的挂载路径或者关闭了 automountServiceAccountToken,要确认应用读取 Token 的逻辑是否兼容轮换机制,也就是每次请求前重新读文件,而不是启动时读一次缓存到内存里。其次,短期 Token 对时钟敏感,务必保证节点之间的时间同步,否则可能出现 Token 被判定为过期的情况。
其次,从安全角度建议遵循最小权限原则:在 Pod 或 ServiceAccount 层面设置 automountServiceAccountToken: false,只有确实需要访问 API Server 的 Pod 才显式声明投射卷,并且通过 audience 字段限定 Token 的使用范围。对于集群外的客户端,比如 CI 流水线需要访问集群,可以考虑用 TokenRequest 配合短期 Token 替代长期 ServiceAccount 密钥,或者直接使用 OIDC 身份认证,整体安全性都会比传统的静态 Token 高出一个档次。
Kubernetes TokenRequestServiceAccount TokenK8s API Server 认证修改时间:2026-09-06 21:46:44