Merge pull request #109078 from tallclair/audit-mutex

Audit mutex
This commit is contained in:
Kubernetes Prow Robot 2022-03-28 20:29:11 -07:00 committed by GitHub
commit c64a8cdc2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 297 additions and 104 deletions

View File

@ -190,8 +190,14 @@ func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admissi
for _, w := range result.Warnings { for _, w := range result.Warnings {
warning.AddWarning(ctx, "", w) warning.AddWarning(ctx, "", w)
} }
if len(result.AuditAnnotations) > 0 {
annotations := make([]string, len(result.AuditAnnotations)*2)
i := 0
for k, v := range result.AuditAnnotations { for k, v := range result.AuditAnnotations {
audit.AddAuditAnnotation(ctx, podsecurityadmissionapi.AuditAnnotationPrefix+k, v) annotations[i], annotations[i+1] = podsecurityadmissionapi.AuditAnnotationPrefix+k, v
i += 2
}
audit.AddAuditAnnotations(ctx, annotations...)
} }
if !result.Allowed { if !result.Allowed {
// start with a generic forbidden error // start with a generic forbidden error

View File

@ -19,19 +19,13 @@ package admission
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/audit"
) )
// auditHandler logs annotations set by other admission handlers // auditHandler logs annotations set by other admission handlers
type auditHandler struct { type auditHandler struct {
Interface Interface
// TODO: move the lock near the Annotations field of the audit event so it is always protected from concurrent access.
// to protect the 'Annotations' map of the audit event from concurrent writes
mutex sync.Mutex
ae *auditinternal.Event
} }
var _ Interface = &auditHandler{} var _ Interface = &auditHandler{}
@ -42,11 +36,11 @@ var _ ValidationInterface = &auditHandler{}
// of attribute into the audit event. Attributes passed to the Admit and // of attribute into the audit event. Attributes passed to the Admit and
// Validate function must be instance of privateAnnotationsGetter or // Validate function must be instance of privateAnnotationsGetter or
// AnnotationsGetter, otherwise an error is returned. // AnnotationsGetter, otherwise an error is returned.
func WithAudit(i Interface, ae *auditinternal.Event) Interface { func WithAudit(i Interface) Interface {
if i == nil || ae == nil { if i == nil {
return i return i
} }
return &auditHandler{Interface: i, ae: ae} return &auditHandler{Interface: i}
} }
func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error { func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
@ -59,7 +53,7 @@ func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectIn
var err error var err error
if mutator, ok := handler.Interface.(MutationInterface); ok { if mutator, ok := handler.Interface.(MutationInterface); ok {
err = mutator.Admit(ctx, a, o) err = mutator.Admit(ctx, a, o)
handler.logAnnotations(a) handler.logAnnotations(ctx, a)
} }
return err return err
} }
@ -74,7 +68,7 @@ func (handler *auditHandler) Validate(ctx context.Context, a Attributes, o Objec
var err error var err error
if validator, ok := handler.Interface.(ValidationInterface); ok { if validator, ok := handler.Interface.(ValidationInterface); ok {
err = validator.Validate(ctx, a, o) err = validator.Validate(ctx, a, o)
handler.logAnnotations(a) handler.logAnnotations(ctx, a)
} }
return err return err
} }
@ -88,23 +82,21 @@ func ensureAnnotationGetter(a Attributes) error {
return fmt.Errorf("attributes must be an instance of privateAnnotationsGetter or AnnotationsGetter") return fmt.Errorf("attributes must be an instance of privateAnnotationsGetter or AnnotationsGetter")
} }
func (handler *auditHandler) logAnnotations(a Attributes) { func (handler *auditHandler) logAnnotations(ctx context.Context, a Attributes) {
if handler.ae == nil { ae := audit.AuditEventFrom(ctx)
if ae == nil {
return return
} }
handler.mutex.Lock()
defer handler.mutex.Unlock()
var annotations map[string]string
switch a := a.(type) { switch a := a.(type) {
case privateAnnotationsGetter: case privateAnnotationsGetter:
for key, value := range a.getAnnotations(handler.ae.Level) { annotations = a.getAnnotations(ae.Level)
audit.LogAnnotation(handler.ae, key, value)
}
case AnnotationsGetter: case AnnotationsGetter:
for key, value := range a.GetAnnotations(handler.ae.Level) { annotations = a.GetAnnotations(ae.Level)
audit.LogAnnotation(handler.ae, key, value)
}
default: default:
// this will never happen, because we have already checked it in ensureAnnotationGetter // this will never happen, because we have already checked it in ensureAnnotationGetter
} }
audit.AddAuditAnnotationsMap(ctx, annotations)
} }

