Merge v2.2 into v2.3

This commit is contained in:
jb doumenjou 2020-07-22 14:39:45 +02:00
commit 207d0bec78
15 changed files with 461 additions and 30 deletions

View file

@ -1,3 +1,21 @@
## [v2.2.7](https://github.com/containous/traefik/tree/v2.2.7) (2020-07-20)
[All Commits](https://github.com/containous/traefik/compare/v2.2.6...v2.2.7)
**Bug fixes:**
- **[server,tls]** fix: drop host port to compare with SNI. ([#7071](https://github.com/containous/traefik/pull/7071) by [ldez](https://github.com/ldez))
## [v2.2.6](https://github.com/containous/traefik/tree/v2.2.6) (2020-07-17)
[All Commits](https://github.com/containous/traefik/compare/v2.2.5...v2.2.6)
**Bug fixes:**
- **[logs]** fix: access logs header names filtering is case insensitive ([#6900](https://github.com/containous/traefik/pull/6900) by [mjeanroy](https://github.com/mjeanroy))
- **[provider]** Get Entrypoints Port Address without protocol for redirect ([#7047](https://github.com/containous/traefik/pull/7047) by [SantoDE](https://github.com/SantoDE))
- **[tls]** Fix domain fronting ([#7064](https://github.com/containous/traefik/pull/7064) by [juliens](https://github.com/juliens))
**Documentation:**
- fix: documentation references. ([#7049](https://github.com/containous/traefik/pull/7049) by [ldez](https://github.com/ldez))
- Add example for entrypoint on one ip address ([#6483](https://github.com/containous/traefik/pull/6483) by [SimonHeimberg](https://github.com/SimonHeimberg))
## [v2.3.0-rc2](https://github.com/containous/traefik/tree/v2.3.0-rc2) (2020-07-15) ## [v2.3.0-rc2](https://github.com/containous/traefik/tree/v2.3.0-rc2) (2020-07-15)
[All Commits](https://github.com/containous/traefik/compare/v2.3.0-rc1...v2.3.0-rc2) [All Commits](https://github.com/containous/traefik/compare/v2.3.0-rc1...v2.3.0-rc2)

View file

@ -362,7 +362,7 @@ For complete details, refer to your provider's _Additional configuration_ link.
| [Zonomi](https://zonomi.com) | `zonomi` | `ZONOMI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zonomi) | | [Zonomi](https://zonomi.com) | `zonomi` | `ZONOMI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/zonomi) |
[^1]: more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/) [^1]: more information about the HTTP message format can be found [here](https://go-acme.github.io/lego/dns/httpreq/)
[^2]: [providing_credentials_to_your_application](https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application) [^2]: [providing_credentials_to_your_application](https://cloud.google.com/docs/authentication/production)
[^3]: [google/default.go](https://github.com/golang/oauth2/blob/36a7019397c4c86cf59eeab3bc0d188bac444277/google/default.go#L61-L76) [^3]: [google/default.go](https://github.com/golang/oauth2/blob/36a7019397c4c86cf59eeab3bc0d188bac444277/google/default.go#L61-L76)
[^4]: `docker stack` remark: there is no way to support terminal attached to container when deploying with `docker stack`, so you might need to run container with `docker run -it` to generate certificates using `manual` provider. [^4]: `docker stack` remark: there is no way to support terminal attached to container when deploying with `docker stack`, so you might need to run container with `docker run -it` to generate certificates using `manual` provider.
[^5]: The `Global API Key` needs to be used, not the `Origin CA Key`. [^5]: The `Global API Key` needs to be used, not the `Origin CA Key`.

View file

@ -168,7 +168,7 @@ The format is:
If both TCP and UDP are wanted for the same port, two entryPoints definitions are needed, such as in the example below. If both TCP and UDP are wanted for the same port, two entryPoints definitions are needed, such as in the example below.
??? example "Both TCP and UDP on port 3179" ??? example "Both TCP and UDP on Port 3179"
```toml tab="File (TOML)" ```toml tab="File (TOML)"
## Static configuration ## Static configuration
@ -194,6 +194,30 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
--entryPoints.udpep.address=:3179/udp --entryPoints.udpep.address=:3179/udp
``` ```
??? example "Listen on Specific IP Addresses Only"
```toml tab="File (TOML)"
[entryPoints.specificIPv4]
address = "192.168.2.7:8888"
[entryPoints.specificIPv6]
address = "[2001:db8::1]:8888"
```
```yaml tab="File (yaml)"
entryPoints:
specificIPv4:
address: "192.168.2.7:8888"
specificIPv6:
address: "[2001:db8::1]:8888"
```
```bash tab="CLI"
entrypoints.specificIPv4.address=192.168.2.7:8888
entrypoints.specificIPv6.address=[2001:db8::1]:8888
```
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
### Forwarded Headers ### Forwarded Headers
You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`). You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`).

View file

@ -472,6 +472,11 @@ It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied
the TLS option is picked from the mapping mentioned above and based on the server name provided during the TLS handshake, the TLS option is picked from the mapping mentioned above and based on the server name provided during the TLS handshake,
and it all happens before routing actually occurs. and it all happens before routing actually occurs.
!!! info "Domain Fronting"
In the case of domain fronting,
if the TLS options associated with the Host Header and the SNI are different then Traefik will respond with a status code `421`.
??? example "Configuring the TLS options" ??? example "Configuring the TLS options"
```toml tab="File (TOML)" ```toml tab="File (TOML)"

View file

@ -444,7 +444,7 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
// A real file is needed to have the right mode on acme.json file // A real file is needed to have the right mode on acme.json file
defer os.Remove("/tmp/acme.json") defer os.Remove("/tmp/acme.json")
backend := startTestServer("9010", http.StatusOK) backend := startTestServer("9010", http.StatusOK, "")
defer backend.Close() defer backend.Close()
for _, sub := range testCase.subCases { for _, sub := range testCase.subCases {

View file

@ -0,0 +1,53 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.websecure]
address = ":4443"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers.router1]
rule = "Host(`site1.www.snitest.com`)"
service = "service1"
[http.routers.router1.tls]
[http.routers.router2]
rule = "Host(`site2.www.snitest.com`)"
service = "service2"
[http.routers.router2.tls]
[http.routers.router3]
rule = "Host(`site3.www.snitest.com`)"
service = "service3"
[http.routers.router3.tls]
options = "mytls"
[http.services.service1]
[[http.services.service1.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[http.services.service2]
[[http.services.service2.loadBalancer.servers]]
url = "http://127.0.0.1:9020"
[http.services.service3]
[[http.services.service3.loadBalancer.servers]]
url = "http://127.0.0.1:9030"
[[tls.certificates]]
certFile = "fixtures/https/wildcard.www.snitest.com.cert"
keyFile = "fixtures/https/wildcard.www.snitest.com.key"
[tls.options]
[tls.options.mytls]
maxVersion = "VersionTLS12"

View file

@ -35,7 +35,7 @@ func (s *HeadersSuite) TestCorsResponses(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK) backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close() defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
@ -124,7 +124,7 @@ func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK) backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close() defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
@ -173,7 +173,7 @@ func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
defer cmd.Process.Kill() defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK) backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close() defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))

View file

@ -73,8 +73,8 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close() defer backend1.Close()
defer backend2.Close() defer backend2.Close()
@ -129,8 +129,8 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close() defer backend1.Close()
defer backend2.Close() defer backend2.Close()
@ -215,8 +215,8 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.net`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.net`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close() defer backend1.Close()
defer backend2.Close() defer backend2.Close()
@ -733,9 +733,12 @@ func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
func startTestServer(port string, statusCode int) (ts *httptest.Server) { func startTestServer(port string, statusCode int, textContent string) (ts *httptest.Server) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
if textContent != "" {
_, _ = w.Write([]byte(textContent))
}
}) })
listener, err := net.Listen("tcp", "127.0.0.1:"+port) listener, err := net.Listen("tcp", "127.0.0.1:"+port)
if err != nil { if err != nil {
@ -787,8 +790,8 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close() defer backend1.Close()
defer backend2.Close() defer backend2.Close()
@ -856,8 +859,8 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent) backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close() defer backend1.Close()
defer backend2.Close() defer backend2.Close()
@ -919,7 +922,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)")) err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
backend2 := startTestServer("9020", http.StatusResetContent) backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend2.Close() defer backend2.Close()
@ -1111,3 +1114,115 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
proto := conn.ConnectionState().NegotiatedProtocol proto := conn.ConnectionState().NegotiatedProtocol
c.Assert(proto, checker.Equals, "h2") c.Assert(proto, checker.Equals, "h2")
} }
// TestWithDomainFronting verify the domain fronting behavior
func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
backend := startTestServer("9010", http.StatusOK, "server1")
defer backend.Close()
backend2 := startTestServer("9020", http.StatusOK, "server2")
defer backend2.Close()
backend3 := startTestServer("9030", http.StatusOK, "server3")
defer backend3.Close()
file := s.adaptFile(c, "fixtures/https/https_domain_fronting.toml", struct{}{})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`site1.www.snitest.com`)"))
c.Assert(err, checker.IsNil)
testCases := []struct {
desc string
hostHeader string
serverName string
expectedContent string
expectedStatusCode int
}{
{
desc: "SimpleCase",
hostHeader: "site1.www.snitest.com",
serverName: "site1.www.snitest.com",
expectedContent: "server1",
expectedStatusCode: http.StatusOK,
},
{
desc: "Simple case with port in the Host Header",
hostHeader: "site3.www.snitest.com:4443",
serverName: "site3.www.snitest.com",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Spaces after the host header",
hostHeader: "site3.www.snitest.com ",
serverName: "site3.www.snitest.com",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Spaces after the servername",
hostHeader: "site3.www.snitest.com",
serverName: "site3.www.snitest.com ",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Spaces after the servername and host header",
hostHeader: "site3.www.snitest.com ",
serverName: "site3.www.snitest.com ",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Domain Fronting with same tlsOptions should follow header",
hostHeader: "site1.www.snitest.com",
serverName: "site2.www.snitest.com",
expectedContent: "server1",
expectedStatusCode: http.StatusOK,
},
{
desc: "Domain Fronting with same tlsOptions should follow header (2)",
hostHeader: "site2.www.snitest.com",
serverName: "site1.www.snitest.com",
expectedContent: "server2",
expectedStatusCode: http.StatusOK,
},
{
desc: "Domain Fronting with different tlsOptions should produce a 421",
hostHeader: "site2.www.snitest.com",
serverName: "site3.www.snitest.com",
expectedContent: "",
expectedStatusCode: http.StatusMisdirectedRequest,
},
{
desc: "Domain Fronting with different tlsOptions should produce a 421 (2)",
hostHeader: "site3.www.snitest.com",
serverName: "site1.www.snitest.com",
expectedContent: "",
expectedStatusCode: http.StatusMisdirectedRequest,
},
{
desc: "Case insensitive",
hostHeader: "sIte1.www.snitest.com",
serverName: "sitE1.www.snitest.com",
expectedContent: "server1",
expectedStatusCode: http.StatusOK,
},
}
for _, test := range testCases {
test := test
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil)
c.Assert(err, checker.IsNil)
req.Host = test.hostHeader
err = try.RequestWithTransport(req, 500*time.Millisecond, &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, ServerName: test.serverName}}, try.StatusCodeIs(test.expectedStatusCode), try.BodyContains(test.expectedContent))
c.Assert(err, checker.IsNil)
}
}

View file

@ -6,6 +6,7 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"net/textproto"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
@ -100,6 +101,17 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
Level: logrus.InfoLevel, 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{ logHandler := &Handler{
config: config, config: config,
logger: logger, logger: logger,

View file

@ -41,11 +41,7 @@ var (
) )
func TestLogRotation(t *testing.T) { func TestLogRotation(t *testing.T) {
tempDir, err := ioutil.TempDir("", "traefik_") tempDir := createTempDir(t, "traefik_")
if err != nil {
t.Fatalf("Error setting up temporary directory: %s", err)
}
defer os.RemoveAll(tempDir)
fileName := filepath.Join(tempDir, "traefik.log") fileName := filepath.Join(tempDir, "traefik.log")
rotatedFileName := fileName + ".rotated" rotatedFileName := fileName + ".rotated"
@ -119,9 +115,106 @@ func lineCount(t *testing.T, fileName string) int {
return count 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) { func TestLoggerCLF(t *testing.T) {
tmpDir := createTempDir(t, CommonFormat) tmpDir := createTempDir(t, CommonFormat)
defer os.RemoveAll(tmpDir)
logFilePath := filepath.Join(tmpDir, logFileNameSuffix) logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat} config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat}
@ -136,7 +229,6 @@ func TestLoggerCLF(t *testing.T) {
func TestAsyncLoggerCLF(t *testing.T) { func TestAsyncLoggerCLF(t *testing.T) {
tmpDir := createTempDir(t, CommonFormat) tmpDir := createTempDir(t, CommonFormat)
defer os.RemoveAll(tmpDir)
logFilePath := filepath.Join(tmpDir, logFileNameSuffix) logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024} config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024}
@ -358,7 +450,6 @@ func TestLoggerJSON(t *testing.T) {
t.Parallel() t.Parallel()
tmpDir := createTempDir(t, JSONFormat) tmpDir := createTempDir(t, JSONFormat)
defer os.RemoveAll(tmpDir)
logFilePath := filepath.Join(tmpDir, logFileNameSuffix) logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
@ -642,6 +733,8 @@ func createTempDir(t *testing.T, prefix string) string {
tmpDir, err := ioutil.TempDir("", prefix) tmpDir, err := ioutil.TempDir("", prefix)
require.NoError(t, err, "failed to create temp dir") require.NoError(t, err, "failed to create temp dir")
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
return tmpDir return tmpDir
} }

View 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": {}
}

View file

@ -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) 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 { if err != nil {
return "", fmt.Errorf("invalid entry point %q address %q: %w", return "", fmt.Errorf("invalid entry point %q address %q: %w",
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err) name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)

View file

@ -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 { for _, test := range testCases {

View file

@ -5,7 +5,9 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"strings"
"github.com/containous/traefik/v2/pkg/config/runtime" "github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log" "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) log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
} }
router.HTTPSHandler(handlerHTTPS, defaultTLSConf)
if len(configsHTTP) > 0 { if len(configsHTTP) > 0 {
router.AddRouteHTTPTLS("*", defaultTLSConf) router.AddRouteHTTPTLS("*", defaultTLSConf)
} }
// Keyed by domain, then by options reference. // Keyed by domain, then by options reference.
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{} tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
tlsOptionsForHost := map[string]string{}
for routerHTTPName, routerHTTPConfig := range configsHTTP { for routerHTTPName, routerHTTPConfig := range configsHTTP {
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName { if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName {
continue continue
@ -141,6 +142,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
continue continue
} }
// domain is already in lower case thanks to the domain parsing
if tlsOptionsForHostSNI[domain] == nil { if tlsOptionsForHostSNI[domain] == nil {
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig) tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
} }
@ -148,10 +150,52 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
routerName: routerHTTPName, routerName: routerHTTPName,
TLSConfig: tlsConf, 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) logger := log.FromContext(ctx)
for hostSNI, tlsConfigs := range tlsOptionsForHostSNI { for hostSNI, tlsConfigs := range tlsOptionsForHostSNI {
if len(tlsConfigs) == 1 { if len(tlsConfigs) == 1 {
@ -248,3 +292,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return router, nil 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"
}

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/types"
) )
// Router is a TCP router. // Router is a TCP router.
@ -65,7 +66,7 @@ func (r *Router) ServeTCP(conn WriteCloser) {
} }
// FIXME Optimize and test the routing table before helloServerName // FIXME Optimize and test the routing table before helloServerName
serverName = strings.ToLower(serverName) serverName = types.CanonicalDomain(serverName)
if r.routingTable != nil && serverName != "" { if r.routingTable != nil && serverName != "" {
if target, ok := r.routingTable[serverName]; ok { if target, ok := r.routingTable[serverName]; ok {
target.ServeTCP(r.GetConn(conn, peeked)) target.ServeTCP(r.GetConn(conn, peeked))