189 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2020 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"
 | |
| 	"sync"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/types"
 | |
| 	auditinternal "k8s.io/apiserver/pkg/apis/audit"
 | |
| 	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
 | |
| 	"k8s.io/klog/v2"
 | |
| )
 | |
| 
 | |
| // The key type is unexported to prevent collisions
 | |
| type key int
 | |
| 
 | |
| // auditKey is the context key for storing the audit context that is being
 | |
| // captured and the evaluated policy that applies to the given request.
 | |
| 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
 | |
| 	// the API audit log.
 | |
| 	Event auditinternal.Event
 | |
| 
 | |
| 	// annotationMutex guards event.Annotations
 | |
| 	annotationMutex sync.Mutex
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 | |
| 
 | |
| // AddAuditAnnotation sets the audit annotation for the given key, value pair.
 | |
| // It is safe to call at most parts of request flow that come after WithAuditAnnotations.
 | |
| // The notable exception being that this function must not be called via a
 | |
| // defer statement (i.e. after ServeHTTP) in a handler that runs before WithAudit
 | |
| // as at that point the audit event has already been sent to the audit sink.
 | |
| // Handlers that are unaware of their position in the overall request flow should
 | |
| // prefer AddAuditAnnotation over LogAnnotation to avoid dropping annotations.
 | |
| func AddAuditAnnotation(ctx context.Context, key, value string) {
 | |
| 	ac := AuditContextFrom(ctx)
 | |
| 	if !ac.Enabled() {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ac.annotationMutex.Lock()
 | |
| 	defer ac.annotationMutex.Unlock()
 | |
| 
 | |
| 	addAuditAnnotationLocked(ac, 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) {
 | |
| 	ac := AuditContextFrom(ctx)
 | |
| 	if !ac.Enabled() {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ac.annotationMutex.Lock()
 | |
| 	defer ac.annotationMutex.Unlock()
 | |
| 
 | |
| 	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(ac, 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) {
 | |
| 	ac := AuditContextFrom(ctx)
 | |
| 	if !ac.Enabled() {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ac.annotationMutex.Lock()
 | |
| 	defer ac.annotationMutex.Unlock()
 | |
| 
 | |
| 	for k, v := range annotations {
 | |
| 		addAuditAnnotationLocked(ac, k, v)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // addAuditAnnotationLocked records the audit annotation on the event.
 | |
| func addAuditAnnotationLocked(ac *AuditContext, key, value string) {
 | |
| 	ae := &ac.Event
 | |
| 
 | |
| 	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 AuditContext.
 | |
| func WithAuditContext(parent context.Context) context.Context {
 | |
| 	if AuditContextFrom(parent) != nil {
 | |
| 		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
 | |
| }
 | |
| 
 | |
| // AuditContextFrom returns the pair of the audit configuration object
 | |
| // that applies to the given request and the audit event that is going to
 | |
| // be written to the API audit log.
 | |
| func AuditContextFrom(ctx context.Context) *AuditContext {
 | |
| 	ev, _ := ctx.Value(auditKey).(*AuditContext)
 | |
| 	return ev
 | |
| }
 | |
| 
 | |
| // WithAuditID sets the AuditID on the AuditContext. The AuditContext must already be present in the
 | |
| // request context. If the specified auditID is empty, no value is set.
 | |
| func WithAuditID(ctx context.Context, auditID types.UID) {
 | |
| 	if auditID == "" {
 | |
| 		return
 | |
| 	}
 | |
| 	if ac := AuditContextFrom(ctx); ac != nil {
 | |
| 		ac.Event.AuditID = auditID
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // AuditIDFrom returns the value of the audit ID from the request context, along with whether
 | |
| // auditing is enabled.
 | |
| func AuditIDFrom(ctx context.Context) (types.UID, bool) {
 | |
| 	if ac := AuditContextFrom(ctx); ac != nil {
 | |
| 		return ac.Event.AuditID, true
 | |
| 	}
 | |
| 	return "", false
 | |
| }
 | |
| 
 | |
| // GetAuditIDTruncated returns the audit ID (truncated) from the request context.
 | |
| // If the length of the Audit-ID value exceeds the limit, we truncate it to keep
 | |
| // the first N (maxAuditIDLength) characters.
 | |
| // This is intended to be used in logging only.
 | |
| func GetAuditIDTruncated(ctx context.Context) string {
 | |
| 	auditID, ok := AuditIDFrom(ctx)
 | |
| 	if !ok {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	// if the user has specified a very long audit ID then we will use the first N characters
 | |
| 	// Note: assuming Audit-ID header is in ASCII
 | |
| 	const maxAuditIDLength = 64
 | |
| 	if len(auditID) > maxAuditIDLength {
 | |
| 		auditID = auditID[:maxAuditIDLength]
 | |
| 	}
 | |
| 
 | |
| 	return string(auditID)
 | |
| }
 |