Raise errors for non-ASCII domain names in a router's rules
This commit is contained in:
parent
1e716a93ff
commit
a513a05b7a
6 changed files with 118 additions and 23 deletions
|
@ -336,3 +336,18 @@ The file parser has been changed, since v2.3 the unknown options/fields in a dyn
|
||||||
|
|
||||||
In `v2.3`, the support of `IngressClass`, which is available since Kubernetes version `1.18`, has been introduced.
|
In `v2.3`, the support of `IngressClass`, which is available since Kubernetes version `1.18`, has been introduced.
|
||||||
In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated.
|
In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated.
|
||||||
|
|
||||||
|
## v2.4.7 to v2.4.8
|
||||||
|
|
||||||
|
### Non-ASCII Domain Names
|
||||||
|
|
||||||
|
In `v2.4.8` we introduced a new check on domain names used in HTTP router rule `Host` and `HostRegexp` expressions,
|
||||||
|
and in TCP router rule `HostSNI` expression.
|
||||||
|
This check ensures that provided domain names don't contain non-ASCII characters.
|
||||||
|
If not, an error is raised, and the associated router will be shown as invalid in the dashboard.
|
||||||
|
|
||||||
|
This new behavior is intended to show what was failing silently previously and to help troubleshooting configuration issues.
|
||||||
|
It doesn't change the support for non-ASCII domain names in routers rules, which is not part of the Traefik feature set so far.
|
||||||
|
|
||||||
|
In order to use non-ASCII domain names in a router's rule, one should use the Punycode form of the domain name.
|
||||||
|
For more information, please read the [HTTP routers rule](../routing/routers/index.md#rule) part or [TCP router rules](../routing/routers/index.md#rule_1) part of the documentation.
|
||||||
|
|
|
@ -240,11 +240,17 @@ The table below lists all the available matchers:
|
||||||
| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
||||||
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
||||||
|
|
||||||
|
!!! important "Non-ASCII Domain Names"
|
||||||
|
|
||||||
|
Non-ASCII characters are not supported in `Host` and `HostRegexp` expressions, and by doing so the associated router will be invalid.
|
||||||
|
For the `Host` expression, domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)).
|
||||||
|
As well, when using the `HostRegexp` expressions, in order to match domain names containing non-ASCII characters, the regular expression should match a punycode encoded domain name.
|
||||||
|
|
||||||
!!! important "Regexp Syntax"
|
!!! important "Regexp Syntax"
|
||||||
|
|
||||||
In order to use regular expressions with `Host` and `Path` expressions,
|
`HostRegexp` and `Path` accept an expression with zero or more groups enclosed by curly braces.
|
||||||
you must declare an arbitrarily named variable followed by the colon-separated regular expression, all enclosed in curly braces.
|
Named groups can be like `{name:pattern}` that matches the given regexp pattern or like `{name}` that matches anything until the next dot.
|
||||||
Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `/posts/{id:[0-9]+}`).
|
Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `{subdomain:[a-z]+}.{domain}.com`).
|
||||||
|
|
||||||
!!! info "Combining Matchers Using Operators and Parenthesis"
|
!!! info "Combining Matchers Using Operators and Parenthesis"
|
||||||
|
|
||||||
|
@ -782,6 +788,11 @@ If you want to limit the router scope to a set of entry points, set the entry po
|
||||||
|--------------------------------|-------------------------------------------------------------------------|
|
|--------------------------------|-------------------------------------------------------------------------|
|
||||||
| ```HostSNI(`domain-1`, ...)``` | Check if the Server Name Indication corresponds to the given `domains`. |
|
| ```HostSNI(`domain-1`, ...)``` | Check if the Server Name Indication corresponds to the given `domains`. |
|
||||||
|
|
||||||
|
!!! important "Non-ASCII Domain Names"
|
||||||
|
|
||||||
|
Non-ASCII characters are not supported in the `HostSNI` expression, and by doing so the associated TCP router will be invalid.
|
||||||
|
Domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)).
|
||||||
|
|
||||||
!!! important "HostSNI & TLS"
|
!!! important "HostSNI & TLS"
|
||||||
|
|
||||||
It is important to note that the Server Name Indication is an extension of the TLS protocol.
|
It is important to note that the Server Name Indication is an extension of the TLS protocol.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
|
@ -102,6 +103,10 @@ func pathPrefix(route *mux.Route, paths ...string) error {
|
||||||
|
|
||||||
func host(route *mux.Route, hosts ...string) error {
|
func host(route *mux.Route, hosts ...string) error {
|
||||||
for i, host := range hosts {
|
for i, host := range hosts {
|
||||||
|
if !IsASCII(host) {
|
||||||
|
return fmt.Errorf("invalid value %q for \"Host\" matcher, non-ASCII characters are not allowed", host)
|
||||||
|
}
|
||||||
|
|
||||||
hosts[i] = strings.ToLower(host)
|
hosts[i] = strings.ToLower(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +157,10 @@ func host(route *mux.Route, hosts ...string) error {
|
||||||
func hostRegexp(route *mux.Route, hosts ...string) error {
|
func hostRegexp(route *mux.Route, hosts ...string) error {
|
||||||
router := route.Subrouter()
|
router := route.Subrouter()
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
|
if !IsASCII(host) {
|
||||||
|
return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host)
|
||||||
|
}
|
||||||
|
|
||||||
tmpRt := router.Host(host)
|
tmpRt := router.Host(host)
|
||||||
if tmpRt.GetError() != nil {
|
if tmpRt.GetError() != nil {
|
||||||
return tmpRt.GetError()
|
return tmpRt.GetError()
|
||||||
|
@ -250,3 +259,14 @@ func checkRule(rule *tree) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsASCII checks if the given string contains only ASCII characters.
|
||||||
|
func IsASCII(s string) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,16 @@ func Test_addRoute(t *testing.T) {
|
||||||
"http://localhost/foo": http.StatusOK,
|
"http://localhost/foo": http.StatusOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Non-ASCII Host",
|
||||||
|
rule: "Host(`locàlhost`)",
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Non-ASCII HostRegexp",
|
||||||
|
rule: "HostRegexp(`locàlhost`)",
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "HostHeader equivalent to Host",
|
desc: "HostHeader equivalent to Host",
|
||||||
rule: "HostHeader(`localhost`)",
|
rule: "HostHeader(`localhost`)",
|
||||||
|
|
|
@ -258,9 +258,18 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
logger.Debugf("Adding route %s on TCP", domain)
|
logger.Debugf("Adding route %s on TCP", domain)
|
||||||
switch {
|
switch {
|
||||||
case routerConfig.TLS != nil:
|
case routerConfig.TLS != nil:
|
||||||
|
if !rules.IsASCII(domain) {
|
||||||
|
asciiError := fmt.Errorf("invalid domain name value %q, non-ASCII characters are not allowed", domain)
|
||||||
|
routerConfig.AddError(asciiError, true)
|
||||||
|
logger.Debug(asciiError)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if routerConfig.TLS.Passthrough {
|
if routerConfig.TLS.Passthrough {
|
||||||
router.AddRoute(domain, handler)
|
router.AddRoute(domain, handler)
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
tlsOptionsName := routerConfig.TLS.Options
|
tlsOptionsName := routerConfig.TLS.Options
|
||||||
|
|
||||||
if len(tlsOptionsName) == 0 {
|
if len(tlsOptionsName) == 0 {
|
||||||
|
@ -279,7 +288,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
}
|
}
|
||||||
|
|
||||||
router.AddRouteTLS(domain, handler, tlsConf)
|
router.AddRouteTLS(domain, handler, tlsConf)
|
||||||
}
|
|
||||||
case domain == "*":
|
case domain == "*":
|
||||||
router.AddCatchAllNoTLS(handler)
|
router.AddCatchAllNoTLS(handler)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -71,6 +71,37 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectedError: 0,
|
expectedError: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Non-ASCII domain error",
|
||||||
|
tcpServiceConfig: map[string]*runtime.TCPServiceInfo{
|
||||||
|
"foo-service": {
|
||||||
|
TCPService: &dynamic.TCPService{
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Port: "8085",
|
||||||
|
Address: "127.0.0.1:8085",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tcpRouterConfig: map[string]*runtime.TCPRouterInfo{
|
||||||
|
"foo": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service",
|
||||||
|
Rule: "HostSNI(`bàr.foo`)",
|
||||||
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: false,
|
||||||
|
Options: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "HTTP routers with same domain but different TLS options",
|
desc: "HTTP routers with same domain but different TLS options",
|
||||||
httpServiceConfig: map[string]*runtime.ServiceInfo{
|
httpServiceConfig: map[string]*runtime.ServiceInfo{
|
||||||
|
|
Loading…
Reference in a new issue