k8s源码分析8-Audit审计功能
audit审计的总结
- Kubernetes 审计(Auditing) 功能提供了与安全相关的、按时间顺序排列的记录集,记录每个用户、使用 Kubernetes API 的应用以及控制面自身引发的活动
- 审计功能使得集群管理员能够回答以下问题:
- 发生了什么?
- 什么时候发生的?
- 谁触发的?
- 活动发生在哪个(些)对象上?
- 在哪观察到的?
- 它从哪触发的?
- 活动的后续处理行为是什么?
- 审计策略
- None - 符合这条规则的日志将不会记录。
- Metadata - 记录请求的元数据(请求的用户、时间戳、资源、动词等等), 但是不记录请求或者响应的消息体。
- Request - 记录事件的元数据和请求的消息体,但是不记录响应的消息体。 这不适用于非资源类型的请求。
- RequestResponse - 记录事件的元数据,请求和响应的消息体。这不适用于非资源类型的请求。
审计功能介绍
audit源码阅读
- 入口位置 D:\go_path\src\github.com\kubernetes\kubernetes\cmd\kube-apiserver\app\server.go
- buildGenericConfig
lastErr = s.Audit.ApplyTo(genericConfig)
if lastErr != nil {
return
}
1. 从配置的 --audit-policy-file加载audit策略
- 你可以使用 --audit-policy-file 标志将包含策略的文件传递给 kube-apiserver
- 如果不设置该标志,则不记录事件
- rules 字段 必须 在审计策略文件中提供。没有(0)规则的策略将被视为非法配置。
// 1. Build policy evaluator
evaluator, err := o.newPolicyRuleEvaluator()
if err != nil {
return err
}
2. 从配置的 --audit-log-path设置 logBackend
// 2. Build log backend
var logBackend audit.Backend
w, err := o.LogOptions.getWriter()
if err != nil {
return err
}
- 如果后端 --audit-log-path="-" 代表记录到标准输出
func (o *AuditLogOptions) getWriter() (io.Writer, error) {
if !o.enabled() {
return nil, nil
}
if o.Path == "-" {
return os.Stdout, nil
}
if err := o.ensureLogFile(); err != nil {
return nil, fmt.Errorf("ensureLogFile: %w", err)
}
return &lumberjack.Logger{
Filename: o.Path,
MaxAge: o.MaxAge,
MaxBackups: o.MaxBackups,
MaxSize: o.MaxSize,
Compress: o.Compress,
}, nil
}
- ensureLogFile 尝试打开一下log文件,做日志文件是否可用的验证
- 底层使用 https://github.com/natefinch/lumberjack,是一个带有滚动功能的日志库
获取到日志writer对象后校验下有没有evaluator
- 如果没有evaluator,打印条提示日志
- 如果将backend设置为w
if w != nil {
if evaluator == 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的 后端
// 3. Build webhook backend
var webhookBackend audit.Backend
if o.WebhookOptions.enabled() {
if evaluator == nil {
klog.V(2).Info("No audit policy file provided, no events will be recorded for webhook backend")
} else {
if c.EgressSelector != nil {
var egressDialer utilnet.DialFunc
egressDialer, err = c.EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
if err != nil {
return err
}
webhookBackend, err = o.WebhookOptions.newUntruncatedBackend(egressDialer)
} else {
webhookBackend, err = o.WebhookOptions.newUntruncatedBackend(nil)
}
if err != nil {
return err
}
}
}
4 如果有webhook就把它封装为dynamicBackend
// 4. Apply dynamic options.
var dynamicBackend audit.Backend
if webhookBackend != nil {
// if only webhook is enabled wrap it in the truncate options
dynamicBackend = o.WebhookOptions.TruncateOptions.wrapBackend(webhookBackend, groupVersion)
}
5. 设置审计的策略计算对象 evaluator
// 5. Set the policy rule evaluator
c.AuditPolicyRuleEvaluator = evaluator
6 把logBackend和 dynamicBackend 做union
// 6. Join the log backend with the webhooks
c.AuditBackend = appendBackend(logBackend, dynamicBackend)
func appendBackend(existing, newBackend audit.Backend) audit.Backend {
if existing == nil {
return newBackend
}
if newBackend == nil {
return existing
}
return audit.Union(existing, newBackend)
}
7.最终的运行方法
- backend接口方法
- D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\audit\types.go
type Sink interface {
// ProcessEvents handles events. Per audit ID it might be that ProcessEvents is called up to three times.
// Errors might be logged by the sink itself. If an error should be fatal, leading to an internal
// error, ProcessEvents is supposed to panic. The event must not be mutated and is reused by the caller
// after the call returns, i.e. the sink has to make a deepcopy to keep a copy around if necessary.
// Returns true on success, may return false on error.
ProcessEvents(events ...*auditinternal.Event) bool
}
type Backend interface {
Sink
// Run will initialize the backend. It must not block, but may run go routines in the background. If
// stopCh is closed, it is supposed to stop them. Run will be called before the first call to ProcessEvents.
Run(stopCh <-chan struct{}) error
// Shutdown will synchronously shut down the backend while making sure that all pending
// events are delivered. It can be assumed that this method is called after
// the stopCh channel passed to the Run method has been closed.
Shutdown()
// Returns the backend PluginName.
String() string
}
- 最终调用audit的ProcessEvents方法,以log举例,位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\plugin\pkg\audit\log\backend.go
- 流程分析,根据format类型做最后的line生成,然后调用 fmt.Fprint(b.out, line),向b.out中写入line即可
func (b *backend) ProcessEvents(events ...*auditinternal.Event) bool {
success := true
for _, ev := range events {
success = b.logEvent(ev) && success
}
return success
}
func (b *backend) logEvent(ev *auditinternal.Event) bool {
line := ""
switch b.format {
case FormatLegacy:
line = audit.EventString(ev) + "\n"
case FormatJson:
bs, err := runtime.Encode(b.encoder, ev)
if err != nil {
audit.HandlePluginError(PluginName, err, ev)
return false
}
line = string(bs[:])
default:
audit.HandlePluginError(PluginName, fmt.Errorf("log format %q is not in list of known formats (%s)",
b.format, strings.Join(AllowedFormats, ",")), ev)
return false
}
if _, err := fmt.Fprint(b.out, line); err != nil {
audit.HandlePluginError(PluginName, err, ev)
return false
}
return true
}
8. http侧调用的handler
- D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\endpoints\filters\audit.go
// WithAudit decorates a http.Handler with audit logging information for all the
// requests coming to the server. Audit level is decided according to requests'
// attributes and audit policy. Logs are emitted to the audit sink to
// process events. If sink or audit policy is nil, no decoration takes place.
func WithAudit(handler http.Handler, sink audit.Sink, policy audit.PolicyRuleEvaluator, longRunningCheck request.LongRunningRequestCheck) http.Handler {
if sink == nil || policy == nil {
return handler
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
req, ev, omitStages, err := createAuditEventAndAttachToContext(req, policy)
if err != nil {
utilruntime.HandleError(fmt.Errorf("failed to create audit event: %v", err))
responsewriters.InternalError(w, req, errors.New("failed to create audit event"))
return
}
ctx := req.Context()
if ev == nil || ctx == nil {
handler.ServeHTTP(w, req)
return
}
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
}
// intercept the status code
var longRunningSink audit.Sink
if longRunningCheck != nil {
ri, _ := request.RequestInfoFrom(ctx)
if longRunningCheck(req, ri) {
longRunningSink = sink
}
}
respWriter := decorateResponseWriter(ctx, w, ev, longRunningSink, omitStages)
// send audit event when we leave this func, either via a panic or cleanly. In the case of long
// running requests, this will be the second audit event.
defer func() {
if r := recover(); r != nil {
defer panic(r)
ev.Stage = auditinternal.StagePanic
ev.ResponseStatus = &metav1.Status{
Code: http.StatusInternalServerError,
Status: metav1.StatusFailure,
Reason: metav1.StatusReasonInternalError,
Message: fmt.Sprintf("APIServer panic'd: %v", r),
}
processAuditEvent(ctx, sink, ev, omitStages)
return
}
// if no StageResponseStarted event was sent b/c neither a status code nor a body was sent, fake it here
// But Audit-Id http header will only be sent when http.ResponseWriter.WriteHeader is called.
fakedSuccessStatus := &metav1.Status{
Code: http.StatusOK,
Status: metav1.StatusSuccess,
Message: "Connection closed early",
}
if ev.ResponseStatus == nil && longRunningSink != nil {
ev.ResponseStatus = fakedSuccessStatus
ev.Stage = auditinternal.StageResponseStarted
processAuditEvent(ctx, longRunningSink, ev, omitStages)
}
ev.Stage = auditinternal.StageResponseComplete
if ev.ResponseStatus == nil {
ev.ResponseStatus = fakedSuccessStatus
}
processAuditEvent(ctx, sink, ev, omitStages)
}()
handler.ServeHTTP(respWriter, req)
})
}
audit审计的总结
- Kubernetes 审计(Auditing) 功能提供了与安全相关的、按时间顺序排列的记录集,记录每个用户、使用 Kubernetes API 的应用以及控制面自身引发的活动
- 审计功能使得集群管理员能够回答以下问题:
- 发生了什么?
- 什么时候发生的?
- 谁触发的?
- 活动发生在哪个(些)对象上?
- 在哪观察到的?
- 它从哪触发的?
- 活动的后续处理行为是什么?
- 审计策略
- None - 符合这条规则的日志将不会记录。
- Metadata - 记录请求的元数据(请求的用户、时间戳、资源、动词等等), 但是不记录请求或者响应的消息体。
- Request - 记录事件的元数据和请求的消息体,但是不记录响应的消息体。 这不适用于非资源类型的请求。
- RequestResponse - 记录事件的元数据,请求和响应的消息体。这不适用于非资源类型的请求。
如对您有帮助,支持下呗!
微信
支付宝