mirror of
https://github.com/minio/minio.git
synced 2025-11-09 13:39:46 -05:00
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:
committed by
kannappanr
parent
d71b1d25f8
commit
e4f6877c8b
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user