Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .aspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ allowed:
- frontent
- pprof
- preload
- hostname
- str
212 changes: 212 additions & 0 deletions deploy/tests/tnr/routeacl/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,215 @@ func (suite *UseBackendSuite) UseBackendFixture() (eventChan chan k8ssync.SyncDa
<-controllerHasWorked
return eventChan
}

func (suite *UseBackendSuite) NonWildcardHostFixture() (eventChan chan k8ssync.SyncDataEvent) {
var osArgs utils.OSArgs
os.Args = []string{os.Args[0], "-e", "-t", "--config-dir=" + suite.test.TempDir}
parser := flags.NewParser(&osArgs, flags.IgnoreUnknown)
_, errParsing := parser.Parse() //nolint:ifshort
if errParsing != nil {
suite.T().Fatal(errParsing)
}

s := store.NewK8sStore(osArgs)

haproxyEnv := env.Env{
CfgDir: suite.test.TempDir,
Proxies: env.Proxies{
FrontHTTP: "http",
FrontHTTPS: "https",
FrontSSL: "ssl",
BackSSL: "ssl-backend",
},
}

eventChan = make(chan k8ssync.SyncDataEvent, watch.DefaultChanSize*6)
controller := c.NewBuilder().
WithHaproxyCfgFile([]byte(haproxyConfig)).
WithEventChan(eventChan).
WithStore(s).
WithHaproxyEnv(haproxyEnv).
WithUpdateStatusManager(&updateStatusManager{}).
WithArgs(osArgs).Build()

go controller.Start()

// Now sending store events for test setup
ns := store.Namespace{Name: "ns", Status: store.ADDED}
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.NAMESPACE, Namespace: ns.Name, Data: &ns}

endpoints := &store.Endpoints{
SliceName: "api-service",
Service: "api-service",
Namespace: ns.Name,
Ports: map[string]*store.PortEndpoints{
"https": {
Port: int64(3001),
Addresses: map[string]struct{}{"10.244.0.11": {}},
},
},
Status: store.ADDED,
}

eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.ENDPOINTS, Namespace: endpoints.Namespace, Data: endpoints}

service := &store.Service{
Name: "api-service",
Namespace: ns.Name,
Annotations: map[string]string{"route-acl": "path_reg path-in-bug-repro$"},
Ports: []store.ServicePort{
{
Name: "https",
Protocol: "TCP",
Port: 8443,
Status: store.ADDED,
},
},
Status: store.ADDED,
}
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.SERVICE, Namespace: service.Namespace, Data: service}

ingressClass := &store.IngressClass{
Name: "haproxy",
Controller: "haproxy.org/ingress-controller",
Status: store.ADDED,
}
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.INGRESS_CLASS, Data: ingressClass}

prefixPathType := networkingv1.PathTypePrefix
ingress := &store.Ingress{
IngressCore: store.IngressCore{
APIVersion: store.NETWORKINGV1,
Name: "api-ingress",
Namespace: ns.Name,
Class: "haproxy",
Rules: map[string]*store.IngressRule{
"api.example.local": {
Host: "api.example.local", // Explicitly set the Host field
Paths: map[string]*store.IngressPath{
string(prefixPathType) + "-/": {
Path: "/",
PathTypeMatch: string(prefixPathType),
SvcNamespace: service.Namespace,
SvcPortString: "https",
SvcName: service.Name,
},
},
},
},
},
Status: store.ADDED,
}

eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.INGRESS, Namespace: ingress.Namespace, Data: ingress}
controllerHasWorked := make(chan struct{})
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.COMMAND, EventProcessed: controllerHasWorked}
<-controllerHasWorked
return eventChan
}

func (suite *UseBackendSuite) WildcardHostFixture() (eventChan chan k8ssync.SyncDataEvent) {
var osArgs utils.OSArgs
os.Args = []string{os.Args[0], "-e", "-t", "--config-dir=" + suite.test.TempDir}
parser := flags.NewParser(&osArgs, flags.IgnoreUnknown)
_, errParsing := parser.Parse() //nolint:ifshort
if errParsing != nil {
suite.T().Fatal(errParsing)
}

s := store.NewK8sStore(osArgs)

haproxyEnv := env.Env{
CfgDir: suite.test.TempDir,
Proxies: env.Proxies{
FrontHTTP: "http",
FrontHTTPS: "https",
FrontSSL: "ssl",
BackSSL: "ssl-backend",
},
}

eventChan = make(chan k8ssync.SyncDataEvent, watch.DefaultChanSize*6)
controller := c.NewBuilder().
WithHaproxyCfgFile([]byte(haproxyConfig)).
WithEventChan(eventChan).
WithStore(s).
WithHaproxyEnv(haproxyEnv).
WithUpdateStatusManager(&updateStatusManager{}).
WithArgs(osArgs).Build()

go controller.Start()

// Now sending store events for test setup
ns := store.Namespace{Name: "ns", Status: store.ADDED}
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.NAMESPACE, Namespace: ns.Name, Data: &ns}

endpoints := &store.Endpoints{
SliceName: "wildcard-service",
Service: "wildcard-service",
Namespace: ns.Name,
Ports: map[string]*store.PortEndpoints{
"https": {
Port: int64(3001),
Addresses: map[string]struct{}{"10.244.0.10": {}},
},
},
Status: store.ADDED,
}

eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.ENDPOINTS, Namespace: endpoints.Namespace, Data: endpoints}

service := &store.Service{
Name: "wildcard-service",
Namespace: ns.Name,
Annotations: map[string]string{"route-acl": "path_reg path-in-bug-repro$"},
Ports: []store.ServicePort{
{
Name: "https",
Protocol: "TCP",
Port: 8443,
Status: store.ADDED,
},
},
Status: store.ADDED,
}
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.SERVICE, Namespace: service.Namespace, Data: service}

ingressClass := &store.IngressClass{
Name: "haproxy",
Controller: "haproxy.org/ingress-controller",
Status: store.ADDED,
}
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.INGRESS_CLASS, Data: ingressClass}

prefixPathType := networkingv1.PathTypePrefix
ingress := &store.Ingress{
IngressCore: store.IngressCore{
APIVersion: store.NETWORKINGV1,
Name: "wildcard-ingress",
Namespace: ns.Name,
Class: "haproxy",
Rules: map[string]*store.IngressRule{
"*.example.local": {
Host: "*.example.local", // Explicitly set the Host field
Paths: map[string]*store.IngressPath{
string(prefixPathType) + "-/": {
Path: "/",
PathTypeMatch: string(prefixPathType),
SvcNamespace: service.Namespace,
SvcPortString: "https",
SvcName: service.Name,
},
},
},
},
},
Status: store.ADDED,
}

eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.INGRESS, Namespace: ingress.Namespace, Data: ingress}
controllerHasWorked := make(chan struct{})
eventChan <- k8ssync.SyncDataEvent{SyncType: k8ssync.COMMAND, EventProcessed: controllerHasWorked}
<-controllerHasWorked
return eventChan
}
50 changes: 50 additions & 0 deletions deploy/tests/tnr/routeacl/usebackend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,53 @@ func (suite *UseBackendSuite) TestUseBackend() {
suite.Exactly(c, 2, "use_backend for route-acl is repeated %d times but expected 2", c)
})
}

func (suite *UseBackendSuite) TestNonWildcardHostWithRouteACL() {
// Test non-wildcard host first to ensure route-acl works
suite.NonWildcardHostFixture()
suite.Run("Non-wildcard host should use string matching (-m str) with route-acl", func() {
contents, err := os.ReadFile(filepath.Join(suite.test.TempDir, "haproxy.cfg"))
if err != nil {
suite.T().Error(err.Error())
}

// Check that -m str is used with non-wildcard hosts in route-acl
if !strings.Contains(string(contents), "var(txn.host) -m str api.example.local") {
suite.T().Error("Expected to find 'var(txn.host) -m str api.example.local' in HAProxy config")
}

// Check that route-acl annotation is applied
if !strings.Contains(string(contents), "path_reg path-in-bug-repro$") {
suite.T().Error("Expected to find route-acl pattern 'path_reg path-in-bug-repro$' in HAProxy config")
}
})
}

func (suite *UseBackendSuite) TestWildcardHostWithRouteACL() {
// This test addresses https://github.com/haproxytech/kubernetes-ingress/issues/734
suite.WildcardHostFixture()
suite.Run("Wildcard host should use suffix matching (-m end) with route-acl", func() {
contents, err := os.ReadFile(filepath.Join(suite.test.TempDir, "haproxy.cfg"))
if err != nil {
suite.T().Error(err.Error())
}

// Debug: Print the actual config to see what's generated
suite.T().Logf("Generated HAProxy config:\n%s", string(contents))

// Check that -m end is used with wildcard hosts in route-acl
if !strings.Contains(string(contents), "var(txn.host) -m end .example.local") {
suite.T().Error("Expected to find 'var(txn.host) -m end .example.local' in HAProxy config")
}

// Check that the buggy -m str pattern is NOT used
if strings.Contains(string(contents), "var(txn.host) -m str *.example.local") {
suite.T().Error("Found buggy pattern 'var(txn.host) -m str *.example.local' in HAProxy config")
}

// Check that route-acl annotation is applied
if !strings.Contains(string(contents), "path_reg path-in-bug-repro$") {
suite.T().Error("Expected to find route-acl pattern 'path_reg path-in-bug-repro$' in HAProxy config")
}
})
}
8 changes: 7 additions & 1 deletion pkg/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ func AddHostPathRoute(route Route, mapFiles maps.Maps) error {
func AddCustomRoute(route Route, routeACLAnn string, api api.HAProxyClient) (err error) {
var routeCond string
if route.Host != "" {
routeCond = fmt.Sprintf("{ var(txn.host) -m str %s } ", route.Host)
if route.Host[0] == '*' {
// Wildcard host - use suffix matching
routeCond = fmt.Sprintf("{ var(txn.host) -m end %s } ", route.Host[1:])
} else {
// Regular host - use string matching
routeCond = fmt.Sprintf("{ var(txn.host) -m str %s } ", route.Host)
}
}
if route.Path.Path != "" {
if route.Path.PathTypeMatch == store.PATH_TYPE_EXACT {
Expand Down
Loading