View File

@ -24,6 +24,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
auditinternal "k8s.io/apiserver/pkg/apis/audit" auditinternal "k8s.io/apiserver/pkg/apis/audit"
"k8s.io/apiserver/pkg/audit"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -142,7 +143,8 @@ func TestWithAudit(t *testing.T) {
for tcName, tc := range testCases { for tcName, tc := range testCases {
var handler Interface = fakeHandler{tc.admit, tc.admitAnnotations, tc.validate, tc.validateAnnotations, tc.handles} var handler Interface = fakeHandler{tc.admit, tc.admitAnnotations, tc.validate, tc.validateAnnotations, tc.handles}
ae := &auditinternal.Event{Level: auditinternal.LevelMetadata} ae := &auditinternal.Event{Level: auditinternal.LevelMetadata}
auditHandler := WithAudit(handler, ae) ctx := audit.WithAuditContext(context.Background(), &audit.AuditContext{Event: ae})
auditHandler := WithAudit(handler)
a := attributes() a := attributes()
assert.Equal(t, handler.Handles(Create), auditHandler.Handles(Create), tcName+": WithAudit decorator should not effect the return value") assert.Equal(t, handler.Handles(Create), auditHandler.Handles(Create), tcName+": WithAudit decorator should not effect the return value")
@ -151,13 +153,13 @@ func TestWithAudit(t *testing.T) {
require.True(t, ok) require.True(t, ok)
auditMutator, ok := auditHandler.(MutationInterface) auditMutator, ok := auditHandler.(MutationInterface)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, mutator.Admit(context.TODO(), a, nil), auditMutator.Admit(context.TODO(), a, nil), tcName+": WithAudit decorator should not effect the return value") assert.Equal(t, mutator.Admit(ctx, a, nil), auditMutator.Admit(ctx, a, nil), tcName+": WithAudit decorator should not effect the return value")
validator, ok := handler.(ValidationInterface) validator, ok := handler.(ValidationInterface)
require.True(t, ok) require.True(t, ok)
auditValidator, ok := auditHandler.(ValidationInterface) auditValidator, ok := auditHandler.(ValidationInterface)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, validator.Validate(context.TODO(), a, nil), auditValidator.Validate(context.TODO(), a, nil), tcName+": WithAudit decorator should not effect the return value") assert.Equal(t, validator.Validate(ctx, a, nil), auditValidator.Validate(ctx, a, nil), tcName+": WithAudit decorator should not effect the return value")
annotations := make(map[string]string, len(tc.admitAnnotations)+len(tc.validateAnnotations)) annotations := make(map[string]string, len(tc.admitAnnotations)+len(tc.validateAnnotations))
for k, v := range tc.admitAnnotations { for k, v := range tc.admitAnnotations {
@ -183,7 +185,8 @@ func TestWithAuditConcurrency(t *testing.T) {
} }
var handler Interface = fakeHandler{admitAnnotations: admitAnnotations, handles: true} var handler Interface = fakeHandler{admitAnnotations: admitAnnotations, handles: true}
ae := &auditinternal.Event{Level: auditinternal.LevelMetadata} ae := &auditinternal.Event{Level: auditinternal.LevelMetadata}
auditHandler := WithAudit(handler, ae) ctx := audit.WithAuditContext(context.Background(), &audit.AuditContext{Event: ae})
auditHandler := WithAudit(handler)
a := attributes() a := attributes()
// Simulate the scenario store.DeleteCollection // Simulate the scenario store.DeleteCollection
@ -197,7 +200,7 @@ func TestWithAuditConcurrency(t *testing.T) {
require.True(t, ok) require.True(t, ok)
auditMutator, ok := auditHandler.(MutationInterface) auditMutator, ok := auditHandler.(MutationInterface)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, mutator.Admit(context.TODO(), a, nil), auditMutator.Admit(context.TODO(), a, nil), "WithAudit decorator should not effect the return value") assert.Equal(t, mutator.Admit(ctx, a, nil), auditMutator.Admit(ctx, a, nil), "WithAudit decorator should not effect the return value")
}() }()
} }
wg.Wait() wg.Wait()

