From 41449525673371da86692b7230f75cea0b852fda Mon Sep 17 00:00:00 2001 From: Mustafa Abdelrahman Date: Fri, 1 Nov 2024 15:53:10 +0100 Subject: [PATCH] host2regex: doesn't take in consideration `*` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make eskip rewrite the host to the right regex in case it starts with `*.` In case matching of wildcard leaf match don't increase leaf weight Tested with ```bash ➜ ./bin/skipper -inline-routes 'r0: Host("*.mu.sa") -> inlineContent("hi from 0") -> ; r1: Host("ab.mu.sa") -> inlineContent("hi from 1") -> ;' -address=:8080 ➜ curl -i -H "Host: ba.mu.sa" http://127.0.0.1:8080 HTTP/1.1 200 OK Content-Length: 9 Content-Type: text/plain; charset=utf-8 Server: Skipper Date: Tue, 03 Jun 2025 11:18:23 GMT hi from 0% ➜ curl -i -H "Host: ab.mu.sa" http://127.0.0.1:8080 HTTP/1.1 200 OK Content-Length: 9 Content-Type: text/plain; charset=utf-8 Server: Skipper Date: Tue, 03 Jun 2025 11:18:28 GMT hi from 1% ``` Signed-off-by: Mustafa Abdelrahman --- dataclients/kubernetes/hosts.go | 5 ++ dataclients/kubernetes/hosts_test.go | 31 ++++++++++++ .../ingress-data/wildcard-ing-prefix.eskip | 3 ++ .../ingress-data/wildcard-ing-prefix.yaml | 49 +++++++++++++++++++ eskip/eskip.go | 4 ++ predicates/forwarded/forwarded_test.go | 46 ++++++++++++++++- predicates/host/any.go | 9 ++-- routing/matcher.go | 11 ++++- 8 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 dataclients/kubernetes/hosts_test.go create mode 100644 dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.eskip create mode 100644 dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.yaml diff --git a/dataclients/kubernetes/hosts.go b/dataclients/kubernetes/hosts.go index 24712d5287..766ad56edc 100644 --- a/dataclients/kubernetes/hosts.go +++ b/dataclients/kubernetes/hosts.go @@ -5,6 +5,7 @@ import ( "regexp" "strings" + log "github.com/sirupsen/logrus" "github.com/zalando/skipper/eskip" ) @@ -15,6 +16,10 @@ func createHostRx(hosts ...string) string { hrx := make([]string, len(hosts)) for i, host := range hosts { + if strings.HasPrefix(host, "*.") { + log.Debugf("Host %q starts with '*.'; replacing with regex", host) + host = strings.Replace(host, "*", "[a-z0-9]+((-[a-z0-9]+)?)*", 1) + } // trailing dots and port are not allowed in kube // ingress spec, so we can append optional setting // without check diff --git a/dataclients/kubernetes/hosts_test.go b/dataclients/kubernetes/hosts_test.go new file mode 100644 index 0000000000..3e2f668ae0 --- /dev/null +++ b/dataclients/kubernetes/hosts_test.go @@ -0,0 +1,31 @@ +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHostsToRegex(t *testing.T) { + for _, ti := range []struct { + msg string + host string + regex string + }{ + { + msg: "simple", + host: "simple.example.org", + regex: "^(simple[.]example[.]org[.]?(:[0-9]+)?)$", + }, + { + msg: "wildcard", + host: "*.example.org", + regex: "^([a-z0-9]+((-[a-z0-9]+)?)*[.]example[.]org[.]?(:[0-9]+)?)$", + }, + } { + t.Run(ti.msg, func(t *testing.T) { + regex := createHostRx(ti.host) + require.Equal(t, ti.regex, regex) + }) + } +} diff --git a/dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.eskip b/dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.eskip new file mode 100644 index 0000000000..99c886544e --- /dev/null +++ b/dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.eskip @@ -0,0 +1,3 @@ +kube_foo__qux____example_org_____qux: + Host("^([a-z0-9]+((-[a-z0-9]+)?)*[.]example[.]org[.]?(:[0-9]+)?)$") && PathSubtree("/") + -> ; diff --git a/dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.yaml b/dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.yaml new file mode 100644 index 0000000000..9c3c914786 --- /dev/null +++ b/dataclients/kubernetes/testdata/ingressV1/ingress-data/wildcard-ing-prefix.yaml @@ -0,0 +1,49 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: qux + namespace: foo +spec: + rules: + - host: "*.example.org" + http: + paths: + - path: "/" + pathType: Prefix + backend: + service: + name: qux + port: + name: baz +--- +apiVersion: v1 +kind: Service +metadata: + name: qux + namespace: foo +spec: + clusterIP: 10.3.190.97 + ports: + - name: baz + port: 8181 + protocol: TCP + targetPort: 8080 + selector: + application: myapp + type: ClusterIP +--- +apiVersion: v1 +kind: Endpoints +metadata: + labels: + application: myapp + name: qux + namespace: foo +subsets: +- addresses: + - ip: 10.2.9.103 + - ip: 10.2.9.104 + ports: + - name: baz + port: 8080 + protocol: TCP diff --git a/eskip/eskip.go b/eskip/eskip.go index eb35e45af8..a799fa7f8a 100644 --- a/eskip/eskip.go +++ b/eskip/eskip.go @@ -493,6 +493,10 @@ func applyPredicates(route *Route, proute *parsedRoute) error { } case "Host": if args, err = getStringArgs(p, 1); err == nil { + if strings.HasPrefix(args[0], "*.") { + log.Infof("Host %q starts with '*.'; replacing with regex", args[0]) + args[0] = strings.Replace(args[0], "*", "[a-z0-9]+((-[a-z0-9]+)?)*", 1) + } route.HostRegexps = append(route.HostRegexps, args[0]) } case "PathRegexp": diff --git a/predicates/forwarded/forwarded_test.go b/predicates/forwarded/forwarded_test.go index a8dc3a7313..7b05341af6 100644 --- a/predicates/forwarded/forwarded_test.go +++ b/predicates/forwarded/forwarded_test.go @@ -162,6 +162,50 @@ func TestForwardedHost(t *testing.T) { }, matches: true, isError: false, + }, { + msg: "wildcard host should match", + host: "^([a-z0-9]+((-[a-z0-9]+)?)*[.]example[.]org[.]?(:[0-9]+)?)$", // *.example.org + r: request{ + url: "https://test.example.org/index.html", + headers: http.Header{ + "Forwarded": []string{`host="test.example.org"`}, + }, + }, + matches: true, + isError: false, + }, { + msg: "wildcard 2 host should match", + host: "^([a-z0-9]+((-[a-z0-9]+)?)*[.]example[.]org[.]?(:[0-9]+)?)$", // *.example.org + r: request{ + url: "https://test-v2.example.org/index.html", + headers: http.Header{ + "Forwarded": []string{`host="test-v2.example.org"`}, + }, + }, + matches: true, + isError: false, + }, { + msg: "wildcard 3 host should match", + host: "^([a-z0-9]+((-[a-z0-9]+)?)*[.]example[.]org[.]?(:[0-9]+)?)$", // *.example.org + r: request{ + url: "https://test-v2-v3.example.org/index.html", + headers: http.Header{ + "Forwarded": []string{`host="test-v2-v3.example.org"`}, + }, + }, + matches: true, + isError: false, + }, { + msg: "wildcard 4 host shouldn't match", + host: "^([a-z0-9]+((-[a-z0-9]+)?)*[.]example[.]org[.]?(:[0-9]+)?)$", // *.example.org + r: request{ + url: "https://test-.example.org/index.html", + headers: http.Header{ + "Forwarded": []string{`host="test-.example.org"`}, + }, + }, + matches: false, + isError: false, }} for _, tc := range testCases { @@ -173,7 +217,7 @@ func TestForwardedHost(t *testing.T) { hasError := err != nil if hasError || tc.isError { if !tc.isError { - t.Fatal("Predicate creation failed") + t.Fatalf("Predicate creation failed, %s", err) } if !hasError { diff --git a/predicates/host/any.go b/predicates/host/any.go index b15a2edb22..f4d6b5fb6b 100644 --- a/predicates/host/any.go +++ b/predicates/host/any.go @@ -3,6 +3,8 @@ package host import ( "net/http" + "slices" + "github.com/zalando/skipper/predicates" "github.com/zalando/skipper/routing" ) @@ -40,10 +42,5 @@ func (*anySpec) Create(args []interface{}) (routing.Predicate, error) { } func (ap *anyPredicate) Match(r *http.Request) bool { - for _, host := range ap.hosts { - if host == r.Host { - return true - } - } - return false + return slices.Contains(ap.hosts, r.Host) } diff --git a/routing/matcher.go b/routing/matcher.go index 686fb8c40a..59f60abadc 100644 --- a/routing/matcher.go +++ b/routing/matcher.go @@ -51,7 +51,16 @@ func leafWeight(l *leafMatcher) int { w++ } - w += len(l.hostRxs) + for _, rx := range l.hostRxs { + if strings.HasPrefix(rx.String(), "[a-z0-9]+((-[a-z0-9]+)?)*") { + // this is a free wildcard, skip it from the first matching + w += 0 + } else { + w += 1 + } + } + + // w += len(l.hostRxs) w += len(l.pathRxs) w += len(l.headersExact) w += len(l.headersRegexp)