TUN-6801: Add punycode alternatives for ingress rules
This commit is contained in:
parent
be0305ec58
commit
b3e26420c0
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/ingress/middleware"
|
||||
|
@ -275,6 +276,16 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
|||
return Ingress{}, err
|
||||
}
|
||||
|
||||
isCatchAllRule := (r.Hostname == "" || r.Hostname == "*") && r.Path == ""
|
||||
punycodeHostname := ""
|
||||
if !isCatchAllRule {
|
||||
punycode, err := idna.Lookup.ToASCII(r.Hostname)
|
||||
// Don't provide the punycode hostname if it is the same as the original hostname
|
||||
if err == nil && punycode != r.Hostname {
|
||||
punycodeHostname = punycode
|
||||
}
|
||||
}
|
||||
|
||||
var pathRegexp *Regexp
|
||||
if r.Path != "" {
|
||||
var err error
|
||||
|
@ -286,11 +297,12 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
|||
}
|
||||
|
||||
rules[i] = Rule{
|
||||
Hostname: r.Hostname,
|
||||
Service: service,
|
||||
Path: pathRegexp,
|
||||
Handlers: handlers,
|
||||
Config: cfg,
|
||||
Hostname: r.Hostname,
|
||||
punycodeHostname: punycodeHostname,
|
||||
Service: service,
|
||||
Path: pathRegexp,
|
||||
Handlers: handlers,
|
||||
Config: cfg,
|
||||
}
|
||||
}
|
||||
return Ingress{Rules: rules, Defaults: defaults}, nil
|
||||
|
|
|
@ -130,6 +130,36 @@ ingress:
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unicode domain",
|
||||
args: args{rawYAML: `
|
||||
ingress:
|
||||
- hostname: môô.cloudflare.com
|
||||
service: https://localhost:8000
|
||||
- service: https://localhost:8001
|
||||
`},
|
||||
want: []Rule{
|
||||
{
|
||||
Hostname: "môô.cloudflare.com",
|
||||
punycodeHostname: "xn--m-xgaa.cloudflare.com",
|
||||
Service: &httpService{url: localhost8000},
|
||||
Config: defaultConfig,
|
||||
},
|
||||
{
|
||||
Service: &httpService{url: localhost8001},
|
||||
Config: defaultConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid unicode domain",
|
||||
args: args{rawYAML: fmt.Sprintf(`
|
||||
ingress:
|
||||
- hostname: %s
|
||||
service: https://localhost:8000
|
||||
`, string(rune(0xd8f3))+".cloudflare.com")},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid service",
|
||||
args: args{rawYAML: `
|
||||
|
|
|
@ -14,6 +14,9 @@ type Rule struct {
|
|||
// Requests for this hostname will be proxied to this rule's service.
|
||||
Hostname string `json:"hostname"`
|
||||
|
||||
// punycodeHostname is an additional optional hostname converted to punycode.
|
||||
punycodeHostname string
|
||||
|
||||
// Path is an optional regex that can specify path-driven ingress rules.
|
||||
Path *Regexp `json:"path"`
|
||||
|
||||
|
@ -50,9 +53,18 @@ func (r Rule) MultiLineString() string {
|
|||
|
||||
// Matches checks if the rule matches a given hostname/path combination.
|
||||
func (r *Rule) Matches(hostname, path string) bool {
|
||||
hostMatch := r.Hostname == "" || r.Hostname == "*" || matchHost(r.Hostname, hostname)
|
||||
hostMatch := false
|
||||
if r.Hostname == "" || r.Hostname == "*" {
|
||||
hostMatch = true
|
||||
} else {
|
||||
hostMatch = matchHost(r.Hostname, hostname)
|
||||
}
|
||||
punycodeHostMatch := false
|
||||
if r.punycodeHostname != "" {
|
||||
punycodeHostMatch = matchHost(r.punycodeHostname, hostname)
|
||||
}
|
||||
pathMatch := r.Path == nil || r.Path.Regexp == nil || r.Path.Regexp.MatchString(path)
|
||||
return hostMatch && pathMatch
|
||||
return (hostMatch || punycodeHostMatch) && pathMatch
|
||||
}
|
||||
|
||||
// Regexp adds unmarshalling from json for regexp.Regexp
|
||||
|
|
|
@ -14,23 +14,18 @@ import (
|
|||
)
|
||||
|
||||
func Test_rule_matches(t *testing.T) {
|
||||
type fields struct {
|
||||
Hostname string
|
||||
Path *Regexp
|
||||
Service OriginService
|
||||
}
|
||||
type args struct {
|
||||
requestURL *url.URL
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
name string
|
||||
rule Rule
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Just hostname, pass",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "example.com",
|
||||
},
|
||||
args: args{
|
||||
|
@ -38,9 +33,31 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Unicode hostname with unicode request, pass",
|
||||
rule: Rule{
|
||||
Hostname: "môô.cloudflare.com",
|
||||
punycodeHostname: "xn--m-xgaa.cloudflare.com",
|
||||
},
|
||||
args: args{
|
||||
requestURL: MustParseURL(t, "https://môô.cloudflare.com"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Unicode hostname with punycode request, pass",
|
||||
rule: Rule{
|
||||
Hostname: "môô.cloudflare.com",
|
||||
punycodeHostname: "xn--m-xgaa.cloudflare.com",
|
||||
},
|
||||
args: args{
|
||||
requestURL: MustParseURL(t, "https://xn--m-xgaa.cloudflare.com"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Entire hostname is wildcard, should match everything",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "*",
|
||||
},
|
||||
args: args{
|
||||
|
@ -50,7 +67,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Just hostname, fail",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "example.com",
|
||||
},
|
||||
args: args{
|
||||
|
@ -60,7 +77,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Just wildcard hostname, pass",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "*.example.com",
|
||||
},
|
||||
args: args{
|
||||
|
@ -70,7 +87,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Just wildcard hostname, fail",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "*.example.com",
|
||||
},
|
||||
args: args{
|
||||
|
@ -80,7 +97,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Just wildcard outside of subdomain in hostname, fail",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "*example.com",
|
||||
},
|
||||
args: args{
|
||||
|
@ -90,7 +107,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Wildcard over multiple subdomains",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "*.example.com",
|
||||
},
|
||||
args: args{
|
||||
|
@ -100,7 +117,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Hostname and path",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "*.example.com",
|
||||
Path: &Regexp{Regexp: regexp.MustCompile("/static/.*\\.html")},
|
||||
},
|
||||
|
@ -111,7 +128,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Hostname and empty Regex",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "example.com",
|
||||
Path: &Regexp{},
|
||||
},
|
||||
|
@ -122,7 +139,7 @@ func Test_rule_matches(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Hostname and nil path",
|
||||
fields: fields{
|
||||
rule: Rule{
|
||||
Hostname: "example.com",
|
||||
Path: nil,
|
||||
},
|
||||
|
@ -134,13 +151,8 @@ func Test_rule_matches(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := Rule{
|
||||
Hostname: tt.fields.Hostname,
|
||||
Path: tt.fields.Path,
|
||||
Service: tt.fields.Service,
|
||||
}
|
||||
u := tt.args.requestURL
|
||||
if got := r.Matches(u.Hostname(), u.Path); got != tt.want {
|
||||
if got := tt.rule.Matches(u.Hostname(), u.Path); got != tt.want {
|
||||
t.Errorf("rule.matches() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue