Kubernetes 基础体系 · 第 43/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes 认证:证书、Token、OIDC、Webhook 和用户生命周期

Kubernetes 的“登录”不是一个独立的用户系统,而是 API Server 对每个请求执行的一组判断:

  1. 请求是否携带了某种可验证的凭据;
  2. 凭据验证后,对应的用户名和用户组是什么;
  3. 该身份是否有权执行目标操作;
  4. 请求是否还受到准入控制、审计和其他策略约束。

其中,**认证(Authentication)**回答“你是谁”,**授权(Authorization)**回答“你能做什么”。证书、Token、OIDC 和认证 Webhook 都属于认证机制;RoleClusterRole 和各种 Binding 属于授权机制。认证成功并不意味着请求一定能执行。

可以把一次 API 请求抽象为:

RequestAuthentication(u,G,extra)Authorization{allow,deny}\text{Request} \xrightarrow{\text{Authentication}} (u, G, \text{extra}) \xrightarrow{\text{Authorization}} \{\text{allow},\text{deny}\}

其中:

  • uu 是用户名;
  • GG 是用户组集合;
  • extra 是认证器可能附加的扩展属性;
  • 授权器根据请求动作、资源和这些身份属性作出判断。

例如,一个客户端使用客户端证书访问:

GET /api/v1/namespaces/dev/pods
Authorization: 通过客户端证书完成 TLS 认证

认证器可能得到:

username = alice
groups   = [developers]

随后 RBAC 检查 alicedevelopers 是否拥有:

get pods -n dev

如果没有,即使证书完全有效,最终仍然会得到 403 Forbidden

一次 Kubernetes API 请求经过哪些认证组件

通常客户端先通过 TLS 连接 API Server。API Server 可以配置多个认证器,例如:

  • X.509 客户端证书;
  • 静态 Token 文件;
  • Bearer Token;
  • ServiceAccount Token;
  • OIDC;
  • 认证 Webhook;
  • 匿名请求。

认证器的具体启用方式由 kube-apiserver 参数或结构化认证配置决定。不同认证器的组合方式、优先级和失败处理受 Kubernetes 版本与启动参数影响,不能假设所有发行版都启用了相同配置。

典型请求路径如下:

sequenceDiagram
    participant C as 客户端
    participant A as kube-apiserver
    participant X as 证书认证器
    participant O as OIDC/JWT 认证器
    participant W as 认证 Webhook
    participant Z as 授权器
    participant K as Kubernetes API

    C->>A: HTTPS 请求 + 证书或 Bearer Token
    A->>X: 尝试验证客户端证书
    X-->>A: 成功:username/groups,或不适用
    A->>O: 尝试验证 JWT
    O-->>A: 成功:username/groups,或不适用
    A->>W: TokenReview(必要时)
    W-->>A: authenticated + identity,或失败
    A->>Z: 检查身份和请求权限
    Z-->>A: allow 或 deny
    A->>K: 执行 API 操作
    K-->>C: 200/201,或 401/403 等响应

这里的“尝试”不等于所有认证器都会顺序访问。某些认证器可以根据请求特征直接判断是否适用,例如没有 Bearer Token 时不会进入 Token 认证流程。认证成功后,API Server 使用得到的身份继续授权;认证失败通常是 401 Unauthorized,授权失败通常是 403 Forbidden

认证身份不是 Kubernetes 中的 User 对象

Kubernetes 原生 API 没有一个类似数据库 users 表的内置 User 资源。UserGroup 在认证阶段通常只是字符串:

subjects:
- kind: User
  name: alice
- kind: Group
  name: developers

RBAC 并不验证 alice 是否真的存在。它只会把认证结果中的用户名或组名与 Binding 中的字符串进行匹配。

这带来一个重要后果:

  • 删除一个外部 IdP 用户,不会自动删除 Kubernetes 中的 RoleBinding
  • 修改 OIDC 的用户名映射,可能让原权限失效,也可能把旧权限绑定“遗留”给另一个身份字符串;
  • 一个被回收的客户端证书,如果仍未过期,Kubernetes 本身通常不会知道它已经被撤销。