View File

@ -18,9 +18,12 @@ package audit
import ( import (
"context" "context"
"fmt"
"sync"
auditinternal "k8s.io/apiserver/pkg/apis/audit" auditinternal "k8s.io/apiserver/pkg/apis/audit"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request" genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/klog/v2"
) )
// The key type is unexported to prevent collisions // The key type is unexported to prevent collisions
@ -28,15 +31,15 @@ type key int
const ( const (
// auditAnnotationsKey is the context key for the audit annotations. // auditAnnotationsKey is the context key for the audit annotations.
// TODO: it's wasteful to store the audit annotations under a separate key, we // TODO: consolidate all audit info under the AuditContext, rather than storing 3 separate keys.
// copy the request context twice for audit purposes. We should move the audit
// annotations under AuditContext so we can get rid of the additional request
// context copy.
auditAnnotationsKey key = iota auditAnnotationsKey key = iota
// auditKey is the context key for storing the audit event that is being // auditKey is the context key for storing the audit event that is being
// captured and the evaluated policy that applies to the given request. // captured and the evaluated policy that applies to the given request.
auditKey auditKey
// auditAnnotationsMutexKey is the context key for the audit annotations mutex.
auditAnnotationsMutexKey
) )
// annotations = *[]annotation instead of a map to preserve order of insertions // annotations = *[]annotation instead of a map to preserve order of insertions
@ -54,6 +57,7 @@ func WithAuditAnnotations(parent context.Context) context.Context {
if _, ok := parent.Value(auditAnnotationsKey).(*[]annotation); ok { if _, ok := parent.Value(auditAnnotationsKey).(*[]annotation); ok {
return parent return parent
} }
parent = withAuditAnnotationsMutex(parent)
var annotations []annotation // avoid allocations until we actually need it var annotations []annotation // avoid allocations until we actually need it
return genericapirequest.WithValue(parent, auditAnnotationsKey, &annotations) return genericapirequest.WithValue(parent, auditAnnotationsKey, &annotations)
@ -67,35 +71,126 @@ func WithAuditAnnotations(parent context.Context) context.Context {
// Handlers that are unaware of their position in the overall request flow should // Handlers that are unaware of their position in the overall request flow should
// prefer AddAuditAnnotation over LogAnnotation to avoid dropping annotations. // prefer AddAuditAnnotation over LogAnnotation to avoid dropping annotations.
func AddAuditAnnotation(ctx context.Context, key, value string) { func AddAuditAnnotation(ctx context.Context, key, value string) {
// use the audit event directly if we have it mutex, ok := auditAnnotationsMutex(ctx)
if ae := AuditEventFrom(ctx); ae != nil { if !ok {
LogAnnotation(ae, key, value) klog.ErrorS(nil, "Attempted to add audit annotations from unsupported request chain", "annotation", fmt.Sprintf("%s=%s", key, value))
return return
} }
annotations, ok := ctx.Value(auditAnnotationsKey).(*[]annotation) mutex.Lock()
if !ok { defer mutex.Unlock()
return // adding audit annotation is not supported at this call site
ae := AuditEventFrom(ctx)
var ctxAnnotations *[]annotation
if ae == nil {
ctxAnnotations, _ = ctx.Value(auditAnnotationsKey).(*[]annotation)
} }
addAuditAnnotationLocked(ae, ctxAnnotations, key, value)
}
// AddAuditAnnotations is a bulk version of AddAuditAnnotation. Refer to AddAuditAnnotation for
// restrictions on when this can be called.
// keysAndValues are the key-value pairs to add, and must have an even number of items.
func AddAuditAnnotations(ctx context.Context, keysAndValues ...string) {
mutex, ok := auditAnnotationsMutex(ctx)
if !ok {
klog.ErrorS(nil, "Attempted to add audit annotations from unsupported request chain", "annotations", keysAndValues)
return
}
mutex.Lock()
defer mutex.Unlock()
ae := AuditEventFrom(ctx)
var ctxAnnotations *[]annotation
if ae == nil {
ctxAnnotations, _ = ctx.Value(auditAnnotationsKey).(*[]annotation)
}
if len(keysAndValues)%2 != 0 {
klog.Errorf("Dropping mismatched audit annotation %q", keysAndValues[len(keysAndValues)-1])
}
for i := 0; i < len(keysAndValues); i += 2 {
addAuditAnnotationLocked(ae, ctxAnnotations, keysAndValues[i], keysAndValues[i+1])
}
}
// AddAuditAnnotationsMap is a bulk version of AddAuditAnnotation. Refer to AddAuditAnnotation for
// restrictions on when this can be called.
func AddAuditAnnotationsMap(ctx context.Context, annotations map[string]string) {
mutex, ok := auditAnnotationsMutex(ctx)
if !ok {
klog.ErrorS(nil, "Attempted to add audit annotations from unsupported request chain", "annotations", annotations)
return
}
mutex.Lock()
defer mutex.Unlock()
ae := AuditEventFrom(ctx)
var ctxAnnotations *[]annotation
if ae == nil {
ctxAnnotations, _ = ctx.Value(auditAnnotationsKey).(*[]annotation)
}
for k, v := range annotations {
addAuditAnnotationLocked(ae, ctxAnnotations, k, v)
}
}
// addAuditAnnotationLocked is the shared code for recording an audit annotation. This method should
// only be called while the auditAnnotationsMutex is locked.
func addAuditAnnotationLocked(ae *auditinternal.Event, annotations *[]annotation, key, value string) {
if ae != nil {
logAnnotation(ae, key, value)
} else if annotations != nil {
*annotations = append(*annotations, annotation{key: key, value: value}) *annotations = append(*annotations, annotation{key: key, value: value})
} }
}
// This is private to prevent reads/write to the slice from outside of this package. // This is private to prevent reads/write to the slice from outside of this package.
// The audit event should be directly read to get access to the annotations. // The audit event should be directly read to get access to the annotations.
func auditAnnotationsFrom(ctx context.Context) []annotation { func addAuditAnnotationsFrom(ctx context.Context, ev *auditinternal.Event) {
annotations, ok := ctx.Value(auditAnnotationsKey).(*[]annotation) mutex, ok := auditAnnotationsMutex(ctx)
if !ok { if !ok {
return nil // adding audit annotation is not supported at this call site klog.Errorf("Attempted to copy audit annotations from unsupported request chain")
return
} }
return *annotations mutex.Lock()
defer mutex.Unlock()
annotations, ok := ctx.Value(auditAnnotationsKey).(*[]annotation)
if !ok {
return // no annotations to copy
}
for _, kv := range *annotations {
logAnnotation(ev, kv.key, kv.value)
}
}
// LogAnnotation fills in the Annotations according to the key value pair.
func logAnnotation(ae *auditinternal.Event, key, value string) {
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
return
}
if ae.Annotations == nil {
ae.Annotations = make(map[string]string)
}
if v, ok := ae.Annotations[key]; ok && v != value {
klog.Warningf("Failed to set annotations[%q] to %q for audit:%q, it has already been set to %q", key, value, ae.AuditID, ae.Annotations[key])
return
}
ae.Annotations[key] = value
} }
// WithAuditContext returns a new context that stores the pair of the audit // WithAuditContext returns a new context that stores the pair of the audit
// configuration object that applies to the given request and // configuration object that applies to the given request and
// the audit event that is going to be written to the API audit log. // the audit event that is going to be written to the API audit log.
func WithAuditContext(parent context.Context, ev *AuditContext) context.Context { func WithAuditContext(parent context.Context, ev *AuditContext) context.Context {
parent = withAuditAnnotationsMutex(parent)
return genericapirequest.WithValue(parent, auditKey, ev) return genericapirequest.WithValue(parent, auditKey, ev)
} }
@ -114,3 +209,18 @@ func AuditContextFrom(ctx context.Context) *AuditContext {
ev, _ := ctx.Value(auditKey).(*AuditContext) ev, _ := ctx.Value(auditKey).(*AuditContext)
return ev return ev
} }
// WithAuditAnnotationMutex adds a mutex for guarding context.AddAuditAnnotation.
func withAuditAnnotationsMutex(parent context.Context) context.Context {
if _, ok := parent.Value(auditAnnotationsMutexKey).(*sync.Mutex); ok {
return parent
}
var mutex sync.Mutex
return genericapirequest.WithValue(parent, auditAnnotationsMutexKey, &mutex)
}
// AuditAnnotationsMutex returns the audit annotations mutex from the context.
func auditAnnotationsMutex(ctx context.Context) (*sync.Mutex, bool) {
mutex, ok := ctx.Value(auditAnnotationsMutexKey).(*sync.Mutex)
return mutex, ok
}

