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。这种设计带来三个关键特性:

  1. 洋葱模型:请求从外层 Filter 逐层穿透到核心业务 Handler,响应从内层逐层返回。外层先执行"前处理",内层先执行"后处理"。
  2. 顺序敏感:Filter 的注册顺序决定了执行顺序,且对语义正确性至关重要。例如认证必须在授权之前,RequestInfo 解析必须在认证之前。
  3. 可组合可替换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 在流量控制之后但在认证之后之前——这确保了:

  1. 请求已经通过了流量控制(不会因为模拟逻辑引入额外开销导致限流失效)
  2. 认证已经确定了原始请求者身份(Impersonation 需要知道"谁在模拟谁")

但等等——在代码中,Impersonation 在 Authentication 之前(外层)?不对,让我重新审视顺序。代码书写是从内到外的,所以执行顺序(从外到内)是:

  1. WithPanicRecovery(最外层)
  2. WithRequestReceivedTimestamp
  3. WithHSTS
  4. WithCacheControl
  5. WithWarningRecorder
  6. WithAuditAnnotations
  7. WithProbabilisticGoaway(条件)
  8. WithRequestInfo
  9. WithWaitGroup
  10. WithRequestDeadline
  11. WithTimeoutForNonLongRunningRequests
  12. WithCORS
  13. WithAuthentication
  14. WithAudit
  15. WithImpersonation
  16. WithPriorityAndFairness/WithMaxInFlightLimit
  17. WithAuthorization(最内层业务Filter)

等等,我需要再仔细看代码。代码从上到下是先处理 apiHandler → WithAuthorization → FlowControl → WithImpersonation → WithAudit → WithAuthentication → … → WithPanicRecovery。因为每一步 handler = XXX(handler, ...) 是包装前一个 handler,所以最后包装的最先执行。

所以实际请求执行顺序(从外层到内层)是:

  1. WithPanicRecovery ← 最先执行(最后包装)
  2. WithRequestReceivedTimestamp
  3. WithHSTS
  4. WithCacheControl
  5. WithWarningRecorder
  6. WithAuditAnnotations
  7. WithProbabilisticGoaway
  8. WithRequestInfo
  9. WithWaitGroup
  10. WithRequestDeadline
  11. WithTimeoutForNonLongRunningRequests
  12. WithCORS
  13. WithAuthentication ← 认证
  14. WithAudit ← 审计
  15. WithImpersonation ← 模拟
  16. WithPriorityAndFairness ← 流量控制
  17. WithAuthorization ← 授权
  18. 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 的构建——认证失败时走专门的失败处理链:

  1. Unauthorized() 返回 401 响应
  2. WithFailedAuthenticationAudit 对失败请求也记录审计日志
  3. 延迟追踪也覆盖了失败路径

认证成功后:

  • 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或503
  • WithRequestDeadline:设置请求级别的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)
    })
}

关键设计决策

  1. Audience 机制:API Audiences 是 Kubernetes 认证系统的一个安全特性。Token 可能是为不同受众(audience)签发的,例如一个 ServiceAccount Token 可能是为外部系统签发的,apiserver 只应接受为自己签发的 Token。audiencesAreAcceptable 检查认证器返回的 audiences 与 apiserver 期望的 audiences 是否有交集。

  2. Authorization 头删除:一旦认证成功,立即删除 Authorization 头,防止下游Handler意外泄露凭据。

  3. 错误 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.InfoRequestInfo 组装为 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.goNew() 方法中构建:

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 认证子链(按优先级):

  1. Static Token File:从 CSV 文件加载的静态 Token
  2. ServiceAccount (Legacy):使用 Legacy Issuer 签发的 SA Token
  3. ServiceAccount (JWT):使用配置的 Issuer 签发的 SA Token
  4. Bootstrap Token:节点引导 Token(kubeadm 使用)
  5. OIDC:OpenID Connect Token
  6. Webhook Token:远程 Webhook 验证

Token 缓存层tokencache.New 对 Token 认证结果进行缓存,避免每次请求都做完整的 Token 验证(特别是 OIDC 和 Webhook 等远程验证),显著提升性能。成功和失败结果分别有不同的 TTL。

