github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/logger/audit.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package logger 19 20 import ( 21 "context" 22 "fmt" 23 "net/http" 24 "strconv" 25 "time" 26 27 internalAudit "github.com/minio/minio/internal/logger/message/audit" 28 "github.com/minio/minio/internal/mcontext" 29 "github.com/minio/pkg/v2/logger/message/audit" 30 31 xhttp "github.com/minio/minio/internal/http" 32 ) 33 34 const contextAuditKey = contextKeyType("audit-entry") 35 36 // SetAuditEntry sets Audit info in the context. 37 func SetAuditEntry(ctx context.Context, audit *audit.Entry) context.Context { 38 if ctx == nil { 39 LogIf(context.Background(), fmt.Errorf("context is nil")) 40 return nil 41 } 42 return context.WithValue(ctx, contextAuditKey, audit) 43 } 44 45 // GetAuditEntry returns Audit entry if set. 46 func GetAuditEntry(ctx context.Context) *audit.Entry { 47 if ctx != nil { 48 r, ok := ctx.Value(contextAuditKey).(*audit.Entry) 49 if ok { 50 return r 51 } 52 r = &audit.Entry{ 53 Version: internalAudit.Version, 54 DeploymentID: xhttp.GlobalDeploymentID, 55 Time: time.Now().UTC(), 56 } 57 return r 58 } 59 return nil 60 } 61 62 // AuditLog - logs audit logs to all audit targets. 63 func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request, reqClaims map[string]interface{}, filterKeys ...string) { 64 auditTgts := AuditTargets() 65 if len(auditTgts) == 0 { 66 return 67 } 68 69 var entry audit.Entry 70 if w != nil && r != nil { 71 reqInfo := GetReqInfo(ctx) 72 if reqInfo == nil { 73 return 74 } 75 reqInfo.RLock() 76 defer reqInfo.RUnlock() 77 78 entry = internalAudit.ToEntry(w, r, reqClaims, xhttp.GlobalDeploymentID) 79 // indicates all requests for this API call are inbound 80 entry.Trigger = "incoming" 81 82 for _, filterKey := range filterKeys { 83 delete(entry.ReqClaims, filterKey) 84 delete(entry.ReqQuery, filterKey) 85 delete(entry.ReqHeader, filterKey) 86 delete(entry.RespHeader, filterKey) 87 } 88 89 var ( 90 statusCode int 91 timeToResponse time.Duration 92 timeToFirstByte time.Duration 93 outputBytes int64 = -1 // -1: unknown output bytes 94 headerBytes int64 95 ) 96 97 tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt) 98 if ok { 99 statusCode = tc.ResponseRecorder.StatusCode 100 outputBytes = int64(tc.ResponseRecorder.Size()) 101 headerBytes = int64(tc.ResponseRecorder.HeaderSize()) 102 timeToResponse = time.Now().UTC().Sub(tc.ResponseRecorder.StartTime) 103 timeToFirstByte = tc.ResponseRecorder.TimeToFirstByte 104 } 105 106 entry.AccessKey = reqInfo.Cred.AccessKey 107 entry.ParentUser = reqInfo.Cred.ParentUser 108 109 entry.API.Name = reqInfo.API 110 entry.API.Bucket = reqInfo.BucketName 111 entry.API.Object = reqInfo.ObjectName 112 entry.API.Objects = make([]audit.ObjectVersion, 0, len(reqInfo.Objects)) 113 for _, ov := range reqInfo.Objects { 114 entry.API.Objects = append(entry.API.Objects, audit.ObjectVersion{ 115 ObjectName: ov.ObjectName, 116 VersionID: ov.VersionID, 117 }) 118 } 119 entry.API.Status = http.StatusText(statusCode) 120 entry.API.StatusCode = statusCode 121 entry.API.InputBytes = r.ContentLength 122 entry.API.OutputBytes = outputBytes 123 entry.API.HeaderBytes = headerBytes 124 entry.API.TimeToResponse = strconv.FormatInt(timeToResponse.Nanoseconds(), 10) + "ns" 125 entry.API.TimeToResponseInNS = strconv.FormatInt(timeToResponse.Nanoseconds(), 10) 126 // We hold the lock, so we cannot call reqInfo.GetTagsMap(). 127 tags := make(map[string]interface{}, len(reqInfo.tags)) 128 for _, t := range reqInfo.tags { 129 tags[t.Key] = t.Val 130 } 131 entry.Tags = tags 132 // ignore cases for ttfb when its zero. 133 if timeToFirstByte != 0 { 134 entry.API.TimeToFirstByte = strconv.FormatInt(timeToFirstByte.Nanoseconds(), 10) + "ns" 135 entry.API.TimeToFirstByteInNS = strconv.FormatInt(timeToFirstByte.Nanoseconds(), 10) 136 } 137 } else { 138 auditEntry := GetAuditEntry(ctx) 139 if auditEntry != nil { 140 entry = *auditEntry 141 } 142 } 143 144 // Send audit logs only to http targets. 145 for _, t := range auditTgts { 146 if err := t.Send(ctx, entry); err != nil { 147 LogOnceIf(ctx, fmt.Errorf("Unable to send an audit event to the target `%v`: %v", t, err), "send-audit-event-failure") 148 } 149 } 150 }