View File

@ -0,0 +1,120 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package audit
import (
"context"
"fmt"
"sync"
"testing"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"github.com/stretchr/testify/assert"
)
func TestAddAuditAnnotation(t *testing.T) {
const (
annotationKeyTemplate = "test-annotation-%d"
annotationValue = "test-annotation-value"
numAnnotations = 10
)
expectAnnotations := func(t *testing.T, annotations map[string]string) {
assert.Len(t, annotations, numAnnotations)
}
noopValidator := func(_ *testing.T, _ context.Context) {}
preEventValidator := func(t *testing.T, ctx context.Context) {
ev := auditinternal.Event{
Level: auditinternal.LevelMetadata,
}
addAuditAnnotationsFrom(ctx, &ev)
expectAnnotations(t, ev.Annotations)
}
postEventValidator := func(t *testing.T, ctx context.Context) {
ev := AuditEventFrom(ctx)
expectAnnotations(t, ev.Annotations)
}
postEventEmptyValidator := func(t *testing.T, ctx context.Context) {
ev := AuditEventFrom(ctx)
assert.Empty(t, ev.Annotations)
}
tests := []struct {
description string
ctx context.Context
validator func(t *testing.T, ctx context.Context)
}{{
description: "no audit",
ctx: context.Background(),
validator: noopValidator,
}, {
description: "no annotations context",
ctx: WithAuditContext(context.Background(), newAuditContext(auditinternal.LevelMetadata)),
validator: postEventValidator,
}, {
description: "no audit context",
ctx: WithAuditAnnotations(context.Background()),
validator: preEventValidator,
}, {
description: "both contexts metadata level",
ctx: WithAuditContext(WithAuditAnnotations(context.Background()), newAuditContext(auditinternal.LevelMetadata)),
validator: postEventValidator,
}, {
description: "both contexts none level",
ctx: WithAuditContext(WithAuditAnnotations(context.Background()), newAuditContext(auditinternal.LevelNone)),
validator: postEventEmptyValidator,
}}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
var wg sync.WaitGroup
wg.Add(numAnnotations)
for i := 0; i < numAnnotations; i++ {
go func(i int) {
AddAuditAnnotation(test.ctx, fmt.Sprintf(annotationKeyTemplate, i), annotationValue)
wg.Done()
}(i)
}
wg.Wait()
test.validator(t, test.ctx)
})
}
}
func TestLogAnnotation(t *testing.T) {
ev := &auditinternal.Event{
Level: auditinternal.LevelMetadata,
AuditID: "fake id",
}
logAnnotation(ev, "foo", "bar")
logAnnotation(ev, "foo", "baz")
assert.Equal(t, "bar", ev.Annotations["foo"], "audit annotation should not be overwritten.")
logAnnotation(ev, "qux", "")
logAnnotation(ev, "qux", "baz")
assert.Equal(t, "", ev.Annotations["qux"], "audit annotation should not be overwritten.")
}
func newAuditContext(l auditinternal.Level) *AuditContext {
return &AuditContext{
Event: &auditinternal.Event{
Level: l,
},
}
}

