深入kube-apiserver审计机制:从策略配置到事件记录的全流程解析
2026/6/17 22:10:18 网站建设 项目流程

前言

去年处理过一次安全事件:有人删除了生产环境的核心ConfigMap,导致服务中断。事后排查时,我们无法确定是谁在什么时间执行了删除操作——因为apiserver的审计日志没有开启。

这次事件让我深刻认识到审计(Audit)的重要性。审计日志是K8s安全运营的基石,它记录了谁在什么时间做了什么操作,是事后追溯和安全分析的关键证据。

今天就带大家深入源码,看看kube-apiserver的审计机制是如何实现的。

什么是审计?

K8s审计功能提供了按时间顺序排列的安全相关记录集,记录了:

  • 每个用户对API的操作
  • 使用K8s API的应用的行为
  • 控制面自身的活动

审计能回答的问题

问题审计日志字段
发生了什么?verb, resource, name
什么时候发生的?timestamp
谁触发的?user, groups
发生在哪个对象上?namespace, name, resource
从哪触发的?sourceIPs
后续处理行为是什么?responseStatus, stage

审计架构概览

用户请求 │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ HTTP Handler Chain │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ WithAudit Filter │ │ │ │ │ │ │ │ 1. 创建审计事件 │ │ │ │ 2. 根据策略评估审计级别 │ │ │ │ 3. 记录请求接收(StageRequestReceived) │ │ │ │ 4. 包装ResponseWriter │ │ │ │ 5. 记录响应开始(StageResponseStarted) │ │ │ │ 6. 记录响应完成(StageResponseComplete) │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Audit Backend │ │ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ Log Backend │ │ Webhook Backend │ │ │ │ │ │ │ │ │ │ --audit-log-path │ │ --audit-webhook- │ │ │ │ │ │ config-file │ │ │ └─────────────────────┘ └─────────────────────┘ │ │ │ │ │ │ └──────────┬───────────────┘ │ │ ▼ │ │ Union Backend │ └─────────────────────────────────────────────────────────────────┘

审计策略的4个级别

审计策略定义了记录什么内容,有4个级别:

级别说明适用场景
None不记录高频、不敏感的操作(如健康检查)
Metadata只记录元数据(用户、时间、资源、动词等),不记录请求/响应体一般操作记录
Request记录元数据和请求体,不记录响应体需要知道改了什么
RequestResponse记录元数据、请求体和响应体完整的操作审计

策略配置示例

# audit-policy.yamlapiVersion:audit.k8s.io/v1kind:Policyrules:# 不记录健康检查-level:NonenonResourceURLs:-/healthz-/livez-/readyz# 记录ConfigMap的变更,包含请求体-level:Requestresources:-group:""resources:["configmaps"]verbs:["create","update","patch","delete"]# 记录Secret的所有操作,包含请求和响应-level:RequestResponseresources:-group:""resources:["secrets"]# 默认只记录元数据-level:Metadata

源码解析:审计的初始化

初始化入口

审计的初始化在buildGenericConfig中完成:

// cmd/kube-apiserver/app/server.gofuncbuildGenericConfig(s*options.ServerRunOptions,...)(genericConfig,...){// ... 其他配置// 应用审计配置iflastErr=s.Audit.ApplyTo(genericConfig);lastErr!=nil{return}}

ApplyTo方法:构建审计后端

// pkg/kubeapiserver/options/audit.gofunc(o*AuditOptions)ApplyTo(c*server.Config)error{// 1. 构建策略评估器(根据audit-policy-file)evaluator,err:=o.newPolicyRuleEvaluator()iferr!=nil{returnerr}// 2. 构建日志后端(--audit-log-path)varlogBackend audit.Backend w,err:=o.LogOptions.getWriter()iferr!=nil{returnerr}ifw!=nil{ifevaluator==nil{klog.V(2).Info("No audit policy file provided, no events will be recorded for log backend")}else{logBackend=o.LogOptions.newBackend(w)}}// 3. 构建webhook后端(--audit-webhook-config-file)varwebhookBackend audit.Backendifo.WebhookOptions.enabled(){ifevaluator==nil{klog.V(2).Info("No audit policy file provided, no events will be recorded for webhook backend")}else{webhookBackend,err=o.WebhookOptions.newUntruncatedBackend(egressDialer)iferr!=nil{returnerr}}}// 4. 封装为动态后端(支持截断)vardynamicBackend audit.BackendifwebhookBackend!=nil{dynamicBackend=o.WebhookOptions.TruncateOptions.wrapBackend(webhookBackend,groupVersion)}// 5. 设置策略评估器c.AuditPolicyRuleEvaluator=evaluator// 6. 合并所有后端c.AuditBackend=appendBackend(logBackend,dynamicBackend)returnnil}

