Merge current v2.9 into master

This commit is contained in:
Tom Moulard 2022-12-07 15:33:51 +01:00
commit 517917cd7c
No known key found for this signature in database
GPG key ID: 521ABE0C1A0DEAF6
16 changed files with 439 additions and 175 deletions

View file

@ -1,3 +1,22 @@
## [v2.9.6](https://github.com/traefik/traefik/tree/v2.9.6) (2022-12-07)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.5...v2.9.6)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.9.1 ([#9550](https://github.com/traefik/traefik/pull/9550) by [ldez](https://github.com/ldez))
- **[k8s/crd]** Support of allowEmptyServices in TraefikService ([#9424](https://github.com/traefik/traefik/pull/9424) by [jeromeguiard](https://github.com/jeromeguiard))
- **[logs]** Remove logs of the request ([#9574](https://github.com/traefik/traefik/pull/9574) by [ldez](https://github.com/ldez))
- **[plugins]** Increase the timeout on plugin download ([#9529](https://github.com/traefik/traefik/pull/9529) by [ldez](https://github.com/ldez))
- **[server]** Update golang.org/x/net ([#9582](https://github.com/traefik/traefik/pull/9582) by [ldez](https://github.com/ldez))
- **[tls]** Handle broken TLS conf better ([#9572](https://github.com/traefik/traefik/pull/9572) by [mpl](https://github.com/mpl))
- **[tracing]** Update DataDog tracing dependency to v1.43.1 ([#9526](https://github.com/traefik/traefik/pull/9526) by [rtribotte](https://github.com/rtribotte))
- **[webui]** Add missing serialNumber passTLSClientCert option to middleware panel ([#9539](https://github.com/traefik/traefik/pull/9539) by [rtribotte](https://github.com/rtribotte))
**Documentation:**
- **[docker]** Add networking example ([#9542](https://github.com/traefik/traefik/pull/9542) by [Janik-Haag](https://github.com/Janik-Haag))
- **[hub]** Add information about the Hub Agent ([#9560](https://github.com/traefik/traefik/pull/9560) by [nmengin](https://github.com/nmengin))
- **[k8s/helm]** Update Helm installation section ([#9564](https://github.com/traefik/traefik/pull/9564) by [mloiseleur](https://github.com/mloiseleur))
- **[middleware]** Clarify PathPrefix matcher greediness ([#9519](https://github.com/traefik/traefik/pull/9519) by [mpl](https://github.com/mpl))
## [v3.0.0-beta1](https://github.com/traefik/traefik/tree/v3.0.0-beta1) (2022-12-05) ## [v3.0.0-beta1](https://github.com/traefik/traefik/tree/v3.0.0-beta1) (2022-12-05)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.0-rc1...v3.0.0-beta1) [All Commits](https://github.com/traefik/traefik/compare/v2.9.0-rc1...v3.0.0-beta1)

8
go.mod
View file

@ -91,8 +91,8 @@ require (
go.opentelemetry.io/otel/trace v1.11.1 go.opentelemetry.io/otel/trace v1.11.1
golang.org/x/exp v0.0.0-20221114191408-850992195362 golang.org/x/exp v0.0.0-20221114191408-850992195362
golang.org/x/mod v0.6.0 golang.org/x/mod v0.6.0
golang.org/x/net v0.1.0 golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10
golang.org/x/text v0.4.0 golang.org/x/text v0.5.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.org/x/tools v0.2.0 golang.org/x/tools v0.2.0
google.golang.org/grpc v1.50.1 google.golang.org/grpc v1.50.1
@ -360,8 +360,8 @@ require (
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.1.0 // indirect golang.org/x/term v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.57.0 // indirect google.golang.org/api v0.57.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect

16
go.sum
View file

@ -2224,8 +2224,8 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -2395,14 +2395,14 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2413,8 +2413,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View file

@ -0,0 +1,60 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.websecure]
address = ":4443"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
entryPoints = ["websecure"]
service = "service1"
rule = "Host(`snitest.com`)"
[http.routers.router1.tls]
options = "invalidTLSOptions"
[http.routers.router2]
entryPoints = ["websecure"]
service = "service1"
rule = "Host(`snitest.org`)"
[http.routers.router2.tls]
# fallback router
[http.routers.router3]
entryPoints = ["websecure"]
service = "service1"
rule = "Path(`/`)"
[http.routers.router3.tls]
[[http.services.service1.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[[tls.certificates]]
certFile = "fixtures/https/snitest.com.cert"
keyFile = "fixtures/https/snitest.com.key"
[[tls.certificates]]
certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key"
[tls.options]
[tls.options.default.clientAuth]
# Missing caFile to have an invalid mTLS configuration.
clientAuthType = "RequireAndVerifyClientCert"
[tls.options.invalidTLSOptions.clientAuth]
# Missing caFile to have an invalid mTLS configuration.
clientAuthType = "RequireAndVerifyClientCert"

View file

@ -34,6 +34,13 @@
[tcp.routers.to-whoami-sni-strict.tls] [tcp.routers.to-whoami-sni-strict.tls]
options = "bar" options = "bar"
[tcp.routers.to-whoami-invalid-tls]
rule = "HostSNI(`whoami-i.test`)"
service = "whoami-no-cert"
entryPoints = [ "tcp" ]
[tcp.routers.to-whoami-invalid-tls.tls]
options = "invalid"
[tcp.services.whoami-no-cert] [tcp.services.whoami-no-cert]
[tcp.services.whoami-no-cert.loadBalancer] [tcp.services.whoami-no-cert.loadBalancer]
[[tcp.services.whoami-no-cert.loadBalancer.servers]] [[tcp.services.whoami-no-cert.loadBalancer.servers]]
@ -46,3 +53,7 @@
[tls.options.bar] [tls.options.bar]
minVersion = "VersionTLS13" minVersion = "VersionTLS13"
[tls.options.invalid.clientAuth]
# Missing CA files to have an invalid mTLS configuration.
clientAuthType = "RequireAndVerifyClientCert"

View file

@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
} }
// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration.
func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) {
backend := startTestServer("9010", http.StatusOK, "server1")
defer backend.Close()
file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil)
testCases := []struct {
desc string
serverName string
}{
{
desc: "With invalid TLS Options specified",
serverName: "snitest.com",
},
{
desc: "With invalid Default TLS Options",
serverName: "snitest.org",
},
{
desc: "With TLS Options without servername (fallback to default)",
},
}
for _, test := range testCases {
test := test
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
if test.serverName != "" {
tlsConfig.ServerName = test.serverName
}
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully"))
c.Assert(conn, checker.IsNil)
}
}

View file

@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) {
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12) _, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
c.Assert(err, checker.NotNil) c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, "protocol version not supported") c.Assert(err.Error(), checker.Contains, "protocol version not supported")
// Check that we can't reach a route with an invalid mTLS configuration.
conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{
ServerName: "whoami-i.test",
InsecureSkipVerify: true,
})
c.Assert(conn, checker.IsNil)
c.Assert(err, checker.NotNil)
} }
func (s *TCPSuite) TestNonTLSFallback(c *check.C) { func (s *TCPSuite) TestNonTLSFallback(c *check.C) {

View file

@ -3,6 +3,7 @@ package router
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/http" "net/http"
"github.com/containous/alice" "github.com/containous/alice"
@ -17,6 +18,7 @@ import (
httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http" httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
"github.com/traefik/traefik/v2/pkg/server/middleware" "github.com/traefik/traefik/v2/pkg/server/middleware"
"github.com/traefik/traefik/v2/pkg/server/provider" "github.com/traefik/traefik/v2/pkg/server/provider"
"github.com/traefik/traefik/v2/pkg/tls"
) )
type middlewareBuilder interface { type middlewareBuilder interface {
@ -36,10 +38,11 @@ type Manager struct {
middlewaresBuilder middlewareBuilder middlewaresBuilder middlewareBuilder
chainBuilder *middleware.ChainBuilder chainBuilder *middleware.ChainBuilder
conf *runtime.Configuration conf *runtime.Configuration
tlsManager *tls.Manager
} }
// NewManager Creates a new Manager. // NewManager creates a new Manager.
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager { func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
return &Manager{ return &Manager{
routerHandlers: make(map[string]http.Handler), routerHandlers: make(map[string]http.Handler),
serviceManager: serviceManager, serviceManager: serviceManager,
@ -47,6 +50,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd
middlewaresBuilder: middlewaresBuilder, middlewaresBuilder: middlewaresBuilder,
chainBuilder: chainBuilder, chainBuilder: chainBuilder,
conf: conf, conf: conf,
tlsManager: tlsManager,
} }
} }
@ -145,6 +149,17 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
return handler, nil return handler, nil
} }
if routerConfig.TLS != nil {
// Don't build the router if the TLSOptions configuration is invalid.
tlsOptionsName := tls.DefaultTLSConfigName
if len(routerConfig.TLS.Options) > 0 && routerConfig.TLS.Options != tls.DefaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctx, routerConfig.TLS.Options)
}
if _, err := m.tlsManager.Get(tls.DefaultTLSStoreName, tlsOptionsName); err != nil {
return nil, fmt.Errorf("building router handler: %w", err)
}
}
handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName) handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -22,6 +22,7 @@ import (
"github.com/traefik/traefik/v2/pkg/server/middleware" "github.com/traefik/traefik/v2/pkg/server/middleware"
"github.com/traefik/traefik/v2/pkg/server/service" "github.com/traefik/traefik/v2/pkg/server/service"
"github.com/traefik/traefik/v2/pkg/testhelpers" "github.com/traefik/traefik/v2/pkg/testhelpers"
"github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types" "github.com/traefik/traefik/v2/pkg/types"
) )
@ -319,8 +320,9 @@ func TestRouterManager_Get(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager()
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -425,8 +427,9 @@ func TestAccessLog(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager()
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -464,6 +467,7 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceConfig map[string]*dynamic.Service serviceConfig map[string]*dynamic.Service
routerConfig map[string]*dynamic.Router routerConfig map[string]*dynamic.Router
middlewareConfig map[string]*dynamic.Middleware middlewareConfig map[string]*dynamic.Middleware
tlsOptions map[string]tls.Options
expectedError int expectedError int
}{ }{
{ {
@ -667,7 +671,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
expectedError: 1, expectedError: 1,
}, },
{ {
desc: "Router with broken middleware", desc: "Router with broken middleware",
serviceConfig: map[string]*dynamic.Service{ serviceConfig: map[string]*dynamic.Service{
@ -698,8 +701,71 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
expectedError: 2, expectedError: 2,
}, },
{
desc: "Router with broken tlsOption",
serviceConfig: map[string]*dynamic.Service{
"foo-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
},
},
},
},
},
middlewareConfig: map[string]*dynamic.Middleware{},
routerConfig: map[string]*dynamic.Router{
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
TLS: &dynamic.RouterTLSConfig{
Options: "broken-tlsOption",
},
},
},
tlsOptions: map[string]tls.Options{
"broken-tlsOption": {
ClientAuth: tls.ClientAuth{
ClientAuthType: "foobar",
},
},
},
expectedError: 1,
},
{
desc: "Router with broken default tlsOption",
serviceConfig: map[string]*dynamic.Service{
"foo-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
},
},
},
},
},
middlewareConfig: map[string]*dynamic.Middleware{},
routerConfig: map[string]*dynamic.Router{
"bar": {
EntryPoints: []string{"web"},
Service: "foo-service",
Rule: "Host(`foo.bar`)",
TLS: &dynamic.RouterTLSConfig{},
},
},
tlsOptions: map[string]tls.Options{
"default": {
ClientAuth: tls.ClientAuth{
ClientAuthType: "foobar",
},
},
},
expectedError: 1,
},
} }
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
@ -713,6 +779,9 @@ func TestRuntimeConfiguration(t *testing.T) {
Routers: test.routerConfig, Routers: test.routerConfig,
Middlewares: test.middlewareConfig, Middlewares: test.middlewareConfig,
}, },
TLS: &dynamic.TLSConfiguration{
Options: test.tlsOptions,
},
}) })
roundTripperManager := service.NewRoundTripperManager(nil) roundTripperManager := service.NewRoundTripperManager(nil)
@ -720,10 +789,13 @@ func TestRuntimeConfiguration(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager()
tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
// even though rtConf was passed by argument to the manager builders above, // even though rtConf was passed by argument to the manager builders above,
// it's ok to use it as the result we check, because everything worth checking // it's ok to use it as the result we check, because everything worth checking
@ -795,8 +867,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager()
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -863,8 +936,9 @@ func BenchmarkRouterServe(b *testing.B) {
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(nil, nil, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
tlsManager := tls.NewManager()
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)

View file

@ -105,18 +105,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
router.SetHTTPHandler(handlerHTTP) router.SetHTTPHandler(handlerHTTP)
// Even though the error is seemingly ignored (aside from logging it),
// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
// when assigning a handler to a route.
defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName) defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName)
if err != nil { if err != nil {
log.Ctx(ctx).Error().Err(err).Msg("Error during the build of the default TLS configuration") log.Ctx(ctx).Error().Err(err).Msg("Error during the build of the default TLS configuration")
} }
// Keyed by domain. The source of truth for doing SNI checking, and for what TLS // Keyed by domain. The source of truth for doing SNI checking (domain fronting).
// options will actually be used for the connection.
// As soon as there's (at least) two different tlsOptions found for the same domain, // As soon as there's (at least) two different tlsOptions found for the same domain,
// we set the value to the default TLS conf. // we set the value to the default TLS conf.
tlsOptionsForHost := map[string]string{} tlsOptionsForHost := map[string]string{}
// Keyed by domain, then by options reference. // Keyed by domain, then by options reference.
// The actual source of truth for what TLS options will actually be used for the connection.
// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS // As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
// options that occur for a given host name, so that later on we can set relevant // options that occur for a given host name, so that later on we can set relevant
// errors and logging for all the routers concerned (i.e. wrongly configured). // errors and logging for all the routers concerned (i.e. wrongly configured).
@ -144,21 +147,20 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
} }
if len(domains) == 0 { if len(domains) == 0 {
// Extra Host(*) rule, for HTTPS routers with no Host rule, and for requests for // Extra Host(*) rule, for HTTPS routers with no Host rule,
// which the SNI does not match _any_ of the other existing routers Host. This is // and for requests for which the SNI does not match _any_ of the other existing routers Host.
// only about choosing the TLS configuration. The actual routing will be done // This is only about choosing the TLS configuration.
// further on by the HTTPS handler. See examples below. // The actual routing will be done further on by the HTTPS handler.
// See examples below.
router.AddHTTPTLSConfig("*", defaultTLSConf) router.AddHTTPTLSConfig("*", defaultTLSConf)
// The server name (from a Host(SNI) rule) is the only parameter (available in // The server name (from a Host(SNI) rule) is the only parameter (available in HTTP routing rules) on which we can map a TLS config,
// HTTP routing rules) on which we can map a TLS config, because it is the only one // because it is the only one accessible before decryption (we obtain it during the ClientHello).
// accessible before decryption (we obtain it during the ClientHello). Therefore, // Therefore, when a router has no Host rule, it does not make any sense to specify some TLS options.
// when a router has no Host rule, it does not make any sense to specify some TLS // Consequently, when it comes to deciding what TLS config will be used,
// options. Consequently, when it comes to deciding what TLS config will be used, // for a request that will match an HTTPS router with no Host rule,
// for a request that will match an HTTPS router with no Host rule, the result will // the result will depend on the _others_ existing routers (their Host rule, to be precise), and the TLS options associated with them,
// depend on the _others_ existing routers (their Host rule, to be precise), and // even though they don't match the incoming request. Consider the following examples:
// the TLS options associated with them, even though they don't match the incoming
// request. Consider the following examples:
// # conf1 // # conf1
// httpRouter1: // httpRouter1:
@ -172,17 +174,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
// httpRouter2: // httpRouter2:
// rule: Host("foo.com") && PathPrefix("/bar") // rule: Host("foo.com") && PathPrefix("/bar")
// tlsoptions: myTLSOptions // tlsoptions: myTLSOptions
// # When a request for "/foo" comes, even though it won't be routed by // # When a request for "/foo" comes, even though it won't be routed by httpRouter2,
// httpRouter2, if its SNI is set to foo.com, myTLSOptions will be used for the TLS // # if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection.
// connection. Otherwise, it will fallback to the default TLS config. // # Otherwise, it will fallback to the default TLS config.
logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule) logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
} }
tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName) // Even though the error is seemingly ignored (aside from logging it),
if err != nil { // we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
routerHTTPConfig.AddError(err, true) // when assigning a handler to a route.
logger.Error().Err(err).Send() tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
continue if tlsConfErr != nil {
// Note: we do not call AddError here because we already did so when buildRouterHandler errored for the same reason.
logger.Error().Err(tlsConfErr).Send()
} }
for _, domain := range domains { for _, domain := range domains {
@ -206,6 +210,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS) sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
// Keep in mind that defaultTLSConf might be nil here.
router.SetHTTPSHandler(sniCheck, defaultTLSConf) router.SetHTTPSHandler(sniCheck, defaultTLSConf)
logger := log.Ctx(ctx) logger := log.Ctx(ctx)
@ -219,22 +224,42 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
break break
} }
logger.Debug().Msgf("Adding route for %s with TLS options %s", hostSNI, optionsName) if config == nil {
// we use nil config as a signal to insert a handler
router.AddHTTPTLSConfig(hostSNI, config) // that enforces that TLS connection attempts to the corresponding (broken) router should fail.
} else { logger.Debug().Msgf("Adding special closing route for %s because broken TLS options %s", hostSNI, optionsName)
routers := make([]string, 0, len(tlsConfigs)) router.AddHTTPTLSConfig(hostSNI, nil)
for _, v := range tlsConfigs { continue
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
routers = append(routers, v.routerName)
} }
logger.Warn().Msgf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers) logger.Debug().Msgf("Adding route for %s with TLS options %s", hostSNI, optionsName)
router.AddHTTPTLSConfig(hostSNI, config)
router.AddHTTPTLSConfig(hostSNI, defaultTLSConf) continue
} }
// multiple tlsConfigs
routers := make([]string, 0, len(tlsConfigs))
for _, v := range tlsConfigs {
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
routers = append(routers, v.routerName)
}
logger.Warn().Msgf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
if defaultTLSConf == nil {
logger.Debug().Msgf("Adding special closing route for %s because broken default TLS options", hostSNI)
}
router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
} }
m.addTCPHandlers(ctx, configs, router)
return router, nil
}
// addTCPHandlers creates the TCP handlers defined in configs, and adds them to router.
func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, router *Router) {
for routerName, routerConfig := range configs { for routerName, routerConfig := range configs {
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger() logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName)) ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
@ -253,13 +278,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
continue continue
} }
handler, err := m.buildTCPHandler(ctxRouter, routerConfig)
if err != nil {
routerConfig.AddError(err, true)
logger.Error().Err(err).Send()
continue
}
domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule) domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule)
if err != nil { if err != nil {
routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err) routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err)
@ -276,6 +294,16 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
logger.Error().Err(routerErr).Send() logger.Error().Err(routerErr).Send()
} }
var handler tcp.Handler
if routerConfig.TLS == nil || routerConfig.TLS.Passthrough {
handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
if err != nil {
routerConfig.AddError(err, true)
logger.Error().Err(err).Send()
continue
}
}
if routerConfig.TLS == nil { if routerConfig.TLS == nil {
logger.Debug().Msgf("Adding route for %q", routerConfig.Rule) logger.Debug().Msgf("Adding route for %q", routerConfig.Rule)
if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
@ -287,7 +315,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
if routerConfig.TLS.Passthrough { if routerConfig.TLS.Passthrough {
logger.Debug().Msgf("Adding Passthrough route for %q", routerConfig.Rule) logger.Debug().Msgf("Adding Passthrough route for %q", routerConfig.Rule)
if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, nil); err != nil { if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
routerConfig.AddError(err, true) routerConfig.AddError(err, true)
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
} }
@ -318,6 +346,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
if err != nil { if err != nil {
routerConfig.AddError(err, true) routerConfig.AddError(err, true)
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
logger.Debug().Msgf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{})
if err != nil {
routerConfig.AddError(err, true)
logger.Error().Err(err).Send()
}
continue continue
} }
@ -329,20 +365,30 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
// rule: HostSNI(foo.com) && ClientIP(IP2) // rule: HostSNI(foo.com) && ClientIP(IP2)
// tlsOption: tlsTwo // tlsOption: tlsTwo
// i.e. same HostSNI but different tlsOptions // i.e. same HostSNI but different tlsOptions
// This is only applicable if the muxer can decide about the routing _before_ // This is only applicable if the muxer can decide about the routing _before_ telling the client about the tlsConf (i.e. before the TLS HandShake).
// telling the client about the tlsConf (i.e. before the TLS HandShake). This seems // This seems to be the case so far with the existing matchers (HostSNI, and ClientIP), so it's all good.
// to be the case so far with the existing matchers (HostSNI, and ClientIP), so // Otherwise, we would have to do as for HTTPS, i.e. disallow different TLS configs for the same HostSNIs.
// it's all good. Otherwise, we would have to do as for HTTPS, i.e. disallow
// different TLS configs for the same HostSNIs. handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
if err != nil {
routerConfig.AddError(err, true)
logger.Error().Err(err).Send()
continue
}
handler = &tcp.TLSHandler{
Next: handler,
Config: tlsConf,
}
logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule) logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule)
if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, tlsConf); err != nil {
err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
if err != nil {
routerConfig.AddError(err, true) routerConfig.AddError(err, true)
logger.Error().Err(err).Send() logger.Error().Err(err).Send()
} }
} }
return router, nil
} }
func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) { func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) {

View file

@ -27,19 +27,20 @@ type Router struct {
muxerHTTPS tcpmuxer.Muxer muxerHTTPS tcpmuxer.Muxer
// Forwarder handlers. // Forwarder handlers.
// Handles all HTTP requests. // httpForwarder handles all HTTP requests.
httpForwarder tcp.Handler httpForwarder tcp.Handler
// Handles (indirectly through muxerHTTPS, or directly) all HTTPS requests. // httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
httpsForwarder tcp.Handler httpsForwarder tcp.Handler
// Neither is used directly, but they are held here, and recreated on config // Neither is used directly, but they are held here, and recreated on config reload,
// reload, so that they can be passed to the Switcher at the end of the config // so that they can be passed to the Switcher at the end of the config reload phase.
// reload phase.
httpHandler http.Handler httpHandler http.Handler
httpsHandler http.Handler httpsHandler http.Handler
// TLS configs. // TLS configs.
httpsTLSConfig *tls.Config // default TLS config httpsTLSConfig *tls.Config // default TLS config
// hostHTTPTLSConfig contains TLS configs keyed by SNI.
// A nil config is the hint to set up a brokenTLSRouter.
hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
} }
@ -80,11 +81,11 @@ func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Con
// ServeTCP forwards the connection to the right TCP/HTTP handler. // ServeTCP forwards the connection to the right TCP/HTTP handler.
func (r *Router) ServeTCP(conn tcp.WriteCloser) { func (r *Router) ServeTCP(conn tcp.WriteCloser) {
// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS // Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint,
// routers on the entryPoint, and if there is at least one non-TLS TCP router. // and if there is at least one non-TLS TCP router.
// In the case of a non-TLS TCP client (that does not "send" first), we would // In the case of a non-TLS TCP client (that does not "send" first),
// block forever on clientHelloInfo, which is why we want to detect and // we would block forever on clientHelloInfo,
// handle that case first and foremost. // which is why we want to detect and handle that case first and foremost.
if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() { if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
connData, err := tcpmuxer.NewConnData("", conn, nil) connData, err := tcpmuxer.NewConnData("", conn, nil)
if err != nil { if err != nil {
@ -163,9 +164,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
// (wrapped inside the returned handler) requested for the given HostSNI. // (wrapped inside the returned handler) requested for the given HostSNI.
handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData) handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
if handlerHTTPS != nil && !catchAllHTTPS { if handlerHTTPS != nil && !catchAllHTTPS {
// In order not to depart from the behavior in 2.6, we only allow an HTTPS router // In order not to depart from the behavior in 2.6,
// to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router (so // we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router
// basically any router that has a specific HostSNI based rule). // (so basically any router that has a specific HostSNI based rule).
handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked)) handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked))
return return
} }
@ -191,7 +192,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
return return
} }
// needed to handle 404s for HTTPS, as well as all non-Host (e.g. PathPrefix) matches. // To handle 404s for HTTPS.
if r.httpsForwarder != nil { if r.httpsForwarder != nil {
r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked)) r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked))
return return
@ -205,19 +206,6 @@ func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error {
return r.muxerTCP.AddRoute(rule, priority, target) return r.muxerTCP.AddRoute(rule, priority, target)
} }
// AddRouteTLS defines a handler for a given rule and sets the matching tlsConfig.
func (r *Router) AddRouteTLS(rule string, priority int, target tcp.Handler, config *tls.Config) error {
// TLS PassThrough
if config == nil {
return r.muxerTCPTLS.AddRoute(rule, priority, target)
}
return r.muxerTCPTLS.AddRoute(rule, priority, &tcp.TLSHandler{
Next: target,
Config: config,
})
}
// AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig. // AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) { func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
if r.hostHTTPTLSConfig == nil { if r.hostHTTPTLSConfig == nil {
@ -253,20 +241,44 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
r.httpForwarder = handler r.httpForwarder = handler
} }
// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler. // brokenTLSRouter is associated to a Host(SNI) rule for which we know the TLS conf is broken.
// It is used to make sure any attempt to connect to that hostname is closed,
// since we cannot proceed with the intended TLS conf.
type brokenTLSRouter struct{}
// ServeTCP instantly closes the connection.
func (t *brokenTLSRouter) ServeTCP(conn tcp.WriteCloser) {
_ = conn.Close()
}
// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an HTTP handler.
// It also sets up each TLS handler (with its TLS config) for each Host(SNI) rule we previously kept track of.
// It sets up a special handler that closes the connection if a TLS config is nil.
func (r *Router) SetHTTPSForwarder(handler tcp.Handler) { func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
for sniHost, tlsConf := range r.hostHTTPTLSConfig { for sniHost, tlsConf := range r.hostHTTPTLSConfig {
var tcpHandler tcp.Handler
if tlsConf == nil {
tcpHandler = &brokenTLSRouter{}
} else {
tcpHandler = &tcp.TLSHandler{
Next: handler,
Config: tlsConf,
}
}
// muxerHTTPS only contains single HostSNI rules (and no other kind of rules), // muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
// so there's no need for specifying a priority for them. // so there's no need for specifying a priority for them.
err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{ err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler)
Next: handler,
Config: tlsConf,
})
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error while adding route for host") log.Error().Err(err).Msg("Error while adding route for host")
} }
} }
if r.httpsTLSConfig == nil {
r.httpsForwarder = &brokenTLSRouter{}
return
}
r.httpsForwarder = &tcp.TLSHandler{ r.httpsForwarder = &tcp.TLSHandler{
Next: handler, Next: handler,
Config: r.httpsTLSConfig, Config: r.httpsTLSConfig,
@ -286,15 +298,14 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
// Conn is a connection proxy that handles Peeked bytes. // Conn is a connection proxy that handles Peeked bytes.
type Conn struct { type Conn struct {
// Peeked are the bytes that have been read from Conn for the // Peeked are the bytes that have been read from Conn for the purposes of route matching,
// purposes of route matching, but have not yet been consumed // but have not yet been consumed by Read calls.
// by Read calls. It is set to nil by Read when fully consumed. // It set to nil by Read when fully consumed.
Peeked []byte Peeked []byte
// Conn is the underlying connection. // Conn is the underlying connection.
// It can be type asserted against *net.TCPConn or other types // It can be type asserted against *net.TCPConn or other types as needed.
// as needed. It should not be read from directly unless // It should not be read from directly unless Peeked is nil.
// Peeked is nil.
tcp.WriteCloser tcp.WriteCloser
} }
@ -331,15 +342,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
return nil, err return nil, err
} }
// No valid TLS record has a type of 0x80, however SSLv2 handshakes // No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
// start with a uint16 length where the MSB is set and the first record // where the MSB is set and the first record is always < 256 bytes long.
// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests // Therefore, typ == 0x80 strongly suggests an SSLv2 client.
// an SSLv2 client.
const recordTypeSSLv2 = 0x80 const recordTypeSSLv2 = 0x80
const recordTypeHandshake = 0x16 const recordTypeHandshake = 0x16
if hdr[0] != recordTypeHandshake { if hdr[0] != recordTypeHandshake {
if hdr[0] == recordTypeSSLv2 { if hdr[0] == recordTypeSSLv2 {
// we consider SSLv2 as TLS and it will be refused by real TLS handshake. // we consider SSLv2 as TLS, and it will be refused by real TLS handshake.
return &clientHello{ return &clientHello{
isTLS: true, isTLS: true,
peeked: getPeeked(br), peeked: getPeeked(br),

View file

@ -930,7 +930,7 @@ func TestPostgres(t *testing.T) {
// This test requires to have a TLS route, but does not actually check the // This test requires to have a TLS route, but does not actually check the
// content of the handler. It would require to code a TLS handshake to // content of the handler. It would require to code a TLS handshake to
// check the SNI and content of the handlerFunc. // check the SNI and content of the handlerFunc.
err = router.AddRouteTLS("HostSNI(`test.localhost`)", 0, nil, &tls.Config{}) err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", 0, nil)
require.NoError(t, err) require.NoError(t, err)
err = router.AddRoute("HostSNI(`*`)", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) { err = router.AddRoute("HostSNI(`*`)", 0, tcp2.HandlerFunc(func(conn tcp2.WriteCloser) {

View file

@ -79,7 +79,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry) routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)

View file

@ -158,19 +158,16 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
m.lock.RLock() m.lock.RLock()
defer m.lock.RUnlock() defer m.lock.RUnlock()
var tlsConfig *tls.Config
var err error
sniStrict := false sniStrict := false
config, ok := m.configs[configName] config, ok := m.configs[configName]
if ok { if !ok {
sniStrict = config.SniStrict return nil, fmt.Errorf("unknown TLS options: %s", configName)
tlsConfig, err = buildTLSConfig(config)
} else {
err = fmt.Errorf("unknown TLS options: %s", configName)
} }
sniStrict = config.SniStrict
tlsConfig, err := buildTLSConfig(config)
if err != nil { if err != nil {
tlsConfig = &tls.Config{} return nil, fmt.Errorf("building TLS config: %w", err)
} }
store := m.getStore(storeName) store := m.getStore(storeName)
@ -178,7 +175,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
err = fmt.Errorf("TLS store %s not found", storeName) err = fmt.Errorf("TLS store %s not found", storeName)
} }
acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol) acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
if acmeTLSStore == nil { if acmeTLSStore == nil && err == nil {
err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol) err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol)
} }
@ -189,15 +186,12 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
certificate := acmeTLSStore.GetBestCertificate(clientHello) certificate := acmeTLSStore.GetBestCertificate(clientHello)
if certificate == nil { if certificate == nil {
log.Debug().Msgf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck) log.Debug().Msgf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck)
// We want the user to eventually get the (alertUnrecognizedName) "unrecognized // We want the user to eventually get the (alertUnrecognizedName) "unrecognized name" error.
// name" error. // Unfortunately, if we returned an error here,
// Unfortunately, if we returned an error here, since we can't use // since we can't use the unexported error (errNoCertificates) that our caller (config.getCertificate in crypto/tls) uses as a sentinel,
// the unexported error (errNoCertificates) that our caller (config.getCertificate // it would report an (alertInternalError) "internal error" instead of an alertUnrecognizedName.
// in crypto/tls) uses as a sentinel, it would report an (alertInternalError) // Which is why we return no error, and we let the caller detect that there's actually no certificate,
// "internal error" instead of an alertUnrecognizedName. // and fall back into the flow that will report the desired error.
// Which is why we return no error, and we let the caller detect that there's
// actually no certificate, and fall back into the flow that will report
// the desired error.
// https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058 // https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058
return nil, nil return nil, nil
} }

View file

@ -119,8 +119,9 @@ func TestManager_Get(t *testing.T) {
}} }}
tlsConfigs := map[string]Options{ tlsConfigs := map[string]Options{
"foo": {MinVersion: "VersionTLS12"}, "foo": {MinVersion: "VersionTLS12"},
"bar": {MinVersion: "VersionTLS11"}, "bar": {MinVersion: "VersionTLS11"},
"invalid": {CurvePreferences: []string{"42"}},
} }
testCases := []struct { testCases := []struct {
@ -140,15 +141,20 @@ func TestManager_Get(t *testing.T) {
expectedMinVersion: uint16(tls.VersionTLS11), expectedMinVersion: uint16(tls.VersionTLS11),
}, },
{ {
desc: "Get an tls config from an invalid name", desc: "Get a tls config from an invalid name",
tlsOptionsName: "unknown", tlsOptionsName: "unknown",
expectedError: true, expectedError: true,
}, },
{ {
desc: "Get an tls config from unexisting 'default' name", desc: "Get a tls config from unexisting 'default' name",
tlsOptionsName: "default", tlsOptionsName: "default",
expectedError: true, expectedError: true,
}, },
{
desc: "Get an invalid tls config",
tlsOptionsName: "invalid",
expectedError: true,
},
} }
tlsManager := NewManager() tlsManager := NewManager()
@ -161,42 +167,13 @@ func TestManager_Get(t *testing.T) {
config, err := tlsManager.Get("default", test.tlsOptionsName) config, err := tlsManager.Get("default", test.tlsOptionsName)
if test.expectedError { if test.expectedError {
assert.Error(t, err) require.Nil(t, config)
require.Error(t, err)
return return
} }
assert.NoError(t, err)
assert.Equal(t, config.MinVersion, test.expectedMinVersion)
})
}
}
func TestManager_Get_GetCertificate(t *testing.T) {
testCases := []struct {
desc string
expectedGetConfigErr require.ErrorAssertionFunc
expectedCertificate assert.ValueAssertionFunc
}{
{
desc: "Get a default certificate from non-existing store",
expectedGetConfigErr: require.Error,
expectedCertificate: assert.Nil,
},
}
tlsManager := NewManager()
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
config, err := tlsManager.Get("default", "foo")
test.expectedGetConfigErr(t, err)
certificate, err := config.GetCertificate(&tls.ClientHelloInfo{})
require.NoError(t, err) require.NoError(t, err)
test.expectedCertificate(t, certificate) assert.Equal(t, config.MinVersion, test.expectedMinVersion)
}) })
} }
} }

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file" OutputType = "file"
FileName = "traefik_changelog.md" FileName = "traefik_changelog.md"
# example new bugfix v2.9.5 # example new bugfix v2.9.6
CurrentRef = "v2.9" CurrentRef = "v2.9"
PreviousRef = "v2.9.4" PreviousRef = "v2.9.5"
BaseBranch = "v2.9" BaseBranch = "v2.9"
FutureCurrentRefName = "v2.9.5" FutureCurrentRefName = "v2.9.6"
ThresholdPreviousRef = 10 ThresholdPreviousRef = 10
ThresholdCurrentRef = 10 ThresholdCurrentRef = 10