1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package audit 18 19import ( 20 "bytes" 21 "fmt" 22 "net/http" 23 "reflect" 24 "time" 25 26 "github.com/google/uuid" 27 "k8s.io/klog/v2" 28 29 authnv1 "k8s.io/api/authentication/v1" 30 "k8s.io/apimachinery/pkg/api/meta" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apimachinery/pkg/types" 35 utilnet "k8s.io/apimachinery/pkg/util/net" 36 auditinternal "k8s.io/apiserver/pkg/apis/audit" 37 "k8s.io/apiserver/pkg/authentication/user" 38 "k8s.io/apiserver/pkg/authorization/authorizer" 39) 40 41const ( 42 maxUserAgentLength = 1024 43 userAgentTruncateSuffix = "...TRUNCATED" 44) 45 46func NewEventFromRequest(req *http.Request, requestReceivedTimestamp time.Time, level auditinternal.Level, attribs authorizer.Attributes) (*auditinternal.Event, error) { 47 ev := &auditinternal.Event{ 48 RequestReceivedTimestamp: metav1.NewMicroTime(requestReceivedTimestamp), 49 Verb: attribs.GetVerb(), 50 RequestURI: req.URL.RequestURI(), 51 UserAgent: maybeTruncateUserAgent(req), 52 Level: level, 53 } 54 55 // prefer the id from the headers. If not available, create a new one. 56 // TODO(audit): do we want to forbid the header for non-front-proxy users? 57 ids := req.Header.Get(auditinternal.HeaderAuditID) 58 if ids != "" { 59 ev.AuditID = types.UID(ids) 60 } else { 61 ev.AuditID = types.UID(uuid.New().String()) 62 } 63 64 ips := utilnet.SourceIPs(req) 65 ev.SourceIPs = make([]string, len(ips)) 66 for i := range ips { 67 ev.SourceIPs[i] = ips[i].String() 68 } 69 70 if user := attribs.GetUser(); user != nil { 71 ev.User.Username = user.GetName() 72 ev.User.Extra = map[string]authnv1.ExtraValue{} 73 for k, v := range user.GetExtra() { 74 ev.User.Extra[k] = authnv1.ExtraValue(v) 75 } 76 ev.User.Groups = user.GetGroups() 77 ev.User.UID = user.GetUID() 78 } 79 80 if attribs.IsResourceRequest() { 81 ev.ObjectRef = &auditinternal.ObjectReference{ 82 Namespace: attribs.GetNamespace(), 83 Name: attribs.GetName(), 84 Resource: attribs.GetResource(), 85 Subresource: attribs.GetSubresource(), 86 APIGroup: attribs.GetAPIGroup(), 87 APIVersion: attribs.GetAPIVersion(), 88 } 89 } 90 91 for _, kv := range auditAnnotationsFrom(req.Context()) { 92 LogAnnotation(ev, kv.key, kv.value) 93 } 94 95 return ev, nil 96} 97 98// LogImpersonatedUser fills in the impersonated user attributes into an audit event. 99func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) { 100 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 101 return 102 } 103 ae.ImpersonatedUser = &authnv1.UserInfo{ 104 Username: user.GetName(), 105 } 106 ae.ImpersonatedUser.Groups = user.GetGroups() 107 ae.ImpersonatedUser.UID = user.GetUID() 108 ae.ImpersonatedUser.Extra = map[string]authnv1.ExtraValue{} 109 for k, v := range user.GetExtra() { 110 ae.ImpersonatedUser.Extra[k] = authnv1.ExtraValue(v) 111 } 112} 113 114// LogRequestObject fills in the request object into an audit event. The passed runtime.Object 115// will be converted to the given gv. 116func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) { 117 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 118 return 119 } 120 121 // complete ObjectRef 122 if ae.ObjectRef == nil { 123 ae.ObjectRef = &auditinternal.ObjectReference{} 124 } 125 126 // meta.Accessor is more general than ObjectMetaAccessor, but if it fails, we can just skip setting these bits 127 if meta, err := meta.Accessor(obj); err == nil { 128 if len(ae.ObjectRef.Namespace) == 0 { 129 ae.ObjectRef.Namespace = meta.GetNamespace() 130 } 131 if len(ae.ObjectRef.Name) == 0 { 132 ae.ObjectRef.Name = meta.GetName() 133 } 134 if len(ae.ObjectRef.UID) == 0 { 135 ae.ObjectRef.UID = meta.GetUID() 136 } 137 if len(ae.ObjectRef.ResourceVersion) == 0 { 138 ae.ObjectRef.ResourceVersion = meta.GetResourceVersion() 139 } 140 } 141 if len(ae.ObjectRef.APIVersion) == 0 { 142 ae.ObjectRef.APIGroup = gvr.Group 143 ae.ObjectRef.APIVersion = gvr.Version 144 } 145 if len(ae.ObjectRef.Resource) == 0 { 146 ae.ObjectRef.Resource = gvr.Resource 147 } 148 if len(ae.ObjectRef.Subresource) == 0 { 149 ae.ObjectRef.Subresource = subresource 150 } 151 152 if ae.Level.Less(auditinternal.LevelRequest) { 153 return 154 } 155 156 // TODO(audit): hook into the serializer to avoid double conversion 157 var err error 158 ae.RequestObject, err = encodeObject(obj, gvr.GroupVersion(), s) 159 if err != nil { 160 // TODO(audit): add error slice to audit event struct 161 klog.Warningf("Auditing failed of %v request: %v", reflect.TypeOf(obj).Name(), err) 162 return 163 } 164} 165 166// LogRequestPatch fills in the given patch as the request object into an audit event. 167func LogRequestPatch(ae *auditinternal.Event, patch []byte) { 168 if ae == nil || ae.Level.Less(auditinternal.LevelRequest) { 169 return 170 } 171 172 ae.RequestObject = &runtime.Unknown{ 173 Raw: patch, 174 ContentType: runtime.ContentTypeJSON, 175 } 176} 177 178// LogResponseObject fills in the response object into an audit event. The passed runtime.Object 179// will be converted to the given gv. 180func LogResponseObject(ae *auditinternal.Event, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) { 181 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 182 return 183 } 184 if status, ok := obj.(*metav1.Status); ok { 185 // selectively copy the bounded fields. 186 ae.ResponseStatus = &metav1.Status{ 187 Status: status.Status, 188 Reason: status.Reason, 189 Code: status.Code, 190 } 191 } 192 193 if ae.Level.Less(auditinternal.LevelRequestResponse) { 194 return 195 } 196 // TODO(audit): hook into the serializer to avoid double conversion 197 var err error 198 ae.ResponseObject, err = encodeObject(obj, gv, s) 199 if err != nil { 200 klog.Warningf("Audit failed for %q response: %v", reflect.TypeOf(obj).Name(), err) 201 } 202} 203 204func encodeObject(obj runtime.Object, gv schema.GroupVersion, serializer runtime.NegotiatedSerializer) (*runtime.Unknown, error) { 205 const mediaType = runtime.ContentTypeJSON 206 info, ok := runtime.SerializerInfoForMediaType(serializer.SupportedMediaTypes(), mediaType) 207 if !ok { 208 return nil, fmt.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType) 209 } 210 211 enc := serializer.EncoderForVersion(info.Serializer, gv) 212 var buf bytes.Buffer 213 if err := enc.Encode(obj, &buf); err != nil { 214 return nil, fmt.Errorf("encoding failed: %v", err) 215 } 216 217 return &runtime.Unknown{ 218 Raw: buf.Bytes(), 219 ContentType: runtime.ContentTypeJSON, 220 }, nil 221} 222 223// LogAnnotation fills in the Annotations according to the key value pair. 224func LogAnnotation(ae *auditinternal.Event, key, value string) { 225 if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) { 226 return 227 } 228 if ae.Annotations == nil { 229 ae.Annotations = make(map[string]string) 230 } 231 if v, ok := ae.Annotations[key]; ok && v != value { 232 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]) 233 return 234 } 235 ae.Annotations[key] = value 236} 237 238// truncate User-Agent if too long, otherwise return it directly. 239func maybeTruncateUserAgent(req *http.Request) string { 240 ua := req.UserAgent() 241 if len(ua) > maxUserAgentLength { 242 ua = ua[:maxUserAgentLength] + userAgentTruncateSuffix 243 } 244 245 return ua 246} 247