因此,用户生命周期必须由证书 CA、身份提供商、Webhook 后端以及 RBAC 清理流程共同管理。

X.509 客户端证书认证

认证原理

X.509 客户端证书认证使用双向 TLS。API Server 信任由某个客户端 CA 签发的证书;客户端在 TLS 握手中提交证书链,API Server 验证:

  1. 证书是否由配置的客户端 CA 签发;
  2. 证书是否在有效期内;
  3. 证书用途是否允许客户端认证;
  4. TLS 握手是否成功。

认证成功后,Kubernetes 通常使用证书的 Subject 字段映射身份:

  • Common Name(CN)映射为用户名;
  • Organization(O)映射为用户组。

例如证书的 Subject 为:

Subject: CN=alice, O=developers, O=project-a

常见认证结果相当于:

username = alice
groups   = [developers, project-a]

这不是 RBAC 的特殊语法,而是认证器将证书字段转换成身份字符串。

用 OpenSSL 生成测试证书

以下示例用于理解机制,不应直接替代生产 CA 管理。假设已经有一个可用于签发客户端证书的 CA:

# 生成客户端私钥
openssl genrsa -out alice.key 2048

# 生成 CSR:
# CN 是用户名,O 是组名
openssl req -new \
  -key alice.key \
  -out alice.csr \
  -subj "/CN=alice/O=developers"

# 为客户端证书声明 clientAuth 用途
cat > client-ext.cnf <<'EOF'
extendedKeyUsage = clientAuth
EOF

# 使用 CA 签发证书
openssl x509 -req \
  -in alice.csr \
  -CA client-ca.crt \
  -CAkey client-ca.key \
  -CAcreateserial \
  -out alice.crt \
  -days 30 \
  -sha256 \
  -extfile client-ext.cnf

需要满足以下前提:

  • client-ca.crt 已配置给 API Server 的客户端 CA 参数;
  • client-ca.key 必须受到严格保护;
  • API Server 的地址和 CA 配置正确;
  • 客户端私钥文件权限不能让无关用户读取。

将证书写入 kubeconfig:

kubectl config set-credentials alice \
  --client-certificate=alice.crt \
  --client-key=alice.key \
  --embed-certs=true

kubectl config set-context alice \
  --cluster=my-cluster \
  --user=alice \
  --namespace=dev

kubectl config use-context alice
kubectl auth whoami
kubectl auth can-i get pods -n dev

kubectl auth whoami 用于查看 API Server 返回的认证身份;kubectl auth can-i 用于检查授权结果。前者成功不代表后者一定成功。

如果没有为 alice 绑定任何权限,常见结果是:

kubectl auth whoami
# User: alice
# Groups:
#   developers
#   ...

kubectl get pods -n dev
# Error from server (Forbidden): pods is forbidden ...

这说明 TLS 证书验证成功,但 RBAC 拒绝了操作。

证书认证的边界

客户端证书适合需要长期、强约束、由组织 CA 管理的客户端,例如:

  • 集群管理员的受控工作站;
  • 节点或系统组件;
  • 由企业 PKI 签发的运维身份。

但它有几个关键限制。

第一,Kubernetes 通常只验证证书有效性和签发关系,不提供通用的在线证书撤销检查。证书被盗后,删除对应的 RoleBinding 只能阻止绑定到该用户名或组的权限;如果攻击者仍能使用证书,而且该身份还通过其他组获得权限,风险仍然存在。紧急处置通常需要撤销或替换信任 CA、停止接受该证书对应的身份,或者在外层网络设备阻断访问。

第二,证书中的用户名和组是身份字符串,不是动态查询结果。用户从 developers 离职后,旧证书在有效期内仍可能携带 developers 组。

第三,不能把客户端证书私钥放入镜像、Git 仓库或普通 ConfigMap。kubeconfig 中若使用 --embed-certs=true,文件本身会包含私钥,必须按凭据处理。

Token 认证

Bearer Token 的基本形式

Token 认证通常通过 HTTP Header 发送:

Authorization: Bearer eyJ...

