-
Couldn't load subscription status.
- Fork 92
Log X-Forwarded-For (or similar) for every request #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bboreham
merged 14 commits into
weaveworks:master
from
MichelHollands:make_middleware_overridable
Aug 20, 2020
Merged
Changes from 11 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
436e653
Log X-Forwarded-For for every request
MichelHollands bc9a4d1
Address review comments and add Forwarded and X-Real-IP support
MichelHollands d2b0322
Revert go.mod changes
MichelHollands f69ab04
Remove making standard middleware optional
MichelHollands a91cba9
Remove unused fields as well
MichelHollands c2ecf87
Add option to turn logging of source IPs on
MichelHollands 2ad2d89
Add sourceIPs tag in tracing
MichelHollands bc75667
Add custom regex for extracting IP from header
MichelHollands d47f004
Rename file so it's clearer what it does
MichelHollands 03477c7
Use better names
MichelHollands 8069698
Do no use log prefix in config var name
MichelHollands f6c1147
Address review comments
MichelHollands 2c7a6ca
Use same way of access struct
MichelHollands 7900f78
Address review comments
MichelHollands File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net" | ||
| "net/http" | ||
| "regexp" | ||
| "strings" | ||
| ) | ||
|
|
||
| // Parts copied and changed from gorilla mux proxy_headers.go | ||
|
|
||
| var ( | ||
| // De-facto standard header keys. | ||
| xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") | ||
| xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host") | ||
| xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto") | ||
| xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme") | ||
| 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=)([^(;|,| )]+)`) | ||
| // Allows for a sub-match for the first instance of scheme (http|https) | ||
| // prefixed by 'proto='. The match is case-insensitive. | ||
| protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`) | ||
| ) | ||
|
|
||
| // SourceIPExtractor extracts the source IPs from a HTTP request | ||
| type SourceIPExtractor struct { | ||
| // The header to search for | ||
| header string | ||
| // A regex that extracts the IP address from the header. | ||
| // It should contain at least one capturing group the first of which will be returned. | ||
| regex *regexp.Regexp | ||
| } | ||
|
|
||
| // NewSourceIPs creates a new SourceIPs | ||
| func NewSourceIPs(header, regex string) (*SourceIPExtractor, error) { | ||
| if (header == "" && regex != "") || (header != "" && regex == "") { | ||
| return nil, fmt.Errorf("either both a header field and a regex have to be given or neither") | ||
| } | ||
| re, err := regexp.Compile(regex) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("invalid regex given") | ||
| } | ||
|
|
||
| return &SourceIPExtractor{ | ||
| header: header, | ||
| regex: re, | ||
| }, nil | ||
| } | ||
|
|
||
| // extractHost returns the Host IP address without any port information | ||
| func extractHost(address string) string { | ||
| hostIP := net.ParseIP(address) | ||
| if hostIP != nil { | ||
| return hostIP.String() | ||
| } | ||
| var err error | ||
| hostStr, _, err := net.SplitHostPort(address) | ||
| if err != nil { | ||
| // Invalid IP address, just return it so it shows up in the logs | ||
| return address | ||
| } | ||
| return hostStr | ||
| } | ||
|
|
||
| // Get returns any source addresses we can find in the request, comma-separated | ||
| func (sips SourceIPExtractor) Get(req *http.Request) string { | ||
| fwd := extractHost(sips.getIP(req)) | ||
| if fwd == "" { | ||
| if req.RemoteAddr == "" { | ||
| return "" | ||
| } | ||
| return extractHost(req.RemoteAddr) | ||
| } | ||
| // If RemoteAddr is empty just return the header | ||
| if req.RemoteAddr == "" { | ||
| return fwd | ||
| } | ||
| remoteIP := extractHost(req.RemoteAddr) | ||
| if fwd == remoteIP { | ||
| return remoteIP | ||
| } | ||
| // If both a header and RemoteAddr are present return them both, stripping off any port info from the RemoteAddr | ||
| return fmt.Sprintf("%v, %v", fwd, remoteIP) | ||
| } | ||
|
|
||
| // getIP retrieves the IP from the RFC7239 Forwarded headers, | ||
| // X-Real-IP and X-Forwarded-For (in that order) or from the | ||
| // custom regex. | ||
| func (sips SourceIPExtractor) getIP(r *http.Request) string { | ||
| var addr string | ||
|
|
||
| // Use the custom regex only if it was setup | ||
| if sips.header != "" { | ||
| hdr := r.Header.Get(sips.header) | ||
| if hdr == "" { | ||
| return "" | ||
| } | ||
| allMatches := sips.regex.FindAllStringSubmatch(hdr, 1) | ||
| if allMatches == nil { | ||
MichelHollands marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return "" | ||
| } | ||
| firstMatch := allMatches[0] | ||
| // Check there is at least 1 submatch | ||
| if len(firstMatch) < 2 { | ||
| return "" | ||
| } | ||
| return firstMatch[1] | ||
| } | ||
|
|
||
| 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], `"`) | ||
| } | ||
| } 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(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, ", ") | ||
MichelHollands marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if s == -1 { | ||
| s = len(fwd) | ||
| } | ||
| addr = fwd[:s] | ||
| } | ||
|
|
||
| return addr | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.