k8s.io/apiserver@v0.31.1/pkg/audit/context.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package audit
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  
    23  	"k8s.io/apimachinery/pkg/types"
    24  	auditinternal "k8s.io/apiserver/pkg/apis/audit"
    25  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    26  	"k8s.io/klog/v2"
    27  )
    28  
    29  // The key type is unexported to prevent collisions
    30  type key int
    31  
    32  // auditKey is the context key for storing the audit context that is being
    33  // captured and the evaluated policy that applies to the given request.
    34  const auditKey key = iota
    35  
    36  // AuditContext holds the information for constructing the audit events for the current request.
    37  type AuditContext struct {
    38  	// RequestAuditConfig is the audit configuration that applies to the request
    39  	RequestAuditConfig RequestAuditConfig
    40  
    41  	// Event is the audit Event object that is being captured to be written in
    42  	// the API audit log.
    43  	Event auditinternal.Event
    44  
    45  	// annotationMutex guards event.Annotations
    46  	annotationMutex sync.Mutex
    47  }
    48  
    49  // Enabled checks whether auditing is enabled for this audit context.
    50  func (ac *AuditContext) Enabled() bool {
    51  	// Note: An unset Level should be considered Enabled, so that request data (e.g. annotations)
    52  	// can still be captured before the audit policy is evaluated.
    53  	return ac != nil && ac.RequestAuditConfig.Level != auditinternal.LevelNone
    54  }
    55  
    56  // AddAuditAnnotation sets the audit annotation for the given key, value pair.
    57  // It is safe to call at most parts of request flow that come after WithAuditAnnotations.
    58  // The notable exception being that this function must not be called via a
    59  // defer statement (i.e. after ServeHTTP) in a handler that runs before WithAudit
    60  // as at that point the audit event has already been sent to the audit sink.
    61  // Handlers that are unaware of their position in the overall request flow should
    62  // prefer AddAuditAnnotation over LogAnnotation to avoid dropping annotations.
    63  func AddAuditAnnotation(ctx context.Context, key, value string) {
    64  	ac := AuditContextFrom(ctx)
    65  	if !ac.Enabled() {
    66  		return
    67  	}
    68  
    69  	ac.annotationMutex.Lock()
    70  	defer ac.annotationMutex.Unlock()
    71  
    72  	addAuditAnnotationLocked(ac, key, value)
    73  }
    74  
    75  // AddAuditAnnotations is a bulk version of AddAuditAnnotation. Refer to AddAuditAnnotation for
    76  // restrictions on when this can be called.
    77  // keysAndValues are the key-value pairs to add, and must have an even number of items.
    78  func AddAuditAnnotations(ctx context.Context, keysAndValues ...string) {
    79  	ac := AuditContextFrom(ctx)
    80  	if !ac.Enabled() {
    81  		return
    82  	}
    83  
    84  	ac.annotationMutex.Lock()
    85  	defer ac.annotationMutex.Unlock()
    86  
    87  	if len(keysAndValues)%2 != 0 {
    88  		klog.Errorf("Dropping mismatched audit annotation %q", keysAndValues[len(keysAndValues)-1])
    89  	}
    90  	for i := 0; i < len(keysAndValues); i += 2 {
    91  		addAuditAnnotationLocked(ac, keysAndValues[i], keysAndValues[i+1])
    92  	}
    93  }
    94  
    95  // AddAuditAnnotationsMap is a bulk version of AddAuditAnnotation. Refer to AddAuditAnnotation for
    96  // restrictions on when this can be called.
    97  func AddAuditAnnotationsMap(ctx context.Context, annotations map[string]string) {
    98  	ac := AuditContextFrom(ctx)
    99  	if !ac.Enabled() {
   100  		return
   101  	}
   102  
   103  	ac.annotationMutex.Lock()
   104  	defer ac.annotationMutex.Unlock()
   105  
   106  	for k, v := range annotations {
   107  		addAuditAnnotationLocked(ac, k, v)
   108  	}
   109  }
   110  
   111  // addAuditAnnotationLocked records the audit annotation on the event.
   112  func addAuditAnnotationLocked(ac *AuditContext, key, value string) {
   113  	ae := &ac.Event
   114  
   115  	if ae.Annotations == nil {
   116  		ae.Annotations = make(map[string]string)
   117  	}
   118  	if v, ok := ae.Annotations[key]; ok && v != value {
   119  		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])
   120  		return
   121  	}
   122  	ae.Annotations[key] = value
   123  }
   124  
   125  // WithAuditContext returns a new context that stores the AuditContext.
   126  func WithAuditContext(parent context.Context) context.Context {
   127  	if AuditContextFrom(parent) != nil {
   128  		return parent // Avoid double registering.
   129  	}
   130  
   131  	return genericapirequest.WithValue(parent, auditKey, &AuditContext{})
   132  }
   133  
   134  // AuditEventFrom returns the audit event struct on the ctx
   135  func AuditEventFrom(ctx context.Context) *auditinternal.Event {
   136  	if ac := AuditContextFrom(ctx); ac.Enabled() {
   137  		return &ac.Event
   138  	}
   139  	return nil
   140  }
   141  
   142  // AuditContextFrom returns the pair of the audit configuration object
   143  // that applies to the given request and the audit event that is going to
   144  // be written to the API audit log.
   145  func AuditContextFrom(ctx context.Context) *AuditContext {
   146  	ev, _ := ctx.Value(auditKey).(*AuditContext)
   147  	return ev
   148  }
   149  
   150  // WithAuditID sets the AuditID on the AuditContext. The AuditContext must already be present in the
   151  // request context. If the specified auditID is empty, no value is set.
   152  func WithAuditID(ctx context.Context, auditID types.UID) {
   153  	if auditID == "" {
   154  		return
   155  	}
   156  	if ac := AuditContextFrom(ctx); ac != nil {
   157  		ac.Event.AuditID = auditID
   158  	}
   159  }
   160  
   161  // AuditIDFrom returns the value of the audit ID from the request context, along with whether
   162  // auditing is enabled.
   163  func AuditIDFrom(ctx context.Context) (types.UID, bool) {
   164  	if ac := AuditContextFrom(ctx); ac != nil {
   165  		return ac.Event.AuditID, true
   166  	}
   167  	return "", false
   168  }
   169  
   170  // GetAuditIDTruncated returns the audit ID (truncated) from the request context.
   171  // If the length of the Audit-ID value exceeds the limit, we truncate it to keep
   172  // the first N (maxAuditIDLength) characters.
   173  // This is intended to be used in logging only.
   174  func GetAuditIDTruncated(ctx context.Context) string {
   175  	auditID, ok := AuditIDFrom(ctx)
   176  	if !ok {
   177  		return ""
   178  	}
   179  
   180  	// if the user has specified a very long audit ID then we will use the first N characters
   181  	// Note: assuming Audit-ID header is in ASCII
   182  	const maxAuditIDLength = 64
   183  	if len(auditID) > maxAuditIDLength {
   184  		auditID = auditID[:maxAuditIDLength]
   185  	}
   186  
   187  	return string(auditID)
   188  }