审计后端详解

1. 日志后端(Log Backend)

日志后端将审计事件写入本地文件。

// staging/src/k8s.io/apiserver/plugin/pkg/audit/log/backend.gotypebackendstruct{out io.Writer// 输出流formatstring// 格式:legacy或jsonencoder runtime.Encoder}// 创建日志后端func(o*AuditLogOptions)newBackend(w io.Writer)audit.Backend{return&backend{out:w,format:o.Format,}}// 处理审计事件func(b*backend)ProcessEvents(events...*auditinternal.Event)bool{success:=truefor_,ev:=rangeevents{success=b.logEvent(ev)&&success}returnsuccess}func(b*backend)logEvent(ev*auditinternal.Event)bool{line:=""switchb.format{caseFormatLegacy:line=audit.EventString(ev)+"\n"caseFormatJson:bs,err:=runtime.Encode(b.encoder,ev)iferr!=nil{audit.HandlePluginError(PluginName,err,ev)returnfalse}line=string(bs[:])}// 写入日志if_,err:=fmt.Fprint(b.out,line);err!=nil{audit.HandlePluginError(PluginName,err,ev)returnfalse}returntrue}

日志轮转:使用lumberjack库实现自动轮转

import"gopkg.in/natefinch/lumberjack.v2"return&lumberjack.Logger{Filename:o.Path,// 日志文件路径MaxAge:o.MaxAge,// 最大保留天数MaxBackups:o.MaxBackups,// 最大备份数MaxSize:o.MaxSize,// 单个文件最大大小(MB)Compress:o.Compress,// 是否压缩},nil

2. Webhook后端

Webhook后端将审计事件发送到远程HTTP服务。

// staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.gofunc(o*AuditWebhookOptions)newUntruncatedBackend(egressDialer utilnet.DialFunc)(audit.Backend,error){// 创建REST客户端webhookClient,err:=o.webhookClient(egressDialer)iferr!=nil{returnnil,err}return&backend{webhookClient:webhookClient,},nil}// 发送审计事件到webhookfunc(b*backend)ProcessEvents(events...*auditinternal.Event)bool{success:=truefor_,ev:=rangeevents{success=b.sendEvent(ev)&&success}returnsuccess}func(b*backend)sendEvent(ev*auditinternal.Event)bool{// 发送HTTP POST请求result:=b.webhookClient.Create()iferr:=result.Error();err!=nil{audit.HandlePluginError(PluginName,err,ev)returnfalse}returntrue}

Webhook配置

# webhook-config.yamlapiVersion:v1kind:Configclusters:-name:audit-servercluster:certificate-authority:/path/to/ca.crtserver:https://audit.example.com/webhookusers:-name:apiserveruser:client-certificate:/path/to/client.crtclient-key:/path/to/client.keycurrent-context:webhookcontexts:-context:cluster:audit-serveruser:apiservername:webhook

3. Union后端:多后端组合

// staging/src/k8s.io/apiserver/pkg/audit/union.go// Union将多个后端组合成一个typeunionBackendstruct{backends[]audit.Backend}funcUnion(backends...audit.Backend)audit.Backend{returnunionBackend{backends:backends}}func(u unionBackend)ProcessEvents(events...*auditinternal.Event)bool{success:=truefor_,backend:=rangeu.backends{success=backend.ProcessEvents(events...)&&success}returnsuccess}

HTTP处理链中的审计

审计是在HTTP handler chain中通过WithAudit中间件实现的。

WithAudit中间件

// staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit.gofuncWithAudit(handler http.Handler,sink audit.Sink,policy audit.PolicyRuleEvaluator,)http.Handler{ifsink==nil||policy==nil{returnhandler}returnhttp.HandlerFunc(func(w http.ResponseWriter,req*http.Request){// 1. 创建审计事件并附加到contextreq,ev,omitStages,err:=createAuditEventAndAttachToContext(req,policy)iferr!=nil{responsewriters.InternalError(w,req,errors.New("failed to create audit event"))return}// 2. 如果没有事件(策略为None),直接处理ifev==nil{handler.ServeHTTP(w,req)return}ctx:=req.Context()// 3. 记录请求接收阶段ev.Stage=auditinternal.StageRequestReceivedprocessAuditEvent(ctx,sink,ev,omitStages)// 4. 包装ResponseWriter以拦截响应respWriter:=decorateResponseWriter(ctx,w,ev,sink,omitStages)// 5. 使用defer确保响应完成阶段被记录deferfunc(){ifr:=recover();r!=nil{// 记录panicev.Stage=auditinternal.StagePanic ev.ResponseStatus=&metav1.Status{Code:http.StatusInternalServerError,Status:metav1.StatusFailure,}processAuditEvent(ctx,sink,ev,omitStages)panic(r)}// 记录响应完成ev.Stage=auditinternal.StageResponseCompleteprocessAuditEvent(ctx,sink,ev,omitStages)}()// 6. 处理请求handler.ServeHTTP(respWriter,req)})}

审计事件的3个阶段

请求处理时间线 ─────────────────────────────────────────────────────────────► │ │ │ │ │ │ ▼ ▼ ▼ RequestReceived ResponseStarted ResponseComplete (请求接收) (响应开始) (响应完成) │────────────────────│─────────────────────│ 长运行请求 响应发送
阶段触发时机记录内容
RequestReceived收到请求请求元数据、请求体(根据策略)
ResponseStarted开始发送响应响应头、状态码(长运行请求)
ResponseComplete响应发送完成完整的响应信息

创建审计事件

funccreateAuditEventAndAttachToContext(req*http.Request,policy audit.PolicyRuleEvaluator,)(*http.Request,*auditinternal.Event,[]auditinternal.Stage,error){// 获取请求信息ctx:=req.Context()attribs,err:=GetAuthorizerAttributes(ctx)// 评估审计级别level,omitStages:=policy.LevelAndStages(attribs)iflevel==auditinternal.LevelNone{returnreq,nil,nil,nil// 不记录}// 创建审计事件ev:=&auditinternal.Event{Timestamp:metav1.NowMicro(),AuditID:types.UID(uuid.New().String()),Level:level,Verb:attribs.GetVerb(),RequestURI:req.URL.RequestURI(),User:attribs.GetUser(),SourceIPs:sourceIPs(req),ObjectRef:objectRef(attribs),}// 根据级别记录请求体iflevel>=auditinternal.LevelRequest{ev.RequestObject=recordRequestObject(req,level)}// 将事件附加到contextctx=audit.WithAuditContext(ctx,ev)req=req.WithContext(ctx)returnreq,ev,omitStages,nil}

配置审计

基本配置

kube-apiserver\--audit-policy-file=/etc/kubernetes/audit-policy.yaml\--audit-log-path=/var/log/kubernetes/audit.log\--audit-log-format=json\--audit-log-maxsize=100\--audit-log-maxbackup=10\--audit-log-maxage=30

高级配置:Webhook

kube-apiserver\--audit-policy-file=/etc/kubernetes/audit-policy.yaml\--audit-webhook-config-file=/etc/kubernetes/audit-webhook.yaml\--audit-webhook-mode=batch\--audit-webhook-batch-max-size=100\--audit-webhook-batch-max-wait=1s

Webhook模式

  • blocking:同步发送,可能影响API响应时间
  • batching:批量异步发送,性能更好

审计日志分析

日志示例

{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Request","auditID":"c5d4e6f7-a8b9-4c0d-1e2f-3a4b5c6d7e8f","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/default/pods/nginx","verb":"create","user":{"username":"admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["192.168.1.100"],"objectRef":{"resource":"pods","namespace":"default","name":"nginx"},"responseStatus":{"code":201},"requestObject":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"nginx","namespace":"default"},"spec":{"containers":[{"name":"nginx","image":"nginx:1.19"}]}},"timestamp":"2024-01-15T10:30:00.123456Z"}