Bearer Token 提取器bearertoken.NewAuthorization: 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 设计哲学

  • 默认情况下,非 email claim 的用户名会被加 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:

  1. 每次启动生成 UUID 作为 UID
  2. 用户为 system:apiserver,组为 system:masters(超级用户组)
  3. 在认证器链的最前面插入(优先级最高)
  4. 在授权器链的最前面插入一个特权授权器——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 安全措施
  1. 逐项授权:每个模拟维度(用户、组、Extra)都需要独立授权
  2. 头清除:模拟生效后,所有 Impersonation 头从请求中删除,防止传递到下游
  3. 审计记录:原始用户和模拟用户都记录在审计日志中
  4. 自动组添加:模拟非匿名用户自动加入 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,拦截 WriteHeaderWrite 调用:

  • 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 添加键值对注解到审计事件中。例如准入控制器可以添加 decisionreason 注解。

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 总览

Handler Chain (从外到内执行)

HTTP Request

Incoming HTTP Request

WithPanicRecovery
最外层: panic恢复

WithRequestReceivedTimestamp
记录到达时间戳

WithHSTS
安全传输头

WithCacheControl
缓存控制

WithWarningRecorder
Warning收集

WithAuditAnnotations
审计注解收集

WithProbabilisticGoaway
HTTP/2重平衡

WithRequestInfo
URL→RequestInfo解析

WithWaitGroup
优雅退出等待

WithRequestDeadline
请求deadline

WithTimeoutForNonLongRunning
非长运行超时

WithCORS
跨域处理

WithAuthentication
身份认证

WithAudit
审计日志

WithImpersonation
身份模拟

WithPriorityAndFairness
流量控制/限流

WithAuthorization
权限授权

Core API Handler
Director/GoRestful

图2:认证完整流程

ok=true, err=nil

ok=false, err=nil

err!=nil

交集非空

交集为空

请求到达

WithAuthentication Filter

API Audiences
是否配置?

注入Audiences到Context

调用 Authenticator.AuthenticateRequest

认证结果

Audience校验
apiAuds ∩ resp.Audiences

认证失败: 无效凭据

认证错误: 系统故障

认证成功

Audience不匹配

删除Authorization头

注入user.Info到Context

继续下一个Filter

failedHandler
401 Unauthorized

WithFailedAuthenticationAudit
记录审计日志

返回401响应

图3:Authenticator Union 组合图

HTTP Request

unionAuthRequestHandler
Authenticator Union

1. Loopback Token
system:apiserver

2. RequestHeader
Front-Proxy X-Remote-*

3. X509 Client Cert
mTLS CN→User

Bearer Token Wrapper

Token Union
tokenunion.New

Static Token File
CSV文件

ServiceAccount Legacy
Legacy Issuer JWT

ServiceAccount JWT
新Issuer JWT

Bootstrap Token
kubeadm引导

OIDC
OpenID Connect

Webhook Token
远程TokenReview

WebSocket Protocol
子协议Token

group.NewAuthenticatedGroupAdder
+system:authenticated

anonymous.NewAuthenticator
FailOnError包装

认证结果

图4:X509 客户端证书认证流程

无效

有效

TLS 握手完成

提取客户端证书
req.TLS.PeerCertificates

是否存在
客户端证书?

nil, false, nil
不匹配, 不是错误

使用 ClientCA 验证证书链
dynamiccertificates.VerifyOptions

证书是否有效?

提取 Subject.CommonName
和 Organization

CommonNameUserConversion
CN → Username
Org → Groups

user.DefaultInfo
Name: CN
Groups: [Org1, Org2...]

返回认证成功
Response{User: info}

交给下一个认证器

图5:Token 认证流程(Bearer Token 完整链路)

命中

未命中

匹配

不匹配

匹配

不匹配

匹配

不匹配

匹配

不匹配

匹配

不匹配

匹配

不匹配

Authorization: Bearer <token>

bearertoken.New
提取Token字符串

Token缓存
命中?

返回缓存结果

Token Union Chain

Static Token File
内存查找

返回user.Info

SA Legacy JWT
签名验证+Issuer检查

返回user.Info

SA JWT 新模式
完整JWT验证

返回user.Info

Bootstrap Token
Secret查找

返回user.Info

OIDC
JWT签名+JWKS+Claims

返回user.Info

Webhook
TokenReview远程调用

返回user.Info

全部不匹配
返回nil, false, aggregateErr

缓存结果

返回认证成功

图6:OIDC 认证流程

Bearer Token = JWT

解析 JWT Header
提取 kid

JWKS缓存
有kid对应的公钥?

