diff --git a/api/reqinfo.go b/api/reqinfo.go index cc4dc2e0..606ada4e 100644 --- a/api/reqinfo.go +++ b/api/reqinfo.go @@ -2,7 +2,14 @@ package api import ( "context" + "net" + "net/http" + "net/url" + "regexp" + "strings" "sync" + + "github.com/gorilla/mux" ) type ( @@ -25,6 +32,12 @@ type ( ObjectName string // Object name tags []KeyVal // Any additional info not accommodated by above fields } + + ObjectRequest struct { + Bucket string + Object string + Method string + } ) // Key used for Get/SetReqInfo @@ -32,17 +45,97 @@ type contextKeyType string const ctxRequestInfo = contextKeyType("NeoFS-S3-Gate") +var ( + // De-facto standard header keys. + xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") + xRealIP = http.CanonicalHeaderKey("X-Real-IP") +) + +var ( + // RFC7239 defines a new "Forwarded: " header designed to replace the + // existing use of X-Forwarded-* headers. + // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 + forwarded = http.CanonicalHeaderKey("Forwarded") + // Allows for a sub-match of the first value after 'for=' to the next + // comma, semi-colon or space. The match is case-insensitive. + forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|, )]+)(.*)`) +) + +// GetSourceIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239 +// Forwarded headers (in that order), falls back to r.RemoteAddr when all +// else fails. +func GetSourceIP(r *http.Request) string { + var addr string + + if fwd := r.Header.Get(xForwardedFor); fwd != "" { + // Only grab the first (client) address. Note that '192.168.0.1, + // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after + // the first may represent forwarding proxies earlier in the chain. + s := strings.Index(fwd, ", ") + if s == -1 { + s = len(fwd) + } + addr = fwd[:s] + } else if fwd := r.Header.Get(xRealIP); fwd != "" { + // X-Real-IP should only contain one IP address (the client making the + // request). + addr = fwd + } else if fwd := r.Header.Get(forwarded); fwd != "" { + // match should contain at least two elements if the protocol was + // specified in the Forwarded header. The first element will always be + // the 'for=' capture, which we ignore. In the case of multiple IP + // addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only + // extract the first, which should be the client IP. + if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { + // IPv6 addresses in Forwarded headers are quoted-strings. We strip + // these quotes. + addr = strings.Trim(match[1], `"`) + } + } + + if addr != "" { + return addr + } + + // Default to remote address if headers not set. + addr, _, _ = net.SplitHostPort(r.RemoteAddr) + return addr +} + +func prepareContext(w http.ResponseWriter, r *http.Request) context.Context { + vars := mux.Vars(r) + bucket := vars["bucket"] + object, err := url.PathUnescape(vars["object"]) + if err != nil { + object = vars["object"] + } + prefix, err := url.QueryUnescape(vars["prefix"]) + if err != nil { + prefix = vars["prefix"] + } + if prefix != "" { + object = prefix + } + return SetReqInfo(r.Context(), + // prepare request info + NewReqInfo(w, r, ObjectRequest{ + Bucket: bucket, + Object: object, + Method: mux.CurrentRoute(r).GetName(), + })) +} + // NewReqInfo : -func NewReqInfo(remoteHost, userAgent, deploymentID, requestID, api, bucket, object string) *ReqInfo { - req := ReqInfo{} - req.RemoteHost = remoteHost - req.UserAgent = userAgent - req.API = api - req.DeploymentID = deploymentID - req.RequestID = requestID - req.BucketName = bucket - req.ObjectName = object - return &req +func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest) *ReqInfo { + return &ReqInfo{ + API: req.Method, + BucketName: req.Bucket, + ObjectName: req.Object, + UserAgent: r.UserAgent(), + RemoteHost: GetSourceIP(r), + RequestID: GetRequestID(w), + DeploymentID: deploymentID.String(), + } } // AppendTags - appends key/val to ReqInfo.tags @@ -99,13 +192,9 @@ func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context { // GetReqInfo returns ReqInfo if set. func GetReqInfo(ctx context.Context) *ReqInfo { - if ctx != nil { - r, ok := ctx.Value(ctxRequestInfo).(*ReqInfo) - if ok { - return r - } - r = &ReqInfo{} - SetReqInfo(ctx, r) + if ctx == nil { + return nil + } else if r, ok := ctx.Value(ctxRequestInfo).(*ReqInfo); ok { return r } return nil