Merge v2.2 into v2.3
This commit is contained in:
commit
207d0bec78
15 changed files with 461 additions and 30 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -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)
|
||||
[All Commits](https://github.com/containous/traefik/compare/v2.3.0-rc1...v2.3.0-rc2)
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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-*`).
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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 {
|
||||
|
|
53
integration/fixtures/https/https_domain_fronting.toml
Normal file
53
integration/fixtures/https/https_domain_fronting.toml
Normal 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"
|
|
@ -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))
|
||||
|
@ -173,7 +173,7 @@ func (s *HeadersSuite) TestMultipleSecureHeadersResponses(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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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…
Reference in a new issue