Bearer 的含义是“持有者即可使用”。服务器不需要证明请求者是哪个人,只要 Token 有效就接受它。因此 Token 的泄露等价于凭据泄露。

Token 可以是:

  • 不透明随机字符串;
  • JWT;
  • ServiceAccount Token;
  • 外部身份系统签发的访问令牌;
  • 静态 Token 文件中的字符串。

API Server 具体能验证哪一种 Token,取决于启用的认证器。

静态 Token 文件

静态 Token 文件通常是 API Server 本地的 CSV 配置,包含 Token、用户名和组等字段。它的概念形式类似:

token123,alice,1001,"developers,project-a"

这类方式适合非常有限的引导或实验环境,但生产中风险较大:

  • Token 通常是长期凭据;
  • 修改文件需要重新加载配置或重启,具体行为取决于实现和版本;
  • 文件本身就是高价值秘密;
  • 缺少成熟的单个 Token 撤销和轮换机制;
  • Token 泄露后难以追踪真实持有者。

不要把静态 Token 当作普通配置文件提交到版本控制系统。

JWT Token 的验证条件

JWT 通常由三部分组成:

header.payload.signature

认证器验证的不是“字符串看起来像 JWT”,而是至少需要检查:

  1. 签名算法和签名是否正确;
  2. 签发者 iss 是否是受信任的发行者;
  3. 受众 aud 是否包含 API Server 所接受的受众;
  4. 时间字段,如 expnbfiat,是否满足有效期要求;
  5. 用户名和组声明是否按配置映射;
  6. 必要的声明是否存在且类型正确。

形式化地说,对一个 Token tt,认证成功至少需要:

VerifySig(t,Ktrusted)=true\operatorname{VerifySig}(t, K_{\text{trusted}})=\text{true}

并且:

t.iss=I,Aapit.aud,t.exp>nowt.\mathrm{iss}=I,\quad A_{\text{api}}\cap t.\mathrm{aud}\neq\varnothing,\quad t.\mathrm{exp} > \mathrm{now}

其中:

  • KtrustedK_{\text{trusted}} 是受信任的签名密钥集合;
  • II 是配置的发行者;
  • AapiA_{\text{api}} 是 API Server 接受的 audience 集合。

只验证签名而不检查 aud,可能导致“发给服务 A 的 Token 被服务 B 接受”的混淆代理问题。

ServiceAccount Token:工作负载身份

ServiceAccount 是 Kubernetes 中面向 Pod 的身份。Pod 中的进程通过 ServiceAccount Token 访问 API Server 或其他支持该 Token 的服务。

当前推荐的是 Bound ServiceAccount Token,通常通过 Projected Volume 注入 Pod。它与早期长期存在于 Secret 中的 ServiceAccount Token 不同:

  • Token 具有有限有效期;
  • Token 与 Pod、ServiceAccount 等对象建立绑定关系;
  • 可以指定 audience;
  • kubelet 负责在接近过期时轮换投影文件;
  • 删除 Pod 后,绑定关系可使旧 Token 失效或不再被接受,具体验证行为由发行者和 API Server 实现共同决定。

一个显式配置的投影卷示例:

apiVersion: v1
kind: Pod
metadata:
  name: token-demo
  namespace: dev
spec:
  serviceAccountName: app
  containers:
  - name: app
    image: curlimages/curl:8.10.1
    command: ["sleep", "3600"]
    volumeMounts:
    - name: api-token
      mountPath: /var/run/secrets/tokens
      readOnly: true
  volumes:
  - name: api-token
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 3600
          audience: https://kubernetes.default.svc

前提是命名空间中存在 app

kubectl -n dev create serviceaccount app
kubectl apply -f token-demo.yaml

进入容器后,可以使用 Token 访问 API:

kubectl -n dev exec -it token-demo -- sh

TOKEN="$(cat /var/run/secrets/tokens/token)"

curl --fail-with-body \
  --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  -H "Authorization: Bearer ${TOKEN}" \
  https://kubernetes.default.svc/api

