Support audit logs with additional fields (#6738)

This PR adds support

- Request query params
- Request headers
- Response headers

AuditLogEntry is exported and versioned as well
starting with this PR.
master
Harshavardhana 6 years ago committed by Dee Koder
parent 3f19ea98bb
commit bef0318c36
  1. 4
      cmd/acl-handlers.go
  2. 4
      cmd/bucket-handlers-listobjects.go
  3. 24
      cmd/bucket-handlers.go
  4. 6
      cmd/bucket-notification-handlers.go
  5. 6
      cmd/bucket-policy-handlers.go
  6. 25
      cmd/common-main.go
  7. 3
      cmd/gateway-main.go
  8. 13
      cmd/handler-utils.go
  9. 97
      cmd/logger/audit-logger.go
  10. 52
      cmd/logger/logger.go
  11. 9
      cmd/notification.go
  12. 76
      cmd/object-handlers.go
  13. 20
      cmd/web-handlers.go
  14. 108
      docs/logging/README.md

@ -57,7 +57,7 @@ type accessControlPolicy struct {
func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketACL") ctx := newContext(r, w, "GetBucketACL")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] 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) { func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetObjectACL") ctx := newContext(r, w, "GetObjectACL")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]

@ -59,7 +59,7 @@ func validateListObjectsArgs(prefix, marker, delimiter string, maxKeys int) APIE
func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectsV2") ctx := newContext(r, w, "ListObjectsV2")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] 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) { func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectsV1") ctx := newContext(r, w, "ListObjectsV1")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]

@ -89,7 +89,7 @@ func initFederatorBackend(objLayer ObjectLayer) {
func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketLocation") ctx := newContext(r, w, "GetBucketLocation")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] 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) { func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListMultipartUploads") ctx := newContext(r, w, "ListMultipartUploads")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -192,7 +192,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListBuckets") ctx := newContext(r, w, "ListBuckets")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { 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) { func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DeleteMultipleObjects") ctx := newContext(r, w, "DeleteMultipleObjects")
defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -381,6 +383,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
Name: dobj.ObjectName, Name: dobj.ObjectName,
}, },
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: host, Host: host,
Port: port, Port: port,
@ -394,7 +397,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PutBucket") ctx := newContext(r, w, "PutBucket")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { 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) { func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PostPolicyBucket") ctx := newContext(r, w, "PostPolicyBucket")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -658,6 +661,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
BucketName: objInfo.Bucket, BucketName: objInfo.Bucket,
Object: objInfo, Object: objInfo,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: host, Host: host,
Port: port, Port: port,
@ -685,12 +689,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
default: default:
writeSuccessNoContent(w) writeSuccessNoContent(w)
} }
for k, v := range objInfo.UserDefined {
logger.GetReqInfo(ctx).SetTags(k, v)
}
logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag)
} }
// HeadBucketHandler - HEAD Bucket // 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) { func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "HeadBucket") ctx := newContext(r, w, "HeadBucket")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] 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) { func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DeleteBucket") ctx := newContext(r, w, "DeleteBucket")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]

@ -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) { func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketNotification") ctx := newContext(r, w, "GetBucketNotification")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucketName := vars["bucket"] bucketName := vars["bucket"]
@ -98,7 +98,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PutBucketNotification") ctx := newContext(r, w, "PutBucketNotification")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -160,7 +160,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListenBucketNotification") ctx := newContext(r, w, "ListenBucketNotification")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
// Validate if bucket exists. // Validate if bucket exists.
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()