View File

@ -87,9 +87,7 @@ func NewEventFromRequest(req *http.Request, requestReceivedTimestamp time.Time,
} }
} }
for _, kv := range auditAnnotationsFrom(req.Context()) { addAuditAnnotationsFrom(req.Context(), ev)
LogAnnotation(ev, kv.key, kv.value)
}
return ev, nil return ev, nil
} }
@ -245,21 +243,6 @@ func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime
}, nil }, nil
} }
// LogAnnotation fills in the Annotations according to the key value pair.
func LogAnnotation(ae *auditinternal.Event, key, value string) {
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
return
}
if ae.Annotations == nil {
ae.Annotations = make(map[string]string)
}
if v, ok := ae.Annotations[key]; ok && v != value {
klog.Warningf("Failed to set annotations[%q] to %q for audit:%q, it has already been set to %q", key, value, ae.AuditID, ae.Annotations[key])
return
}
ae.Annotations[key] = value
}
// truncate User-Agent if too long, otherwise return it directly. // truncate User-Agent if too long, otherwise return it directly.
func maybeTruncateUserAgent(req *http.Request) string { func maybeTruncateUserAgent(req *http.Request) string {
ua := req.UserAgent() ua := req.UserAgent()

View File

@ -25,26 +25,11 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestLogAnnotation(t *testing.T) {
ev := &auditinternal.Event{
Level: auditinternal.LevelMetadata,
AuditID: "fake id",
}
LogAnnotation(ev, "foo", "bar")
LogAnnotation(ev, "foo", "baz")
assert.Equal(t, "bar", ev.Annotations["foo"], "audit annotation should not be overwritten.")
LogAnnotation(ev, "qux", "")
LogAnnotation(ev, "qux", "baz")
assert.Equal(t, "", ev.Annotations["qux"], "audit annotation should not be overwritten.")
}
func TestMaybeTruncateUserAgent(t *testing.T) { func TestMaybeTruncateUserAgent(t *testing.T) {
req := &http.Request{} req := &http.Request{}
req.Header = http.Header{} req.Header = http.Header{}

View File

@ -177,10 +177,8 @@ func writeLatencyToAnnotation(ctx context.Context, ev *auditinternal.Event) {
} }
// record the total latency for this request, for convenience. // record the total latency for this request, for convenience.
audit.LogAnnotation(ev, "apiserver.latency.k8s.io/total", latency.String()) layerLatencies["apiserver.latency.k8s.io/total"] = latency.String()
for k, v := range layerLatencies { audit.AddAuditAnnotationsMap(ctx, layerLatencies)
audit.LogAnnotation(ev, k, v)
}
} }
func processAuditEvent(ctx context.Context, sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool { func processAuditEvent(ctx context.Context, sink audit.Sink, ev *auditinternal.Event, omitStages []auditinternal.Stage) bool {

View File

@ -49,7 +49,6 @@ func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.
} }
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context() ctx := req.Context()
ae := audit.AuditEventFrom(ctx)
attributes, err := GetAuthorizerAttributes(ctx) attributes, err := GetAuthorizerAttributes(ctx)
if err != nil { if err != nil {
@ -59,20 +58,22 @@ func WithAuthorization(handler http.Handler, a authorizer.Authorizer, s runtime.
authorized, reason, err := a.Authorize(ctx, attributes) authorized, reason, err := a.Authorize(ctx, attributes)
// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here. // an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
if authorized == authorizer.DecisionAllow { if authorized == authorizer.DecisionAllow {
audit.LogAnnotation(ae, decisionAnnotationKey, decisionAllow) audit.AddAuditAnnotations(ctx,
audit.LogAnnotation(ae, reasonAnnotationKey, reason) decisionAnnotationKey, decisionAllow,
reasonAnnotationKey, reason)
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
return return
} }
if err != nil { if err != nil {
audit.LogAnnotation(ae, reasonAnnotationKey, reasonError) audit.AddAuditAnnotation(ctx, reasonAnnotationKey, reasonError)
responsewriters.InternalError(w, req, err) responsewriters.InternalError(w, req, err)
return return
} }
klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "Reason", reason) klog.V(4).InfoS("Forbidden", "URI", req.RequestURI, "Reason", reason)
audit.LogAnnotation(ae, decisionAnnotationKey, decisionForbid) audit.AddAuditAnnotations(ctx,
audit.LogAnnotation(ae, reasonAnnotationKey, reason) decisionAnnotationKey, decisionForbid,
reasonAnnotationKey, reason)
responsewriters.Forbidden(ctx, attributes, w, req, reason, s) responsewriters.Forbidden(ctx, attributes, w, req, reason, s)
}) })
} }

