Compare commits

...

11 Commits

Author SHA1 Message Date
Ludovic Fernandez
fc52d1cfba Prepare release v2.2.8 2020-07-28 17:34:03 +02:00
Stephan Müller
fdf2a68a11 doc: add name of used key for kubernetes client auth 2020-07-28 17:18:03 +02:00
Ludovic Fernandez
e63db782c1 fix: clean X-Forwarded-Prefix header for the dashboard. 2020-07-28 10:08:03 +02:00
Filip Kszczot
a6c6127e33 spelling(docs/content/routing/providers/docker.md) 2020-07-28 01:02:03 +02:00
Romain
bbbc18fd84 Prepare release 2.2.7 2020-07-20 18:48:04 +02:00
Ludovic Fernandez
2c7f6e4def fix: drop host port to compare with SNI. 2020-07-20 18:32:03 +02:00
Romain
ff16925f63 Prepare release v2.2.6 2020-07-17 17:54:04 +02:00
Julien Salleyron
0b7aaa3643 Fix domain fronting
Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
2020-07-17 15:38:04 +02:00
Mickael Jeanroy
45f52ca29c fix: access logs header names filtering is case insensitive 2020-07-16 17:36:04 +02:00
Manuel Zapf
fae2d93525 Get Entrypoints Port Address without protocol for redirect 2020-07-16 17:18:03 +02:00
Simon Heimberg
25b74ce1f3 Add example for entrypoint on one ip address 2020-07-16 12:38:03 +02:00
20 changed files with 549 additions and 33 deletions

View File

@@ -1,3 +1,31 @@
## [v2.2.8](https://github.com/containous/traefik/tree/v2.2.8) (2020-07-28)
[All Commits](https://github.com/containous/traefik/compare/v2.2.7...v2.2.8)
**Bug fixes:**
- **[webui]** fix: clean X-Forwarded-Prefix header for the dashboard. ([#7109](https://github.com/containous/traefik/pull/7109) by [ldez](https://github.com/ldez))
**Documentation:**
- **[docker]** spelling(docs/content/routing/providers/docker.md) ([#7101](https://github.com/containous/traefik/pull/7101) by [szczot3k](https://github.com/szczot3k))
- **[k8s]** doc: add name of used key for kubernetes client auth ([#7068](https://github.com/containous/traefik/pull/7068) by [smueller18](https://github.com/smueller18))
## [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.2.5](https://github.com/containous/traefik/tree/v2.2.5) (2020-07-13)
[All Commits](https://github.com/containous/traefik/compare/v2.2.4...v2.2.5)

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) |
[^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)
[^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`.

View File

@@ -428,6 +428,7 @@ metadata:
spec:
clientAuth:
# the CA certificate is extracted from key `tls.ca` of the given secrets.
secretNames:
- secretCA
clientAuthType: RequireAndVerifyClientCert

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.
??? example "Both TCP and UDP on port 3179"
??? example "Both TCP and UDP on Port 3179"
```toml tab="File (TOML)"
## 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
```
??? 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
You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`).

View File

@@ -535,7 +535,7 @@ You can declare UDP Routers and/or Services using labels.
my-container:
# ...
labels:
- "traefik.udp.routers.my-router.entrypoint=udp"
- "traefik.udp.routers.my-router.entrypoints=udp"
- "traefik.udp.services.my-service.loadbalancer.server.port=4123"
```

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,
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"
```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
defer os.Remove("/tmp/acme.json")
backend := startTestServer("9010", http.StatusOK)
backend := startTestServer("9010", http.StatusOK, "")
defer backend.Close()
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)
defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK)
backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close()
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)
defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK)
backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))

View File

@@ -16,9 +16,10 @@ It has these top-level messages:
package helloworld
import (
proto "github.com/golang/protobuf/proto"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
)
import (

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`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.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`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.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`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close()
defer backend2.Close()
@@ -733,9 +733,12 @@ func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) {
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) {
w.WriteHeader(statusCode)
if textContent != "" {
_, _ = w.Write([]byte(textContent))
}
})
listener, err := net.Listen("tcp", "127.0.0.1:"+port)
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+"`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.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+"`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.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+"`)"))
c.Assert(err, checker.IsNil)
backend2 := startTestServer("9020", http.StatusResetContent)
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend2.Close()
@@ -1111,3 +1114,115 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
proto := conn.ConnectionState().NegotiatedProtocol
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

@@ -2,6 +2,7 @@ package api
import (
"net/http"
"net/url"
"github.com/containous/traefik/v2/pkg/log"
assetfs "github.com/elazarl/go-bindata-assetfs"
@@ -23,11 +24,29 @@ func (g DashboardHandler) Append(router *mux.Router) {
// Expose dashboard
router.Methods(http.MethodGet).
Path("/").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", http.StatusFound)
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
http.Redirect(resp, req, safePrefix(req)+"/dashboard/", http.StatusFound)
})
router.Methods(http.MethodGet).
PathPrefix("/dashboard/").
Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets)))
}
func safePrefix(req *http.Request) string {
prefix := req.Header.Get("X-Forwarded-Prefix")
if prefix == "" {
return ""
}
parse, err := url.Parse(prefix)
if err != nil {
return ""
}
if parse.Host != "" {
return ""
}
return parse.Path
}

54
pkg/api/dashboard_test.go Normal file
View File

@@ -0,0 +1,54 @@
package api
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_safePrefix(t *testing.T) {
testCases := []struct {
desc string
value string
expected string
}{
{
desc: "host",
value: "https://example.com",
expected: "",
},
{
desc: "host with path",
value: "https://example.com/foo/bar?test",
expected: "",
},
{
desc: "path",
value: "/foo/bar",
expected: "/foo/bar",
},
{
desc: "path without leading slash",
value: "foo/bar",
expected: "foo/bar",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
req, err := http.NewRequest(http.MethodGet, "http://localhost", nil)
require.NoError(t, err)
req.Header.Set("X-Forwarded-Prefix", test.value)
prefix := safePrefix(req)
assert.Equal(t, test.expected, prefix)
})
}
}

View File

@@ -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,

View File

@@ -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
}

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)
}
_, 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)

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 {

View File

@@ -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"
}

View File

@@ -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))