1
0
Fork 0

Merge current v2.5 into master

This commit is contained in:
romain 2021-08-30 14:51:57 +02:00
commit f81ceaef8a
64 changed files with 949 additions and 208 deletions

View file

@ -15,6 +15,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares"
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
"github.com/traefik/traefik/v2/pkg/tracing"
"github.com/vulcand/oxy/forward"
"github.com/vulcand/oxy/utils"
@ -89,7 +90,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
fa.authResponseHeadersRegex = re
}
return fa, nil
return connectionheader.Remover(fa), nil
}
func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) {

View file

@ -0,0 +1,46 @@
package connectionheader
import (
"net/http"
"net/textproto"
"strings"
"golang.org/x/net/http/httpguts"
)
const (
connectionHeader = "Connection"
upgradeHeader = "Upgrade"
)
// Remover removes hop-by-hop headers listed in the "Connection" header.
// See RFC 7230, section 6.1.
func Remover(next http.Handler) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {
var reqUpType string
if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) {
reqUpType = req.Header.Get(upgradeHeader)
}
removeConnectionHeaders(req.Header)
if reqUpType != "" {
req.Header.Set(connectionHeader, upgradeHeader)
req.Header.Set(upgradeHeader, reqUpType)
} else {
req.Header.Del(connectionHeader)
}
next.ServeHTTP(rw, req)
}
}
func removeConnectionHeaders(h http.Header) {
for _, f := range h[connectionHeader] {
for _, sf := range strings.Split(f, ",") {
if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf)
}
}
}
}

View file

@ -0,0 +1,71 @@
package connectionheader
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemover(t *testing.T) {
testCases := []struct {
desc string
reqHeaders map[string]string
expected http.Header
}{
{
desc: "simple remove",
reqHeaders: map[string]string{
"Foo": "bar",
connectionHeader: "foo",
},
expected: http.Header{},
},
{
desc: "remove and Upgrade",
reqHeaders: map[string]string{
upgradeHeader: "test",
"Foo": "bar",
connectionHeader: "Upgrade,foo",
},
expected: http.Header{
upgradeHeader: []string{"test"},
connectionHeader: []string{"Upgrade"},
},
},
{
desc: "no remove",
reqHeaders: map[string]string{
"Foo": "bar",
connectionHeader: "fii",
},
expected: http.Header{
"Foo": []string{"bar"},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
h := Remover(next)
req := httptest.NewRequest(http.MethodGet, "https://localhost", nil)
for k, v := range test.reqHeaders {
req.Header.Set(k, v)
}
rw := httptest.NewRecorder()
h.ServeHTTP(rw, req)
assert.Equal(t, test.expected, req.Header)
})
}
}

View file

