diff --git a/cmd/acl-handlers.go b/cmd/acl-handlers.go index cea36659d..5849eb7db 100644 --- a/cmd/acl-handlers.go +++ b/cmd/acl-handlers.go @@ -57,7 +57,7 @@ type accessControlPolicy struct { func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetBucketACL") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -106,7 +106,7 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http. func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetObjectACL") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] diff --git a/cmd/bucket-handlers-listobjects.go b/cmd/bucket-handlers-listobjects.go index f8e0ebb08..6ce1c142e 100644 --- a/cmd/bucket-handlers-listobjects.go +++ b/cmd/bucket-handlers-listobjects.go @@ -59,7 +59,7 @@ func validateListObjectsArgs(prefix, marker, delimiter string, maxKeys int) APIE func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ListObjectsV2") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -140,7 +140,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ListObjectsV1") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index fb99076bc..6320524a5 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -89,7 +89,7 @@ func initFederatorBackend(objLayer ObjectLayer) { func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetBucketLocation") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -139,7 +139,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r * func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ListMultipartUploads") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -192,7 +192,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ListBuckets") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -250,6 +250,8 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "DeleteMultipleObjects") + defer logger.AuditLog(ctx, w, r) + vars := mux.Vars(r) bucket := vars["bucket"] @@ -380,10 +382,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, Object: ObjectInfo{ Name: dobj.ObjectName, }, - ReqParams: extractReqParams(r), - UserAgent: r.UserAgent(), - Host: host, - Port: port, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, }) } } @@ -394,7 +397,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutBucket") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -472,7 +475,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PostPolicyBucket") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -654,13 +657,14 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h // Notify object created event. defer sendEvent(eventArgs{ - EventName: event.ObjectCreatedPost, - BucketName: objInfo.Bucket, - Object: objInfo, - ReqParams: extractReqParams(r), - UserAgent: r.UserAgent(), - Host: host, - Port: port, + EventName: event.ObjectCreatedPost, + BucketName: objInfo.Bucket, + Object: objInfo, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, }) if successRedirect != "" { @@ -685,12 +689,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h default: writeSuccessNoContent(w) } - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } // HeadBucketHandler - HEAD Bucket @@ -702,7 +700,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "HeadBucket") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -734,7 +732,7 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "DeleteBucket") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] diff --git a/cmd/bucket-notification-handlers.go b/cmd/bucket-notification-handlers.go index 6195d268a..4874a1aab 100644 --- a/cmd/bucket-notification-handlers.go +++ b/cmd/bucket-notification-handlers.go @@ -44,7 +44,7 @@ var errNoSuchNotifications = errors.New("The specified bucket does not have buck func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetBucketNotification") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucketName := vars["bucket"] @@ -98,7 +98,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutBucketNotification") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -160,7 +160,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ListenBucketNotification") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) // Validate if bucket exists. objAPI := api.ObjectAPI() diff --git a/cmd/bucket-policy-handlers.go b/cmd/bucket-policy-handlers.go index 92c0aa586..117963fdb 100644 --- a/cmd/bucket-policy-handlers.go +++ b/cmd/bucket-policy-handlers.go @@ -40,7 +40,7 @@ const ( func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutBucketPolicy") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objAPI := api.ObjectAPI() if objAPI == nil { @@ -103,7 +103,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "DeleteBucketPolicy") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objAPI := api.ObjectAPI() if objAPI == nil { @@ -141,7 +141,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetBucketPolicy") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objAPI := api.ObjectAPI() if objAPI == nil { diff --git a/cmd/common-main.go b/cmd/common-main.go index 98ce4b6d9..3780240fe 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -51,10 +51,23 @@ func checkUpdate(mode string) { // Load logger targets based on user's configuration func loadLoggers() { - if endpoint, ok := os.LookupEnv("MINIO_LOGGER_HTTP_ENDPOINT"); ok { - // Enable http logging through ENV, this is specifically added gateway audit logging. - logger.AddTarget(logger.NewHTTP(endpoint, NewCustomHTTPTransport())) - return + auditEndpoint, ok := os.LookupEnv("MINIO_AUDIT_LOGGER_HTTP_ENDPOINT") + if ok { + // Enable audit HTTP logging through ENV. + logger.AddAuditTarget(logger.NewHTTP(auditEndpoint, NewCustomHTTPTransport())) + } + + loggerEndpoint, ok := os.LookupEnv("MINIO_LOGGER_HTTP_ENDPOINT") + if ok { + // Enable HTTP logging through ENV. + logger.AddTarget(logger.NewHTTP(loggerEndpoint, NewCustomHTTPTransport())) + } else { + for _, l := range globalServerConfig.Logger.HTTP { + if l.Enabled { + // Enable http logging + logger.AddTarget(logger.NewHTTP(l.Endpoint, NewCustomHTTPTransport())) + } + } } if globalServerConfig.Logger.Console.Enabled { @@ -62,12 +75,6 @@ func loadLoggers() { logger.AddTarget(logger.NewConsole()) } - for _, l := range globalServerConfig.Logger.HTTP { - if l.Enabled { - // Enable http logging - logger.AddTarget(logger.NewHTTP(l.Endpoint, NewCustomHTTPTransport())) - } - } } func handleCommonCmdArgs(ctx *cli.Context) { diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index 075436443..8fb0076bc 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -249,9 +249,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) { logger.FatalIf(err, "Unable to initialize disk caching") } - // Load logger subsystem - loadLoggers() - // Re-enable logging logger.Disable = false diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index c701cd8a1..f25076c32 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -176,14 +176,26 @@ func getRedirectPostRawQuery(objInfo ObjectInfo) string { return redirectValues.Encode() } +// Returns access key in the request Authorization header. +func getReqAccessKey(r *http.Request, region string) (accessKey string) { + accessKey, _, _ = getReqAccessKeyV4(r, region) + if accessKey == "" { + accessKey, _, _ = getReqAccessKeyV2(r) + } + return accessKey +} + // Extract request params to be sent with event notifiation. func extractReqParams(r *http.Request) map[string]string { if r == nil { return nil } + region := globalServerConfig.GetRegion() // Success. return map[string]string{ + "region": region, + "accessKey": getReqAccessKey(r, region), "sourceIPAddress": handlers.GetSourceIP(r), // Add more fields here. } @@ -193,6 +205,7 @@ func extractReqParams(r *http.Request) map[string]string { func extractRespElements(w http.ResponseWriter) map[string]string { return map[string]string{ + "requestId": w.Header().Get(responseRequestIDKey), "content-length": w.Header().Get("Content-Length"), // Add more fields here. } diff --git a/cmd/logger/audit-logger.go b/cmd/logger/audit-logger.go new file mode 100644 index 000000000..f09e0b409 --- /dev/null +++ b/cmd/logger/audit-logger.go @@ -0,0 +1,97 @@ +/* + * Minio Cloud Storage, (C) 2018 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package logger + +import ( + "context" + "net/http" + "strings" + "time" +) + +// Represents the current version of audit log structure. +const auditLogVersion = "1" + +// AuditEntry - audit entry logs. +type AuditEntry struct { + Version string `json:"version"` + DeploymentID string `json:"deploymentid,omitempty"` + Time string `json:"time"` + API *api `json:"api,omitempty"` + RemoteHost string `json:"remotehost,omitempty"` + RequestID string `json:"requestID,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + ReqQuery map[string]string `json:"requestQuery,omitempty"` + ReqHeader map[string]string `json:"requestHeader,omitempty"` + RespHeader map[string]string `json:"responseHeader,omitempty"` +} + +// AuditTargets is the list of enabled audit loggers +var AuditTargets = []LoggingTarget{} + +// AddAuditTarget adds a new audit logger target to the +// list of enabled loggers +func AddAuditTarget(t LoggingTarget) { + AuditTargets = append(AuditTargets, t) +} + +// AuditLog - logs audit logs to all targets. +func AuditLog(ctx context.Context, w http.ResponseWriter, r *http.Request) { + if Disable { + return + } + + req := GetReqInfo(ctx) + if req == nil { + req = &ReqInfo{API: "SYSTEM"} + } + + reqQuery := make(map[string]string) + for k, v := range r.URL.Query() { + reqQuery[k] = strings.Join(v, ",") + } + reqHeader := make(map[string]string) + for k, v := range r.Header { + reqHeader[k] = strings.Join(v, ",") + } + respHeader := make(map[string]string) + for k, v := range w.Header() { + respHeader[k] = strings.Join(v, ",") + } + + // Send audit logs only to http targets. + for _, t := range AuditTargets { + t.send(AuditEntry{ + Version: auditLogVersion, + DeploymentID: deploymentID, + RemoteHost: req.RemoteHost, + RequestID: req.RequestID, + UserAgent: req.UserAgent, + Time: time.Now().UTC().Format(time.RFC3339Nano), + API: &api{ + Name: req.API, + Args: &args{ + Bucket: req.BucketName, + Object: req.ObjectName, + }, + }, + ReqQuery: reqQuery, + ReqHeader: reqHeader, + RespHeader: respHeader, + }) + } +} diff --git a/cmd/logger/logger.go b/cmd/logger/logger.go index eea3a06ee..92169a190 100644 --- a/cmd/logger/logger.go +++ b/cmd/logger/logger.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "go/build" - "net/http" "os" "path/filepath" "runtime" @@ -117,9 +116,11 @@ type traceEntry struct { Source []string `json:"source,omitempty"` Variables map[string]string `json:"variables,omitempty"` } + type args struct { - Bucket string `json:"bucket,omitempty"` - Object string `json:"object,omitempty"` + Bucket string `json:"bucket,omitempty"` + Object string `json:"object,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` } type api struct { @@ -342,55 +343,6 @@ func logIf(ctx context.Context, err error) { } } -type auditEntry struct { - DeploymentID string `json:"deploymentid,omitempty"` - Time string `json:"time"` - API *api `json:"api,omitempty"` - RemoteHost string `json:"remotehost,omitempty"` - RequestID string `json:"requestID,omitempty"` - UserAgent string `json:"userAgent,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// AuditLog - logs audit logs to all targets. -func AuditLog(ctx context.Context, r *http.Request) { - if Disable { - return - } - - req := GetReqInfo(ctx) - if req == nil { - req = &ReqInfo{API: "SYSTEM"} - } - - API := "SYSTEM" - if req.API != "" { - API = req.API - } - - tags := make(map[string]string) - for _, entry := range req.GetTags() { - tags[entry.Key] = entry.Val - } - - entry := auditEntry{ - DeploymentID: deploymentID, - RemoteHost: req.RemoteHost, - RequestID: req.RequestID, - UserAgent: req.UserAgent, - Time: time.Now().UTC().Format(time.RFC3339Nano), - API: &api{Name: API, Args: &args{Bucket: req.BucketName, Object: req.ObjectName}}, - Metadata: tags, - } - - // Send audit logs only to http targets. - for _, t := range Targets { - if _, ok := t.(*HTTPTarget); ok { - t.send(entry) - } - } -} - // ErrCritical is the value panic'd whenever CriticalIf is called. var ErrCritical struct{} diff --git a/cmd/notification.go b/cmd/notification.go index d2b44b09d..a913d79c1 100644 --- a/cmd/notification.go +++ b/cmd/notification.go @@ -494,12 +494,11 @@ func (args eventArgs) ToEvent() event.Event { return fmt.Sprintf("%s://%s:%s", getURLScheme(globalIsSSL), host, globalMinioPort) } - creds := globalServerConfig.GetCredential() eventTime := UTCNow() uniqueID := fmt.Sprintf("%X", eventTime.UnixNano()) respElements := map[string]string{ - "x-amz-request-id": uniqueID, + "x-amz-request-id": args.RespElements["requestId"], "x-minio-origin-endpoint": getOriginEndpoint(), // Minio specific custom elements. } if args.RespElements["content-length"] != "" { @@ -508,10 +507,10 @@ func (args eventArgs) ToEvent() event.Event { newEvent := event.Event{ EventVersion: "2.0", EventSource: "minio:s3", - AwsRegion: globalServerConfig.GetRegion(), + AwsRegion: args.ReqParams["region"], EventTime: eventTime.Format(event.AMZTimeFormat), EventName: args.EventName, - UserIdentity: event.Identity{creds.AccessKey}, + UserIdentity: event.Identity{args.ReqParams["accessKey"]}, RequestParameters: args.ReqParams, ResponseElements: respElements, S3: event.Metadata{ @@ -519,7 +518,7 @@ func (args eventArgs) ToEvent() event.Event { ConfigurationID: "Config", Bucket: event.Bucket{ Name: args.BucketName, - OwnerIdentity: event.Identity{creds.AccessKey}, + OwnerIdentity: event.Identity{args.ReqParams["accessKey"]}, ARN: policy.ResourceARNPrefix + args.BucketName, }, Object: event.Object{ diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 5551d5476..632bd5cfc 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -79,7 +79,7 @@ func setHeadGetRespHeaders(w http.ResponseWriter, reqParams url.Values) { func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "SelectObject") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) // Fetch object stat info. objectAPI := api.ObjectAPI() @@ -271,11 +271,23 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r // Executes the query on data-set s3select.Execute(w, s3s) - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) + // Get host and port from Request.RemoteAddr. + host, port, err := net.SplitHostPort(handlers.GetSourceIP(r)) + if err != nil { + host, port = "", "" } - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) + // Notify object accessed via a GET request. + sendEvent(eventArgs{ + EventName: event.ObjectAccessedGet, + BucketName: bucket, + Object: objInfo, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, + }) } // GetObjectHandler - GET Object @@ -285,7 +297,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "GetObject") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -443,12 +455,6 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req Host: host, Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } // HeadObjectHandler - HEAD Object @@ -457,7 +463,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "HeadObject") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -597,12 +603,6 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re Host: host, Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } // Extract metadata relevant for an CopyObject operation based on conditional @@ -643,7 +643,7 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "CopyObject") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -984,20 +984,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re // Notify object created event. sendEvent(eventArgs{ - EventName: event.ObjectCreatedCopy, - BucketName: dstBucket, - Object: objInfo, - ReqParams: extractReqParams(r), - UserAgent: r.UserAgent(), - Host: host, - Port: port, + EventName: event.ObjectCreatedCopy, + BucketName: dstBucket, + Object: objInfo, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } // PutObjectHandler - PUT Object @@ -1011,7 +1006,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutObject") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -1244,20 +1239,15 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req // Notify object created event. sendEvent(eventArgs{ - EventName: event.ObjectCreatedPut, - BucketName: bucket, - Object: objInfo, - ReqParams: extractReqParams(r), - UserAgent: r.UserAgent(), - Host: host, - Port: port, + EventName: event.ObjectCreatedPut, + BucketName: bucket, + Object: objInfo, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } /// Multipart objectAPIHandlers @@ -1271,7 +1261,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "NewMultipartUpload") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -1365,7 +1355,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "CopyObjectPart") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -1597,7 +1587,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "PutObjectPart") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := api.ObjectAPI() if objectAPI == nil { @@ -1841,7 +1831,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "AbortMultipartUpload") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -1879,6 +1869,7 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter, writeErrorResponse(w, toAPIErrorCode(err), r.URL) return } + writeSuccessNoContent(w) } @@ -1886,7 +1877,7 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter, func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "ListObjectParts") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -1932,7 +1923,7 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "CompleteMultipartUpload") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] @@ -2036,20 +2027,15 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite // Notify object created event. sendEvent(eventArgs{ - EventName: event.ObjectCreatedCompleteMultipartUpload, - BucketName: bucket, - Object: objInfo, - ReqParams: extractReqParams(r), - UserAgent: r.UserAgent(), - Host: host, - Port: port, + EventName: event.ObjectCreatedCompleteMultipartUpload, + BucketName: bucket, + Object: objInfo, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } /// Delete objectAPIHandlers @@ -2058,7 +2044,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "DeleteObject") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) vars := mux.Vars(r) bucket := vars["bucket"] diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 6b54670a0..556387d04 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -707,7 +707,7 @@ func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs, func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "WebUpload") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) objectAPI := web.ObjectAPI() if objectAPI == nil { @@ -830,27 +830,22 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { // Notify object created event. sendEvent(eventArgs{ - EventName: event.ObjectCreatedPut, - BucketName: bucket, - Object: objInfo, - ReqParams: extractReqParams(r), - UserAgent: r.UserAgent(), - Host: host, - Port: port, + EventName: event.ObjectCreatedPut, + BucketName: bucket, + Object: objInfo, + ReqParams: extractReqParams(r), + RespElements: extractRespElements(w), + UserAgent: r.UserAgent(), + Host: host, + Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } // Download - file download handler. func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "WebDownload") - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) var wg sync.WaitGroup objectAPI := web.ObjectAPI() @@ -1012,12 +1007,6 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { Host: host, Port: port, }) - - for k, v := range objInfo.UserDefined { - logger.GetReqInfo(ctx).SetTags(k, v) - } - - logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag) } // DownloadZipArgs - Argument for downloading a bunch of files as a zip file. @@ -1038,8 +1027,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { } ctx := newContext(r, w, "WebDownloadZip") - - defer logger.AuditLog(ctx, r) + defer logger.AuditLog(ctx, w, r) var wg sync.WaitGroup objectAPI := web.ObjectAPI() diff --git a/docs/logging/README.md b/docs/logging/README.md new file mode 100644 index 000000000..05c91027d --- /dev/null +++ b/docs/logging/README.md @@ -0,0 +1,108 @@ +# Minio Logging Quickstart Guide [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io) +This document explains how to configure Minio server to log to different logging targets. + +## Log Targets +Minio supports currently two target types + +- console +- http + +### Console Target +Console target logs to `/dev/stderr` and is enabled by default. To turn-off console logging you would have to update your Minio server configuration using `mc admin config set` command. + +Assuming `mc` is already [configured](https://docs.minio.io/docs/minio-client-quickstart-guide.html) +``` +mc admin config get myminio/ > /tmp/config +``` + +Edit the `/tmp/config` and toggle `console` field `enabled` from `true` to `false`. + +```json + "logger": { + "console": { + "enabled": false + } + }, +``` + +Once changed, now you may set the changed config to server through following commands. +``` +mc admin config set myminio/ < /tmp/config +mc admin restart myminio/ +``` + +### HTTP Target +HTTP target logs to a generic HTTP endpoint in JSON format and is not enabled by default. To enable HTTP target logging you would have to update your Minio server configuration using `mc admin config set` command. + +Assuming `mc` is already [configured](https://docs.minio.io/docs/minio-client-quickstart-guide.html) +``` +mc admin config get myminio/ > /tmp/config +``` + +Edit the `/tmp/config` and toggle `http` field `enabled` from `false` to `true`. +```json + "logger": { + "console": { + "enabled": false + }, + "http": { + "1": { + "enabled": true, + "endpoint": "http://endpoint:port/path" + } + } + }, +``` +NOTE: `http://endpoint:port/path` is a placeholder value to indicate the URL format, please change this accordingly as per your configuration. + +Once changed, now you may set the changed config to server through following commands. +``` +mc admin config set myminio/ < /tmp/config +mc admin restart myminio/ +``` + +Minio also honors environment variable for HTTP target logging as shown below, this setting will override the endpoint settings in the Minio server config. +``` +MINIO_LOGGER_HTTP_ENDPOINT=http://localhost:8080/minio/logs minio server /mnt/data +``` + +## Audit Targets +For audit logging Minio supports only HTTP target type for now. Audit logging is currently only available through environment variable. +``` +MINIO_AUDIT_LOGGER_HTTP_ENDPOINT=http://localhost:8080/minio/logs/audit minio server /mnt/data +``` + +Setting this environment variable automatically enables audit logging to the HTTP target. The audit logging is in JSON format as described below. +```json +{ + "version": "1", + "deploymentid": "1b3002bf-5005-4d9b-853e-64a05008ebb2", + "time": "2018-11-02T21:57:58.231480177Z", + "api": { + "name": "ListBuckets", + "args": {} + }, + "remotehost": "127.0.0.1", + "requestID": "15636D7C53428FD4", + "userAgent": "Minio (linux; amd64) minio-go/v6.0.8 mc/2018-11-02T21:13:30Z", + "requestHeader": { + "Authorization": "AWS4-HMAC-SHA256 Credential=minio/20181102/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=6db486b42a85b23bffba66d654ce60242a7e92fb27cd4a1756e68082c02cc204", + "User-Agent": "Minio (linux; amd64) minio-go/v6.0.8 mc/2018-11-02T21:13:30Z", + "X-Amz-Content-Sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "X-Amz-Date": "20181102T215758Z" + }, + "responseHeader": { + "Accept-Ranges": "bytes", + "Content-Security-Policy": "block-all-mixed-content", + "Content-Type": "application/xml", + "Server": "Minio/DEVELOPMENT.2018-11-02T21-57-15Z (linux; amd64)", + "Vary": "Origin", + "X-Amz-Request-Id": "15636D7C53428FD4", + "X-Xss-Protection": "1; mode=block" + } +} +``` + +## Explore Further +* [Minio Quickstart Guide](https://docs.minio.io/docs/minio-quickstart-guide) +* [Configure Minio Server with TLS](https://docs.minio.io/docs/how-to-secure-access-to-minio-server-with-tls)