验证 JWT 签名

获取 JWKS
GET issuer/.well-known/openid-configuration
GET issuer/keys

更新JWKS缓存

签名有效?

认证失败

验证 Claims

iss == OIDCIssuerURL?

aud 包含 OIDCClientID?

exp > now?

RequiredClaims
全部匹配?

提取用户信息
UsernameClaim → Name
GroupsClaim → Groups
添加前缀

构建 user.DefaultInfo
Name: prefix + claim
Groups: groupsPrefix + groups

认证成功

图7:授权完整流程

DecisionAllow

DecisionDeny

DecisionNoOpinion

DecisionAllow

DecisionDeny

DecisionNoOpinion

DecisionAllow

DecisionDeny

DecisionNoOpinion

请求到达 WithAuthorization

GetAuthorizerAttributes
从Context构建属性

AttributesRecord
User + Verb + Resource
+ Namespace + Name + APIGroup

调用 Authorizer.Authorize
ctx, attributes

Loopback Token
Authorizer
system:masters?

DecisionAllow
短路允许

Node Authorizer
请求者是kubelet?

DecisionDeny
短路拒绝

RBAC Authorizer
Role/ClusterRole匹配?

Webhook Authorizer
SubjectAccessReview?

所有授权器无意见
= 隐式拒绝

审计: decision=allow
reason=...

继续下一个Handler

审计: decision=forbid
reason=...

返回403 Forbidden

图8:Authorizer Union 组合图

DecisionAllow

DecisionDeny

DecisionNoOpinion

authorizer.Attributes

unionAuthzHandler
Authorizer Union

1. Loopback Token Authorizer
NewPrivilegedGroups
system:masters → Allow

2. Node Authorizer
节点关系图匹配

3. RBAC Authorizer
Role/ClusterRole匹配

4. Webhook Authorizer
SubjectAccessReview

5. ABAC Authorizer
JSON策略文件

决策结果

短路: 允许

短路: 拒绝

继续下一个授权器

图9:RBAC 授权流程

无更多Binding

无更多Rule

RBAC Authorize

获取用户身份
user.GetName, user.GetGroups

查找匹配的 Bindings
RoleBindings + ClusterRoleBindings

遍历每个 Binding

Subject 匹配?

下一个 Binding

获取引用的 Role/ClusterRole

遍历 Role 的 Rules

Verb 匹配?
verbs contains verb or '*'

下一条 Rule

APIGroup 匹配?

Resource 匹配?

Namespace 匹配?
RoleBinding=ns内
ClusterRoleBinding=集群级

ResourceName 匹配?
空=所有名称

DecisionAllow

DecisionNoOpinion

图10:Node 授权流程

Pod

Secret

ConfigMap

PV

Node

其他

Node Authorizer

IdentifyNode
用户名 → NodeName

请求者是
system:node:*?

DecisionNoOpinion
交给下一个授权器

查询节点关系图

请求资源类型

只允许 get/list/watch
本节点上的 Pod

只允许 get
本节点Pod引用的Secret

只允许 get
本节点Pod引用的ConfigMap

只允许 get
本节点Pod使用的PV

允许 get/update/status
自身Node对象

检查 NodeRules
bootstrappolicy

图中存在
对应关系?

请求的Node
== 自身?

匹配
NodeRules?

DecisionAllow

图11:Webhook 授权流程

命中: allowed

命中: denied

未命中

否, 重试耗尽

否, 成功

true

false

无响应/解析失败

Webhook Authorizer

缓存
命中?

DecisionAllow

DecisionDeny

构造 SubjectAccessReview

SubjectAccessReview
spec.user + spec.groups
spec.resourceAttributes
或 spec.nonResourceAttributes

POST 到 Webhook 服务
使用kubeconfig配置

请求失败?

指数退避重试
WebhookRetryBackoff

返回 error

解析响应

status.allowed?

缓存: allowed

缓存: denied

DecisionNoOpinion + error

图12:Impersonation 模拟流程

User

ServiceAccount

Group

UserExtra

请求到达 WithImpersonation

buildImpersonationRequests
解析模拟请求头

有模拟请求?

直接通过
handler.ServeHTTP

获取当前用户
request.UserFrom ctx

遍历每个 impersonationRequest

模拟类型

构建授权属性
verb=impersonate
resource=users
name=目标用户名

