storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/logger/audit.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018, 2019 MinIO, Inc. 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 logger 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "io" 24 "net/http" 25 "strconv" 26 "time" 27 28 "storj.io/minio/cmd/logger/message/audit" 29 ) 30 31 // ResponseWriter - is a wrapper to trap the http response status code. 32 type ResponseWriter struct { 33 http.ResponseWriter 34 StatusCode int 35 // Log body of 4xx or 5xx responses 36 LogErrBody bool 37 // Log body of all responses 38 LogAllBody bool 39 40 TimeToFirstByte time.Duration 41 StartTime time.Time 42 // number of bytes written 43 bytesWritten int 44 // Internal recording buffer 45 headers bytes.Buffer 46 body bytes.Buffer 47 // Indicate if headers are written in the log 48 headersLogged bool 49 } 50 51 // NewResponseWriter - returns a wrapped response writer to trap 52 // http status codes for auditing purposes. 53 func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { 54 return &ResponseWriter{ 55 ResponseWriter: w, 56 StatusCode: http.StatusOK, 57 StartTime: time.Now().UTC(), 58 } 59 } 60 61 func (lrw *ResponseWriter) Write(p []byte) (int, error) { 62 if !lrw.headersLogged { 63 // We assume the response code to be '200 OK' when WriteHeader() is not called, 64 // that way following Golang HTTP response behavior. 65 lrw.WriteHeader(http.StatusOK) 66 } 67 n, err := lrw.ResponseWriter.Write(p) 68 lrw.bytesWritten += n 69 if lrw.TimeToFirstByte == 0 { 70 lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime) 71 } 72 if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody { 73 // Always logging error responses. 74 lrw.body.Write(p) 75 } 76 if err != nil { 77 return n, err 78 } 79 return n, err 80 } 81 82 // Write the headers into the given buffer 83 func (lrw *ResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) { 84 n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode)) 85 lrw.bytesWritten += n 86 for k, v := range headers { 87 n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0]) 88 lrw.bytesWritten += n 89 } 90 } 91 92 // BodyPlaceHolder returns a dummy body placeholder 93 var BodyPlaceHolder = []byte("<BODY>") 94 95 // Body - Return response body. 96 func (lrw *ResponseWriter) Body() []byte { 97 // If there was an error response or body logging is enabled 98 // then we return the body contents 99 if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody { 100 return lrw.body.Bytes() 101 } 102 // ... otherwise we return the <BODY> place holder 103 return BodyPlaceHolder 104 } 105 106 // WriteHeader - writes http status code 107 func (lrw *ResponseWriter) WriteHeader(code int) { 108 if !lrw.headersLogged { 109 lrw.StatusCode = code 110 lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header()) 111 lrw.headersLogged = true 112 lrw.ResponseWriter.WriteHeader(code) 113 } 114 } 115 116 // Flush - Calls the underlying Flush. 117 func (lrw *ResponseWriter) Flush() { 118 lrw.ResponseWriter.(http.Flusher).Flush() 119 } 120 121 // Size - reutrns the number of bytes written 122 func (lrw *ResponseWriter) Size() int { 123 return lrw.bytesWritten 124 } 125 126 const contextAuditKey = contextKeyType("audit-entry") 127 128 // SetAuditEntry sets Audit info in the context. 129 func SetAuditEntry(ctx context.Context, audit *audit.Entry) context.Context { 130 if ctx == nil { 131 LogIf(context.Background(), fmt.Errorf("context is nil")) 132 return nil 133 } 134 return context.WithValue(ctx, contextAuditKey, audit) 135 } 136 137 // GetAuditEntry returns Audit entry if set. 138 func GetAuditEntry(ctx context.Context) *audit.Entry { 139 if ctx != nil { 140 r, ok := ctx.Value(contextAuditKey).(*audit.Entry) 141 if ok { 142 return r 143 } 144 r = &audit.Entry{} 145 SetAuditEntry(ctx, r) 146 return r 147 } 148 return nil 149 } 150 151 // AuditLog - logs audit logs to all audit targets. 152 func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request, reqClaims map[string]interface{}, filterKeys ...string) { 153 // Fast exit if there is not audit target configured 154 if len(AuditTargets) == 0 { 155 return 156 } 157 158 var entry audit.Entry 159 160 if w != nil && r != nil { 161 reqInfo := GetReqInfo(ctx) 162 if reqInfo == nil { 163 return 164 } 165 166 entry = audit.ToEntry(w, r, reqClaims, globalDeploymentID) 167 entry.Trigger = "external-request" 168 169 for _, filterKey := range filterKeys { 170 delete(entry.ReqClaims, filterKey) 171 delete(entry.ReqQuery, filterKey) 172 delete(entry.ReqHeader, filterKey) 173 delete(entry.RespHeader, filterKey) 174 } 175 176 var ( 177 statusCode int 178 timeToResponse time.Duration 179 timeToFirstByte time.Duration 180 ) 181 182 st, ok := w.(*ResponseWriter) 183 if ok { 184 statusCode = st.StatusCode 185 timeToResponse = time.Now().UTC().Sub(st.StartTime) 186 timeToFirstByte = st.TimeToFirstByte 187 } 188 189 entry.API.Name = reqInfo.API 190 entry.API.Bucket = reqInfo.BucketName 191 entry.API.Object = reqInfo.ObjectName 192 entry.API.Status = http.StatusText(statusCode) 193 entry.API.StatusCode = statusCode 194 entry.API.TimeToResponse = strconv.FormatInt(timeToResponse.Nanoseconds(), 10) + "ns" 195 entry.Tags = reqInfo.GetTagsMap() 196 // ttfb will be recorded only for GET requests, Ignore such cases where ttfb will be empty. 197 if timeToFirstByte != 0 { 198 entry.API.TimeToFirstByte = strconv.FormatInt(timeToFirstByte.Nanoseconds(), 10) + "ns" 199 } 200 } else { 201 auditEntry := GetAuditEntry(ctx) 202 if auditEntry != nil { 203 entry = *auditEntry 204 } 205 } 206 207 // Send audit logs only to http targets. 208 for _, t := range AuditTargets { 209 _ = t.Send(entry, string(All)) 210 } 211 }