这里每一步的因果关系是:

  1. serviceAccountName: app 使 Pod 使用 app 身份;
  2. projected volume 请求 kubelet 获取指定 audience 和有效期的 Token;
  3. 容器读取文件,而不是把 Token 固化到镜像;
  4. API Server 验证签名、发行者、有效期和 audience;
  5. 认证得到的用户名通常形如:
    system:serviceaccount:dev:app
    
  6. RBAC 再判断这个 ServiceAccount 是否拥有访问 /api 或其他资源的权限。

给它最小的只读权限:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: dev
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-pod-reader
  namespace: dev
subjects:
- kind: ServiceAccount
  name: app
  namespace: dev
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-reader

验证:

kubectl auth can-i \
  --as=system:serviceaccount:dev:app \
  list pods -n dev
# yes

kubectl auth can-i \
  --as=system:serviceaccount:dev:app \
  get secrets -n dev
# no

--as 是管理员或具有 impersonation 权限的客户端模拟身份进行检查,不是普通用户可以任意伪造身份。实际 Pod 中的 Token 仍由 API Server 独立验证。

audience 和轮换不能省略

audience 表示 Token 预期被哪个服务接受。一个面向 Kubernetes API 的 Token 不应默认被任意内部服务接受;外部 Workload Identity 系统也不应盲目接受 Kubernetes API Token。

应用必须能够重新读取投影文件,或者使用支持文件轮换的凭据加载方式。错误做法是:

容器启动时读取 Token
→ 保存到进程内存
→ 永远不再读取文件

这样 Token 轮换后,应用仍使用旧 Token,最终可能收到 401 Unauthorized。推荐应用在请求失败或定期刷新时重新读取文件;具体刷新策略应服从客户端库的实现。

早期 Secret 自动生成的长期 ServiceAccount Token 在现代集群中不再是推荐方式。版本升级、发行版默认设置和兼容性策略可能不同;排查时应确认集群是否启用了 TokenRequest API 以及 projected service account token。

OIDC:把外部身份提供商接入 Kubernetes

OIDC 的角色

OIDC(OpenID Connect)是在 OAuth 2.0 之上定义的身份层。对 Kubernetes 来说,典型流程是:

  1. 用户在外部身份提供商(IdP)登录;
  2. IdP 签发 ID Token 或访问 Kubernetes 的 JWT;
  3. kubectl 把 JWT 作为 Bearer Token 发送给 API Server;
  4. API Server 根据 OIDC 配置验证 JWT;
  5. API Server 将声明映射为 Kubernetes 用户名和组;
  6. RBAC 根据这些字符串授权。

OIDC 并不把用户同步进 Kubernetes。Kubernetes 仍然只看到认证结果中的用户名和组。

API Server 如何验证 OIDC Token

API Server 通常需要知道:

  • issuer URL
  • 客户端 ID或受众;
  • 用户名声明;
  • 组声明;
  • 可选的用户名和组前缀。

发行者的发现文档通常位于:

https://idp.example.com/.well-known/openid-configuration

其中可能提供 JWKS 地址。API Server 使用 JWKS 中的公钥验证 JWT 签名,并根据 kid 选择密钥。密钥轮换时,发现文档和 JWKS 缓存更新是认证可用性的关键。

一个 JWT 负载可能是:

{
  "iss": "https://idp.example.com",
  "sub": "00u123",
  "aud": ["kubernetes"],
  "exp": 1735689600,
  "email": "alice@example.com",
  "groups": ["platform", "developers"]
}

如果配置:

username claim = email
groups claim   = groups

则可能得到:

username = alice@example.com
groups   = [platform, developers]

如果多个身份系统都可能产生相同的用户名或组名,应使用前缀区分,例如把组映射为:

oidc:platform
oidc:developers

否则,绑定到 developers 的 RBAC 规则可能同时影响来自不同身份源的用户。

OIDC 配置的版本和云厂商差异

OIDC 的 API Server 参数和结构化认证配置在 Kubernetes 版本中持续演进。常见发行版可能通过:

  • 托管控制平面选项;
  • 集群创建参数;
  • 身份代理;
  • 云厂商专用集成;
  • 反向代理或外部认证层