View File

@ -157,8 +157,7 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
} }
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
ae := audit.AuditEventFrom(ctx) admit = admission.WithAudit(admit)
admit = admission.WithAudit(admit, ae)
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer) audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
userInfo, _ := request.UserFrom(ctx) userInfo, _ := request.UserFrom(ctx)

View File

@ -67,8 +67,7 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
defer cancel() defer cancel()
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
ae := audit.AuditEventFrom(ctx) admit = admission.WithAudit(admit)
admit = admission.WithAudit(admit, ae)
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
if err != nil { if err != nil {
@ -190,7 +189,6 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
defer cancel() defer cancel()
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
ae := audit.AuditEventFrom(ctx)
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope) outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
if err != nil { if err != nil {
@ -268,7 +266,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
} }
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions")) options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
admit = admission.WithAudit(admit, ae) admit = admission.WithAudit(admit)
userInfo, _ := request.UserFrom(ctx) userInfo, _ := request.UserFrom(ctx)
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo) staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) { result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {

View File

@ -124,8 +124,7 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
} }
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions")) options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions"))
ae := audit.AuditEventFrom(ctx) admit = admission.WithAudit(admit)
admit = admission.WithAudit(admit, ae)
audit.LogRequestPatch(req.Context(), patchBytes) audit.LogRequestPatch(req.Context(), patchBytes)
trace.Step("Recorded the audit event") trace.Step("Recorded the audit event")

