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/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/net/idna"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/ingress/middleware"
|
"github.com/cloudflare/cloudflared/ingress/middleware"
|
||||||
|
@ -275,6 +276,16 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
||||||
return Ingress{}, err
|
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
|
var pathRegexp *Regexp
|
||||||
if r.Path != "" {
|
if r.Path != "" {
|
||||||
var err error
|
var err error
|
||||||
|
@ -287,6 +298,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
|
||||||
|
|
||||||
rules[i] = Rule{
|
rules[i] = Rule{
|
||||||
Hostname: r.Hostname,
|
Hostname: r.Hostname,
|
||||||
|
punycodeHostname: punycodeHostname,
|
||||||
Service: service,
|
Service: service,
|
||||||
Path: pathRegexp,
|
Path: pathRegexp,
|
||||||
Handlers: handlers,
|
Handlers: handlers,
|
||||||
|
|
|
@ -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",
|
name: "Invalid service",
|
||||||
args: args{rawYAML: `
|
args: args{rawYAML: `
|
||||||
|
|
|
@ -14,6 +14,9 @@ type Rule struct {
|
||||||
// Requests for this hostname will be proxied to this rule's service.
|
// Requests for this hostname will be proxied to this rule's service.
|
||||||
Hostname string `json:"hostname"`
|
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 is an optional regex that can specify path-driven ingress rules.
|
||||||
Path *Regexp `json:"path"`
|
Path *Regexp `json:"path"`
|
||||||
|
|
||||||
|
@ -50,9 +53,18 @@ func (r Rule) MultiLineString() string {
|
||||||
|
|
||||||
// Matches checks if the rule matches a given hostname/path combination.
|
// Matches checks if the rule matches a given hostname/path combination.
|
||||||
func (r *Rule) Matches(hostname, path string) bool {
|
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)
|
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
|
// Regexp adds unmarshalling from json for regexp.Regexp
|
||||||
|
|
|
@ -14,23 +14,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_rule_matches(t *testing.T) {
|
func Test_rule_matches(t *testing.T) {
|
||||||
type fields struct {
|
|
||||||
Hostname string
|
|
||||||
Path *Regexp
|
|
||||||
Service OriginService
|
|
||||||
}
|
|
||||||
type args struct {
|
type args struct {
|
||||||
requestURL *url.URL
|
requestURL *url.URL
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fields fields
|
rule Rule
|
||||||
args args
|
args args
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Just hostname, pass",
|
name: "Just hostname, pass",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "example.com",
|
Hostname: "example.com",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -38,9 +33,31 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: true,
|
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",
|
name: "Entire hostname is wildcard, should match everything",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "*",
|
Hostname: "*",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -50,7 +67,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Just hostname, fail",
|
name: "Just hostname, fail",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "example.com",
|
Hostname: "example.com",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -60,7 +77,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Just wildcard hostname, pass",
|
name: "Just wildcard hostname, pass",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "*.example.com",
|
Hostname: "*.example.com",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -70,7 +87,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Just wildcard hostname, fail",
|
name: "Just wildcard hostname, fail",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "*.example.com",
|
Hostname: "*.example.com",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -80,7 +97,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Just wildcard outside of subdomain in hostname, fail",
|
name: "Just wildcard outside of subdomain in hostname, fail",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "*example.com",
|
Hostname: "*example.com",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -90,7 +107,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Wildcard over multiple subdomains",
|
name: "Wildcard over multiple subdomains",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "*.example.com",
|
Hostname: "*.example.com",
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
|
@ -100,7 +117,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Hostname and path",
|
name: "Hostname and path",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "*.example.com",
|
Hostname: "*.example.com",
|
||||||
Path: &Regexp{Regexp: regexp.MustCompile("/static/.*\\.html")},
|
Path: &Regexp{Regexp: regexp.MustCompile("/static/.*\\.html")},
|
||||||
},
|
},
|
||||||
|
@ -111,7 +128,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Hostname and empty Regex",
|
name: "Hostname and empty Regex",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "example.com",
|
Hostname: "example.com",
|
||||||
Path: &Regexp{},
|
Path: &Regexp{},
|
||||||
},
|
},
|
||||||
|
@ -122,7 +139,7 @@ func Test_rule_matches(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Hostname and nil path",
|
name: "Hostname and nil path",
|
||||||
fields: fields{
|
rule: Rule{
|
||||||
Hostname: "example.com",
|
Hostname: "example.com",
|
||||||
Path: nil,
|
Path: nil,
|
||||||
},
|
},
|
||||||
|
@ -134,13 +151,8 @@ func Test_rule_matches(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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
|
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)
|
t.Errorf("rule.matches() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue