【kubernetes v1.21】(kube-apiserver 2)kube-apiserver 请求处理链路与认证授权超深度分析
Part 2: kube-apiserver 请求处理链路与认证授权超深度分析
一、模块定位:请求处理链路业务职责
kube-apiserver 是 Kubernetes 控制平面的核心入口,所有对集群状态的读写操作都必须经过它。请求处理链路(Handler Chain)是 apiserver 的"脊梁骨",承担着从原始 HTTP 请求到最终业务处理之间的所有横切关注点(cross-cutting concerns)。
1.1 核心业务职责
请求处理链路的业务职责可以归纳为以下几大类:
| 职责类别 | 具体内容 | 核心Filter |
|---|---|---|
| 请求解析 | URL解析、RequestInfo构建、Verb映射 | WithRequestInfo |
| 身份认证(AuthN) | 确定请求者是谁 | WithAuthentication |
| 身份模拟(Impersonation) | 允许已认证用户模拟另一用户 | WithImpersonation |
| 权限授权(AuthZ) | 确定请求者是否有权执行操作 | WithAuthorization |
| 审计日志(Audit) | 记录请求全生命周期事件 | WithAudit / WithAuditAnnotations |
| 流量控制 | 限流、优先级与公平性 | WithPriorityAndFairness / WithMaxInFlightLimit |
| 超时管理 | 非长运行请求超时、请求deadline | WithTimeoutForNonLongRunningRequests / WithRequestDeadline |
| 安全防护 | CORS、HSTS、Panic恢复 | WithCORS / WithHSTS / WithPanicRecovery |
| 可观测性 | Filter延迟追踪、Warning记录 | filterlatency / WithWarningRecorder |
| 协议优化 | HTTP/2 GOAWAY概率发送、连接重平衡 | WithProbabilisticGoaway |
1.2 设计哲学
请求处理链路遵循 Go 标准库的 http.Handler 装饰器模式——每个 Filter 都是一个 http.Handler,它接收内部 Handler,返回增强后的 Handler。这种设计带来三个关键特性:
- 洋葱模型:请求从外层 Filter 逐层穿透到核心业务 Handler,响应从内层逐层返回。外层先执行"前处理",内层先执行"后处理"。
- 顺序敏感:Filter 的注册顺序决定了执行顺序,且对语义正确性至关重要。例如认证必须在授权之前,RequestInfo 解析必须在认证之前。
- 可组合可替换:
BuildHandlerChainFunc作为配置项暴露,允许自定义 Handler Chain(例如扩展 apiserver 时),默认实现为DefaultBuildHandlerChain。
二、模块整体结构
2.1 Handler Chain 层次模型
┌─────────────────────────────────────────────────────────────────┐
│ HTTP/HTTPS Listener │
├─────────────────────────────────────────────────────────────────┤
│ WithPanicRecovery ← 最外层:panic恢复 │
│ WithRequestReceivedTimestamp ← 请求到达时间戳 │
│ WithHSTS ← 安全头注入 │
│ WithCacheControl ← 缓存控制头 │
│ WithWarningRecorder ← Warning收集器 │
│ WithAuditAnnotations ← 审计注解收集 │
│ WithProbabilisticGoaway ← HTTP/2 GOAWAY │
│ WithRequestInfo ← 请求信息解析 │
│ WithWaitGroup ← 优雅退出等待组 │
│ WithRequestDeadline ← 请求deadline设置 │
│ WithTimeoutForNonLongRunning ← 非长运行请求超时 │
│ WithCORS ← 跨域处理 │
│ WithAuthentication ← 身份认证 │
│ WithAudit ← 审计日志记录 │
│ WithImpersonation ← 身份模拟 │
│ WithPriorityAndFairness ← 优先级与公平性(或MaxInFlight) │
│ WithAuthorization ← 权限授权 │
├─────────────────────────────────────────────────────────────────┤
│ Core API Handler (Director/GoRestful) │
└─────────────────────────────────────────────────────────────────┘
注意:上面的顺序是从最外层到最内层,即请求先经过 WithPanicRecovery,最后到达 WithAuthorization。但在源码的 DefaultBuildHandlerChain 函数中,代码的书写顺序是从内到外——先对 apiHandler 包装 WithAuthorization,再包装 WithPriorityAndFairness,依次向外。这是因为装饰器模式的嵌套特性:最后包装的 Filter 最先执行。
2.2 核心接口定义
2.2.1 Authenticator 接口
// k8s.io/apiserver/pkg/authentication/authenticator/interfaces.go
type Request interface {
AuthenticateRequest(req *http.Request) (*Response, bool, error)
}
type Response struct {
User user.Info
Audiences Audiences
}
- 输入:原始
*http.Request - 输出三元组:
(Response, ok, error)ok=true:认证成功,Response.User为认证后的用户信息ok=false, error=nil:认证信息不匹配(如无Token、无证书),不是错误,只是"我没法处理"error!=nil:认证过程中出现系统错误(如Webhook不可达)
2.2.2 Authorizer 接口
// k8s.io/apiserver/pkg/authorization/authorizer/interfaces.go
type Authorizer interface {
Authorize(ctx context.Context, a Attributes) (Decision, string, error)
}
type Decision int
const (
DecisionDeny Decision = iota // 明确拒绝
DecisionAllow // 明确允许
DecisionNoOpinion // 无意见,交给下一个授权器
)
type Attributes interface {
GetUser() user.Info
GetVerb() string
IsReadOnly() bool
GetNamespace() string
GetResource() string
GetSubresource() string
GetName() string
GetAPIGroup() string
GetAPIVersion() string
IsResourceRequest() bool
GetPath() string
// ...
}
三值决策是 Kubernetes 授权系统的精妙设计:
- DecisionAllow:短路返回——一旦允许,后续授权器不再评估
- DecisionDeny:短路拒绝——一旦拒绝,请求直接失败
- DecisionNoOpinion:继续交给下一个授权器评估
2.2.3 RequestInfoResolver 接口
type RequestInfoResolver interface {
NewRequestInfo(req *http.Request) (*RequestInfo, error)
}
RequestInfo 是整个请求处理链路的核心数据结构之一,它将原始 HTTP URL 解析为语义化的 Kubernetes 资源操作描述:
type RequestInfo struct {
IsResourceRequest bool
Path string
Verb string // kube verb: create/get/list/watch/update/patch/delete/deletecollection
APIPrefix string // "api" or "apis"
APIGroup string
APIVersion string
Namespace string
Resource string
Subresource string
Name string
Parts []string
}
2.3 Config 中的关键字段
在 Config 结构体中,与请求处理链路直接相关的字段包括:
type Config struct {
BuildHandlerChainFunc func(apiHandler http.Handler, c *Config) (secure http.Handler)
RequestInfoResolver apirequest.RequestInfoResolver
Authentication AuthenticationInfo // 包含 Authenticator
Authorization AuthorizationInfo // 包含 Authorizer
FlowControl utilflowcontrol.Interface
AuditBackend audit.Backend
AuditPolicyChecker auditpolicy.Checker
// ...
}
NewConfig() 中将 BuildHandlerChainFunc 默认设为 DefaultBuildHandlerChain,这是整个链路的入口函数。
三、核心业务逻辑深度解析
3.1 DefaultBuildHandlerChain 完整逐行解析
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
// ====== 最内层:授权 ======
handler := filterlatency.TrackCompleted(apiHandler)
handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
handler = filterlatency.TrackStarted(handler, "authorization")
第1-3行:授权Filter是最内层的业务Filter。filterlatency.TrackCompleted/TrackStarted 成对出现,用于测量授权Filter的执行延迟。TrackCompleted 在请求返回路径上记录完成时间,TrackStarted 在请求进入路径上记录开始时间,两者差值即为Filter延迟。
为什么授权在最内层? 因为授权需要在认证和模拟之后执行——此时请求者的身份已经最终确定。
// ====== 流量控制 ======
if c.FlowControl != nil {
handler = filterlatency.TrackCompleted(handler)
handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl)
handler = filterlatency.TrackStarted(handler, "priorityandfairness")
} else {
handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight,
c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
}
第4-11行:流量控制层。如果启用了 APIPriorityAndFairness 特性门控,使用优先级与公平性调度器(FlowControl);否则退化为简单的最大并发数限制。默认 MaxRequestsInFlight=400(读请求),MaxMutatingRequestsInFlight=200(写请求)。流量控制放在授权之后是因为:即使请求最终被授权拒绝,也需要占用流量控制配额(防止恶意请求通过大量未授权请求绕过限流)。
// ====== 身份模拟 ======
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
handler = filterlatency.TrackStarted(handler, "impersonation")
第12-14行:Impersonation Filter 处理请求头中的模拟指令。它需要 Authorizer 来验证当前用户是否有权模拟目标用户/组。Impersonation 在流量控制之后但在认证之后之前——这确保了:
- 请求已经通过了流量控制(不会因为模拟逻辑引入额外开销导致限流失效)
- 认证已经确定了原始请求者身份(Impersonation 需要知道"谁在模拟谁")
但等等——在代码中,Impersonation 在 Authentication 之前(外层)?不对,让我重新审视顺序。代码书写是从内到外的,所以执行顺序(从外到内)是:
- WithPanicRecovery(最外层)
- WithRequestReceivedTimestamp
- WithHSTS
- WithCacheControl
- WithWarningRecorder
- WithAuditAnnotations
- WithProbabilisticGoaway(条件)
- WithRequestInfo
- WithWaitGroup
- WithRequestDeadline
- WithTimeoutForNonLongRunningRequests
- WithCORS
- WithAuthentication
- WithAudit
- WithImpersonation
- WithPriorityAndFairness/WithMaxInFlightLimit
- WithAuthorization(最内层业务Filter)
等等,我需要再仔细看代码。代码从上到下是先处理 apiHandler → WithAuthorization → FlowControl → WithImpersonation → WithAudit → WithAuthentication → … → WithPanicRecovery。因为每一步 handler = XXX(handler, ...) 是包装前一个 handler,所以最后包装的最先执行。
所以实际请求执行顺序(从外层到内层)是:
- WithPanicRecovery ← 最先执行(最后包装)
- WithRequestReceivedTimestamp
- WithHSTS
- WithCacheControl
- WithWarningRecorder
- WithAuditAnnotations
- WithProbabilisticGoaway
- WithRequestInfo
- WithWaitGroup
- WithRequestDeadline
- WithTimeoutForNonLongRunningRequests
- WithCORS
- WithAuthentication ← 认证
- WithAudit ← 审计
- WithImpersonation ← 模拟
- WithPriorityAndFairness ← 流量控制
- WithAuthorization ← 授权
- Core API Handler
这个顺序的精妙之处:
- RequestInfo 先于一切:后续所有 Filter(认证、授权、审计)都需要 RequestInfo
- Authentication → Impersonation → Authorization:先确认真实身份,再处理模拟,最后基于最终身份做授权
- Audit 在 AuthN 和 Impersonation 之间:审计需要记录认证结果和模拟行为
- FlowControl 在 Authorization 之前(内层):流量控制在授权判断之前就执行限流,防止资源耗尽
// ====== 审计 ======
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
handler = filterlatency.TrackStarted(handler, "audit")
第15-17行:审计Filter。它包装后续的Handler,在整个请求生命周期中记录审计事件:StageRequestReceived → StageResponseStarted → StageResponseComplete。审计Filter的位置确保了:
- 它能看到认证结果(Authentication在它内层,认证成功/失败会反映在上下文中)
- 它能记录Impersonation行为(Impersonation在它内层)
// ====== 认证 ======
failedHandler := genericapifilters.Unauthorized(c.Serializer)
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyChecker)
failedHandler = filterlatency.TrackCompleted(failedHandler)
handler = filterlatency.TrackCompleted(handler)
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
handler = filterlatency.TrackStarted(handler, "authentication")
第18-24行:认证Filter。这是整个安全链的起点。注意 failedHandler 的构建——认证失败时走专门的失败处理链:
Unauthorized()返回 401 响应WithFailedAuthenticationAudit对失败请求也记录审计日志- 延迟追踪也覆盖了失败路径
认证成功后:
- 从
Authorization请求头中删除凭据(安全措施,防止泄露到后续Handler) - 将
user.Info注入到请求 Context 中
// ====== CORS ======
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
CORS 处理。对于跨域预检请求(OPTIONS),直接返回允许,不进入后续链路。
// ====== 超时控制 ======
handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc)
handler = genericapifilters.WithRequestDeadline(handler, c.AuditBackend, c.AuditPolicyChecker,
c.LongRunningFunc, c.Serializer, c.RequestTimeout)
两个超时Filter协同工作:
WithTimeoutForNonLongRunningRequests:对非长运行请求(非watch等)设置超时context,超时后返回429或503WithRequestDeadline:设置请求级别的deadline,并与审计系统集成
// ====== WaitGroup ======
handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup)
WaitGroup 用于优雅关闭:apiserver 收到终止信号后,等待所有正在处理的请求完成。
// ====== RequestInfo ======
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
RequestInfo 解析——将 URL 路径解析为结构化的请求信息。这是认证和授权的前置依赖。
// ====== GOAWAY ======
if c.SecureServing != nil && !c.SecureServing.DisableHTTP2 && c.GoawayChance > 0 {
handler = genericfilters.WithProbabilisticGoaway(handler, c.GoawayChance)
}
HTTP/2 连接重平衡:以 GoawayChance 概率发送 GOAWAY 帧,迫使客户端重新建立连接,实现负载均衡器后的连接重分配。
// ====== 审计注解 / Warning / CacheControl / HSTS ======
handler = genericapifilters.WithAuditAnnotations(handler, c.AuditBackend, c.AuditPolicyChecker)
handler = genericapifilters.WithWarningRecorder(handler)
handler = genericapifilters.WithCacheControl(handler)
handler = genericfilters.WithHSTS(handler, c.HSTSDirectives)
handler = genericapifilters.WithRequestReceivedTimestamp(handler)
handler = genericfilters.WithPanicRecovery(handler, c.RequestInfoResolver)
return handler
}
- WithAuditAnnotations:允许后续Handler通过Context添加审计注解
- WithWarningRecorder:收集 Warning 头信息(如 deprecation warnings)
- WithCacheControl:设置
Cache-Control: no-store防止缓存 - WithHSTS:HTTP Strict Transport Security
- WithRequestReceivedTimestamp:记录请求到达的精确时间戳,供审计和超时计算使用
- WithPanicRecovery:最外层安全网,捕获 panic 并返回 500 错误
3.2 认证流程:RequestInfo → AuthN → AuthZ
3.2.1 完整的请求处理时序
一个请求从到达 apiserver 到被处理或拒绝,经历以下关键步骤:
HTTP Request
│
▼
[PanicRecovery] ─── 捕获panic,返回500
│
▼
[RequestReceivedTimestamp] ─── 记录到达时间
│
▼
[HSTS/CORS/CacheControl] ─── 安全头处理
│
▼
[RequestInfo] ─── URL解析为RequestInfo结构体
│ ↓ IsResourceRequest, Verb, Resource, Namespace...
│ 注入Context
▼
[WaitGroup] ─── 注册到优雅退出等待组
│
▼
[Timeout/Deadline] ─── 设置请求超时Context
│
▼
[CORS] ─── 处理跨域
│
▼
[Authentication] ─── 身份认证
│ │
│ ├─ 成功 → user.Info注入Context,删除Authorization头
│ └─ 失败 → 401 Unauthorized + 审计日志
│
▼
[Audit] ─── 审计事件创建
│ StageRequestReceived → 检查审计策略 → 发送事件
│
▼
[Impersonation] ─── 身份模拟处理
│ │
│ ├─ 有模拟头 → 逐项授权检查 → 替换Context中的user.Info
│ └─ 无模拟头 → 直接通过
│
▼
[PriorityAndFairness] ─── 流量控制
│ │
│ ├─ 排队 → 等待调度
│ └─ 执行 → 继续处理
│
▼
[Authorization] ─── 权限授权
│ │
│ ├─ DecisionAllow → 继续处理
│ ├─ DecisionDeny → 403 Forbidden + 审计记录
│ └─ DecisionNoOpinion → 403 Forbidden(所有授权器无意见=拒绝)
│
▼
Core API Handler ─── 业务逻辑处理
3.2.2 WithAuthentication 深度解析
func withAuthentication(handler http.Handler, auth authenticator.Request, failed http.Handler,
apiAuds authenticator.Audiences, metrics recordMetrics) http.Handler {
if auth == nil {
klog.Warning("Authentication is disabled")
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
authenticationStart := time.Now()
// 1. 将API Audiences注入Context,供下游认证器使用
if len(apiAuds) > 0 {
req = req.WithContext(authenticator.WithAudiences(req.Context(), apiAuds))
}
// 2. 调用认证器链进行认证
resp, ok, err := auth.AuthenticateRequest(req)
authenticationFinish := time.Now()
defer func() {
metrics(req.Context(), resp, ok, err, apiAuds, authenticationStart, authenticationFinish)
}()
// 3. 认证失败处理
if err != nil || !ok {
if err != nil {
klog.ErrorS(err, "Unable to authenticate the request")
}
failed.ServeHTTP(w, req) // 走失败Handler(401 + 审计)
return
}
// 4. Audience校验
if !audiencesAreAcceptable(apiAuds, resp.Audiences) {
err = fmt.Errorf("unable to match the audience: %v , accepted: %v", resp.Audiences, apiAuds)
klog.Error(err)
failed.ServeHTTP(w, req)
return
}
// 5. 安全措施:删除Authorization头
req.Header.Del("Authorization")
// 6. 将用户信息注入Context
req = req.WithContext(genericapirequest.WithUser(req.Context(), resp.User))
handler.ServeHTTP(w, req)
})
}
关键设计决策:
-
Audience 机制:API Audiences 是 Kubernetes 认证系统的一个安全特性。Token 可能是为不同受众(audience)签发的,例如一个 ServiceAccount Token 可能是为外部系统签发的,apiserver 只应接受为自己签发的 Token。
audiencesAreAcceptable检查认证器返回的 audiences 与 apiserver 期望的 audiences 是否有交集。 -
Authorization 头删除:一旦认证成功,立即删除
Authorization头,防止下游Handler意外泄露凭据。 -
错误 vs 认证失败:
err != nil(系统错误)和!ok(认证信息不匹配)都走失败路径,但语义不同。系统错误意味着基础设施问题(Webhook不可达等),认证失败意味着凭据无效。
3.2.3 WithAuthorization 深度解析
func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
if a == nil {
klog.Warning("Authorization is disabled")
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
ae := request.AuditEventFrom(ctx)
// 1. 从Context构建授权属性
attributes, err := GetAuthorizerAttributes(ctx)
if err != nil {
responsewriters.InternalError(w, req, err)
return
}
// 2. 调用授权器
authorized, reason, err := a.Authorize(ctx, attributes)
// 3. 授权允许
if authorized == authorizer.DecisionAllow {
audit.LogAnnotation(ae, decisionAnnotationKey, decisionAllow)
audit.LogAnnotation(ae, reasonAnnotationKey, reason)
handler.ServeHTTP(w, req)
return
}
// 4. 授权器内部错误
if err != nil {
audit.LogAnnotation(ae, reasonAnnotationKey, reasonError)
responsewriters.InternalError(w, req, err)
return
}
// 5. 授权拒绝
klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "Reason", reason)
audit.LogAnnotation(ae, decisionAnnotationKey, decisionForbid)
audit.LogAnnotation(ae, reasonAnnotationKey, reason)
responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
})
}
GetAuthorizerAttributes 函数将 Context 中的 user.Info 和 RequestInfo 组装为 authorizer.AttributesRecord:
func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
attribs := authorizer.AttributesRecord{}
user, ok := request.UserFrom(ctx) // 从认证阶段注入
if ok {
attribs.User = user
}
requestInfo, found := request.RequestInfoFrom(ctx) // 从RequestInfo阶段注入
if !found {
return nil, errors.New("no RequestInfo found in the context")
}
attribs.ResourceRequest = requestInfo.IsResourceRequest
attribs.Path = requestInfo.Path
attribs.Verb = requestInfo.Verb
attribs.APIGroup = requestInfo.APIGroup
attribs.APIVersion = requestInfo.APIVersion
attribs.Resource = requestInfo.Resource
attribs.Subresource = requestInfo.Subresource
attribs.Namespace = requestInfo.Namespace
attribs.Name = requestInfo.Name
return &attribs, nil
}
这是请求处理链路数据流的关键交汇点:认证阶段注入的 user.Info + RequestInfo 阶段解析的请求属性 → 授权决策所需的完整属性集。
3.3 所有认证器逐个解析
3.3.1 认证器组合架构
kube-apiserver 的认证系统采用 Union 模式——多个认证器组成链式结构,依次尝试,第一个成功即返回。这在 pkg/kubeapiserver/authenticator/config.go 的 New() 方法中构建:
Authenticator Union Chain (按优先级):
┌─────────────────────────────────────────┐
│ 1. RequestHeader (Front-Proxy) │ ← 前端代理认证
│ 2. X509 Client Cert │ ← 双向TLS认证
│ 3. Bearer Token Union: │ ← Token认证组
│ ├─ Static Token File │
│ ├─ ServiceAccount (Legacy) │
│ ├─ ServiceAccount (JWT) │
│ ├─ Bootstrap Token │
│ ├─ OIDC │
│ └─ Webhook Token │
│ 4. group.NewAuthenticatedGroupAdder │ ← 自动添加 system:authenticated
│ 5. anonymous.NewAuthenticator (兜底) │ ← 匿名访问(FailOnError)
└─────────────────────────────────────────┘
3.3.2 Authenticator Union 组合机制
// union.go
type unionAuthRequestHandler struct {
Handlers []authenticator.Request
FailOnError bool
}
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
var errlist []error
for _, currAuthRequestHandler := range authHandler.Handlers {
resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
if err != nil {
if authHandler.FailOnError {
return resp, ok, err // FailOnError模式:遇到错误立即返回
}
errlist = append(errlist, err)
continue
}
if ok {
return resp, ok, err // 认证成功立即返回
}
}
return nil, false, utilerrors.NewAggregate(errlist) // 全部失败,聚合错误
}
两种 Union 模式:
New():正常模式,忽略中间错误,继续尝试下一个认证器NewFailOnError():严格模式,任何认证器返回错误即短路——用于匿名认证器的包装,确保"认证器报错"不等于"匿名用户"
3.3.3 X509 客户端证书认证
源码位置:k8s.io/apiserver/pkg/authentication/request/x509/
X509 认证基于 HTTP 双向 TLS(mTLS),客户端在 TLS 握手时出示证书,apiserver 使用配置的 CA 证书验证客户端证书合法性。
认证流程:
TLS Handshake
│
▼
apiserver 提取客户端证书(req.TLS.PeerCertificates)
│
▼
使用 ClientCA 验证证书链
│
├─ 证书无效 → 返回 (nil, false, nil)(不是错误,只是不匹配)
│
└─ 证书有效 → 提取 Subject.CommonName 作为用户名
│
▼
CommonNameUserConversion
│
▼
user.DefaultInfo{Name: CN, Groups: [Org1, Org2, ...]}
关键设计:
NewDynamic支持动态证书更新——CA 证书变更时不需要重启 apiserver- 证书中的
Organization字段映射为用户组 CommonName映射为用户名- 这是唯一一个在 HTTP 层面之前就完成的认证(TLS 层)
3.3.4 Token 认证(Bearer Token)
Token 认证通过 Authorization: Bearer <token> 请求头传递。Bearer Token 认证器本身是一个壳,内部组合了多个 Token 认证器:
if len(tokenAuthenticators) > 0 {
tokenAuth := tokenunion.New(tokenAuthenticators...) // Token Union
if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
tokenAuth = tokencache.New(tokenAuth, true, // 缓存层
config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
}
authenticators = append(authenticators,
bearertoken.New(tokenAuth), // Bearer Token 提取器
websocket.NewProtocolAuthenticator(tokenAuth)) // WebSocket 子协议认证
}
Token 认证子链(按优先级):
- Static Token File:从 CSV 文件加载的静态 Token
- ServiceAccount (Legacy):使用 Legacy Issuer 签发的 SA Token
- ServiceAccount (JWT):使用配置的 Issuer 签发的 SA Token
- Bootstrap Token:节点引导 Token(
kubeadm使用) - OIDC:OpenID Connect Token
- Webhook Token:远程 Webhook 验证
Token 缓存层:tokencache.New 对 Token 认证结果进行缓存,避免每次请求都做完整的 Token 验证(特别是 OIDC 和 Webhook 等远程验证),显著提升性能。成功和失败结果分别有不同的 TTL。
Bearer Token 提取器:bearertoken.New 从 Authorization: Bearer xxx 头中提取 Token 字符串,然后交给内部的 Token 认证器链验证。
WebSocket 子协议认证:websocket.NewProtocolAuthenticator 处理 WebSocket 连接中的 Token 传递方式——某些客户端通过 WebSocket 子协议头(Sec-WebSocket-Protocol)传递 Token。
3.3.5 Static Token File 认证
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
// ...
}
CSV 格式:token,user,uid,group1,group2,...
这是最简单的 Token 认证方式,从文件一次性加载所有 Token 到内存。适合小规模部署或测试场景。缺点是更新 Token 需要重启 apiserver。
3.3.6 Bootstrap Token 认证
Bootstrap Token 是 kubeadm 引入的机制,用于新节点加入集群时的初始认证。Token 格式为 [a-z0-9]{6}.[a-z0-9]{16},存储在 kube-system 命名空间的 Secret 中。
Bootstrap Token 认证器通过 WrapAudienceAgnosticToken 包装,因为它不需要 Audience 校验——Token 本身的生命周期和使用场景已经足够受限。
3.3.7 ServiceAccount Token 认证
Kubernetes 支持两种 ServiceAccount Token 签发方式:
Legacy 模式(newLegacyServiceAccountAuthenticator):
- Issuer 为
https://kubernetes.io/serviceaccounts(硬编码) - 仅验证签名,不验证 Audience
新模式(newServiceAccountAuthenticator):
- Issuer 为配置的
--service-account-issuer - 完整的 JWT 验证:签名、Issuer、Audience、过期时间
- 支持外部消费者(如 Vault)验证 Token
两种模式并存的原因是向后兼容——已签发的 Legacy Token 需要继续被接受。
func newServiceAccountAuthenticator(iss string, keyfiles []string, apiAudiences authenticator.Audiences,
serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
allPublicKeys := []interface{}{}
for _, keyfile := range keyfiles {
publicKeys, _ := keyutil.PublicKeysFromFile(keyfile)
allPublicKeys = append(allPublicKeys, publicKeys...)
}
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, apiAudiences,
serviceaccount.NewValidator(serviceAccountGetter))
return tokenAuthenticator, nil
}
serviceAccountGetter 用于 Token 的吊销检查——当 ServiceAccount 被删除时,其 Token 应该失效。这通过 --service-account-lookup 标志控制。
3.3.8 OIDC 认证
if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(oidc.Options{
IssuerURL: config.OIDCIssuerURL,
ClientID: config.OIDCClientID,
CAFile: config.OIDCCAFile,
UsernameClaim: config.OIDCUsernameClaim,
UsernamePrefix: config.OIDCUsernamePrefix,
GroupsClaim: config.OIDCGroupsClaim,
GroupsPrefix: config.OIDCGroupsPrefix,
SupportedSigningAlgs: config.OIDCSigningAlgs,
RequiredClaims: config.OIDCRequiredClaims,
})
tokenAuthenticators = append(tokenAuthenticators,
authenticator.WrapAudienceAgnosticToken(config.APIAudiences, oidcAuth))
}
OIDC 认证流程:
客户端携带 JWT Token
│
▼
OIDC Authenticator
│
├─ 1. 解析 JWT Header,提取 kid(Key ID)
│
├─ 2. 从 Issuer URL 的 JWKS 端点获取公钥
│ GET https://issuer/.well-known/openid-configuration
│ GET https://issuer/keys(JWKS endpoint)
│
├─ 3. 验证 JWT 签名
│
├─ 4. 验证 Claims:
│ - iss == --oidc-issuer-url
│ - aud 包含 --oidc-client-id
│ - exp > now
│ - RequiredClaims 全部匹配
│
├─ 5. 提取用户信息:
│ - Username: {UsernameClaim}(默认 "sub")
│ - Groups: {GroupsClaim}(默认 不提取)
│ - 自动添加 UsernamePrefix / GroupsPrefix
│
└─ 6. 返回 user.Info
UsernamePrefix 设计哲学:
- 默认情况下,非
emailclaim 的用户名会被加issuerURL#前缀 - 这是为了防止不同 OIDC Provider 之间的用户名冲突
- 特殊值
-表示不加前缀
JWKS 缓存与动态更新:OIDC 认证器会定期刷新 JWKS 公钥(当发现未知 kid 时也会触发刷新),确保支持 Provider 的密钥轮换。
源码注释中的关键提示:
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
//
// Because both plugins verify JWTs whichever comes first in the union experiences
// cache misses for all requests using the other. While the service account plugin
// simply returns an error, the OpenID Connect plugin may query the provider to
// update the keys, causing performance hits.
OIDC 排在 ServiceAccount 之后——因为两者都验证 JWT,排在前面的会经历缓存未命中(当请求使用另一种Token时),而 OIDC 的缓存未命中代价更高(可能触发远程 JWKS 获取)。
3.3.9 Webhook Token 认证
func newWebhookTokenAuthenticator(config Config) (authenticator.Token, error) {
webhookTokenAuthenticator, err := webhook.New(
config.WebhookTokenAuthnConfigFile,
config.WebhookTokenAuthnVersion,
config.APIAudiences,
*config.WebhookRetryBackoff,
config.CustomDial)
// ...
return tokencache.New(webhookTokenAuthenticator, false,
config.WebhookTokenAuthnCacheTTL, config.WebhookTokenAuthnCacheTTL), nil
}
Webhook Token 认证将 Token 验证委托给外部 HTTP 服务:
Bearer Token
│
▼
Webhook Token Authenticator
│
├─ 1. 构造 TokenReview 对象
│ {
│ "apiVersion": "authentication.k8s.io/v1",
│ "kind": "TokenReview",
│ "spec": {
│ "token": "<bearer-token>",
│ "audiences": ["api-audiences"]
│ }
│ }
│
├─ 2. POST 到 Webhook 服务
│
├─ 3. 解析响应
│ {
│ "status": {
│ "authenticated": true,
│ "user": {
│ "username": "admin",
│ "uid": "42",
│ "groups": ["system:masters"]
│ },
│ "audiences": ["api-audiences"]
│ }
│ }
│
└─ 4. 返回 user.Info
关键特性:
- 缓存:Webhook 认证结果被缓存,TTL 可配置(
--authentication-token-webhook-cache-ttl) - 重试:使用指数退避重试机制(
WebhookRetryBackoff),防止 Webhook 服务短时不可用导致认证全部失败 - 版本控制:支持 v1 和 v1beta1 API 版本的 TokenReview
3.3.10 RequestHeader (Front-Proxy) 认证
if config.RequestHeaderConfig != nil {
requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
config.RequestHeaderConfig.AllowedClientNames,
config.RequestHeaderConfig.UsernameHeaders,
config.RequestHeaderConfig.GroupHeaders,
config.RequestHeaderConfig.ExtraHeaderPrefixes,
)
authenticators = append(authenticators,
authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
}
Front-Proxy 认证用于反向代理场景(如 Aggregated API Server 的代理层)。代理层通过 HTTP 头传递已认证的用户信息:
X-Remote-User:用户名X-Remote-Group:用户组X-Remote-Extra-<key>:额外信息
安全模型:apiserver 必须验证代理层的 TLS 客户端证书(由 RequestHeaderConfig.CAContentProvider 签发),确保只有受信任的代理才能设置这些头。AllowedClientNames 限制哪些 CN 的代理证书被允许。
3.3.11 匿名认证
if config.Anonymous {
// If the authenticator chain returns an error, return an error (don't consider a bad bearer token
// or invalid username/password combination anonymous).
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
}
匿名认证作为兜底——当所有认证器都无法识别请求时,将请求者标记为匿名用户(system:anonymous,组 system:unauthenticated)。
使用 NewFailOnError 包装是关键安全设计:如果认证器返回了错误(如 Webhook 不可达),不应该将请求降级为匿名——这可能导致本应被拒绝的请求被匿名用户策略错误允许。只有所有认证器都返回 ok=false, err=nil(即"我确实没看到有效的认证信息")时,才走匿名路径。
3.3.12 group.NewAuthenticatedGroupAdder
authenticator = group.NewAuthenticatedGroupAdder(authenticator)
这个包装器确保所有成功认证的用户自动加入 system:authenticated 组。这是 RBAC 默认角色绑定的基础——很多默认 ClusterRoleBinding 绑定到 system:authenticated 组。
3.3.13 Loopback Token(内部通信)
func AuthorizeClientBearerToken(loopback *restclient.Config, authn *AuthenticationInfo, authz *AuthorizationInfo) {
privilegedLoopbackToken := loopback.BearerToken
var uid = uuid.New().String()
tokens := make(map[string]*user.DefaultInfo)
tokens[privilegedLoopbackToken] = &user.DefaultInfo{
Name: user.APIServerUser, // "system:apiserver"
UID: uid,
Groups: []string{user.SystemPrivilegedGroup}, // "system:masters"
}
tokenAuthenticator := authenticatorfactory.NewFromTokens(tokens)
authn.Authenticator = authenticatorunion.New(tokenAuthenticator, authn.Authenticator)
tokenAuthorizer := authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup)
authz.Authorizer = authorizerunion.New(tokenAuthorizer, authz.Authorizer)
}
Loopback Token 是 apiserver 自身使用的超级 Token:
- 每次启动生成 UUID 作为 UID
- 用户为
system:apiserver,组为system:masters(超级用户组) - 在认证器链的最前面插入(优先级最高)
- 在授权器链的最前面插入一个特权授权器——
system:masters组直接DecisionAllow
这确保了 apiserver 的 PostStartHook 和内部 Informer 总能正常工作。
3.4 所有授权器逐个解析
3.4.1 Authorizer Union 组合机制
// union/union.go
func (authzHandler unionAuthzHandler) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
var (
errlist []error
reasonlist []string
)
for _, currAuthzHandler := range authzHandler {
decision, reason, err := currAuthzHandler.Authorize(ctx, a)
if err != nil {
errlist = append(errlist, err)
}
if len(reason) != 0 {
reasonlist = append(reasonlist, reason)
}
switch decision {
case authorizer.DecisionAllow, authorizer.DecisionDeny:
return decision, reason, err // 短路:Allow或Deny立即返回
case authorizer.DecisionNoOpinion:
// 继续下一个授权器
}
}
return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
}
与 Authenticator Union 的关键区别:
- Authenticator Union 只关心"成功/失败"二元结果
- Authorizer Union 支持三值决策:Allow(短路允许)、Deny(短路拒绝)、NoOpinion(继续评估)
- 允许和拒绝都是短路——这意味着授权器的顺序非常重要
3.4.2 授权器构建过程
在 pkg/kubeapiserver/authorizer/config.go 中,授权器按 --authorization-mode 参数的顺序构建:
func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
if len(config.AuthorizationModes) == 0 {
return nil, nil, fmt.Errorf("at least one authorization mode must be passed")
}
var (
authorizers []authorizer.Authorizer
ruleResolvers []authorizer.RuleResolver
)
for _, authorizationMode := range config.AuthorizationModes {
switch authorizationMode {
case modes.ModeNode: // "Node"
case modes.ModeAlwaysAllow: // "AlwaysAllow"
case modes.ModeAlwaysDeny: // "AlwaysDeny"
case modes.ModeABAC: // "ABAC"
case modes.ModeWebhook: // "Webhook"
case modes.ModeRBAC: // "RBAC"
}
}
return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
}
默认配置(kubeadm):--authorization-mode=Node,RBAC
这意味着授权评估顺序为:Node → RBAC → (Loopback Token Authorizer)。
- Node 授权器先评估,对 kubelet 请求做出特殊处理
- RBAC 授权器评估基于 Role/ClusterRole 的权限
- Loopback Token Authorizer(由
AuthorizeClientBearerToken注入)在链的最前面
3.4.3 RBAC 授权器
RBAC(Role-Based Access Control)是 Kubernetes 最核心的授权模式。
case modes.ModeRBAC:
rbacAuthorizer := rbac.New(
&rbac.RoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().Roles().Lister()},
&rbac.RoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().RoleBindings().Lister()},
&rbac.ClusterRoleGetter{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoles().Lister()},
&rbac.ClusterRoleBindingLister{Lister: config.VersionedInformerFactory.Rbac().V1().ClusterRoleBindings().Lister()},
)
authorizers = append(authorizers, rbacAuthorizer)
ruleResolvers = append(ruleResolvers, rbacAuthorizer)
RBAC 授权评估流程:
Authorize(attributes)
│
▼
1. 检查所有 RoleBindings/ClusterRoleBindings
找到绑定到当前用户/组的所有 Binding
│
▼
2. 对于每个匹配的 Binding,获取引用的 Role/ClusterRole
│
▼
3. 遍历 Role/ClusterRole 的 Rules
对每条 Rule 检查:
├─ Verbs 包含请求的 Verb(或 "*")
├─ Resources 包含请求的 Resource(或 "*")
├─ APIGroups 包含请求的 APIGroup(或 "*")
├─ Namespaces 匹配(Role 是命名空间内的)
└─ ResourceNames 包含请求的 Name(或为空=所有名称)
│
▼
4. 任何一条 Rule 匹配 → DecisionAllow
所有 Rule 都不匹配 → DecisionNoOpinion
RoleBinding vs ClusterRoleBinding:
- RoleBinding:在命名空间内生效,可以引用同命名空间的 Role 或任意 ClusterRole
- ClusterRoleBinding:集群范围生效,只能引用 ClusterRole
关键优化:RBAC 授权器使用 Informer 的 Listers 读取 Role/Binding 数据,这些数据缓存在内存中,不需要每次授权都查询 etcd。
3.4.4 Node 授权器
Node 授权器是专门为 kubelet 设计的特殊授权器,限制 kubelet 只能访问与自身节点相关的资源。
case modes.ModeNode:
node.RegisterMetrics()
graph := node.NewGraph()
node.AddGraphEventHandlers(
graph,
config.VersionedInformerFactory.Core().V1().Nodes(),
config.VersionedInformerFactory.Core().V1().Pods(),
config.VersionedInformerFactory.Storage().V1().PersistentVolumes(),
config.VersionedInformerFactory.Storage().V1().VolumeAttachments(),
)
nodeAuthorizer := node.NewAuthorizer(graph, nodeidentifier.NewDefaultNodeIdentifier(),
bootstrappolicy.NodeRules())
authorizers = append(authorizers, nodeAuthorizer)
ruleResolvers = append(ruleResolvers, nodeAuthorizer)
Node 授权器的核心是一个节点关系图(Graph),基于 Informer 实时维护:
Node Authorization Graph:
┌──────────────────────────────────────────────┐
│ Node "worker-1" │
│ ├── Pod "pod-a" (namespace: default) │
│ │ ├── Secret "secret-x" │
│ │ ├── ConfigMap "cm-y" │
│ │ └── PVC "pvc-z" │
│ ├── Pod "pod-b" (namespace: kube-system) │
│ │ └── Secret "secret-w" │
│ └── PV "pv-1" (mounted by pod-a) │
│ │
│ Node "worker-2" │
│ ├── Pod "pod-c" (namespace: default) │
│ └── ... │
└──────────────────────────────────────────────┘
Node 授权器的评估逻辑:
请求来自 kubelet(用户名 system:node:<nodename>)
│
▼
1. 确认请求者身份是 Node
nodeidentifier.IdentifyNode(user) → NodeName
│
▼
2. 检查请求的资源是否与该 Node 相关
├─ Pod:只允许 get/list/watch 自己节点上的 Pod
├─ Secret/ConfigMap:只允许 get 自己节点上 Pod 引用的
├─ PV:只允许 get 自己节点上 Pod 使用的
└─ 其他:根据 bootstrappolicy.NodeRules() 判断
│
▼
3. 匹配 NodeRules → DecisionAllow
不匹配 → DecisionNoOpinion(交给下一个授权器,通常是 RBAC)
Node 授权器的安全意义:在 Node 授权器出现之前,kubelet 使用 system:node 组的证书,通过 RBAC 获得对所有 Secret 的访问权限——这在多租户集群中是严重的安全问题。Node 授权器将 kubelet 的权限严格限制在其节点实际需要的资源上。
bootstrappolicy.NodeRules() 定义了 kubelet 的基本权限(如创建 Pod 的状态更新、报告节点状态等),这些权限与节点上运行的 Pod 无关。
3.4.5 ABAC 授权器
case modes.ModeABAC:
abacAuthorizer, err := abac.NewFromFile(config.PolicyFile)
ABAC(Attribute-Based Access Control)基于 JSON 策略文件:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"admin", "namespace":"*", "resource":"*", "apiGroup":"*", "readonly": true}}
ABAC 的缺点:
- 策略变更需要重启 apiserver
- 缺乏
kubectl原生管理支持 - 调试困难
目前 ABAC 已基本被 RBAC 取代,仅保留向后兼容。
3.4.6 Webhook 授权器
case modes.ModeWebhook:
webhookAuthorizer, err := webhook.New(
config.WebhookConfigFile,
config.WebhookVersion,
config.WebhookCacheAuthorizedTTL,
config.WebhookCacheUnauthorizedTTL,
*config.WebhookRetryBackoff,
config.CustomDial)
Webhook 授权将授权决策委托给外部 HTTP 服务:
授权请求
│
▼
1. 构造 SubjectAccessReview 对象
{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "default",
"verb": "get",
"group": "",
"resource": "pods",
"name": "my-pod"
},
"user": "alice",
"groups": ["system:authenticated"]
}
}
│
▼
2. POST 到 Webhook 服务
│
▼
3. 解析响应
{
"status": {
"allowed": true,
"reason": "RBAC: allowed by ClusterRoleBinding"
}
}
│
▼
4. allowed=true → DecisionAllow
allowed=false → DecisionDeny
无响应/错误 → DecisionNoOpinion + error
缓存设计:Webhook 授权器内置两层缓存:
WebhookCacheAuthorizedTTL:授权允许结果的缓存时间WebhookCacheUnauthorizedTTL:授权拒绝结果的缓存时间
重试机制:WebhookRetryBackoff 控制重试行为,防止 Webhook 服务短时故障导致授权雪崩。
3.4.7 Loopback Token Authorizer
tokenAuthorizer := authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup)
authz.Authorizer = authorizerunion.New(tokenAuthorizer, authz.Authorizer)
NewPrivilegedGroups 创建一个简单的授权器——如果请求用户的组包含 system:masters,直接返回 DecisionAllow。这个授权器被插入到授权器链的最前面,确保 apiserver 自身的请求永远不会被授权拒绝。
3.5 Impersonation 模拟机制
Impersonation 是 Kubernetes 的一个强大特性,允许已认证用户模拟另一个用户发出请求。这在 kubectl --as 和审计追踪中广泛使用。
3.5.1 WithImpersonation Filter 详解
func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.NegotiatedSerializer) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// 1. 从请求头构建模拟请求列表
impersonationRequests, err := buildImpersonationRequests(req.Header)
if err != nil {
responsewriters.InternalError(w, req, err)
return
}
// 2. 无模拟请求,直接通过
if len(impersonationRequests) == 0 {
handler.ServeHTTP(w, req)
return
}
// 3. 获取当前用户(已通过认证)
ctx := req.Context()
requestor, exists := request.UserFrom(ctx)
if !exists {
responsewriters.InternalError(w, req, errors.New("no user found for request"))
return
}
groupsSpecified := len(req.Header[authenticationv1.ImpersonateGroupHeader]) > 0
// 4. 逐项授权检查
username := ""
groups := []string{}
userExtra := map[string][]string{}
for _, impersonationRequest := range impersonationRequests {
gvk := impersonationRequest.GetObjectKind().GroupVersionKind()
actingAsAttributes := &authorizer.AttributesRecord{
User: requestor,
Verb: "impersonate",
APIGroup: gvk.Group,
APIVersion: gvk.Version,
Namespace: impersonationRequest.Namespace,
Name: impersonationRequest.Name,
ResourceRequest: true,
}
switch gvk.GroupKind() {
case v1.SchemeGroupVersion.WithKind("ServiceAccount").GroupKind():
actingAsAttributes.Resource = "serviceaccounts"
username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name)
if !groupsSpecified {
groups = serviceaccount.MakeGroupNames(impersonationRequest.Namespace)
}
case v1.SchemeGroupVersion.WithKind("User").GroupKind():
actingAsAttributes.Resource = "users"
username = impersonationRequest.Name
case v1.SchemeGroupVersion.WithKind("Group").GroupKind():
actingAsAttributes.Resource = "groups"
groups = append(groups, impersonationRequest.Name)
case authenticationv1.SchemeGroupVersion.WithKind("UserExtra").GroupKind():
actingAsAttributes.Resource = "userextras"
actingAsAttributes.Subresource = extraKey
userExtra[extraKey] = append(userExtra[extraKey], extraValue)
}
// 对每一项模拟请求进行授权检查
decision, reason, err := a.Authorize(ctx, actingAsAttributes)
if err != nil || decision != authorizer.DecisionAllow {
responsewriters.Forbidden(ctx, actingAsAttributes, w, req, reason, s)
return
}
}
// 5. 自动添加 system:authenticated 组
if username != user.Anonymous {
addAuthenticated := true
for _, group := range groups {
if group == user.AllAuthenticated || group == user.AllUnauthenticated {
addAuthenticated = false
break
}
}
if addAuthenticated {
groups = append(groups, user.AllAuthenticated)
}
}
// 6. 替换Context中的用户信息
newUser := &user.DefaultInfo{
Name: username,
Groups: groups,
Extra: userExtra,
}
req = req.WithContext(request.WithUser(ctx, newUser))
// 7. 审计记录
oldUser, _ := request.UserFrom(ctx)
httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser)
ae := request.AuditEventFrom(ctx)
audit.LogImpersonatedUser(ae, newUser)
// 8. 清除模拟头
req.Header.Del(authenticationv1.ImpersonateUserHeader)
req.Header.Del(authenticationv1.ImpersonateGroupHeader)
for headerName := range req.Header {
if strings.HasPrefix(headerName, authenticationv1.ImpersonateUserExtraHeaderPrefix) {
req.Header.Del(headerName)
}
}
handler.ServeHTTP(w, req)
})
}
3.5.2 模拟请求构建
buildImpersonationRequests 解析以下请求头:
| 请求头 | 说明 | 对应Resource |
|---|---|---|
Impersonate-User: alice |
模拟用户 | users / serviceaccounts |
Impersonate-Group: dev |
模拟组 | groups |
Impersonate-Extra-[key]: value |
模拟额外信息 | userextras |
用户名自动检测:如果格式为 system:serviceaccount:<ns>:<name>,识别为 ServiceAccount 模拟,否则为 User 模拟。
3.5.3 授权模型
每个模拟请求被映射为一个标准的授权检查:
- 模拟用户 alice:检查
verb=impersonate, resource=users, name=alice - 模拟SA:检查
verb=impersonate, resource=serviceaccounts, namespace=<ns>, name=<name> - 模拟组 dev:检查
verb=impersonate, resource=groups, name=dev - 模拟额外信息:检查
verb=impersonate, resource=userextras, subresource=<key>
这意味着可以用 RBAC 精细控制模拟权限——例如只允许模拟特定命名空间的 ServiceAccount:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: impersonate-dev-sa
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["impersonate"]
namespace: "dev"
3.5.4 安全措施
- 逐项授权:每个模拟维度(用户、组、Extra)都需要独立授权
- 头清除:模拟生效后,所有 Impersonation 头从请求中删除,防止传递到下游
- 审计记录:原始用户和模拟用户都记录在审计日志中
- 自动组添加:模拟非匿名用户自动加入
system:authenticated,除非请求头显式指定了system:unauthenticated
3.6 Audit 审计链路
3.6.1 WithAudit Filter 深度解析
审计Filter在请求的整个生命周期中记录多个阶段的事件:
StageRequestReceived ── 请求到达(在AuditFilter中记录)
│
▼
[后续Filter执行]
│
▼
StageResponseStarted ── 首次写入响应(WriteHeader/Write时记录)
│
▼
StageResponseComplete ── 响应完成(defer中记录)
│
▼
StagePanic ── 发生panic(recover中记录)
func WithAudit(handler http.Handler, sink audit.Sink, policy policy.Checker,
longRunningCheck request.LongRunningRequestCheck) http.Handler {
if sink == nil || policy == nil {
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// 1. 创建审计事件
req, ev, omitStages, err := createAuditEventAndAttachToContext(req, policy)
if err != nil {
responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
return
}
if ev == nil || ctx == nil {
handler.ServeHTTP(w, req)
return
}
// 2. 记录 StageRequestReceived
ev.Stage = auditinternal.StageRequestReceived
if processed := processAuditEvent(ctx, sink, ev, omitStages); !processed {
audit.ApiserverAuditDroppedCounter.WithContext(ctx).Inc()
responsewriters.InternalError(w, req, errors.New("failed to store audit event"))
return
}
// 3. 包装 ResponseWriter 以拦截响应
var longRunningSink audit.Sink
if longRunningCheck != nil {
ri, _ := request.RequestInfoFrom(ctx)
if longRunningCheck(req, ri) {
longRunningSink = sink
}
}
respWriter := decorateResponseWriter(ctx, w, ev, longRunningSink, omitStages)
// 4. defer记录最终审计事件
defer func() {
if r := recover(); r != nil {
defer panic(r)
ev.Stage = auditinternal.StagePanic
// ...
processAuditEvent(ctx, sink, ev, omitStages)
return
}
// 长运行请求的 StageResponseStarted
if ev.ResponseStatus == nil && longRunningSink != nil {
ev.Stage = auditinternal.StageResponseStarted
processAuditEvent(ctx, longRunningSink, ev, omitStages)
}
ev.Stage = auditinternal.StageResponseComplete
processAuditEvent(ctx, sink, ev, omitStages)
}()
handler.ServeHTTP(respWriter, req)
})
}
3.6.2 审计事件创建
func createAuditEventAndAttachToContext(req *http.Request, policy policy.Checker) (*http.Request, *auditinternal.Event, []auditinternal.Stage, error) {
ctx := req.Context()
// 获取授权属性(用于审计策略判断)
attribs, err := GetAuthorizerAttributes(ctx)
if err != nil {
return req, nil, nil, fmt.Errorf("failed to GetAuthorizerAttributes: %v", err)
}
// 查询审计策略级别
level, omitStages := policy.LevelAndStages(attribs)
audit.ObservePolicyLevel(ctx, level)
if level == auditinternal.LevelNone {
return req, nil, nil, nil // 无需审计
}
requestReceivedTimestamp, ok := request.ReceivedTimestampFrom(ctx)
if !ok {
requestReceivedTimestamp = time.Now()
}
ev, err := audit.NewEventFromRequest(req, requestReceivedTimestamp, level, attribs)
// ...
req = req.WithContext(request.WithAuditEvent(ctx, ev))
return req, ev, omitStages, nil
}
审计策略决定哪些请求需要审计以及审计级别:
| Level | 记录内容 |
|---|---|
| None | 不记录 |
| Metadata | 仅记录请求元数据(用户、动词、资源等),不记录请求/响应体 |
| Request | 记录元数据 + 请求体 |
| RequestResponse | 记录元数据 + 请求体 + 响应体 |
3.6.3 auditResponseWriter
auditResponseWriter 包装原始 ResponseWriter,拦截 WriteHeader 和 Write 调用:
- WriteHeader(code):记录 HTTP 状态码,触发
StageResponseStarted事件 - Write(bs):如果
WriteHeader尚未被调用,先记录200 OK - Hijack():记录
101 SwitchingProtocols(用于 exec/attach 等操作)
长运行请求的特殊处理:对于 watch、exec 等长运行请求,StageResponseStarted 事件使用独立的 longRunningSink,确保长时间运行的请求不会阻塞审计后端。
3.6.4 WithAuditAnnotations
handler = genericapifilters.WithAuditAnnotations(handler, c.AuditBackend, c.AuditPolicyChecker)
AuditAnnotations Filter 允许后续 Handler 通过 Context 添加键值对注解到审计事件中。例如准入控制器可以添加 decision 和 reason 注解。
3.6.5 WithFailedAuthenticationAudit
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyChecker)
认证失败请求也记录审计日志。这是安全审计的关键——记录谁尝试了未授权的访问。
3.6.6 审计后端
审计后端(AuditBackend)负责将审计事件持久化。支持的后端包括:
- Log Backend:写入日志文件
- Webhook Backend:发送到外部 HTTP 服务
四、Mermaid 图表
图1:请求处理 Handler Chain 总览
图2:认证完整流程
图3:Authenticator Union 组合图
图4:X509 客户端证书认证流程
图5:Token 认证流程(Bearer Token 完整链路)
图6:OIDC 认证流程
图7:授权完整流程
图8:Authorizer Union 组合图
图9:RBAC 授权流程
图10:Node 授权流程
图11:Webhook 授权流程
图12:Impersonation 模拟流程
图13:Audit 审计链路图
图14:RequestInfo 解析图
图15:Filter Chain 执行时序图
五、总结
5.1 请求处理链路的设计精髓
-
Filter顺序的严谨性:每个Filter的位置都有充分的安全和语义理由。RequestInfo必须在认证之前(后续Filter需要请求属性),认证必须在模拟之前(模拟需要知道原始用户),模拟必须在授权之前(授权基于最终身份),流量控制在授权之前(防止DoS绕过限流)。
-
Union模式的灵活组合:认证和授权都采用Union模式,允许任意组合多种机制。认证Union是"第一个成功即返回",授权Union是"Allow/Deny短路,NoOpinion继续"。
-
Context作为数据总线:整个链路通过Go Context传递数据——RequestInfo、user.Info、AuditEvent、Audiences等。Context的不可变性(每次WithXxx返回新的Context)确保了并发安全。
-
审计的全生命周期覆盖:审计Filter包装ResponseWriter,从请求到达(StageRequestReceived)到响应完成(StageResponseComplete),包括panic场景,确保审计记录的完整性。
-
Impersonation的安全设计:逐项授权、头清除、自动组添加——每个设计决策都在安全性上有所考量。
-
优雅退出的WaitGroup:HandlerChainWaitGroup确保apiserver关闭时等待所有正在处理的请求完成,实现零宕机升级。
-
延迟可观测性:filterlatency包裹每个关键Filter,为性能诊断提供数据支持。
5.2 关键代码路径总结
| 代码路径 | 文件 | 核心函数 |
|---|---|---|
| Handler Chain构建 | server/config.go |
DefaultBuildHandlerChain |
| 认证Filter | endpoints/filters/authentication.go |
WithAuthentication |
| 授权Filter | endpoints/filters/authorization.go |
WithAuthorization |
| 请求信息解析 | endpoints/filters/requestinfo.go |
WithRequestInfo |
| 模拟Filter | endpoints/filters/impersonation.go |
WithImpersonation |
| 审计Filter | endpoints/filters/audit.go |
WithAudit |
| 认证器Union | authentication/request/union/union.go |
New / AuthenticateRequest |
| 授权器Union | authorization/union/union.go |
New / Authorize |
| 认证器构建 | kubeapiserver/authenticator/config.go |
Config.New |
| 授权器构建 | kubeapiserver/authorizer/config.go |
Config.New |
| RequestInfo解析 | endpoints/request/requestinfo.go |
RequestInfoFactory.NewRequestInfo |
| Loopback Token | server/config.go |
AuthorizeClientBearerToken |
更多推荐


所有评论(0)