@ -10,6 +10,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares"
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
"github.com/traefik/traefik/v2/pkg/tracing"
)
@ -68,11 +69,12 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
if hasCustomHeaders || hasCorsHeaders {
logger.Debugf("Setting up customHeaders/Cors from %v", cfg)
var err error
handler, err = NewHeader(nextHandler, cfg)
h, err := NewHeader(nextHandler, cfg)
if err != nil {
return nil, err
}
handler = connectionheader.Remover(h)
}
return &headers{

View file

@ -22,13 +22,18 @@ type responseModifier struct {
}
// modifier can be nil.
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
return &responseModifier{
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter {
rm := &responseModifier{
req: r,
rw: w,
modifier: modifier,
code: http.StatusOK,
}
if _, ok := w.(http.CloseNotifier); ok {
return responseModifierWithCloseNotify{responseModifier: rm}
}
return rm
}
func (r *responseModifier) WriteHeader(code int) {
@ -93,7 +98,11 @@ func (r *responseModifier) Flush() {
}
}
// CloseNotify implements http.CloseNotifier.
func (r *responseModifier) CloseNotify() <-chan bool {
return r.rw.(http.CloseNotifier).CloseNotify()
type responseModifierWithCloseNotify struct {
*responseModifier
}
// CloseNotify implements http.CloseNotifier.
func (r *responseModifierWithCloseNotify) CloseNotify() <-chan bool {
return r.responseModifier.rw.(http.CloseNotifier).CloseNotify()
}

View file

@ -4,13 +4,17 @@ import (
"net/http"
"net/url"
"regexp"
"strings"
"github.com/opentracing/opentracing-go/ext"
"github.com/traefik/traefik/v2/pkg/tracing"
"github.com/vulcand/oxy/utils"
)
const (
schemeHTTP = "http"
schemeHTTPS = "https"
)
type redirect struct {
next http.Handler
regex *regexp.Regexp
@ -18,10 +22,11 @@ type redirect struct {
permanent bool
errHandler utils.ErrorHandler
name string
rawURL func(*http.Request) string
}
// New creates a Redirect middleware.
func newRedirect(next http.Handler, regex, replacement string, permanent bool, name string) (http.Handler, error) {
func newRedirect(next http.Handler, regex, replacement string, permanent bool, rawURL func(*http.Request) string, name string) (http.Handler, error) {
re, err := regexp.Compile(regex)
if err != nil {
return nil, err
@ -34,6 +39,7 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, n
errHandler: utils.DefaultHandler,
next: next,
name: name,
rawURL: rawURL,
}, nil
}
@ -42,7 +48,7 @@ func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
}
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
oldURL := rawURL(req)
oldURL := r.rawURL(req)
// If the Regexp doesn't match, skip to the next handler.
if !r.regex.MatchString(oldURL) {
@ -98,33 +104,3 @@ func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
func rawURL(req *http.Request) string {
scheme := "http"
host := req.Host
port := ""
uri := req.RequestURI
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
re, _ := regexp.Compile(schemeRegex)
if re.Match([]byte(req.RequestURI)) {
match := re.FindStringSubmatch(req.RequestURI)
scheme = match[1]
if len(match[2]) > 0 {
host = match[2]
}
if len(match[3]) > 0 {
port = match[3]
}
uri = match[4]
}
if req.TLS != nil {
scheme = "https"
}
return strings.Join([]string{scheme, "://", host, port, uri}, "")
}

View file

@ -3,6 +3,8 @@ package redirect
import (
"context"
"net/http"
"regexp"
"strings"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
@ -19,5 +21,35 @@ func NewRedirectRegex(ctx context.Context, next http.Handler, conf dynamic.Redir
logger.Debug("Creating middleware")
logger.Debugf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement)
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, name)
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, rawURL, name)
}
func rawURL(req *http.Request) string {
scheme := schemeHTTP
host := req.Host
port := ""
uri := req.RequestURI
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
re, _ := regexp.Compile(schemeRegex)
if re.Match([]byte(req.RequestURI)) {
match := re.FindStringSubmatch(req.RequestURI)
scheme = match[1]
if len(match[2]) > 0 {
host = match[2]
}
if len(match[3]) > 0 {
port = match[3]
}
uri = match[4]
}
if req.TLS != nil {
scheme = schemeHTTPS
}
return strings.Join([]string{scheme, "://", host, port, uri}, "")
}

View file

@ -3,7 +3,10 @@ package redirect
import (
"context"
"errors"
"net"
"net/http"
"regexp"
"strings"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
@ -26,9 +29,47 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi
}
port := ""
if len(conf.Port) > 0 && !(conf.Scheme == "http" && conf.Port == "80" || conf.Scheme == "https" && conf.Port == "443") {
if len(conf.Port) > 0 && !(conf.Scheme == schemeHTTP && conf.Port == "80" || conf.Scheme == schemeHTTPS && conf.Port == "443") {
port = ":" + conf.Port
}
return newRedirect(next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, name)
return newRedirect(next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, rawURLScheme, name)
}
func rawURLScheme(req *http.Request) string {
scheme := schemeHTTP
host, port, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
} else {
port = ":" + port
}
uri := req.RequestURI
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
re, _ := regexp.Compile(schemeRegex)
if re.Match([]byte(req.RequestURI)) {
match := re.FindStringSubmatch(req.RequestURI)
scheme = match[1]
if len(match[2]) > 0 {
host = match[2]
}
if len(match[3]) > 0 {
port = match[3]
}
uri = match[4]
}
if req.TLS != nil {
scheme = schemeHTTPS
}
if scheme == schemeHTTP && port == ":80" || scheme == schemeHTTPS && port == ":443" || port == "" {
port = ""
}
return strings.Join([]string{scheme, "://", host, port, uri}, "")
}

View file

@ -127,8 +127,18 @@ func TestRedirectSchemeHandler(t *testing.T) {
Port: "80",
},
url: "http://foo:80",
expectedURL: "http://foo",
expectedStatus: http.StatusFound,
expectedURL: "http://foo:80",
expectedStatus: http.StatusOK,
},
{
desc: "to HTTPS 443",
config: dynamic.RedirectScheme{
Scheme: "https",
Port: "443",
},
url: "https://foo:443",
expectedURL: "https://foo:443",
expectedStatus: http.StatusOK,
},
{
desc: "HTTP to wss",
@ -248,6 +258,7 @@ func TestRedirectSchemeHandler(t *testing.T) {
if test.method != "" {
method = test.method
}
req := httptest.NewRequest(method, test.url, nil)
for k, v := range test.headers {

View file

@ -11,6 +11,15 @@ type statusCodeRecoder interface {
Status() int
}
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder {
recorder := &statusCodeWithoutCloseNotify{rw, status}
if _, ok := rw.(http.CloseNotifier); ok {
return &statusCodeWithCloseNotify{recorder}
}
return recorder
}
type statusCodeWithoutCloseNotify struct {
http.ResponseWriter
status int
@ -46,12 +55,3 @@ type statusCodeWithCloseNotify struct {
func (s *statusCodeWithCloseNotify) CloseNotify() <-chan bool {
return s.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder {
recorder := &statusCodeWithoutCloseNotify{rw, status}
if _, ok := rw.(http.CloseNotifier); ok {
return &statusCodeWithCloseNotify{recorder}
}
return recorder
}