diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_endpoint.yml new file mode 100644 index 000000000..b19cd5c3f --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_endpoint.yml @@ -0,0 +1,15 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 +- addresses: + - ip: 10.21.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_ingress.yml new file mode 100644 index 000000000..a8aa09dcd --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_ingress.yml @@ -0,0 +1,23 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "" + namespace: testing + +spec: + rules: + - host: "*.bar" + http: + paths: + - path: /bar + backend: + serviceName: service1 + servicePort: 80 + + - host: "bar" + http: + paths: + - path: /bar + backend: + serviceName: service1 + servicePort: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_service.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_service.yml new file mode 100644 index 000000000..7c58aeed5 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-host_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIp: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_endpoint.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_endpoint.yml new file mode 100644 index 000000000..b19cd5c3f --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_endpoint.yml @@ -0,0 +1,15 @@ +kind: Endpoints +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +subsets: +- addresses: + - ip: 10.10.0.1 + ports: + - port: 8080 +- addresses: + - ip: 10.21.0.1 + ports: + - port: 8080 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_ingress.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_ingress.yml new file mode 100644 index 000000000..abfe74519 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_ingress.yml @@ -0,0 +1,19 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: "" + namespace: testing + +spec: + rules: + - http: + paths: + - path: /foo/bar + backend: + serviceName: service1 + servicePort: 80 + + - path: /foo-bar + backend: + serviceName: service1 + servicePort: 80 diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_service.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_service.yml new file mode 100644 index 000000000..7c58aeed5 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-conflicting-routers-on-path_service.yml @@ -0,0 +1,10 @@ +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIp: 10.0.0.1 diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 8f06b214b..d3995dcb7 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -2,6 +2,7 @@ package ingress import ( "context" + "crypto/sha256" "errors" "fmt" "math" @@ -253,6 +254,8 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl conf.HTTP.Services["default-backend"] = service } + routers := map[string][]*dynamic.Router{} + for _, rule := range ingress.Spec.Rules { if err := p.updateIngressStatus(ingress, client); err != nil { log.FromContext(ctx).Errorf("Error while updating ingress status: %v", err) @@ -276,8 +279,26 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl conf.HTTP.Services[serviceName] = service routerKey := strings.TrimPrefix(provider.Normalize(ingress.Name+"-"+ingress.Namespace+"-"+rule.Host+pa.Path), "-") + routers[routerKey] = append(routers[routerKey], loadRouter(rule, pa, rtConfig, serviceName)) + } + } - conf.HTTP.Routers[routerKey] = loadRouter(rule, pa, rtConfig, serviceName) + for routerKey, conflictingRouters := range routers { + if len(conflictingRouters) == 1 { + conf.HTTP.Routers[routerKey] = conflictingRouters[0] + continue + } + + log.FromContext(ctx).Debugf("Multiple routers are defined with the same key %q, generating hashes to avoid conflicts", routerKey) + + for _, router := range conflictingRouters { + key, err := makeRouterKeyWithHash(routerKey, router.Rule) + if err != nil { + log.FromContext(ctx).Error(err) + continue + } + + conf.HTTP.Routers[key] = router } } } @@ -543,6 +564,17 @@ func getProtocol(portSpec corev1.ServicePort, portName string, svcConfig *Servic return protocol } +func makeRouterKeyWithHash(key, rule string) (string, error) { + h := sha256.New() + if _, err := h.Write([]byte(rule)); err != nil { + return "", err + } + + dupKey := fmt.Sprintf("%s-%.10x", key, h.Sum(nil)) + + return dupKey, nil +} + func loadRouter(rule networkingv1beta1.IngressRule, pa networkingv1beta1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router { var rules []string if len(rule.Host) > 0 { diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index ee9c98347..90c7b45c3 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -169,6 +169,74 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "Ingress with conflicting routers on host", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar-bar-3be6cfd7daba66cf2fdd": { + Rule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar`) && PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + "testing-bar-bar-636bf36c00fedaab3d44": { + Rule: "Host(`bar`) && PathPrefix(`/bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + { + URL: "http://10.21.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, + { + desc: "Ingress with conflicting routers on path", + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-foo-bar-d0b30949e54d6a7515ca": { + Rule: "PathPrefix(`/foo/bar`)", + Service: "testing-service1-80", + }, + "testing-foo-bar-dcd54bae39a6d7557f48": { + Rule: "PathPrefix(`/foo-bar`)", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + PassHostHeader: Bool(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + { + URL: "http://10.21.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + }, { desc: "Ingress one rule with two paths", expected: &dynamic.Configuration{