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 }