diff --git a/docs/content/providers/overview.md b/docs/content/providers/overview.md index 758afedc3..e9e9a12d8 100644 --- a/docs/content/providers/overview.md +++ b/docs/content/providers/overview.md @@ -131,13 +131,14 @@ Below is the list of the currently supported providers in Traefik. | [Docker](./docker.md) | Orchestrator | Label | | [Kubernetes](./kubernetes-crd.md) | Orchestrator | Custom Resource or Ingress | | [Consul Catalog](./consul-catalog.md) | Orchestrator | Label | +| [ECS](./ecs.md) | Orchestrator | Label | | [Marathon](./marathon.md) | Orchestrator | Label | | [Rancher](./rancher.md) | Orchestrator | Label | | [File](./file.md) | Manual | TOML/YAML format | | [Consul](./consul.md) | KV | KV | | [Etcd](./etcd.md) | KV | KV | -| [Redis](./redis.md) | KV | KV | | [ZooKeeper](./zookeeper.md) | KV | KV | +| [Redis](./redis.md) | KV | KV | | [HTTP](./http.md) | Manual | JSON format | !!! info "More Providers" 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 e9f37b7d7..28c0e2a16 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" @@ -252,6 +253,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) @@ -275,8 +278,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 } } } @@ -542,6 +563,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{ diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue index f9776239f..13837b73c 100644 --- a/webui/src/components/_commons/PanelMiddlewares.vue +++ b/webui/src/components/_commons/PanelMiddlewares.vue @@ -330,7 +330,7 @@ v-for="(val, key) in exData(middleware).customRequestHeaders" :key="key" dense class="app-chip app-chip-green"> - {{ val }} + {{ key }}: {{ val }} @@ -344,7 +344,7 @@ v-for="(val, key) in exData(middleware).customResponseHeaders" :key="key" dense class="app-chip app-chip-green"> - {{ val }} + {{ key }}: {{ val }}