Merge v2.2 into v2.3
This commit is contained in:
commit
207d0bec78
15 changed files with 461 additions and 30 deletions
|
@ -6,6 +6,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -100,6 +101,17 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
|
|||
Level: logrus.InfoLevel,
|
||||
}
|
||||
|
||||
// Transform headers names in config to a canonical form, to be used as is without further transformations.
|
||||
if config.Fields != nil && config.Fields.Headers != nil && len(config.Fields.Headers.Names) > 0 {
|
||||
fields := map[string]string{}
|
||||
|
||||
for h, v := range config.Fields.Headers.Names {
|
||||
fields[textproto.CanonicalMIMEHeaderKey(h)] = v
|
||||
}
|
||||
|
||||
config.Fields.Headers.Names = fields
|
||||
}
|
||||
|
||||
logHandler := &Handler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
|
|
|
@ -41,11 +41,7 @@ var (
|
|||
)
|
||||
|
||||
func TestLogRotation(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "traefik_")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up temporary directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
tempDir := createTempDir(t, "traefik_")
|
||||
|
||||
fileName := filepath.Join(tempDir, "traefik.log")
|
||||
rotatedFileName := fileName + ".rotated"
|
||||
|
@ -119,9 +115,106 @@ func lineCount(t *testing.T, fileName string) int {
|
|||
return count
|
||||
}
|
||||
|
||||
func TestLoggerHeaderFields(t *testing.T) {
|
||||
tmpDir := createTempDir(t, CommonFormat)
|
||||
|
||||
expectedValue := "expectedValue"
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
accessLogFields types.AccessLogFields
|
||||
header string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "with default mode",
|
||||
header: "User-Agent",
|
||||
expected: types.AccessLogDrop,
|
||||
accessLogFields: types.AccessLogFields{
|
||||
DefaultMode: types.AccessLogDrop,
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: types.AccessLogDrop,
|
||||
Names: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with exact header name",
|
||||
header: "User-Agent",
|
||||
expected: types.AccessLogKeep,
|
||||
accessLogFields: types.AccessLogFields{
|
||||
DefaultMode: types.AccessLogDrop,
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: types.AccessLogDrop,
|
||||
Names: map[string]string{
|
||||
"User-Agent": types.AccessLogKeep,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with case insensitive match on header name",
|
||||
header: "User-Agent",
|
||||
expected: types.AccessLogKeep,
|
||||
accessLogFields: types.AccessLogFields{
|
||||
DefaultMode: types.AccessLogDrop,
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: types.AccessLogDrop,
|
||||
Names: map[string]string{
|
||||
"user-agent": types.AccessLogKeep,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
logFile, err := ioutil.TempFile(tmpDir, "*.log")
|
||||
require.NoError(t, err)
|
||||
|
||||
config := &types.AccessLog{
|
||||
FilePath: logFile.Name(),
|
||||
Format: CommonFormat,
|
||||
Fields: &test.accessLogFields,
|
||||
}
|
||||
|
||||
logger, err := NewHandler(config)
|
||||
require.NoError(t, err)
|
||||
defer logger.Close()
|
||||
|
||||
if config.FilePath != "" {
|
||||
_, err = os.Stat(config.FilePath)
|
||||
require.NoError(t, err, fmt.Sprintf("logger should create %s", config.FilePath))
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Header: map[string][]string{},
|
||||
URL: &url.URL{
|
||||
Path: testPath,
|
||||
},
|
||||
}
|
||||
req.Header.Set(test.header, expectedValue)
|
||||
|
||||
logger.ServeHTTP(httptest.NewRecorder(), req, http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
logData, err := ioutil.ReadFile(logFile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.expected == types.AccessLogDrop {
|
||||
assert.NotContains(t, string(logData), expectedValue)
|
||||
} else {
|
||||
assert.Contains(t, string(logData), expectedValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerCLF(t *testing.T) {
|
||||
tmpDir := createTempDir(t, CommonFormat)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
|
||||
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat}
|
||||
|
@ -136,7 +229,6 @@ func TestLoggerCLF(t *testing.T) {
|
|||
|
||||
func TestAsyncLoggerCLF(t *testing.T) {
|
||||
tmpDir := createTempDir(t, CommonFormat)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
|
||||
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024}
|
||||
|
@ -358,7 +450,6 @@ func TestLoggerJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
tmpDir := createTempDir(t, JSONFormat)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
|
||||
|
||||
|
@ -642,6 +733,8 @@ func createTempDir(t *testing.T, prefix string) string {
|
|||
tmpDir, err := ioutil.TempDir("", prefix)
|
||||
require.NoError(t, err, "failed to create temp dir")
|
||||
|
||||
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
|
||||
|
||||
return tmpDir
|
||||
}
|
||||
|
||||
|
|
30
pkg/provider/traefik/fixtures/redirection_with_protocol.json
Normal file
30
pkg/provider/traefik/fixtures/redirection_with_protocol.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"http": {
|
||||
"routers": {
|
||||
"web-to-websecure": {
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"middlewares": [
|
||||
"redirect-web-to-websecure"
|
||||
],
|
||||
"service": "noop@internal",
|
||||
"rule": "HostRegexp(`{host:.+}`)"
|
||||
}
|
||||
},
|
||||
"middlewares": {
|
||||
"redirect-web-to-websecure": {
|
||||
"redirectScheme": {
|
||||
"scheme": "https",
|
||||
"port": "443",
|
||||
"permanent": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"noop": {}
|
||||
}
|
||||
},
|
||||
"tcp": {},
|
||||
"tls": {}
|
||||
}
|
|
@ -142,7 +142,7 @@ func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (str
|
|||
return "", fmt.Errorf("'to' entry point field references a non-existing entry point: %s", def.EntryPoint.To)
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(dst.Address)
|
||||
_, port, err := net.SplitHostPort(dst.GetAddress())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid entry point %q address %q: %w",
|
||||
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)
|
||||
|
|
|
@ -232,6 +232,28 @@ func Test_createConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "redirection_with_protocol.json",
|
||||
staticCfg: static.Configuration{
|
||||
EntryPoints: map[string]*static.EntryPoint{
|
||||
"web": {
|
||||
Address: ":80",
|
||||
HTTP: static.HTTPConfig{
|
||||
Redirections: &static.Redirections{
|
||||
EntryPoint: &static.RedirectEntryPoint{
|
||||
To: "websecure",
|
||||
Scheme: "https",
|
||||
Permanent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"websecure": {
|
||||
Address: ":443/tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
|
@ -99,14 +101,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
|
||||
}
|
||||
|
||||
router.HTTPSHandler(handlerHTTPS, defaultTLSConf)
|
||||
|
||||
if len(configsHTTP) > 0 {
|
||||
router.AddRouteHTTPTLS("*", defaultTLSConf)
|
||||
}
|
||||
|
||||
// Keyed by domain, then by options reference.
|
||||
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
|
||||
tlsOptionsForHost := map[string]string{}
|
||||
for routerHTTPName, routerHTTPConfig := range configsHTTP {
|
||||
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName {
|
||||
continue
|
||||
|
@ -141,6 +142,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
continue
|
||||
}
|
||||
|
||||
// domain is already in lower case thanks to the domain parsing
|
||||
if tlsOptionsForHostSNI[domain] == nil {
|
||||
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
|
||||
}
|
||||
|
@ -148,10 +150,52 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
routerName: routerHTTPName,
|
||||
TLSConfig: tlsConf,
|
||||
}
|
||||
|
||||
if _, ok := tlsOptionsForHost[domain]; ok {
|
||||
// Multiple tlsOptions fallback to default
|
||||
tlsOptionsForHost[domain] = "default"
|
||||
} else {
|
||||
tlsOptionsForHost[domain] = routerHTTPConfig.TLS.Options
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sniCheck := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.TLS == nil {
|
||||
handlerHTTPS.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
host = req.Host
|
||||
}
|
||||
|
||||
host = strings.TrimSpace(host)
|
||||
serverName := strings.TrimSpace(req.TLS.ServerName)
|
||||
|
||||
// Domain Fronting
|
||||
if !strings.EqualFold(host, serverName) {
|
||||
tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName)
|
||||
tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host)
|
||||
|
||||
if tlsOptionHeader != tlsOptionSNI {
|
||||
log.WithoutContext().
|
||||
WithField("host", host).
|
||||
WithField("req.Host", req.Host).
|
||||
WithField("req.TLS.ServerName", req.TLS.ServerName).
|
||||
Debugf("TLS options difference: SNI=%s, Header:%s", tlsOptionSNI, tlsOptionHeader)
|
||||
http.Error(rw, http.StatusText(http.StatusMisdirectedRequest), http.StatusMisdirectedRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
handlerHTTPS.ServeHTTP(rw, req)
|
||||
})
|
||||
|
||||
router.HTTPSHandler(sniCheck, defaultTLSConf)
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
for hostSNI, tlsConfigs := range tlsOptionsForHostSNI {
|
||||
if len(tlsConfigs) == 1 {
|
||||
|
@ -248,3 +292,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string {
|
||||
tlsOptions, ok := tlsOptionsForHost[host]
|
||||
if ok {
|
||||
return tlsOptions
|
||||
}
|
||||
|
||||
tlsOptions, ok = tlsOptionsForHost[strings.ToLower(host)]
|
||||
if ok {
|
||||
return tlsOptions
|
||||
}
|
||||
|
||||
return "default"
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
"github.com/containous/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
// Router is a TCP router.
|
||||
|
@ -65,7 +66,7 @@ func (r *Router) ServeTCP(conn WriteCloser) {
|
|||
}
|
||||
|
||||
// FIXME Optimize and test the routing table before helloServerName
|
||||
serverName = strings.ToLower(serverName)
|
||||
serverName = types.CanonicalDomain(serverName)
|
||||
if r.routingTable != nil && serverName != "" {
|
||||
if target, ok := r.routingTable[serverName]; ok {
|
||||
target.ServeTCP(r.GetConn(conn, peeked))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue