mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
rename all remaining packages to internal/ (#12418)
This is to ensure that there are no projects that try to import `minio/minio/pkg` into their own repo. Any such common packages should go to `https://github.com/minio/pkg`
This commit is contained in:
188
internal/handlers/forwarder.go
Normal file
188
internal/handlers/forwarder.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultFlushInterval = time.Duration(100) * time.Millisecond
|
||||
|
||||
// Forwarder forwards all incoming HTTP requests to configured transport.
|
||||
type Forwarder struct {
|
||||
RoundTripper http.RoundTripper
|
||||
PassHost bool
|
||||
Logger func(error)
|
||||
ErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||
|
||||
// internal variables
|
||||
rewriter *headerRewriter
|
||||
}
|
||||
|
||||
// NewForwarder creates an instance of Forwarder based on the provided list of configuration options
|
||||
func NewForwarder(f *Forwarder) *Forwarder {
|
||||
f.rewriter = &headerRewriter{}
|
||||
if f.RoundTripper == nil {
|
||||
f.RoundTripper = http.DefaultTransport
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// ServeHTTP forwards HTTP traffic using the configured transport
|
||||
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, inReq *http.Request) {
|
||||
outReq := new(http.Request)
|
||||
*outReq = *inReq // includes shallow copies of maps, but we handle this in Director
|
||||
|
||||
revproxy := httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
f.modifyRequest(req, inReq.URL)
|
||||
},
|
||||
Transport: f.RoundTripper,
|
||||
FlushInterval: defaultFlushInterval,
|
||||
ErrorHandler: f.customErrHandler,
|
||||
}
|
||||
|
||||
if f.ErrorHandler != nil {
|
||||
revproxy.ErrorHandler = f.ErrorHandler
|
||||
}
|
||||
|
||||
revproxy.ServeHTTP(w, outReq)
|
||||
}
|
||||
|
||||
// customErrHandler is originally implemented to avoid having the following error
|
||||
// `http: proxy error: context canceled` printed by Golang
|
||||
func (f *Forwarder) customErrHandler(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if f.Logger != nil && err != context.Canceled {
|
||||
f.Logger(err)
|
||||
}
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
|
||||
func (f *Forwarder) getURLFromRequest(req *http.Request) *url.URL {
|
||||
// If the Request was created by Go via a real HTTP request, RequestURI will
|
||||
// contain the original query string. If the Request was created in code, RequestURI
|
||||
// will be empty, and we will use the URL object instead
|
||||
u := req.URL
|
||||
if req.RequestURI != "" {
|
||||
parsedURL, err := url.ParseRequestURI(req.RequestURI)
|
||||
if err == nil {
|
||||
u = parsedURL
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// copyURL provides update safe copy by avoiding shallow copying User field
|
||||
func copyURL(i *url.URL) *url.URL {
|
||||
out := *i
|
||||
if i.User != nil {
|
||||
u := *i.User
|
||||
out.User = &u
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
// Modify the request to handle the target URL
|
||||
func (f *Forwarder) modifyRequest(outReq *http.Request, target *url.URL) {
|
||||
outReq.URL = copyURL(outReq.URL)
|
||||
outReq.URL.Scheme = target.Scheme
|
||||
outReq.URL.Host = target.Host
|
||||
|
||||
u := f.getURLFromRequest(outReq)
|
||||
|
||||
outReq.URL.Path = u.Path
|
||||
outReq.URL.RawPath = u.RawPath
|
||||
outReq.URL.RawQuery = u.RawQuery
|
||||
outReq.RequestURI = "" // Outgoing request should not have RequestURI
|
||||
|
||||
// Do not pass client Host header unless requested.
|
||||
if !f.PassHost {
|
||||
outReq.Host = target.Host
|
||||
}
|
||||
|
||||
// TODO: only supports HTTP 1.1 for now.
|
||||
outReq.Proto = "HTTP/1.1"
|
||||
outReq.ProtoMajor = 1
|
||||
outReq.ProtoMinor = 1
|
||||
|
||||
f.rewriter.Rewrite(outReq)
|
||||
|
||||
// Disable closeNotify when method GET for http pipelining
|
||||
if outReq.Method == http.MethodGet {
|
||||
quietReq := outReq.WithContext(context.Background())
|
||||
*outReq = *quietReq
|
||||
}
|
||||
}
|
||||
|
||||
// headerRewriter is responsible for removing hop-by-hop headers and setting forwarding headers
|
||||
type headerRewriter struct{}
|
||||
|
||||
// Clean up IP in case if it is ipv6 address and it has {zone} information in it, like
|
||||
// "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3 Ethernet Adapter - Virtual Switch)]:64692"
|
||||
func ipv6fix(clientIP string) string {
|
||||
return strings.Split(clientIP, "%")[0]
|
||||
}
|
||||
|
||||
func (rw *headerRewriter) Rewrite(req *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
clientIP = ipv6fix(clientIP)
|
||||
if req.Header.Get(xRealIP) == "" {
|
||||
req.Header.Set(xRealIP, clientIP)
|
||||
}
|
||||
}
|
||||
|
||||
xfProto := req.Header.Get(xForwardedProto)
|
||||
if xfProto == "" {
|
||||
if req.TLS != nil {
|
||||
req.Header.Set(xForwardedProto, "https")
|
||||
} else {
|
||||
req.Header.Set(xForwardedProto, "http")
|
||||
}
|
||||
}
|
||||
|
||||
if xfPort := req.Header.Get(xForwardedPort); xfPort == "" {
|
||||
req.Header.Set(xForwardedPort, forwardedPort(req))
|
||||
}
|
||||
|
||||
if xfHost := req.Header.Get(xForwardedHost); xfHost == "" && req.Host != "" {
|
||||
req.Header.Set(xForwardedHost, req.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func forwardedPort(req *http.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if _, port, err := net.SplitHostPort(req.Host); err == nil && port != "" {
|
||||
return port
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
return "443"
|
||||
}
|
||||
|
||||
return "80"
|
||||
}
|
||||
127
internal/handlers/proxy.go
Normal file
127
internal/handlers/proxy.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Originally from https://github.com/gorilla/handlers with following license
|
||||
// https://raw.githubusercontent.com/gorilla/handlers/master/LICENSE, forked
|
||||
// and heavily modified for MinIO's internal needs.
|
||||
|
||||
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")
|
||||
xForwardedPort = http.CanonicalHeaderKey("X-Forwarded-Port")
|
||||
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
|
||||
}
|
||||
|
||||
// GetSourceIPFromHeaders retrieves the IP from the X-Forwarded-For, X-Real-IP
|
||||
// and RFC7239 Forwarded headers (in that order)
|
||||
func GetSourceIPFromHeaders(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], `"`)
|
||||
}
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// GetSourceIP retrieves the IP from the request headers
|
||||
// and falls back to r.RemoteAddr when necessary.
|
||||
func GetSourceIP(r *http.Request) string {
|
||||
addr := GetSourceIPFromHeaders(r)
|
||||
if addr != "" {
|
||||
return addr
|
||||
}
|
||||
|
||||
// Default to remote address if headers not set.
|
||||
addr, _, _ = net.SplitHostPort(r.RemoteAddr)
|
||||
return addr
|
||||
}
|
||||
84
internal/handlers/proxy_test.go
Normal file
84
internal/handlers/proxy_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user