来配置 OIDC。参数名称、可用声明、前缀规则和密钥刷新行为不能仅凭某个云厂商文档推断为通用 Kubernetes 行为。

生产配置至少要验证以下事实:

kubectl auth whoami
kubectl auth can-i get pods -n dev
kubectl auth can-i --list

还应实际检查:

  • Token 的 issaudexp
  • 组声明是否是字符串数组,而不是逗号拼接字符串;
  • IdP 密钥轮换后 API Server 是否能获取新 JWKS;
  • 用户被禁用后,已有 Token 在剩余有效期内是否仍然有效;
  • RBAC 是否绑定了稳定的 sub,还是绑定了可能变化的邮箱地址。

OIDC 用户生命周期

OIDC 的生命周期由多个时间尺度组成:

用户在 IdP 中创建
  → 获得组成员关系
  → 登录获取 Token
  → Token 在有效期内使用
  → 用户被禁用或移出组
  → 新 Token 不再包含身份/组
  → 旧 Token 是否立即失效,取决于 IdP 和验证方式
  → Kubernetes 中旧 RoleBinding 是否仍需清理

如果 RBAC 使用 email 作为用户名,邮箱变更可能导致权限绑定断裂;如果邮箱被重新分配给另一个人,也可能造成身份语义风险。更稳定的 sub 通常更适合作为用户名,但其可读性较差。实际选择应同时考虑 IdP 保证、审计可读性和迁移策略。

认证 Webhook:把认证决策委托给外部服务

Webhook 与 OIDC 的区别

OIDC 要求 API Server 自己理解并验证 JWT。认证 Webhook 则把 Token 认证请求发送给外部 HTTPS 服务,由该服务返回:

  • Token 是否有效;
  • 用户名;
  • 用户组;
  • 额外属性;
  • 可选的过期时间。

Webhook 适合以下情况:

  • Token 是非 JWT 的不透明字符串;
  • 凭据验证需要调用企业会话服务;
  • 需要集中撤销和实时策略;
  • 身份系统已有 Kubernetes 不直接支持的认证协议。

它的代价是:API Server 的认证路径依赖外部服务的网络、证书、性能和可用性。

TokenReview 请求和响应

API Server 会向 Webhook 发送 authentication.k8s.io 组中的 TokenReview。概念上的请求如下:

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "spec": {
    "token": "opaque-token-from-client",
    "audiences": [
      "https://kubernetes.default.svc"
    ]
  }
}

Webhook 成功验证后返回:

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "status": {
    "authenticated": true,
    "user": {
      "username": "alice@example.com",
      "uid": "idp-00u123",
      "groups": ["oidc:developers"],
      "extra": {
        "example.com/tenant": ["team-a"]
      }
    },
    "audiences": [
      "https://kubernetes.default.svc"
    ]
  }
}

Webhook 必须把 authenticated、用户身份和 audience 作为一个一致的验证结果返回。若 Token 无效,应返回:

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "status": {
    "authenticated": false
  }
}

不能仅因为能解析 Token 就返回用户名;外部服务必须验证签名、会话状态、有效期、audience 和撤销状态。

Webhook 的数据流和故障路径

sequenceDiagram
    participant K as kube-apiserver
    participant W as 认证 Webhook
    participant I as 外部 IdP/会话库

    K->>W: POST TokenReview
    W->>I: 查询或验证 Token
    I-->>W: 有效/无效、用户、组
    W-->>K: TokenReview status
    K->>K: 认证后执行授权

可能的故障包括:

  • Webhook TLS 证书过期;
  • API Server 无法解析 Webhook 地址;
  • 网络策略阻断访问;
  • Webhook 响应超时;
  • IdP 不可用;
  • Webhook 返回格式不符合 TokenReview
  • Webhook 误把 audience 忽略;
  • 缓存使撤销后的 Token 在短时间内仍被接受。

认证 Webhook 通常支持超时和缓存配置。缓存能减少外部调用,但会延迟撤销生效;超时时间过长会把 API Server 请求延迟传递给所有客户端,过短则可能在短暂网络抖动时造成大量 401。这些参数应结合认证服务的延迟分布和故障策略验证,不能只追求“更快”或“更安全”。

