3、ServiceAccount 与 Token:SA 创建、Token 自动挂载、TokenRequest API

在 Kubernetes 鉴权体系中,ServiceAccount(SA) 是 Pod 内进程与 API Server 交互的身份凭证。与 User Account 不同,SA 属于命名空间资源,专为工作负载设计。本章深入剖析 SA 的创建机制、传统 Secret 自动挂载的演进,以及现代 TokenRequest API 如何解决安全痛点。

3.1 ServiceAccount 核心概念与创建

ServiceAccount 是 Kubernetes 内置的“机器人账号”,用于为 Pod 中的进程提供身份标识。每个命名空间在创建时都会自动生成一个 default SA。

特性 User Account ServiceAccount
作用域 集群级别(全局) 命名空间级别
使用者 人类管理员/开发者 Pod 内的进程
认证方式 X.509 证书 / OIDC Bearer Token(JWT)
生命周期 长期存在 与工作负载绑定
自动挂载 不适用 默认挂载 Token 到 Pod
最佳实践: 避免使用 default SA 运行生产工作负载。始终为每个应用或微服务创建专用的 ServiceAccount,并绑定最小权限的 RBAC 角色。

创建 ServiceAccount 的 YAML 示例:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  namespace: production
automountServiceAccountToken: false   # 控制是否自动挂载 Token

使用 kubectl 创建并查看:

# 创建 SA
kubectl create serviceaccount my-app-sa -n production

# 查看 SA 详情(注意 Secrets 字段)
kubectl get serviceaccount my-app-sa -n production -o yaml

3.2 Token 自动挂载机制与演进

Kubernetes 早期版本(v1.20 之前)通过 Secret 自动生成并挂载长期有效的 ServiceAccount Token。这种机制存在严重安全风险:Token 永不过期,一旦泄露可被无限期滥用。

安全警告: 传统 Secret 类型的 Token(kubernetes.io/service-account-token)不具备过期时间,且存储在 etcd 中。如果 etcd 备份泄露或节点被攻破,攻击者可提取所有 SA Token。

从 v1.22 开始,Kubernetes 默认使用 TokenRequest API 生成绑定 Pod 生命周期的、有到期时间的 Token。自动挂载的 Token 不再是 Secret,而是由 kubelet 通过投射卷(Projected Volume)注入的 JWT。

Token 自动挂载机制演进对比 传统 Secret 模式(v1.20 之前) 1. API Server 自动创建 Secret 2. Secret 类型:kubernetes.io/service-account-token 3. Token 永不过期,存储在 etcd 4. kubelet 将 Secret 挂载到 Pod 文件系统 TokenRequest API 模式(v1.22+) 1. kubelet 调用 TokenRequest API 2. 生成绑定 Pod 生命周期的 JWT 3. Token 有过期时间(默认 1 年) 4. 通过投射卷挂载,不落盘 etcd 演进 关键变化 Token 从“永久静态凭证”变为“动态、可过期、绑定 Pod 的临时凭证” TokenRequest API 工作流程 Pod 创建 kubelet 请求 API Server 签发 投射卷挂载

3.3 TokenRequest API 深度解析

TokenRequest API 是 Kubernetes 认证子系统的核心扩展,位于 v1/authentication.k8s.io API 组。它允许请求者获取一个绑定到特定 ServiceAccount 和 Pod 的、有时间限制的 Token。

3.3.1 API 调用方式

直接通过 REST API 请求 Token:

POST /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token

# 请求体示例
{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenRequest",
  "spec": {
    "audiences": ["my-api-server"],
    "expirationSeconds": 3600
  }
}

3.3.2 关键参数说明

参数 类型 说明 默认值
audiences []string Token 的目标受众(API Server 验证时会检查) [kubernetes.default.svc]
expirationSeconds int64 Token 有效时长(秒) 86400(1 天)
boundObjectRef ObjectReference 将 Token 绑定到特定 Pod 或 Secret 无(不绑定)
实践建议: 对于需要跨集群或跨服务调用的场景,务必设置 audiences 字段。例如,调用外部 Istio 服务时,设置 audiences: ["istio-ca"] 可防止 Token 被重放攻击。

3.3.3 手动请求 Token 示例

使用 kubectl 创建 Token:

# 创建绑定到 Pod 的 Token(有效期 1 小时)
kubectl create token my-app-sa -n production \
  --duration=3600s \
  --bound-object-kind=Pod \
  --bound-object-name=my-pod-xyz

返回的 Token 结构:

{
  "status": {
    "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9...",
    "expirationTimestamp": "2025-04-10T12:00:00Z"
  }
}

3.4 Pod 内 Token 挂载与使用

当 Pod 启动时,kubelet 自动执行以下操作:

  1. 检测 SA:读取 Pod 的 spec.serviceAccountName 字段。
  2. 调用 TokenRequest:向 API Server 请求一个绑定到该 Pod 的 Token。
  3. 投射卷挂载:将 Token 写入 /var/run/secrets/kubernetes.io/serviceaccount/token。
  4. 注入 CA 证书:同时挂载集群 CA 证书,用于验证 API Server 的 TLS 连接。

Pod 内文件系统结构:

/var/run/secrets/kubernetes.io/serviceaccount/
├── ca.crt          # 集群 CA 证书
├── namespace       # 当前命名空间
└── token           # JWT Token(自动刷新)

在应用代码中读取 Token:

# Python 示例
import os

token_path = "/var/run/secrets/kubernetes.io/serviceaccount/token"
with open(token_path, "r") as f:
    token = f.read().strip()

# 使用 Token 调用 API Server
headers = {"Authorization": f"Bearer {token}"}

3.5 安全最佳实践与避坑指南

场景 推荐做法 避免
Token 过期 使用 TokenRequest API 自动刷新 手动创建长期 Secret Token
最小权限 为每个微服务创建独立 SA 多个服务共享同一个 SA
Token 泄露 设置 audiences 限制使用范围 使用默认 audiences
自动挂载 不需要 API 访问的 Pod 关闭自动挂载 所有 Pod 都挂载 Token
审计 启用 API Server 审计日志记录 TokenRequest 忽略 Token 使用记录
关键避坑: 在 v1.24+ 版本中,创建 ServiceAccount 时不再自动生成 Secret Token。如果仍依赖传统 Secret 方式(如 CI/CD 工具),需手动创建 Secret 并添加注解 kubernetes.io/service-account.name: my-sa。但强烈建议迁移到 TokenRequest API。

3.6 故障排查与调试

常见问题及排查命令:

# 检查 SA 是否存在
kubectl get serviceaccount my-app-sa -n production

# 查看 Pod 使用的 SA
kubectl get pod my-pod -n production -o jsonpath='{.spec.serviceAccountName}'

# 验证 Token 是否挂载
kubectl exec my-pod -n production -- cat /var/run/secrets/kubernetes.io/serviceaccount/token

# 检查 Token 过期时间(解码 JWT)
kubectl exec my-pod -n production -- \
  python3 -c "import jwt; print(jwt.decode(open('/var/run/secrets/kubernetes.io/serviceaccount/token').read(), options={'verify_signature': False}))"

通过以上实践,你可以安全、高效地管理 ServiceAccount 与 Token,为后端鉴权拦截打下坚实基础。