@ -40,7 +40,7 @@ const (
func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PutBucketPolicy") ctx := newContext(r, w, "PutBucketPolicy")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { 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) { func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DeleteBucketPolicy") ctx := newContext(r, w, "DeleteBucketPolicy")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { 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) { func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketPolicy") ctx := newContext(r, w, "GetBucketPolicy")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {

@ -51,17 +51,17 @@ func checkUpdate(mode string) {
// Load logger targets based on user's configuration // Load logger targets based on user's configuration
func loadLoggers() { func loadLoggers() {
if endpoint, ok := os.LookupEnv("MINIO_LOGGER_HTTP_ENDPOINT"); ok { auditEndpoint, ok := os.LookupEnv("MINIO_AUDIT_LOGGER_HTTP_ENDPOINT")
// Enable http logging through ENV, this is specifically added gateway audit logging. if ok {
logger.AddTarget(logger.NewHTTP(endpoint, NewCustomHTTPTransport())) // Enable audit HTTP logging through ENV.
return logger.AddAuditTarget(logger.NewHTTP(auditEndpoint, NewCustomHTTPTransport()))
}
if globalServerConfig.Logger.Console.Enabled {
// Enable console logging
logger.AddTarget(logger.NewConsole())
} }
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 { for _, l := range globalServerConfig.Logger.HTTP {
if l.Enabled { if l.Enabled {
// Enable http logging // Enable http logging
@ -70,6 +70,13 @@ func loadLoggers() {
} }
} }
if globalServerConfig.Logger.Console.Enabled {
// Enable console logging
logger.AddTarget(logger.NewConsole())
}
}
func handleCommonCmdArgs(ctx *cli.Context) { func handleCommonCmdArgs(ctx *cli.Context) {
var configDir string var configDir string

@ -249,9 +249,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
logger.FatalIf(err, "Unable to initialize disk caching") logger.FatalIf(err, "Unable to initialize disk caching")
} }
// Load logger subsystem
loadLoggers()
// Re-enable logging // Re-enable logging
logger.Disable = false logger.Disable = false

@ -176,14 +176,26 @@ func getRedirectPostRawQuery(objInfo ObjectInfo) string {
return redirectValues.Encode() 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. // Extract request params to be sent with event notifiation.
func extractReqParams(r *http.Request) map[string]string { func extractReqParams(r *http.Request) map[string]string {
if r == nil { if r == nil {
return nil return nil
} }
region := globalServerConfig.GetRegion()
// Success. // Success.
return map[string]string{ return map[string]string{
"region": region,
"accessKey": getReqAccessKey(r, region),
"sourceIPAddress": handlers.GetSourceIP(r), "sourceIPAddress": handlers.GetSourceIP(r),
// Add more fields here. // Add more fields here.
} }
@ -193,6 +205,7 @@ func extractReqParams(r *http.Request) map[string]string {
func extractRespElements(w http.ResponseWriter) map[string]string { func extractRespElements(w http.ResponseWriter) map[string]string {
return map[string]string{ return map[string]string{
"requestId": w.Header().Get(responseRequestIDKey),
"content-length": w.Header().Get("Content-Length"), "content-length": w.Header().Get("Content-Length"),
// Add more fields here. // Add more fields here.
} }

@ -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,
})
}
}

@ -21,7 +21,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/build" "go/build"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -117,9 +116,11 @@ type traceEntry struct {
Source []string `json:"source,omitempty"` Source []string `json:"source,omitempty"`
Variables map[string]string `json:"variables,omitempty"` Variables map[string]string `json:"variables,omitempty"`
} }
type args struct { type args struct {
Bucket string `json:"bucket,omitempty"` Bucket string `json:"bucket,omitempty"`
Object string `json:"object,omitempty"` Object string `json:"object,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
} }
type api struct { 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. // ErrCritical is the value panic'd whenever CriticalIf is called.
var ErrCritical struct{} var ErrCritical struct{}

@ -494,12 +494,11 @@ func (args eventArgs) ToEvent() event.Event {
return fmt.Sprintf("%s://%s:%s", getURLScheme(globalIsSSL), host, globalMinioPort) return fmt.Sprintf("%s://%s:%s", getURLScheme(globalIsSSL), host, globalMinioPort)
} }
creds := globalServerConfig.GetCredential()
eventTime := UTCNow() eventTime := UTCNow()
uniqueID := fmt.Sprintf("%X", eventTime.UnixNano()) uniqueID := fmt.Sprintf("%X", eventTime.UnixNano())
respElements := map[string]string{ 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. "x-minio-origin-endpoint": getOriginEndpoint(), // Minio specific custom elements.
} }
if args.RespElements["content-length"] != "" { if args.RespElements["content-length"] != "" {
@ -508,10 +507,10 @@ func (args eventArgs) ToEvent() event.Event {
newEvent := event.Event{ newEvent := event.Event{
EventVersion: "2.0", EventVersion: "2.0",
EventSource: "minio:s3", EventSource: "minio:s3",
AwsRegion: globalServerConfig.GetRegion(), AwsRegion: args.ReqParams["region"],
EventTime: eventTime.Format(event.AMZTimeFormat), EventTime: eventTime.Format(event.AMZTimeFormat),
EventName: args.EventName, EventName: args.EventName,
UserIdentity: event.Identity{creds.AccessKey}, UserIdentity: event.Identity{args.ReqParams["accessKey"]},
RequestParameters: args.ReqParams, RequestParameters: args.ReqParams,
ResponseElements: respElements, ResponseElements: respElements,
S3: event.Metadata{ S3: event.Metadata{
@ -519,7 +518,7 @@ func (args eventArgs) ToEvent() event.Event {
ConfigurationID: "Config", ConfigurationID: "Config",
Bucket: event.Bucket{ Bucket: event.Bucket{
Name: args.BucketName, Name: args.BucketName,
OwnerIdentity: event.Identity{creds.AccessKey}, OwnerIdentity: event.Identity{args.ReqParams["accessKey"]},
ARN: policy.ResourceARNPrefix + args.BucketName, ARN: policy.ResourceARNPrefix + args.BucketName,
}, },
Object: event.Object{ Object: event.Object{

@ -79,7 +79,7 @@ func setHeadGetRespHeaders(w http.ResponseWriter, reqParams url.Values) {
func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "SelectObject") ctx := newContext(r, w, "SelectObject")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
// Fetch object stat info. // Fetch object stat info.
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
@ -271,11 +271,23 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
// Executes the query on data-set // Executes the query on data-set
s3select.Execute(w, s3s) s3select.Execute(w, s3s)
for k, v := range objInfo.UserDefined { // Get host and port from Request.RemoteAddr.
logger.GetReqInfo(ctx).SetTags(k, v) 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 // 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) { func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetObject") ctx := newContext(r, w, "GetObject")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -443,12 +455,6 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
Host: host, Host: host,
Port: port, Port: port,
}) })
for k, v := range objInfo.UserDefined {
logger.GetReqInfo(ctx).SetTags(k, v)
}
logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag)
} }
// HeadObjectHandler - HEAD Object // 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) { func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "HeadObject") ctx := newContext(r, w, "HeadObject")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -597,12 +603,6 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
Host: host, Host: host,
Port: port, 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 // 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) { func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "CopyObject") ctx := newContext(r, w, "CopyObject")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -988,16 +988,11 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
BucketName: dstBucket, BucketName: dstBucket,
Object: objInfo, Object: objInfo,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: host, Host: host,
Port: port, Port: port,
}) })
for k, v := range objInfo.UserDefined {
logger.GetReqInfo(ctx).SetTags(k, v)
}
logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag)
} }
// PutObjectHandler - PUT Object // 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) { func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PutObject") ctx := newContext(r, w, "PutObject")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -1248,16 +1243,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: objInfo,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: host, Host: host,
Port: port, Port: port,
}) })
for k, v := range objInfo.UserDefined {
logger.GetReqInfo(ctx).SetTags(k, v)
}
logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag)
} }
/// Multipart objectAPIHandlers /// 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) { func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "NewMultipartUpload") ctx := newContext(r, w, "NewMultipartUpload")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { 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) { func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "CopyObjectPart") ctx := newContext(r, w, "CopyObjectPart")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { 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) { func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "PutObjectPart") ctx := newContext(r, w, "PutObjectPart")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { 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) { func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AbortMultipartUpload") ctx := newContext(r, w, "AbortMultipartUpload")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -1879,6 +1869,7 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
writeErrorResponse(w, toAPIErrorCode(err), r.URL) writeErrorResponse(w, toAPIErrorCode(err), r.URL)
return return
} }
writeSuccessNoContent(w) writeSuccessNoContent(w)
} }
@ -1886,7 +1877,7 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListObjectParts") ctx := newContext(r, w, "ListObjectParts")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] 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) { func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "CompleteMultipartUpload") ctx := newContext(r, w, "CompleteMultipartUpload")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]
@ -2040,16 +2031,11 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: objInfo,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: host, Host: host,
Port: port, Port: port,
}) })
for k, v := range objInfo.UserDefined {
logger.GetReqInfo(ctx).SetTags(k, v)
}
logger.GetReqInfo(ctx).SetTags("etag", objInfo.ETag)
} }
/// Delete objectAPIHandlers /// Delete objectAPIHandlers
@ -2058,7 +2044,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DeleteObject") ctx := newContext(r, w, "DeleteObject")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
vars := mux.Vars(r) vars := mux.Vars(r)
bucket := vars["bucket"] bucket := vars["bucket"]

@ -707,7 +707,7 @@ func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs,
func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "WebUpload") ctx := newContext(r, w, "WebUpload")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
objectAPI := web.ObjectAPI() objectAPI := web.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
@ -834,23 +834,18 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
BucketName: bucket, BucketName: bucket,
Object: objInfo, Object: objInfo,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent(), UserAgent: r.UserAgent(),
Host: host, Host: host,
Port: port, 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. // Download - file download handler.
func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "WebDownload") ctx := newContext(r, w, "WebDownload")
defer logger.AuditLog(ctx, r) defer logger.AuditLog(ctx, w, r)
var wg sync.WaitGroup var wg sync.WaitGroup
objectAPI := web.ObjectAPI() objectAPI := web.ObjectAPI()
@ -1012,12 +1007,6 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
Host: host, Host: host,
Port: port, 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. // 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") ctx := newContext(r, w, "WebDownloadZip")
defer logger.AuditLog(ctx, w, r)
defer logger.AuditLog(ctx, r)
var wg sync.WaitGroup var wg sync.WaitGroup
objectAPI := web.ObjectAPI() objectAPI := web.ObjectAPI()

@ -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)
Loading…
Cancel
Save