如果 Webhook 不可用,认证失败可能表现为:

401 Unauthorized

也可能表现为 API Server 侧错误或明显延迟,具体取决于配置和实现。排查时应同时查看:

kubectl get --raw='/readyz?verbose'
kubectl get events -A

并检查 API Server 日志、Webhook 服务日志、TLS 信任链和网络连通性。不要通过临时开启匿名认证来“修复”问题;那会改变整个 API Server 的安全边界。

认证之后:RBAC 如何消费身份

RBAC Binding 中的 subjects 只匹配认证阶段产生的身份字符串。例如:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: platform-readonly
subjects:
- kind: Group
  name: oidc:platform
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io

如果 OIDC 配置最终返回的组是 platform 而不是 oidc:platform,该 Binding 不会生效。认证配置中的前缀、大小写、组声明格式和 RBAC 中的名称必须完全一致。

授权计算可以简化为:

Allowed(u,G,r,v,n,a)=s{u}GRBACMatch(s,r,v,n,a)\operatorname{Allowed}(u,G,r,v,n,a) = \bigvee_{s \in \{u\}\cup G} \operatorname{RBACMatch}(s,r,v,n,a)

其中:

  • rr 是资源;
  • vv 是 API Group;
  • nn 是命名空间;
  • aa 是动作,如 getlistcreate
  • 只要用户名或任意用户组匹配某条允许规则,RBAC 就可能允许请求。

因此,把用户加入一个高权限组,效果可能远大于给用户增加一条单独的 RoleBinding。认证组声明必须视为高敏感输入。

特别需要区分:

  • Role 只在一个命名空间内授权;
  • ClusterRole 可以描述集群范围资源,也可被 RoleBinding 引用以授予某命名空间内权限;
  • ClusterRoleBinding 把权限授予整个集群范围;
  • system:masters 等系统组具有极高权限,不应作为普通业务组使用。

用户生命周期与权限回收

人类用户的生命周期

一个完整的人类用户生命周期通常是:

入职
 → IdP/PKI 创建身份
 → 配置组或签发证书
 → Kubernetes RBAC 绑定组
 → 日常认证和审计
 → 岗位变化,组成员变化
 → 凭据轮换
 → 离职,撤销登录和凭据
 → 清理或保留审计所需的 RBAC 记录

每种认证方式的回收速度不同:

认证方式 回收主要依赖 常见延迟来源
客户端证书 CA、证书有效期、Binding 已签发证书通常不会自动在线撤销
OIDC IdP 会话和 Token 策略 旧 Token 在过期前可能仍有效
Webhook 外部验证服务和缓存 Webhook 缓存、网络和后端状态
静态 Token 删除或更换静态 Token 配置 配置加载、API Server 重启
ServiceAccount Token ServiceAccount、Pod 绑定和 TokenRequest 应用缓存旧 Token、验证端缓存

这张表的关键不是“哪种方式一定更安全”,而是明确:身份回收不是 RBAC 单独完成的事情

工作负载的生命周期

工作负载不应共享人类管理员凭据。推荐为每个应用或信任边界创建独立 ServiceAccount:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: billing-reader
  namespace: finance

然后只授予需要的资源和动作。应用删除、迁移或换租户时,应同步处理:

  • Deployment 或 Job 使用的 ServiceAccount;
  • RoleBinding;
  • 外部 Workload Identity 映射;
  • 云厂商 IAM 角色;
  • 外部服务允许的 audience;
  • 旧 Pod 和旧 Token。

如果多个租户共用一个 ServiceAccount,审计只能看到同一个身份,权限回收也会牵连多个应用,导致隔离边界模糊。

诊断:从 HTTP 状态和身份开始

401 Unauthorized

通常表示认证失败,重点检查:

  • 是否发送了 Authorization: Bearer
  • Token 是否过期;
  • JWT 的 issaud 是否正确;
  • 签名密钥是否更新;
  • 客户端证书是否过期;
  • 客户端证书是否由正确 CA 签发;
  • Webhook 是否可达并返回有效响应;
  • ServiceAccount Token 是否仍对应有效 Pod 或 ServiceAccount。

