Handle incoming proxy requests ip, scheme (#5591)
This PR implements functions to get the right ip, scheme from the incoming proxied requests.master
parent
d71b1d25f8
commit
e4f6877c8b
@ -0,0 +1,115 @@ |
||||
/* |
||||
* 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 handlers |
||||
|
||||
import ( |
||||
"net" |
||||
"net/http" |
||||
"regexp" |
||||
"strings" |
||||
) |
||||
|
||||
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)`) |
||||
) |
||||
|
||||
// GetSourceScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
|
||||
// Forwarded headers (in that order).
|
||||
func GetSourceScheme(r *http.Request) string { |
||||
var scheme string |
||||
|
||||
// Retrieve the scheme from X-Forwarded-Proto.
|
||||
if proto := r.Header.Get(xForwardedProto); proto != "" { |
||||
scheme = strings.ToLower(proto) |
||||
} else if proto = r.Header.Get(xForwardedScheme); proto != "" { |
||||
scheme = strings.ToLower(proto) |
||||
} else if proto := r.Header.Get(forwarded); proto != "" { |
||||
// match should contain at least two elements if the protocol was
|
||||
// specified in the Forwarded header. The first element will always be
|
||||
// the 'for=', which we ignore, subsequently we proceed to look for
|
||||
// 'proto=' which should precede right after `for=` if not
|
||||
// we simply ignore the values and return empty. This is in line
|
||||
// with the approach we took for returning first ip from multiple
|
||||
// params.
|
||||
if match := forRegex.FindStringSubmatch(proto); len(match) > 1 { |
||||
if match = protoRegex.FindStringSubmatch(match[2]); len(match) > 1 { |
||||
scheme = strings.ToLower(match[2]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return scheme |
||||
} |
||||
|
||||
// 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 |
||||
} |
@ -0,0 +1,83 @@ |
||||
/* |
||||
* 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 handlers |
||||
|
||||
import ( |
||||
"net/http" |
||||
"testing" |
||||
) |
||||
|
||||
type headerTest struct { |
||||
key string // header key
|
||||
val string // header val
|
||||
expected string // expected result
|
||||
} |
||||
|
||||
func TestGetScheme(t *testing.T) { |
||||
headers := []headerTest{ |
||||
{xForwardedProto, "https", "https"}, |
||||
{xForwardedProto, "http", "http"}, |
||||
{xForwardedProto, "HTTP", "http"}, |
||||
{xForwardedScheme, "https", "https"}, |
||||
{xForwardedScheme, "http", "http"}, |
||||
{xForwardedScheme, "HTTP", "http"}, |
||||
{forwarded, `For="[2001:db8:cafe::17]:4711`, ""}, // No proto
|
||||
{forwarded, `for=192.0.2.43, for=198.51.100.17;proto=https`, ""}, // Multiple params, will be empty.
|
||||
{forwarded, `for=172.32.10.15; proto=https;by=127.0.0.1;`, "https"}, // Space before proto
|
||||
{forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, "http"}, // Multiple params
|
||||
} |
||||
for _, v := range headers { |
||||
req := &http.Request{ |
||||
Header: http.Header{ |
||||
v.key: []string{v.val}, |
||||
}} |
||||
res := GetSourceScheme(req) |
||||
if res != v.expected { |
||||
t.Errorf("wrong header for %s: got %s want %s", v.key, res, |
||||
v.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// TestGetSourceIP - check the source ip of a request is parsed correctly.
|
||||
func TestGetSourceIP(t *testing.T) { |
||||
headers := []headerTest{ |
||||
{xForwardedFor, "8.8.8.8", "8.8.8.8"}, // Single address
|
||||
{xForwardedFor, "8.8.8.8, 8.8.4.4", "8.8.8.8"}, // Multiple
|
||||
{xForwardedFor, "", ""}, // None
|
||||
{xRealIP, "8.8.8.8", "8.8.8.8"}, // Single address
|
||||
{xRealIP, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address
|
||||
{xRealIP, "", ""}, // None
|
||||
{forwarded, `for="_gazonk"`, "_gazonk"}, // Hostname
|
||||
{forwarded, `For="[2001:db8:cafe::17]:4711`, `[2001:db8:cafe::17]:4711`}, // IPv6 address
|
||||
{forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, `192.0.2.60`}, // Multiple params
|
||||
{forwarded, `for=192.0.2.43, for=198.51.100.17`, "192.0.2.43"}, // Multiple params
|
||||
{forwarded, `for="workstation.local",for=198.51.100.17`, "workstation.local"}, // Hostname
|
||||
} |
||||
|
||||
for _, v := range headers { |
||||
req := &http.Request{ |
||||
Header: http.Header{ |
||||
v.key: []string{v.val}, |
||||
}} |
||||
res := GetSourceIP(req) |
||||
if res != v.expected { |
||||
t.Errorf("wrong header for %s: got %s want %s", v.key, res, |
||||
v.expected) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue