1
0
Fork 0

Make the behavior of prefix matching in Ingress consistent with Kubernetes doc

This commit is contained in:
Charlie Chiang 2025-05-20 20:40:05 +08:00 committed by GitHub
parent 8c6ed23c5f
commit 4790e4910f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 262 additions and 2 deletions

View file

@ -3,7 +3,10 @@ package ingress
import (
"context"
"errors"
"fmt"
"math"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
@ -14,6 +17,7 @@ import (
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
traefikhttp "github.com/traefik/traefik/v3/pkg/muxer/http"
"github.com/traefik/traefik/v3/pkg/provider"
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
"github.com/traefik/traefik/v3/pkg/tls"
@ -38,6 +42,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
disableIngressClassLookup bool
disableClusterScopeResources bool
defaultRuleSyntax string
strictPrefixMatching bool
}{
{
desc: "Empty ingresses",
@ -1621,6 +1626,40 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
},
},
},
{
desc: "Ingress with strict prefix matching",
expected: &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "(Path(`/bar`) || PathPrefix(`/bar/`))",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Strategy: dynamic.BalancerStrategyWRR,
PassHostHeader: pointer(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
{
URL: "http://10.21.0.1:8080",
},
},
},
},
},
},
},
strictPrefixMatching: true,
},
}
for _, test := range testCases {
@ -1634,6 +1673,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
DisableIngressClassLookup: test.disableIngressClassLookup,
DisableClusterScopeResources: test.disableClusterScopeResources,
DefaultRuleSyntax: test.defaultRuleSyntax,
StrictPrefixMatching: test.strictPrefixMatching,
}
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
@ -2256,3 +2296,106 @@ func readResources(t *testing.T, paths []string) []runtime.Object {
return k8sObjects
}
func TestStrictPrefixMatchingRule(t *testing.T) {
tests := []struct {
path string
requestPath string
match bool
}{ // The tests are taken from https://kubernetes.io/docs/concepts/services-networking/ingress/#examples
{
path: "/foo",
requestPath: "/foo",
match: true,
},
{
path: "/foo",
requestPath: "/foo/",
match: true,
},
{
path: "/foo/",
requestPath: "/foo",
match: true,
},
{
path: "/foo/",
requestPath: "/foo/",
match: true,
},
{
path: "/aaa/bb",
requestPath: "/aaa/bbb",
match: false,
},
{
path: "/aaa/bbb",
requestPath: "/aaa/bbb",
match: true,
},
{
path: "/aaa/bbb/",
requestPath: "/aaa/bbb",
match: true,
},
{
path: "/aaa/bbb",
requestPath: "/aaa/bbb/",
match: true,
},
{
path: "/aaa/bbb",
requestPath: "/aaa/bbb/ccc",
match: true,
},
{
path: "/aaa/bbb",
requestPath: "/aaa/bbbxyz",
match: false,
},
{
path: "/",
requestPath: "/aaa/ccc",
match: true,
},
{
path: "/aaa",
requestPath: "/aaa/ccc",
match: true,
},
{
path: "/...",
requestPath: "/aaa",
match: false,
},
{
path: "/...",
requestPath: "/.../",
match: true,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("Prefix match case %s", tt.path), func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := traefikhttp.NewMuxer()
require.NoError(t, err)
rule := buildStrictPrefixMatchingRule(tt.path)
err = muxer.AddRoute(rule, "", 0, handler)
require.NoError(t, err)
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, tt.requestPath, http.NoBody)
muxer.ServeHTTP(w, req)
if tt.match {
assert.Equal(t, http.StatusOK, w.Code)
} else {
assert.Equal(t, http.StatusNotFound, w.Code)
}
})
}
}