实用查询

# 查找删除操作jq'select(.verb == "delete")'/var/log/kubernetes/audit.log# 查找特定用户的操作jq'select(.user.username == "admin")'/var/log/kubernetes/audit.log# 查找失败的操作jq'select(.responseStatus.code >= 400)'/var/log/kubernetes/audit.log# 统计各用户的操作次数jq-r'.user.username'/var/log/kubernetes/audit.log|sort|uniq-c|sort-rn

踩坑实录:审计常见问题

坑1:审计日志文件过大

现象:磁盘被审计日志占满

解决方案

# 配置日志轮转和压缩kube-apiserver\--audit-log-maxsize=100\# 单个文件100MB--audit-log-maxbackup=10\# 保留10个备份--audit-log-maxage=30\# 保留30天--audit-log-compress=true# 压缩备份

坑2:审计影响性能

现象:开启审计后API响应变慢

解决方案

# 1. 使用较宽松的策略# 对高频读操作使用Metadata级别# 2. 使用Webhook batch模式--audit-webhook-mode=batch --audit-webhook-batch-max-size=100--audit-webhook-batch-max-wait=1s# 3. 异步后端--audit-log-mode=async

坑3:审计事件丢失

现象:高负载时部分审计事件没有记录

根因:后端处理不过来,事件被丢弃

