diff --git a/go.mod b/go.mod index 103cef33a0..19b8f1187e 100644 --- a/go.mod +++ b/go.mod @@ -198,7 +198,7 @@ replace ( k8s.io/api => k8s.io/api v0.33.3 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.3 k8s.io/apimachinery => k8s.io/apimachinery v0.33.3 - k8s.io/apiserver => github.com/openshift/kubernetes-apiserver v0.0.0-20250917144435-182485d204aa // points to openshift-apiserver-4.20-kubernetes-1.33 + k8s.io/apiserver => github.com/ingvagabund/kubernetes-apiserver v0.0.0-20251124125735-7bc3c9987f36 // points to openshift-apiserver-4.20-kubernetes-1.33 k8s.io/cli-runtime => k8s.io/cli-runtime v0.33.3 k8s.io/client-go => k8s.io/client-go v0.33.3 k8s.io/cloud-provider => k8s.io/cloud-provider v0.33.3 diff --git a/go.sum b/go.sum index 18949176c2..d44da4a5b7 100644 --- a/go.sum +++ b/go.sum @@ -183,6 +183,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/ingvagabund/kubernetes-apiserver v0.0.0-20251124125735-7bc3c9987f36 h1:8dfZmofRL/Q9YmCekniMPthN7feiXKvL6gtI8EyB7SY= +github.com/ingvagabund/kubernetes-apiserver v0.0.0-20251124125735-7bc3c9987f36/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -262,8 +264,6 @@ github.com/openshift/client-go v0.0.0-20250811163556-6193816ae379 h1:Xr47DBqFVjp github.com/openshift/client-go v0.0.0-20250811163556-6193816ae379/go.mod h1:HouQRy4JgvTBpxcyw1YSD/Lp+wjOaUrxjWFHlMtZsk8= github.com/openshift/docker-distribution/v3 v3.0.0-20240215131201-6b2f5d2f1f43 h1:iFiveehT5yqHvAxdTwHGZLTxyxzMqP8bLcQKz6Y7NQQ= github.com/openshift/docker-distribution/v3 v3.0.0-20240215131201-6b2f5d2f1f43/go.mod h1:+fqBJ4vPYo4Uu1ZE4d+bUtTLRXfdSL3NvCZIZ9GHv58= -github.com/openshift/kubernetes-apiserver v0.0.0-20250917144435-182485d204aa h1:rCAG+5H7TRJvPh0H05Lv3dE3SACVsO6Afm/auqjWnDo= -github.com/openshift/kubernetes-apiserver v0.0.0-20250917144435-182485d204aa/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= github.com/openshift/library-go v0.0.0-20250818065802-cf8518058622 h1:IUs2XpDgkCQIIAPCVnHEjIUYiq0dvVskD/ekof7+XjQ= github.com/openshift/library-go v0.0.0-20250818065802-cf8518058622/go.mod h1:tptKNust9MdRI0p90DoBSPHIrBa9oh+Rok59tF0vT8c= github.com/openshift/moby-moby v0.0.0-20190308215630-da810a85109d h1:fLITXDjxMSvUDjnXs/zljIWktbST9+Om8XbrmmM7T4I= diff --git a/vendor/k8s.io/apiserver/pkg/admission/audit.go b/vendor/k8s.io/apiserver/pkg/admission/audit.go index 7c0993f090..f9f90cd024 100644 --- a/vendor/k8s.io/apiserver/pkg/admission/audit.go +++ b/vendor/k8s.io/apiserver/pkg/admission/audit.go @@ -83,7 +83,7 @@ func ensureAnnotationGetter(a Attributes) error { } func (handler *auditHandler) logAnnotations(ctx context.Context, a Attributes) { - ae := audit.AuditEventFrom(ctx) + ae := audit.AuditContextFrom(ctx) if ae == nil { return } @@ -91,9 +91,9 @@ func (handler *auditHandler) logAnnotations(ctx context.Context, a Attributes) { var annotations map[string]string switch a := a.(type) { case privateAnnotationsGetter: - annotations = a.getAnnotations(ae.Level) + annotations = a.getAnnotations(ae.GetEventLevel()) case AnnotationsGetter: - annotations = a.GetAnnotations(ae.Level) + annotations = a.GetAnnotations(ae.GetEventLevel()) default: // this will never happen, because we have already checked it in ensureAnnotationGetter } diff --git a/vendor/k8s.io/apiserver/pkg/audit/context.go b/vendor/k8s.io/apiserver/pkg/audit/context.go index 9648587378..5b93d594bf 100644 --- a/vendor/k8s.io/apiserver/pkg/audit/context.go +++ b/vendor/k8s.io/apiserver/pkg/audit/context.go @@ -18,10 +18,18 @@ package audit import ( "context" + "errors" + "maps" "sync" + "sync/atomic" + "time" + authnv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" auditinternal "k8s.io/apiserver/pkg/apis/audit" + "k8s.io/apiserver/pkg/authentication/user" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/klog/v2" ) @@ -35,22 +43,223 @@ const auditKey key = iota // AuditContext holds the information for constructing the audit events for the current request. type AuditContext struct { - // RequestAuditConfig is the audit configuration that applies to the request - RequestAuditConfig RequestAuditConfig - - // Event is the audit Event object that is being captured to be written in + // initialized indicates whether requestAuditConfig and sink have been populated and are safe to read unguarded. + // This should only be set via Init(). + initialized atomic.Bool + // requestAuditConfig is the audit configuration that applies to the request. + // This should only be written via Init(RequestAuditConfig, Sink), and only read when initialized.Load() is true. + requestAuditConfig RequestAuditConfig + // sink is the sink to use when processing event stages. + // This should only be written via Init(RequestAuditConfig, Sink), and only read when initialized.Load() is true. + sink Sink + + // lock guards event + lock sync.Mutex + + // event is the audit Event object that is being captured to be written in // the API audit log. - Event auditinternal.Event + event auditinternal.Event - // annotationMutex guards event.Annotations - annotationMutex sync.Mutex + // unguarded copy of auditID from the event + auditID atomic.Value } // Enabled checks whether auditing is enabled for this audit context. func (ac *AuditContext) Enabled() bool { - // Note: An unset Level should be considered Enabled, so that request data (e.g. annotations) - // can still be captured before the audit policy is evaluated. - return ac != nil && ac.RequestAuditConfig.Level != auditinternal.LevelNone + if ac == nil { + // protect against nil pointers + return false + } + if !ac.initialized.Load() { + // Note: An unset Level should be considered Enabled, so that request data (e.g. annotations) + // can still be captured before the audit policy is evaluated. + return true + } + return ac.requestAuditConfig.Level != auditinternal.LevelNone +} + +func (ac *AuditContext) Init(requestAuditConfig RequestAuditConfig, sink Sink) error { + ac.lock.Lock() + defer ac.lock.Unlock() + if ac.initialized.Load() { + return errors.New("audit context was already initialized") + } + ac.requestAuditConfig = requestAuditConfig + ac.sink = sink + ac.event.Level = requestAuditConfig.Level + ac.initialized.Store(true) + return nil +} + +func (ac *AuditContext) AuditID() types.UID { + // return the unguarded copy of the auditID + id, _ := ac.auditID.Load().(types.UID) + return id +} + +func (ac *AuditContext) visitEvent(f func(event *auditinternal.Event)) { + ac.lock.Lock() + defer ac.lock.Unlock() + f(&ac.event) +} + +// ProcessEventStage returns true on success, false if there was an error processing the stage. +func (ac *AuditContext) ProcessEventStage(ctx context.Context, stage auditinternal.Stage) bool { + if ac == nil || !ac.initialized.Load() { + return true + } + if ac.sink == nil { + return true + } + for _, omitStage := range ac.requestAuditConfig.OmitStages { + if stage == omitStage { + return true + } + } + + processed := false + ac.visitEvent(func(event *auditinternal.Event) { + event.Stage = stage + if stage == auditinternal.StageRequestReceived { + event.StageTimestamp = event.RequestReceivedTimestamp + } else { + event.StageTimestamp = metav1.NewMicroTime(time.Now()) + } + + ObserveEvent(ctx) + processed = ac.sink.ProcessEvents(event) + }) + return processed +} + +func (ac *AuditContext) LogImpersonatedUser(user user.Info) { + ac.visitEvent(func(ev *auditinternal.Event) { + if ev == nil || ev.Level.Less(auditinternal.LevelMetadata) { + return + } + ev.ImpersonatedUser = &authnv1.UserInfo{ + Username: user.GetName(), + } + ev.ImpersonatedUser.Groups = user.GetGroups() + ev.ImpersonatedUser.UID = user.GetUID() + ev.ImpersonatedUser.Extra = map[string]authnv1.ExtraValue{} + for k, v := range user.GetExtra() { + ev.ImpersonatedUser.Extra[k] = authnv1.ExtraValue(v) + } + }) +} + +func (ac *AuditContext) LogResponseObject(status *metav1.Status, obj *runtime.Unknown) { + ac.visitEvent(func(ae *auditinternal.Event) { + if status != nil { + // selectively copy the bounded fields. + ae.ResponseStatus = &metav1.Status{ + Status: status.Status, + Message: status.Message, + Reason: status.Reason, + Details: status.Details, + Code: status.Code, + } + } + if ae.Level.Less(auditinternal.LevelRequestResponse) { + return + } + ae.ResponseObject = obj + }) +} + +// LogRequestPatch fills in the given patch as the request object into an audit event. +func (ac *AuditContext) LogRequestPatch(patch []byte) { + ac.visitEvent(func(ae *auditinternal.Event) { + ae.RequestObject = &runtime.Unknown{ + Raw: patch, + ContentType: runtime.ContentTypeJSON, + } + }) +} + +func (ac *AuditContext) GetEventAnnotation(key string) (string, bool) { + var val string + var ok bool + ac.visitEvent(func(event *auditinternal.Event) { + val, ok = event.Annotations[key] + }) + return val, ok +} + +func (ac *AuditContext) GetEventLevel() auditinternal.Level { + var level auditinternal.Level + ac.visitEvent(func(event *auditinternal.Event) { + level = event.Level + }) + return level +} + +func (ac *AuditContext) SetEventStage(stage auditinternal.Stage) { + ac.visitEvent(func(event *auditinternal.Event) { + event.Stage = stage + }) +} + +func (ac *AuditContext) GetEventStage() auditinternal.Stage { + var stage auditinternal.Stage + ac.visitEvent(func(event *auditinternal.Event) { + stage = event.Stage + }) + return stage +} + +func (ac *AuditContext) SetEventStageTimestamp(timestamp metav1.MicroTime) { + ac.visitEvent(func(event *auditinternal.Event) { + event.StageTimestamp = timestamp + }) +} + +func (ac *AuditContext) GetEventResponseStatus() *metav1.Status { + var status *metav1.Status + ac.visitEvent(func(event *auditinternal.Event) { + status = event.ResponseStatus + }) + return status +} + +func (ac *AuditContext) GetEventRequestReceivedTimestamp() metav1.MicroTime { + var timestamp metav1.MicroTime + ac.visitEvent(func(event *auditinternal.Event) { + timestamp = event.RequestReceivedTimestamp + }) + return timestamp +} + +func (ac *AuditContext) GetEventStageTimestamp() metav1.MicroTime { + var timestamp metav1.MicroTime + ac.visitEvent(func(event *auditinternal.Event) { + timestamp = event.StageTimestamp + }) + return timestamp +} + +func (ac *AuditContext) SetEventResponseStatus(status *metav1.Status) { + ac.visitEvent(func(event *auditinternal.Event) { + event.ResponseStatus = status + }) +} + +func (ac *AuditContext) SetEventResponseStatusCode(statusCode int32) { + ac.visitEvent(func(event *auditinternal.Event) { + if event.ResponseStatus == nil { + event.ResponseStatus = &metav1.Status{} + } + event.ResponseStatus.Code = statusCode + }) +} + +func (ac *AuditContext) GetEventAnnotations() map[string]string { + var annotations map[string]string + ac.visitEvent(func(event *auditinternal.Event) { + annotations = maps.Clone(event.Annotations) + }) + return annotations } // AddAuditAnnotation sets the audit annotation for the given key, value pair. @@ -66,8 +275,8 @@ func AddAuditAnnotation(ctx context.Context, key, value string) { return } - ac.annotationMutex.Lock() - defer ac.annotationMutex.Unlock() + ac.lock.Lock() + defer ac.lock.Unlock() addAuditAnnotationLocked(ac, key, value) } @@ -81,8 +290,8 @@ func AddAuditAnnotations(ctx context.Context, keysAndValues ...string) { return } - ac.annotationMutex.Lock() - defer ac.annotationMutex.Unlock() + ac.lock.Lock() + defer ac.lock.Unlock() if len(keysAndValues)%2 != 0 { klog.Errorf("Dropping mismatched audit annotation %q", keysAndValues[len(keysAndValues)-1]) @@ -100,8 +309,8 @@ func AddAuditAnnotationsMap(ctx context.Context, annotations map[string]string) return } - ac.annotationMutex.Lock() - defer ac.annotationMutex.Unlock() + ac.lock.Lock() + defer ac.lock.Unlock() for k, v := range annotations { addAuditAnnotationLocked(ac, k, v) @@ -110,8 +319,7 @@ func AddAuditAnnotationsMap(ctx context.Context, annotations map[string]string) // addAuditAnnotationLocked records the audit annotation on the event. func addAuditAnnotationLocked(ac *AuditContext, key, value string) { - ae := &ac.Event - + ae := &ac.event if ae.Annotations == nil { ae.Annotations = make(map[string]string) } @@ -128,15 +336,11 @@ func WithAuditContext(parent context.Context) context.Context { return parent // Avoid double registering. } - return genericapirequest.WithValue(parent, auditKey, &AuditContext{}) -} - -// AuditEventFrom returns the audit event struct on the ctx -func AuditEventFrom(ctx context.Context) *auditinternal.Event { - if ac := AuditContextFrom(ctx); ac.Enabled() { - return &ac.Event - } - return nil + return genericapirequest.WithValue(parent, auditKey, &AuditContext{ + event: auditinternal.Event{ + Stage: auditinternal.StageResponseStarted, + }, + }) } // AuditContextFrom returns the pair of the audit configuration object @@ -154,7 +358,10 @@ func WithAuditID(ctx context.Context, auditID types.UID) { return } if ac := AuditContextFrom(ctx); ac != nil { - ac.Event.AuditID = auditID + ac.visitEvent(func(event *auditinternal.Event) { + ac.auditID.Store(auditID) + event.AuditID = auditID + }) } } @@ -162,7 +369,8 @@ func WithAuditID(ctx context.Context, auditID types.UID) { // auditing is enabled. func AuditIDFrom(ctx context.Context) (types.UID, bool) { if ac := AuditContextFrom(ctx); ac != nil { - return ac.Event.AuditID, true + id, _ := ac.auditID.Load().(types.UID) + return id, true } return "", false } diff --git a/vendor/k8s.io/apiserver/pkg/audit/request.go b/vendor/k8s.io/apiserver/pkg/audit/request.go index 9185278f06..d5f9c730f5 100644 --- a/vendor/k8s.io/apiserver/pkg/audit/request.go +++ b/vendor/k8s.io/apiserver/pkg/audit/request.go @@ -40,110 +40,73 @@ const ( userAgentTruncateSuffix = "...TRUNCATED" ) -func LogRequestMetadata(ctx context.Context, req *http.Request, requestReceivedTimestamp time.Time, level auditinternal.Level, attribs authorizer.Attributes) { +func LogRequestMetadata(ctx context.Context, req *http.Request, requestReceivedTimestamp time.Time, attribs authorizer.Attributes) { ac := AuditContextFrom(ctx) if !ac.Enabled() { return } - ev := &ac.Event - - ev.RequestReceivedTimestamp = metav1.NewMicroTime(requestReceivedTimestamp) - ev.Verb = attribs.GetVerb() - ev.RequestURI = req.URL.RequestURI() - ev.UserAgent = maybeTruncateUserAgent(req) - ev.Level = level - - ips := utilnet.SourceIPs(req) - ev.SourceIPs = make([]string, len(ips)) - for i := range ips { - ev.SourceIPs[i] = ips[i].String() - } - if user := attribs.GetUser(); user != nil { - ev.User.Username = user.GetName() - ev.User.Extra = map[string]authnv1.ExtraValue{} - for k, v := range user.GetExtra() { - ev.User.Extra[k] = authnv1.ExtraValue(v) + ac.visitEvent(func(ev *auditinternal.Event) { + ev.RequestReceivedTimestamp = metav1.NewMicroTime(requestReceivedTimestamp) + ev.Verb = attribs.GetVerb() + ev.RequestURI = req.URL.RequestURI() + ev.UserAgent = maybeTruncateUserAgent(req) + + ips := utilnet.SourceIPs(req) + ev.SourceIPs = make([]string, len(ips)) + for i := range ips { + ev.SourceIPs[i] = ips[i].String() } - ev.User.Groups = user.GetGroups() - ev.User.UID = user.GetUID() - } - if attribs.IsResourceRequest() { - ev.ObjectRef = &auditinternal.ObjectReference{ - Namespace: attribs.GetNamespace(), - Name: attribs.GetName(), - Resource: attribs.GetResource(), - Subresource: attribs.GetSubresource(), - APIGroup: attribs.GetAPIGroup(), - APIVersion: attribs.GetAPIVersion(), + if user := attribs.GetUser(); user != nil { + ev.User.Username = user.GetName() + ev.User.Extra = map[string]authnv1.ExtraValue{} + for k, v := range user.GetExtra() { + ev.User.Extra[k] = authnv1.ExtraValue(v) + } + ev.User.Groups = user.GetGroups() + ev.User.UID = user.GetUID() } - } + + if attribs.IsResourceRequest() { + ev.ObjectRef = &auditinternal.ObjectReference{ + Namespace: attribs.GetNamespace(), + Name: attribs.GetName(), + Resource: attribs.GetResource(), + Subresource: attribs.GetSubresource(), + APIGroup: attribs.GetAPIGroup(), + APIVersion: attribs.GetAPIVersion(), + } + } + }) } // LogImpersonatedUser fills in the impersonated user attributes into an audit event. -func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) { - if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { +func LogImpersonatedUser(ctx context.Context, user user.Info) { + ac := AuditContextFrom(ctx) + if !ac.Enabled() { return } - ae.ImpersonatedUser = &authnv1.UserInfo{ - Username: user.GetName(), - } - ae.ImpersonatedUser.Groups = user.GetGroups() - ae.ImpersonatedUser.UID = user.GetUID() - ae.ImpersonatedUser.Extra = map[string]authnv1.ExtraValue{} - for k, v := range user.GetExtra() { - ae.ImpersonatedUser.Extra[k] = authnv1.ExtraValue(v) - } + ac.LogImpersonatedUser(user) } // LogRequestObject fills in the request object into an audit event. The passed runtime.Object // will be converted to the given gv. func LogRequestObject(ctx context.Context, obj runtime.Object, objGV schema.GroupVersion, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) { - ae := AuditEventFrom(ctx) - if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { + ac := AuditContextFrom(ctx) + if !ac.Enabled() { return } - - // complete ObjectRef - if ae.ObjectRef == nil { - ae.ObjectRef = &auditinternal.ObjectReference{} - } - - // meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits - if meta, err := meta.Accessor(obj); err == nil { - if len(ae.ObjectRef.Namespace) == 0 { - ae.ObjectRef.Namespace = meta.GetNamespace() - } - if len(ae.ObjectRef.Name) == 0 { - ae.ObjectRef.Name = meta.GetName() - } - if len(ae.ObjectRef.UID) == 0 { - ae.ObjectRef.UID = meta.GetUID() - } - if len(ae.ObjectRef.ResourceVersion) == 0 { - ae.ObjectRef.ResourceVersion = meta.GetResourceVersion() - } - } - if len(ae.ObjectRef.APIVersion) == 0 { - ae.ObjectRef.APIGroup = gvr.Group - ae.ObjectRef.APIVersion = gvr.Version - } - if len(ae.ObjectRef.Resource) == 0 { - ae.ObjectRef.Resource = gvr.Resource - } - if len(ae.ObjectRef.Subresource) == 0 { - ae.ObjectRef.Subresource = subresource - } - - if ae.Level.Less(auditinternal.LevelRequest) { + if ac.GetEventLevel().Less(auditinternal.LevelMetadata) { return } - if shouldOmitManagedFields(ctx) { + // meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits + objMeta, _ := meta.Accessor(obj) + if shouldOmitManagedFields(ac) { copy, ok, err := copyWithoutManagedFields(obj) if err != nil { - klog.ErrorS(err, "Error while dropping managed fields from the request", "auditID", ae.AuditID) + klog.ErrorS(err, "Error while dropping managed fields from the request", "auditID", ac.AuditID()) } if ok { obj = copy @@ -151,54 +114,75 @@ func LogRequestObject(ctx context.Context, obj runtime.Object, objGV schema.Grou } // TODO(audit): hook into the serializer to avoid double conversion - var err error - ae.RequestObject, err = encodeObject(obj, objGV, s) + requestObject, err := encodeObject(obj, objGV, s) if err != nil { // TODO(audit): add error slice to audit event struct - klog.ErrorS(err, "Encoding failed of request object", "auditID", ae.AuditID, "gvr", gvr.String(), "obj", obj) + klog.ErrorS(err, "Encoding failed of request object", "auditID", ac.AuditID(), "gvr", gvr.String(), "obj", obj) return } + + ac.visitEvent(func(ae *auditinternal.Event) { + if ae.ObjectRef == nil { + ae.ObjectRef = &auditinternal.ObjectReference{} + } + + if objMeta != nil { + if len(ae.ObjectRef.Namespace) == 0 { + ae.ObjectRef.Namespace = objMeta.GetNamespace() + } + if len(ae.ObjectRef.Name) == 0 { + ae.ObjectRef.Name = objMeta.GetName() + } + if len(ae.ObjectRef.UID) == 0 { + ae.ObjectRef.UID = objMeta.GetUID() + } + if len(ae.ObjectRef.ResourceVersion) == 0 { + ae.ObjectRef.ResourceVersion = objMeta.GetResourceVersion() + } + } + if len(ae.ObjectRef.APIVersion) == 0 { + ae.ObjectRef.APIGroup = gvr.Group + ae.ObjectRef.APIVersion = gvr.Version + } + if len(ae.ObjectRef.Resource) == 0 { + ae.ObjectRef.Resource = gvr.Resource + } + if len(ae.ObjectRef.Subresource) == 0 { + ae.ObjectRef.Subresource = subresource + } + + if ae.Level.Less(auditinternal.LevelRequest) { + return + } + ae.RequestObject = requestObject + }) } // LogRequestPatch fills in the given patch as the request object into an audit event. func LogRequestPatch(ctx context.Context, patch []byte) { - ae := AuditEventFrom(ctx) - if ae == nil || ae.Level.Less(auditinternal.LevelRequest) { + ac := AuditContextFrom(ctx) + if ac.GetEventLevel().Less(auditinternal.LevelRequest) { return } - - ae.RequestObject = &runtime.Unknown{ - Raw: patch, - ContentType: runtime.ContentTypeJSON, - } + ac.LogRequestPatch(patch) } // LogResponseObject fills in the response object into an audit event. The passed runtime.Object // will be converted to the given gv. func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) { - ae := AuditEventFrom(ctx) - if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { + ac := AuditContextFrom(WithAuditContext(ctx)) + status, _ := obj.(*metav1.Status) + if ac.GetEventLevel().Less(auditinternal.LevelMetadata) { return - } - if status, ok := obj.(*metav1.Status); ok { - // selectively copy the bounded fields. - ae.ResponseStatus = &metav1.Status{ - Status: status.Status, - Message: status.Message, - Reason: status.Reason, - Details: status.Details, - Code: status.Code, - } - } - - if ae.Level.Less(auditinternal.LevelRequestResponse) { + } else if ac.GetEventLevel().Less(auditinternal.LevelRequestResponse) { + ac.LogResponseObject(status, nil) return } - if shouldOmitManagedFields(ctx) { + if shouldOmitManagedFields(ac) { copy, ok, err := copyWithoutManagedFields(obj) if err != nil { - klog.ErrorS(err, "Error while dropping managed fields from the response", "auditID", ae.AuditID) + klog.ErrorS(err, "Error while dropping managed fields from the response", "auditID", ac.AuditID()) } if ok { obj = copy @@ -207,10 +191,11 @@ func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupV // TODO(audit): hook into the serializer to avoid double conversion var err error - ae.ResponseObject, err = encodeObject(obj, gv, s) + responseObject, err := encodeObject(obj, gv, s) if err != nil { - klog.ErrorS(err, "Encoding failed of response object", "auditID", ae.AuditID, "obj", obj) + klog.ErrorS(err, "Encoding failed of response object", "auditID", ac.AuditID(), "obj", obj) } + ac.LogResponseObject(status, responseObject) } func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) { @@ -301,9 +286,9 @@ func removeManagedFields(obj runtime.Object) error { return nil } -func shouldOmitManagedFields(ctx context.Context) bool { - if auditContext := AuditContextFrom(ctx); auditContext != nil { - return auditContext.RequestAuditConfig.OmitManagedFields +func shouldOmitManagedFields(ac *AuditContext) bool { + if ac != nil && ac.initialized.Load() && ac.requestAuditConfig.OmitManagedFields { + return true } // If we can't decide, return false to maintain current behavior which is diff --git a/vendor/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go b/vendor/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go index 18167dddc2..9d1556e633 100644 --- a/vendor/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go +++ b/vendor/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go @@ -33,7 +33,6 @@ import ( "golang.org/x/sync/singleflight" apierrors "k8s.io/apimachinery/pkg/api/errors" - auditinternal "k8s.io/apiserver/pkg/apis/audit" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/warning" @@ -199,12 +198,9 @@ func (a *cachedTokenAuthenticator) doAuthenticateToken(ctx context.Context, toke ctx = audit.WithAuditContext(ctx) ac := audit.AuditContextFrom(ctx) - // since this is shared work between multiple requests, we have no way of knowing if any - // particular request supports audit annotations. thus we always attempt to record them. - ac.Event.Level = auditinternal.LevelMetadata record.resp, record.ok, record.err = a.authenticator.AuthenticateToken(ctx, token) - record.annotations = ac.Event.Annotations + record.annotations = ac.GetEventAnnotations() record.warnings = recorder.extractWarnings() if !a.cacheErrs && record.err != nil { diff --git a/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go b/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go index 6f850f728b..d25bf35ae3 100644 --- a/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go +++ b/vendor/k8s.io/apiserver/pkg/endpoints/filters/audit.go @@ -44,7 +44,7 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy audit.PolicyRuleEva return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ac, err := evaluatePolicyAndCreateAuditEvent(req, policy) + ac, err := evaluatePolicyAndCreateAuditEvent(req, policy, sink) 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")) @@ -55,41 +55,37 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy audit.PolicyRuleEva handler.ServeHTTP(w, req) return } - ev := &ac.Event ctx := req.Context() - omitStages := ac.RequestAuditConfig.OmitStages - ev.Stage = auditinternal.StageRequestReceived - if processed := processAuditEvent(ctx, sink, ev, omitStages); !processed { + if processed := ac.ProcessEventStage(ctx, auditinternal.StageRequestReceived); !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 + isLongRunning := false if longRunningCheck != nil { ri, _ := request.RequestInfoFrom(ctx) if longRunningCheck(req, ri) { - longRunningSink = sink + isLongRunning = true } } - respWriter := decorateResponseWriter(ctx, w, ev, longRunningSink, omitStages) + respWriter := decorateResponseWriter(ctx, w, isLongRunning) // 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{ + ac.SetEventResponseStatus(&metav1.Status{ Code: http.StatusInternalServerError, Status: metav1.StatusFailure, Reason: metav1.StatusReasonInternalError, Message: fmt.Sprintf("APIServer panic'd: %v", r), - } - processAuditEvent(ctx, sink, ev, omitStages) + }) + ac.ProcessEventStage(ctx, auditinternal.StagePanic) return } @@ -100,27 +96,25 @@ func WithAudit(handler http.Handler, sink audit.Sink, policy audit.PolicyRuleEva 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 + if ac.GetEventResponseStatus() == nil { + ac.SetEventResponseStatus(fakedSuccessStatus) + if isLongRunning { + // A nil ResponseStatus means the writer never processed the ResponseStarted stage, so do that now. + ac.ProcessEventStage(ctx, auditinternal.StageResponseStarted) + } } - processAuditEvent(ctx, sink, ev, omitStages) + writeLatencyToAnnotation(ctx) + ac.ProcessEventStage(ctx, auditinternal.StageResponseComplete) }() handler.ServeHTTP(respWriter, req) }) } // evaluatePolicyAndCreateAuditEvent is responsible for evaluating the audit -// policy configuration applicable to the request and create a new audit -// event that will be written to the API audit log. +// policy configuration applicable to the request and initializing the audit +// context with the audit config for the request, the sink to write to, and the request metadata. // - error if anything bad happened -func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRuleEvaluator) (*audit.AuditContext, error) { +func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRuleEvaluator, sink audit.Sink) (*audit.AuditContext, error) { ctx := req.Context() ac := audit.AuditContextFrom(ctx) if ac == nil { @@ -135,7 +129,10 @@ func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRul rac := policy.EvaluatePolicyRule(attribs) audit.ObservePolicyLevel(ctx, rac.Level) - ac.RequestAuditConfig = rac + err = ac.Init(rac, sink) + if err != nil { + return nil, fmt.Errorf("failed to initialize audit context: %w", err) + } if rac.Level == auditinternal.LevelNone { // Don't audit. return ac, nil @@ -145,7 +142,7 @@ func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRul if !ok { requestReceivedTimestamp = time.Now() } - audit.LogRequestMetadata(ctx, req, requestReceivedTimestamp, rac.Level, attribs) + audit.LogRequestMetadata(ctx, req, requestReceivedTimestamp, attribs) return ac, nil } @@ -153,13 +150,14 @@ func evaluatePolicyAndCreateAuditEvent(req *http.Request, policy audit.PolicyRul // writeLatencyToAnnotation writes the latency incurred in different // layers of the apiserver to the annotations of the audit object. // it should be invoked after ev.StageTimestamp has been set appropriately. -func writeLatencyToAnnotation(ctx context.Context, ev *auditinternal.Event) { +func writeLatencyToAnnotation(ctx context.Context) { + ac := audit.AuditContextFrom(ctx) // we will track latency in annotation only when the total latency // of the given request exceeds 500ms, this is in keeping with the // traces in rest/handlers for create, delete, update, // get, list, and deletecollection. const threshold = 500 * time.Millisecond - latency := ev.StageTimestamp.Time.Sub(ev.RequestReceivedTimestamp.Time) + latency := ac.GetEventStageTimestamp().Sub(ac.GetEventRequestReceivedTimestamp().Time) if latency <= threshold { return } @@ -177,34 +175,12 @@ func writeLatencyToAnnotation(ctx context.Context, ev *auditinternal.Event) { audit.AddAuditAnnotationsMap(ctx, layerLatencies) } -func processAuditEvent(ctx context.Context, sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool { - for _, stage := range omitStages { - if ev.Stage == stage { - return true - } - } - - switch { - case ev.Stage == auditinternal.StageRequestReceived: - ev.StageTimestamp = metav1.NewMicroTime(ev.RequestReceivedTimestamp.Time) - case ev.Stage == auditinternal.StageResponseComplete: - ev.StageTimestamp = metav1.NewMicroTime(time.Now()) - writeLatencyToAnnotation(ctx, ev) - default: - ev.StageTimestamp = metav1.NewMicroTime(time.Now()) - } - - audit.ObserveEvent(ctx) - return sink.ProcessEvents(ev) -} - -func decorateResponseWriter(ctx context.Context, responseWriter http.ResponseWriter, ev *auditinternal.Event, sink audit.Sink, omitStages []auditinternal.Stage) http.ResponseWriter { +func decorateResponseWriter(ctx context.Context, responseWriter http.ResponseWriter, processResponseStartedStage bool) http.ResponseWriter { delegate := &auditResponseWriter{ ctx: ctx, ResponseWriter: responseWriter, - event: ev, - sink: sink, - omitStages: omitStages, + + processResponseStartedStage: processResponseStartedStage, } return responsewriter.WrapForHTTP1Or2(delegate) @@ -217,11 +193,10 @@ var _ responsewriter.UserProvidedDecorator = &auditResponseWriter{} // create immediately an event (for long running requests). type auditResponseWriter struct { http.ResponseWriter - ctx context.Context - event *auditinternal.Event - once sync.Once - sink audit.Sink - omitStages []auditinternal.Stage + ctx context.Context + once sync.Once + + processResponseStartedStage bool } func (a *auditResponseWriter) Unwrap() http.ResponseWriter { @@ -230,14 +205,10 @@ func (a *auditResponseWriter) Unwrap() http.ResponseWriter { func (a *auditResponseWriter) processCode(code int) { a.once.Do(func() { - if a.event.ResponseStatus == nil { - a.event.ResponseStatus = &metav1.Status{} - } - a.event.ResponseStatus.Code = int32(code) - a.event.Stage = auditinternal.StageResponseStarted - - if a.sink != nil { - processAuditEvent(a.ctx, a.sink, a.event, a.omitStages) + ac := audit.AuditContextFrom(a.ctx) + ac.SetEventResponseStatusCode(int32(code)) + if a.processResponseStartedStage { + ac.ProcessEventStage(a.ctx, auditinternal.StageResponseStarted) } }) } diff --git a/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go b/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go index 4bd6bbc139..d9cdcd2d62 100644 --- a/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go +++ b/vendor/k8s.io/apiserver/pkg/endpoints/filters/authn_audit.go @@ -24,7 +24,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - auditinternal "k8s.io/apiserver/pkg/apis/audit" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" ) @@ -36,7 +35,7 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, sink audit.Sink, return failedHandler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ac, err := evaluatePolicyAndCreateAuditEvent(req, policy) + ac, err := evaluatePolicyAndCreateAuditEvent(req, policy, sink) 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")) @@ -47,13 +46,11 @@ func WithFailedAuthenticationAudit(failedHandler http.Handler, sink audit.Sink, failedHandler.ServeHTTP(w, req) return } - ev := &ac.Event - ev.ResponseStatus = &metav1.Status{} - ev.ResponseStatus.Message = getAuthMethods(req) - ev.Stage = auditinternal.StageResponseStarted - - rw := decorateResponseWriter(req.Context(), w, ev, sink, ac.RequestAuditConfig.OmitStages) + ac.SetEventResponseStatus(&metav1.Status{ + Message: getAuthMethods(req), + }) + rw := decorateResponseWriter(req.Context(), w, true) failedHandler.ServeHTTP(rw, req) }) } diff --git a/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go b/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go index a6d293a159..aa47a7536d 100644 --- a/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go +++ b/vendor/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go @@ -166,8 +166,7 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime. oldUser, _ := request.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is impersonating %v", userString(oldUser), userString(newUser)) - ae := audit.AuditEventFrom(ctx) - audit.LogImpersonatedUser(ae, newUser) + audit.LogImpersonatedUser(audit.WithAuditContext(ctx), newUser) // clear all the impersonation headers from the request req.Header.Del(authenticationv1.ImpersonateUserHeader) diff --git a/vendor/k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go b/vendor/k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go index 7497bc38a4..066d670a2a 100644 --- a/vendor/k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go +++ b/vendor/k8s.io/apiserver/pkg/endpoints/filters/request_deadline.go @@ -108,7 +108,7 @@ func withFailedRequestAudit(failedHandler http.Handler, statusErr *apierrors.Sta return failedHandler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ac, err := evaluatePolicyAndCreateAuditEvent(req, policy) + ac, err := evaluatePolicyAndCreateAuditEvent(req, policy, sink) 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")) @@ -119,15 +119,15 @@ func withFailedRequestAudit(failedHandler http.Handler, statusErr *apierrors.Sta failedHandler.ServeHTTP(w, req) return } - ev := &ac.Event - ev.ResponseStatus = &metav1.Status{} - ev.Stage = auditinternal.StageResponseStarted + respStatus := &metav1.Status{} if statusErr != nil { - ev.ResponseStatus.Message = statusErr.Error() + respStatus.Message = statusErr.Error() } + ac.SetEventResponseStatus(respStatus) + ac.SetEventStage(auditinternal.StageResponseStarted) - rw := decorateResponseWriter(req.Context(), w, ev, sink, ac.RequestAuditConfig.OmitStages) + rw := decorateResponseWriter(req.Context(), w, true) failedHandler.ServeHTTP(rw, req) }) } diff --git a/vendor/modules.txt b/vendor/modules.txt index b1beb4f9b5..81a8741fbc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1401,7 +1401,7 @@ k8s.io/apimachinery/pkg/watch k8s.io/apimachinery/third_party/forked/golang/json k8s.io/apimachinery/third_party/forked/golang/netutil k8s.io/apimachinery/third_party/forked/golang/reflect -# k8s.io/apiserver v0.33.3 => github.com/openshift/kubernetes-apiserver v0.0.0-20250917144435-182485d204aa +# k8s.io/apiserver v0.33.3 => github.com/ingvagabund/kubernetes-apiserver v0.0.0-20251124125735-7bc3c9987f36 ## explicit; go 1.24.0 k8s.io/apiserver/pkg/admission k8s.io/apiserver/pkg/admission/configuration @@ -2451,7 +2451,7 @@ sigs.k8s.io/yaml/goyaml.v3 # k8s.io/api => k8s.io/api v0.33.3 # k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.3 # k8s.io/apimachinery => k8s.io/apimachinery v0.33.3 -# k8s.io/apiserver => github.com/openshift/kubernetes-apiserver v0.0.0-20250917144435-182485d204aa +# k8s.io/apiserver => github.com/ingvagabund/kubernetes-apiserver v0.0.0-20251124125735-7bc3c9987f36 # k8s.io/cli-runtime => k8s.io/cli-runtime v0.33.3 # k8s.io/client-go => k8s.io/client-go v0.33.3 # k8s.io/cloud-provider => k8s.io/cloud-provider v0.33.3