构建授权属性
verb=impersonate
resource=serviceaccounts
namespace+name

构建授权属性
verb=impersonate
resource=groups
name=目标组名

构建授权属性
verb=impersonate
resource=userextras
subresource=extraKey

Authorizer.Authorize
检查模拟权限

DecisionAllow?

403 Forbidden
无权模拟

还有更多
模拟请求?

构建新用户
DefaultInfo
Name+Groups+Extra

自动添加
system:authenticated组
非匿名用户

替换Context中的用户
request.WithUser

审计记录
LogImpersonatedUser

清除模拟请求头
防止传递到下游

继续下一个Filter

图13:Audit 审计链路图

LevelNone

Metadata/Request/RequestResponse

是, 无StageResponseStarted

请求到达 WithAudit

createAuditEventAndAttachToContext
创建审计事件

GetAuthorizerAttributes
获取授权属性

policy.LevelAndStages
查询审计策略

审计级别

不审计
直接通过

audit.NewEventFromRequest
创建Event对象

审计Event注入Context

StageRequestReceived
记录请求到达
processAuditEvent → Sink

decorateResponseWriter
包装ResponseWriter

执行后续Handler

响应写入

WriteHeader code
记录ResponseStatus.Code

Write bytes
自动触发200 if未WriteHeader

StageResponseStarted
processAuditEvent → Sink
设置Audit-ID头

defer: 函数退出

长运行请求
watch/exec?

伪造 StageResponseStarted
Code=200

StageResponseComplete
processAuditEvent → Sink

发生Panic?

StagePanic
ResponseStatus=500
processAuditEvent → Sink
重新panic

图14:RequestInfo 解析图

渲染错误: Mermaid 渲染失败: Parse error on line 11: ...ECK_GROUP -->|否, 如 "apis"| EXTRACT_GROUP -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'STR'

图15:Filter Chain 执行时序图

Core API Handler WithAuthorization WithPriorityAndFairness WithImpersonation WithAudit WithAuthentication WithRequestInfo WithRequestReceivedTimestamp WithPanicRecovery Client Core API Handler WithAuthorization WithPriorityAndFairness WithImpersonation WithAudit WithAuthentication WithRequestInfo WithRequestReceivedTimestamp WithPanicRecovery Client 记录 receivedTimestamp 解析URL → RequestInfo 注入Context AuthenticateRequest() 成功: user.Info → Context 删除Authorization头 失败: 401 创建AuditEvent StageRequestReceived 包装ResponseWriter 解析Impersonate-*头 逐项授权检查 替换Context中的User 优先级队列调度 排队等待/直接执行 GetAuthorizerAttributes Authorizer.Authorize Allow: 继续 Deny: 403 StageResponseStarted (WriteHeader时) StageResponseComplete (defer退出时) 审计事件发送到 Sink: 1. StageRequestReceived 2. StageResponseStarted 3. StageResponseComplete HTTP Request 传递请求 传递请求 传递请求 传递请求(认证成功) 传递请求 传递请求 传递请求 传递请求(授权通过) HTTP Response Response Response Response Response Response Response Response HTTP Response

五、总结

5.1 请求处理链路的设计精髓

  1. Filter顺序的严谨性:每个Filter的位置都有充分的安全和语义理由。RequestInfo必须在认证之前(后续Filter需要请求属性),认证必须在模拟之前(模拟需要知道原始用户),模拟必须在授权之前(授权基于最终身份),流量控制在授权之前(防止DoS绕过限流)。

  2. Union模式的灵活组合:认证和授权都采用Union模式,允许任意组合多种机制。认证Union是"第一个成功即返回",授权Union是"Allow/Deny短路,NoOpinion继续"。

  3. Context作为数据总线:整个链路通过Go Context传递数据——RequestInfo、user.Info、AuditEvent、Audiences等。Context的不可变性(每次WithXxx返回新的Context)确保了并发安全。

  4. 审计的全生命周期覆盖:审计Filter包装ResponseWriter,从请求到达(StageRequestReceived)到响应完成(StageResponseComplete),包括panic场景,确保审计记录的完整性。

  5. Impersonation的安全设计:逐项授权、头清除、自动组添加——每个设计决策都在安全性上有所考量。

  6. 优雅退出的WaitGroup:HandlerChainWaitGroup确保apiserver关闭时等待所有正在处理的请求完成,实现零宕机升级。

  7. 延迟可观测性: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
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