Handle incoming proxy requests ip, scheme (#5591)

This PR implements functions to get the right ip, scheme
from the incoming proxied requests.
This commit is contained in:
Harshavardhana
2018-03-02 15:23:04 -08:00
committed by kannappanr
parent d71b1d25f8
commit e4f6877c8b
9 changed files with 275 additions and 100 deletions

View File

@@ -28,6 +28,7 @@ import (
"github.com/gorilla/mux"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/madmin"
)
@@ -472,17 +473,6 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Helper function to fetch client address - we use the
// X-forwarded-for header if one is present.
getClientAddress := func() string {
addr := r.RemoteAddr
fwdFor := r.Header.Get("X-Forwarded-For")
if fwdFor == "" {
return addr
}
return fwdFor
}
type healResp struct {
respBytes []byte
errCode APIErrorCode
@@ -530,7 +520,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
if clientToken == "" {
// Not a status request
nh := newHealSequence(bucket, objPrefix, getClientAddress(),
nh := newHealSequence(bucket, objPrefix, handlers.GetSourceIP(r),
numDisks, hs, forceStart)
respCh := make(chan healResp)

View File

@@ -22,6 +22,8 @@ import (
"net/url"
"path"
"time"
"github.com/minio/minio/pkg/handlers"
)
const (
@@ -281,15 +283,21 @@ func getURLScheme(tls bool) string {
}
// getObjectLocation gets the fully qualified URL of an object.
func getObjectLocation(host, proto, bucket, object string) string {
func getObjectLocation(r *http.Request, domain, bucket, object string) string {
proto := handlers.GetSourceScheme(r)
if proto == "" {
proto = getURLScheme(globalIsSSL)
}
u := url.URL{
Host: host,
u := &url.URL{
Host: r.Host,
Path: path.Join(slashSeparator, bucket, object),
Scheme: proto,
}
// If domain is set then we need to use bucket DNS style.
if domain != "" {
u.Host = bucket + "." + domain
u.Path = path.Join(slashSeparator, object)
}
return u.String()
}

View File

@@ -17,42 +17,89 @@
package cmd
import (
"net/http"
"testing"
)
// Tests object location.
func TestObjectLocation(t *testing.T) {
testCases := []struct {
host, proto, bucket, object string
expectedLocation string
request *http.Request
bucket, object string
domain string
expectedLocation string
}{
// Server binding to localhost IP with https.
{
host: "127.0.0.1:9000",
proto: httpsScheme,
request: &http.Request{
Host: "127.0.0.1:9000",
Header: map[string][]string{
"X-Forwarded-Scheme": {httpScheme},
},
},
bucket: "testbucket1",
object: "test/1.txt",
expectedLocation: "http://127.0.0.1:9000/testbucket1/test/1.txt",
},
{
request: &http.Request{
Host: "127.0.0.1:9000",
Header: map[string][]string{
"X-Forwarded-Scheme": {httpsScheme},
},
},
bucket: "testbucket1",
object: "test/1.txt",
expectedLocation: "https://127.0.0.1:9000/testbucket1/test/1.txt",
},
// Server binding to fqdn.
{
host: "s3.mybucket.org",
proto: httpScheme,
request: &http.Request{
Host: "s3.mybucket.org",
Header: map[string][]string{
"X-Forwarded-Scheme": {httpScheme},
},
},
bucket: "mybucket",
object: "test/1.txt",
expectedLocation: "http://s3.mybucket.org/mybucket/test/1.txt",
},
// Server binding to fqdn.
{
host: "mys3.mybucket.org",
proto: "",
request: &http.Request{
Host: "mys3.mybucket.org",
Header: map[string][]string{},
},
bucket: "mybucket",
object: "test/1.txt",
expectedLocation: "http://mys3.mybucket.org/mybucket/test/1.txt",
},
// Server with virtual domain name.
{
request: &http.Request{
Host: "mys3.bucket.org",
Header: map[string][]string{},
},
domain: "mys3.bucket.org",
bucket: "mybucket",
object: "test/1.txt",
expectedLocation: "http://mybucket.mys3.bucket.org/test/1.txt",
},
{
request: &http.Request{
Host: "mys3.bucket.org",
Header: map[string][]string{
"X-Forwarded-Scheme": {httpsScheme},
},
},
domain: "mys3.bucket.org",
bucket: "mybucket",
object: "test/1.txt",
expectedLocation: "https://mybucket.mys3.bucket.org/test/1.txt",
},
}
for i, testCase := range testCases {
gotLocation := getObjectLocation(testCase.host, testCase.proto, testCase.bucket, testCase.object)
gotLocation := getObjectLocation(testCase.request, testCase.domain, testCase.bucket, testCase.object)
if testCase.expectedLocation != gotLocation {
t.Errorf("Test %d: expected %s, got %s", i+1, testCase.expectedLocation, gotLocation)
}

View File

@@ -22,6 +22,8 @@ import (
"io/ioutil"
"net/http"
"strings"
"github.com/minio/minio/pkg/handlers"
)
// Verify if request has JWT.
@@ -136,13 +138,12 @@ func checkRequestAuthType(r *http.Request, bucket, policyAction, region string)
if reqAuthType == authTypeAnonymous && policyAction != "" {
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
sourceIP := getSourceIPAddress(r)
resource, err := getResource(r.URL.Path, r.Host, globalDomainName)
if err != nil {
return ErrInternalError
}
return enforceBucketPolicy(bucket, policyAction, resource,
r.Referer(), sourceIP, r.URL.Query())
r.Referer(), handlers.GetSourceIP(r), r.URL.Query())
}
// By default return ErrAccessDenied

View File

@@ -572,8 +572,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return
}
port := r.Header.Get("X-Forward-Proto")
location := getObjectLocation(r.Host, port, bucket, object)
location := getObjectLocation(r, globalDomainName, bucket, object)
w.Header().Set("ETag", `"`+objInfo.ETag+`"`)
w.Header().Set("Location", location)

View File

@@ -32,6 +32,7 @@ import (
mux "github.com/gorilla/mux"
"github.com/minio/minio/pkg/errors"
"github.com/minio/minio/pkg/handlers"
"github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/ioutil"
sha256 "github.com/minio/sha256-simd"
@@ -57,22 +58,6 @@ func setHeadGetRespHeaders(w http.ResponseWriter, reqParams url.Values) {
}
}
// getSourceIPAddress - get the source ip address of the request.
func getSourceIPAddress(r *http.Request) string {
var ip string
// Attempt to get ip from standard headers.
// Do not support X-Forwarded-For because it is easy to spoof.
ip = r.Header.Get("X-Real-Ip")
parsedIP := net.ParseIP(ip)
// Skip non valid IP address.
if parsedIP != nil {
return ip
}
// Default to remote address if headers not set.
ip, _, _ = net.SplitHostPort(r.RemoteAddr)
return ip
}
// errAllowableNotFound - For an anon user, return 404 if have ListBucket, 403 otherwise
// this is in keeping with the permissions sections of the docs of both:
// HEAD Object: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html
@@ -81,7 +66,7 @@ func errAllowableObjectNotFound(bucket string, r *http.Request) APIErrorCode {
if getRequestAuthType(r) == authTypeAnonymous {
// We care about the bucket as a whole, not a particular resource.
resource := "/" + bucket
sourceIP := getSourceIPAddress(r)
sourceIP := handlers.GetSourceIP(r)
if s3Error := enforceBucketPolicy(bucket, "s3:ListBucket", resource,
r.Referer(), sourceIP, r.URL.Query()); s3Error != ErrNone {
return ErrAccessDenied
@@ -631,7 +616,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
sourceIP := getSourceIPAddress(r)
sourceIP := handlers.GetSourceIP(r)
if s3Err = enforceBucketPolicy(bucket, "s3:PutObject", r.URL.Path, r.Referer(), sourceIP, r.URL.Query()); s3Err != ErrNone {
writeErrorResponse(w, s3Err, r.URL)
return
@@ -1049,7 +1034,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
if s3Error := enforceBucketPolicy(bucket, "s3:PutObject", r.URL.Path,
r.Referer(), getSourceIPAddress(r), r.URL.Query()); s3Error != ErrNone {
r.Referer(), handlers.GetSourceIP(r), r.URL.Query()); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL)
return
}

View File

@@ -3563,56 +3563,3 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
// `ExecObjectLayerAPINilTest` sets the Object Layer to `nil` and calls the handler.
ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
}
// TestGetSourceIPAddress - check the source ip of a request is parsed correctly.
func TestGetSourceIPAddress(t *testing.T) {
testCases := []struct {
request *http.Request
expectedIP string
}{
{
// Test Case 1. Use only RemoteAddr as host and port.
request: &http.Request{
RemoteAddr: "127.0.0.1:9000",
},
expectedIP: "127.0.0.1",
},
{
// Test Case 2. Use both RemoteAddr and single header.
request: &http.Request{
RemoteAddr: "127.0.0.1:9000",
Header: map[string][]string{
"X-Real-Ip": {"54.240.143.0"},
},
},
expectedIP: "54.240.143.0", // Use headers before RemoteAddr.
},
{
// Test Case 3. Use both RemoteAddr and several header vals.
// Check that first val in header is used.
request: &http.Request{
RemoteAddr: "127.0.0.1:9000",
Header: map[string][]string{
"X-Real-Ip": {"54.240.143.0", "54.240.143.188"},
},
},
expectedIP: "54.240.143.0",
},
{
// Test Case 4. Use header and corrupt header value.
request: &http.Request{
RemoteAddr: "127.0.0.1:9000",
Header: map[string][]string{
"X-Real-Ip": {"54.240.143.188", "corrupt"},
},
},
expectedIP: "54.240.143.188",
},
}
for i, test := range testCases {
receivedIP := getSourceIPAddress(test.request)
if test.expectedIP != receivedIP {
t.Fatalf("Case %d: Expected the IP to be `%s`, but instead got `%s`", i+1, test.expectedIP, receivedIP)
}
}
}