View File

@ -39,7 +39,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
@ -191,8 +190,7 @@ func ConnectResource(connecter rest.Connecter, scope *RequestScope, admit admiss
} }
ctx := req.Context() ctx := req.Context()
ctx = request.WithNamespace(ctx, namespace) ctx = request.WithNamespace(ctx, namespace)
ae := audit.AuditEventFrom(ctx) admit = admission.WithAudit(admit)
admit = admission.WithAudit(admit, ae)
opts, subpath, subpathKey := connecter.NewConnectOptions() opts, subpath, subpathKey := connecter.NewConnectOptions()
if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil { if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil {

View File

@ -135,9 +135,8 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
} }
trace.Step("Conversion done") trace.Step("Conversion done")
ae := audit.AuditEventFrom(ctx)
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer) audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
admit = admission.WithAudit(admit, ae) admit = admission.WithAudit(admit)
// if this object supports namespace info // if this object supports namespace info
if objectMeta, err := meta.Accessor(obj); err == nil { if objectMeta, err := meta.Accessor(obj); err == nil {

View File

@ -312,9 +312,6 @@ func TestAuthenticationAuditAnnotationsDefaultChain(t *testing.T) {
t.Error("unexpected nil audit event") t.Error("unexpected nil audit event")
} }
// confirm that the direct way of setting audit annotations later in the chain works as expected
audit.LogAnnotation(ae, "snorlax", "is cool too")
// confirm that the indirect way of setting audit annotations later in the chain also works // confirm that the indirect way of setting audit annotations later in the chain also works
audit.AddAuditAnnotation(r.Context(), "dogs", "are okay") audit.AddAuditAnnotation(r.Context(), "dogs", "are okay")
@ -334,7 +331,7 @@ func TestAuthenticationAuditAnnotationsDefaultChain(t *testing.T) {
t.Error("expected audit events, got none") t.Error("expected audit events, got none")
} }
// these should all be the same because the handler chain mutates the event in place // these should all be the same because the handler chain mutates the event in place
want := map[string]string{"pandas": "are awesome", "snorlax": "is cool too", "dogs": "are okay"} want := map[string]string{"pandas": "are awesome", "dogs": "are okay"}
for _, event := range backend.events { for _, event := range backend.events {
if event.Stage != auditinternal.StageResponseComplete { if event.Stage != auditinternal.StageResponseComplete {
t.Errorf("expected event stage to be complete, got: %s", event.Stage) t.Errorf("expected event stage to be complete, got: %s", event.Stage)

View File

@ -126,7 +126,12 @@ func truncate(e *auditinternal.Event) *auditinternal.Event {
newEvent.RequestObject = nil newEvent.RequestObject = nil
newEvent.ResponseObject = nil newEvent.ResponseObject = nil
audit.LogAnnotation(newEvent, annotationKey, annotationValue)
if newEvent.Annotations == nil {
newEvent.Annotations = make(map[string]string)
}
newEvent.Annotations[annotationKey] = annotationValue
return newEvent return newEvent
} }