k8s.io/apiserver@v0.31.1/pkg/audit/request.go (about) 1 /* 2 Copyright 2017 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 "bytes" 21 "context" 22 "fmt" 23 "net/http" 24 "time" 25 26 authnv1 "k8s.io/api/authentication/v1" 27 "k8s.io/apimachinery/pkg/api/meta" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 utilnet "k8s.io/apimachinery/pkg/util/net" 32 auditinternal "k8s.io/apiserver/pkg/apis/audit" 33 "k8s.io/apiserver/pkg/authentication/user" 34 "k8s.io/apiserver/pkg/authorization/authorizer" 35 "k8s.io/klog/v2" 36 ) 37 38 const ( 39 maxUserAgentLength = 1024 40 userAgentTruncateSuffix = "...TRUNCATED" 41 ) 42 43 func LogRequestMetadata(ctx context.Context, req *http.Request, requestReceivedTimestamp time.Time, level auditinternal.Level, attribs authorizer.Attributes) { 44 ac := AuditContextFrom(ctx) 45 if !ac.Enabled() { 46 return 47 } 48 ev := &ac.Event 49 50 ev.RequestReceivedTimestamp = metav1.NewMicroTime(requestReceivedTimestamp) 51 ev.Verb = attribs.GetVerb() 52 ev.RequestURI = req.URL.RequestURI() 53 ev.UserAgent = maybeTruncateUserAgent(req) 54 ev.Level = level 55 56 ips := utilnet.SourceIPs(req) 57 ev.SourceIPs = make([]string, len(ips)) 58 for i := range ips { 59 ev.SourceIPs[i] = ips[i].String() 60 } 61 62 if user := attribs.GetUser(); user != nil { 63 ev.User.Username = user.GetName() 64 ev.User.Extra = map[string]authnv1.ExtraValue{} 65 for k, v := range user.GetExtra() { 66 ev.User.Extra[k] = authnv1.ExtraValue(v) 67 } 68 ev.User.Groups = user.GetGroups() 69 ev.User.UID = user.GetUID() 70 } 71 72 if attribs.IsResourceRequest() { 73 ev.ObjectRef = &auditinternal.ObjectReference{ 74 Namespace: attribs.GetNamespace(), 75 Name: attribs.GetName(), 76 Resource: attribs.GetResource(), 77 Subresource: attribs.GetSubresource(), 78 APIGroup: attribs.GetAPIGroup(), 79 APIVersion: attribs.GetAPIVersion(), 80 } 81 } 82 } 83 84 // LogImpersonatedUser fills in the impersonated user attributes into an audit event. 85 func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) { 86 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 87 return 88 } 89 ae.ImpersonatedUser = &authnv1.UserInfo{ 90 Username: user.GetName(), 91 } 92 ae.ImpersonatedUser.Groups = user.GetGroups() 93 ae.ImpersonatedUser.UID = user.GetUID() 94 ae.ImpersonatedUser.Extra = map[string]authnv1.ExtraValue{} 95 for k, v := range user.GetExtra() { 96 ae.ImpersonatedUser.Extra[k] = authnv1.ExtraValue(v) 97 } 98 } 99 100 // LogRequestObject fills in the request object into an audit event. The passed runtime.Object 101 // will be converted to the given gv. 102 func LogRequestObject(ctx context.Context, obj runtime.Object, objGV schema.GroupVersion, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) { 103 ae := AuditEventFrom(ctx) 104 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 105 return 106 } 107 108 // complete ObjectRef 109 if ae.ObjectRef == nil { 110 ae.ObjectRef = &auditinternal.ObjectReference{} 111 } 112 113 // meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits 114 if meta, err := meta.Accessor(obj); err == nil { 115 if len(ae.ObjectRef.Namespace) == 0 { 116 ae.ObjectRef.Namespace = meta.GetNamespace() 117 } 118 if len(ae.ObjectRef.Name) == 0 { 119 ae.ObjectRef.Name = meta.GetName() 120 } 121 if len(ae.ObjectRef.UID) == 0 { 122 ae.ObjectRef.UID = meta.GetUID() 123 } 124 if len(ae.ObjectRef.ResourceVersion) == 0 { 125 ae.ObjectRef.ResourceVersion = meta.GetResourceVersion() 126 } 127 } 128 if len(ae.ObjectRef.APIVersion) == 0 { 129 ae.ObjectRef.APIGroup = gvr.Group 130 ae.ObjectRef.APIVersion = gvr.Version 131 } 132 if len(ae.ObjectRef.Resource) == 0 { 133 ae.ObjectRef.Resource = gvr.Resource 134 } 135 if len(ae.ObjectRef.Subresource) == 0 { 136 ae.ObjectRef.Subresource = subresource 137 } 138 139 if ae.Level.Less(auditinternal.LevelRequest) { 140 return 141 } 142 143 if shouldOmitManagedFields(ctx) { 144 copy, ok, err := copyWithoutManagedFields(obj) 145 if err != nil { 146 klog.ErrorS(err, "Error while dropping managed fields from the request", "auditID", ae.AuditID) 147 } 148 if ok { 149 obj = copy 150 } 151 } 152 153 // TODO(audit): hook into the serializer to avoid double conversion 154 var err error 155 ae.RequestObject, err = encodeObject(obj, objGV, s) 156 if err != nil { 157 // TODO(audit): add error slice to audit event struct 158 klog.ErrorS(err, "Encoding failed of request object", "auditID", ae.AuditID, "gvr", gvr.String(), "obj", obj) 159 return 160 } 161 } 162 163 // LogRequestPatch fills in the given patch as the request object into an audit event. 164 func LogRequestPatch(ctx context.Context, patch []byte) { 165 ae := AuditEventFrom(ctx) 166 if ae == nil || ae.Level.Less(auditinternal.LevelRequest) { 167 return 168 } 169 170 ae.RequestObject = &runtime.Unknown{ 171 Raw: patch, 172 ContentType: runtime.ContentTypeJSON, 173 } 174 } 175 176 // LogResponseObject fills in the response object into an audit event. The passed runtime.Object 177 // will be converted to the given gv. 178 func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) { 179 ae := AuditEventFrom(ctx) 180 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 181 return 182 } 183 if status, ok := obj.(*metav1.Status); ok { 184 // selectively copy the bounded fields. 185 ae.ResponseStatus = &metav1.Status{ 186 Status: status.Status, 187 Message: status.Message, 188 Reason: status.Reason, 189 Details: status.Details, 190 Code: status.Code, 191 } 192 } 193 194 if ae.Level.Less(auditinternal.LevelRequestResponse) { 195 return 196 } 197 198 if shouldOmitManagedFields(ctx) { 199 copy, ok, err := copyWithoutManagedFields(obj) 200 if err != nil { 201 klog.ErrorS(err, "Error while dropping managed fields from the response", "auditID", ae.AuditID) 202 } 203 if ok { 204 obj = copy 205 } 206 } 207 208 // TODO(audit): hook into the serializer to avoid double conversion 209 var err error 210 ae.ResponseObject, err = encodeObject(obj, gv, s) 211 if err != nil { 212 klog.ErrorS(err, "Encoding failed of response object", "auditID", ae.AuditID, "obj", obj) 213 } 214 } 215 216 func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) { 217 const mediaType = runtime.ContentTypeJSON 218 info, ok := runtime.SerializerInfoForMediaType(serializer.SupportedMediaTypes(), mediaType) 219 if !ok { 220 return nil, fmt.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType) 221 } 222 223 enc := serializer.EncoderForVersion(info.Serializer, gv) 224 var buf bytes.Buffer 225 if err := enc.Encode(obj, &buf); err != nil { 226 return nil, fmt.Errorf("encoding failed: %v", err) 227 } 228 229 return &runtime.Unknown{ 230 Raw: buf.Bytes(), 231 ContentType: mediaType, 232 }, nil 233 } 234 235 // truncate User-Agent if too long, otherwise return it directly. 236 func maybeTruncateUserAgent(req *http.Request) string { 237 ua := req.UserAgent() 238 if len(ua) > maxUserAgentLength { 239 ua = ua[:maxUserAgentLength] + userAgentTruncateSuffix 240 } 241 242 return ua 243 } 244 245 // copyWithoutManagedFields will make a deep copy of the specified object and 246 // will discard the managed fields from the copy. 247 // The specified object is expected to be a meta.Object or a "list". 248 // The specified object obj is treated as readonly and hence not mutated. 249 // On return, an error is set if the function runs into any error while 250 // removing the managed fields, the boolean value is true if the copy has 251 // been made successfully, otherwise false. 252 func copyWithoutManagedFields(obj runtime.Object) (runtime.Object, bool, error) { 253 isAccessor := true 254 if _, err := meta.Accessor(obj); err != nil { 255 isAccessor = false 256 } 257 isList := meta.IsListType(obj) 258 _, isTable := obj.(*metav1.Table) 259 if !isAccessor && !isList && !isTable { 260 return nil, false, nil 261 } 262 263 // TODO a deep copy isn't really needed here, figure out how we can reliably 264 // use shallow copy here to omit the manageFields. 265 copy := obj.DeepCopyObject() 266 267 if isAccessor { 268 if err := removeManagedFields(copy); err != nil { 269 return nil, false, err 270 } 271 } 272 273 if isList { 274 if err := meta.EachListItem(copy, removeManagedFields); err != nil { 275 return nil, false, err 276 } 277 } 278 279 if isTable { 280 table := copy.(*metav1.Table) 281 for i := range table.Rows { 282 rowObj := table.Rows[i].Object 283 if err := removeManagedFields(rowObj.Object); err != nil { 284 return nil, false, err 285 } 286 } 287 } 288 289 return copy, true, nil 290 } 291 292 func removeManagedFields(obj runtime.Object) error { 293 if obj == nil { 294 return nil 295 } 296 accessor, err := meta.Accessor(obj) 297 if err != nil { 298 return err 299 } 300 accessor.SetManagedFields(nil) 301 return nil 302 } 303 304 func shouldOmitManagedFields(ctx context.Context) bool { 305 if auditContext := AuditContextFrom(ctx); auditContext != nil { 306 return auditContext.RequestAuditConfig.OmitManagedFields 307 } 308 309 // If we can't decide, return false to maintain current behavior which is 310 // to retain the manage fields in the audit. 311 return false 312 }