可以先查看客户端身份:

kubectl auth whoami

对于 ServiceAccount,检查 Pod 中实际读取的 Token,而不是只查看 Secret:

kubectl -n finance exec deploy/billing -c app -- \
  sh -c 'test -s /var/run/secrets/kubernetes.io/serviceaccount/token && echo token-present'

不要把 Token 内容打印到共享日志或聊天系统。

403 Forbidden

通常表示认证已经成功,但授权失败。使用:

kubectl auth can-i get deployments -n finance
kubectl auth can-i --list -n finance

如果是管理员进行排查,可以指定身份:

kubectl auth can-i \
  --as=alice@example.com \
  --as-group=oidc:developers \
  get pods -n dev

重点核对:

  • 用户名和组是否与 RBAC Binding 完全一致;
  • RoleBinding 是否位于正确命名空间;
  • roleRef 是否引用了预期的 Role 或 ClusterRole;
  • API Group、资源名和动词是否正确;
  • 是否误用了 ClusterRoleBinding
  • 是否因为聚合 ClusterRole 自动获得了额外权限。

kubectl auth whoami 不能显示完整安全事实

它能帮助确认 API Server 解析出了什么用户名和组,但不能证明:

  • Token 的来源可信;
  • 当前权限符合最小权限;
  • 外部 IdP 已经完成用户回收;
  • 证书没有被复制;
  • Webhook 缓存已经刷新。

必要时应结合 API Server 审计日志。审计记录中的 user.usernameuser.groups、源地址和请求资源,可以确认实际到达授权层的身份,而不是只看客户端配置文件。

常见误解和反例

“证书有效,所以用户一定有权限”

反例:证书由受信任 CA 签发,CN 为 alice,但没有任何 RoleBinding。结果是认证成功、授权失败,即 403

“JWT 能解码,所以它是真的”

反例:攻击者构造一个没有有效签名的 JWT,或者使用签发给另一个 audience 的合法 JWT。若服务只解码 payload,不验证签名和 aud,就会接受错误身份。

“删除 RoleBinding 就完成了用户离职处理”

反例:用户仍持有有效 OIDC Token 或客户端证书,并通过另一个组获得权限。RBAC 中删除一条 Binding 并不能撤销所有外部凭据。

“ServiceAccount Token 放在 Secret 中最方便”

反例:长期 Token 被复制到日志、备份或开发者电脑,Pod 删除后仍可能被使用。Projected Token 的短期有效期、绑定关系和 kubelet 轮换更适合现代工作负载。

“认证 Webhook 返回 username 就够了”

反例:Webhook 不验证 audience,导致面向其他系统的 Token 也能调用 Kubernetes;或者 Webhook 缓存撤销结果过久,离职用户在缓存有效期内仍能访问。

生产取舍

人类用户通常更适合使用企业 IdP 的 OIDC 或受控的短期凭据流程,因为用户生命周期、组管理、多因素认证和登录审计可以集中处理。客户端证书适合 PKI 已成熟、需要强客户端身份并能可靠管理私钥和轮换的场景。

工作负载应优先使用短期、受 audience 限制、可轮换的 ServiceAccount Token,并通过 Workload Identity 与云厂商或外部服务建立明确的信任关系。不要让应用依赖管理员 kubeconfig,也不要把人类 Token 嵌入镜像。

认证 Webhook 提供了实时控制和协议适配能力,但它把 API Server 的可用性依赖扩展到了外部系统。启用前必须验证 TLS、超时、缓存、密钥轮换、后端故障和恢复路径。

最终,Kubernetes 认证设计的核心不是在证书、Token、OIDC 和 Webhook 中选择一个“万能方案”,而是明确三条边界:

  1. 认证器如何证明凭据有效,并映射出稳定的用户名和组;
  2. RBAC 如何根据这些身份授予最小权限;
  3. 外部身份、凭据、Token、Binding 和审计记录如何随用户或工作负载生命周期同步变化。

只有这三条链路同时成立,401403、凭据泄露和离职回收等问题才不会被误认为是同一个“权限配置错误”。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。