解决方案

# 增加缓冲区大小--audit-webhook-truncate-max-batch-size=10000--audit-webhook-truncate-max-event-size=102400

坑4:敏感信息泄露

现象:审计日志中包含Secret的明文内容

解决方案

# 对Secret使用Metadata级别apiVersion:audit.k8s.io/v1kind:Policyrules:-level:Metadataresources:-group:""resources:["secrets"]

审计最佳实践

1. 分层审计策略

apiVersion:audit.k8s.io/v1kind:Policyrules:# 不记录系统组件的读操作-level:Noneusers:["system:kube-proxy","system:kubelet"]verbs:["get","list","watch"]# 详细记录敏感资源-level:RequestResponseresources:-group:"rbac.authorization.k8s.io"resources:["roles","rolebindings","clusterroles","clusterrolebindings"]# 记录默认-level:Metadata

2. 集中化审计日志

# 使用Webhook将审计日志发送到集中存储kube-apiserver\--audit-webhook-config-file=/etc/kubernetes/audit-webhook.yaml\--audit-webhook-mode=batch

3. 定期审计分析

# 查找异常操作jq'select(.responseStatus.code >= 400)'audit.log|jq-s'group_by(.user.username) | map({user: .[0].user.username, count: length})'# 查找权限提升操作jq'select(.verb == "create" or .verb == "update") | select(.objectRef.resource | contains("role"))'audit.log

总结

通过今天的分析,我们深入理解了kube-apiserver的审计机制:

  1. 审计策略:4个级别(None/Metadata/Request/RequestResponse)
  2. 审计后端:Log Backend(本地文件)和Webhook Backend(远程服务)
  3. HTTP处理链:WithAudit中间件拦截请求,记录3个阶段
  4. 事件结构:包含用户、时间、资源、请求/响应等信息
  5. 最佳实践:分层策略、日志轮转、集中化存储

审计是K8s安全运营的基础设施,正确配置审计对事后追溯和合规非常重要。

你踩过这些坑吗?

  1. 你的集群开启了审计功能吗?审计策略是如何配置的?
  2. 你是如何处理审计日志存储和分析的?
  3. 你遇到过审计影响性能的问题吗?是怎么解决的?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询