Merge branch v2.3 into master
This commit is contained in:
commit
eebbe64b36
46 changed files with 1949 additions and 148 deletions
|
@ -119,6 +119,35 @@ subsets:
|
|||
- name: websecure2
|
||||
port: 8443
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami-ipv6
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami-ipv6
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoami-ipv6
|
||||
namespace: default
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
@ -157,5 +186,46 @@ spec:
|
|||
protocol: TCP
|
||||
port: 443
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external-svc-with-ipv6
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: "2001:db8:85a3:8d3:1319:8a2e:370:7347"
|
||||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami-svc
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoami
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoami-svc
|
||||
namespace: cross-ns
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
- ip: 10.10.0.2
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
|
|
|
@ -132,6 +132,36 @@ subsets:
|
|||
- name: myapp4
|
||||
port: 8084
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamitcp-ipv6
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamitcp-ipv6
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamitcp-ipv6
|
||||
namespace: default
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "fd00:10:244:0:1::3"
|
||||
- ip: "2001:db8:85a3:8d3:1319:8a2e:370:7348"
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
@ -167,4 +197,44 @@ spec:
|
|||
type: ExternalName
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
protocol: TCP
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: external.service.with.ipv6
|
||||
namespace: default
|
||||
spec:
|
||||
externalName: "fe80::200:5aee:feaa:20a2"
|
||||
type: ExternalName
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamitcp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamitcp
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamitcp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
- ip: 10.10.0.2
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: HostSNI(`foo.com`)
|
||||
services:
|
||||
- name: whoamitcp-cross-ns
|
||||
namespace: cross-ns
|
||||
port: 8000
|
17
pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml
Normal file
17
pkg/provider/kubernetes/crd/fixtures/tcp/with_ipv6.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteTCP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: HostSNI(`*`)
|
||||
services:
|
||||
- name: whoamitcp-ipv6
|
||||
port: 8080
|
||||
- name: external.service.with.ipv6
|
||||
port: 8080
|
|
@ -101,3 +101,62 @@ subsets:
|
|||
ports:
|
||||
- name: myapp4
|
||||
port: 8084
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamiudp-ipv6
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamiudp-ipv6
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamiudp-ipv6
|
||||
namespace: default
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "fd00:10:244:0:1::3"
|
||||
ports:
|
||||
- name: myapp-ipv6
|
||||
port: 8080
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoamiudp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
selector:
|
||||
app: traefiklabs
|
||||
task: whoamiudp
|
||||
|
||||
---
|
||||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: whoamiudp-cross-ns
|
||||
namespace: cross-ns
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: 10.10.0.1
|
||||
- ip: 10.10.0.2
|
||||
ports:
|
||||
- name: myapp
|
||||
port: 8000
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteUDP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- services:
|
||||
- name: whoamiudp-cross-ns
|
||||
namespace: cross-ns
|
||||
port: 8000
|
14
pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml
Normal file
14
pkg/provider/kubernetes/crd/fixtures/udp/with_ipv6.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteUDP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- services:
|
||||
- name: whoamiudp-ipv6
|
||||
port: 8080
|
|
@ -0,0 +1,91 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: cross-ns-route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami-svc
|
||||
namespace: cross-ns
|
||||
port: 80
|
||||
- name: tr-svc-wrr1
|
||||
kind: TraefikService
|
||||
- name: tr-svc-wrr2
|
||||
namespace: cross-ns
|
||||
kind: TraefikService
|
||||
- name: tr-svc-mirror1
|
||||
kind: TraefikService
|
||||
- name: tr-svc-mirror2
|
||||
namespace: cross-ns
|
||||
kind: TraefikService
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: tr-svc-wrr1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: whoami-svc
|
||||
namespace: cross-ns
|
||||
weight: 1
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: tr-svc-wrr2
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: whoami-svc
|
||||
weight: 1
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: tr-svc-mirror1
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
mirroring:
|
||||
name: whoami
|
||||
port: 80
|
||||
mirrors:
|
||||
- name: whoami-svc
|
||||
namespace: cross-ns
|
||||
percent: 20
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: tr-svc-mirror2
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
mirroring:
|
||||
name: whoami-svc
|
||||
port: 80
|
||||
mirrors:
|
||||
- name: whoami-svc
|
||||
namespace: cross-ns
|
||||
percent: 20
|
||||
port: 80
|
18
pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml
Normal file
18
pkg/provider/kubernetes/crd/fixtures/with_ipv6.yml
Normal file
|
@ -0,0 +1,18 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami-ipv6
|
||||
port: 8080
|
||||
- name: external-svc-with-ipv6
|
||||
port: 8080
|
|
@ -0,0 +1,58 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test-crossnamespace.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: stripprefix
|
||||
namespace: cross-ns
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bir`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: test-errorpage
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: stripprefix
|
||||
namespace: cross-ns
|
||||
|
||||
spec:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- /stripit
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-errorpage
|
||||
namespace: default
|
||||
spec:
|
||||
errors:
|
||||
status:
|
||||
- 500-599
|
||||
query: /{status}.html
|
||||
service:
|
||||
name: whoami-svc
|
||||
namespace: cross-ns
|
||||
port: 80
|
|
@ -43,12 +43,18 @@ type Provider struct {
|
|||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers." json:"disablePassHostHeaders,omitempty" toml:"disablePassHostHeaders,omitempty" yaml:"disablePassHostHeaders,omitempty" export:"true"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
AllowCrossNamespace *bool `description:"Allow cross namespace resource reference." json:"allowCrossNamespace,omitempty" toml:"allowCrossNamespace,omitempty" yaml:"allowCrossNamespace,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(true)
|
||||
}
|
||||
|
||||
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||
_, err := labels.Parse(p.LabelSelector)
|
||||
if err != nil {
|
||||
|
@ -98,6 +104,10 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
return err
|
||||
}
|
||||
|
||||
if p.AllowCrossNamespace == nil || *p.AllowCrossNamespace {
|
||||
logger.Warn("Cross-namespace reference between IngressRoutes and resources is enabled, please ensure that this is expected (see AllowCrossNamespace option)")
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
|
@ -197,7 +207,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
|||
continue
|
||||
}
|
||||
|
||||
errorPage, errorPageService, err := createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
|
||||
errorPage, errorPageService, err := p.createErrorPageMiddleware(client, middleware.Namespace, middleware.Spec.Errors)
|
||||
if err != nil {
|
||||
log.FromContext(ctxMid).Errorf("Error while reading error page middleware: %v", err)
|
||||
continue
|
||||
|
@ -236,7 +246,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
|||
}
|
||||
}
|
||||
|
||||
cb := configBuilder{client}
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
|
||||
for _, service := range client.GetTraefikServices() {
|
||||
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
||||
|
@ -346,7 +356,7 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
|
|||
return &corev1.ServicePort{Port: port}, nil
|
||||
}
|
||||
|
||||
func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
|
||||
func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {
|
||||
if errorPage == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
@ -356,7 +366,7 @@ func createErrorPageMiddleware(client Client, namespace string, errorPage *v1alp
|
|||
Query: errorPage.Query,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := configBuilder{client}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
balancerServerHTTP, err := configBuilder{client, p.AllowCrossNamespace}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -816,3 +826,8 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
|||
|
||||
return eventsChanBuffered
|
||||
}
|
||||
|
||||
func isNamespaceAllowed(allowCrossNamespace *bool, parentNamespace, namespace string) bool {
|
||||
// If allowCrossNamespace option is not defined the default behavior is to allow cross namespace references.
|
||||
return allowCrossNamespace == nil || *allowCrossNamespace || parentNamespace == namespace
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
|
@ -47,7 +49,8 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
ingressName = ingressRoute.GenerateName
|
||||
}
|
||||
|
||||
cb := configBuilder{client}
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
|
||||
for _, route := range ingressRoute.Spec.Routes {
|
||||
if route.Kind != "Rule" {
|
||||
logger.Errorf("Unsupported match kind: %s. Only \"Rule\" is supported for now.", route.Kind)
|
||||
|
@ -65,23 +68,10 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
continue
|
||||
}
|
||||
|
||||
var mds []string
|
||||
for _, mi := range route.Middlewares {
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
logger.
|
||||
WithField(log.MiddlewareName, mi.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
}
|
||||
mds = append(mds, mi.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
ns := mi.Namespace
|
||||
if len(ns) == 0 {
|
||||
ns = ingressRoute.Namespace
|
||||
}
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
mds, err := p.makeMiddlewareKeys(ctx, ingressRoute.Namespace, route.Middlewares)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to create middleware keys: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
normalized := provider.Normalize(makeID(ingressRoute.Namespace, serviceKey))
|
||||
|
@ -152,8 +142,38 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
return conf
|
||||
}
|
||||
|
||||
func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace string, middlewares []v1alpha1.MiddlewareRef) ([]string, error) {
|
||||
var mds []string
|
||||
|
||||
for _, mi := range middlewares {
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
log.FromContext(ctx).
|
||||
WithField(log.MiddlewareName, mi.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
}
|
||||
mds = append(mds, mi.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
ns := ingRouteNamespace
|
||||
if len(mi.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, ingRouteNamespace, mi.Namespace) {
|
||||
return nil, fmt.Errorf("middleware %s/%s is not in the IngressRoute namespace %s", mi.Namespace, mi.Name, ingRouteNamespace)
|
||||
}
|
||||
|
||||
ns = mi.Namespace
|
||||
}
|
||||
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
}
|
||||
|
||||
return mds, nil
|
||||
}
|
||||
|
||||
type configBuilder struct {
|
||||
client Client
|
||||
client Client
|
||||
allowCrossNamespace *bool
|
||||
}
|
||||
|
||||
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
||||
|
@ -270,7 +290,7 @@ func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalance
|
|||
return &dynamic.Service{LoadBalancer: lb}, nil
|
||||
}
|
||||
|
||||
func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
|
||||
func (c configBuilder) loadServers(parentNamespace string, svc v1alpha1.LoadBalancerSpec) ([]dynamic.Server, error) {
|
||||
strategy := svc.Strategy
|
||||
if strategy == "" {
|
||||
strategy = roundRobinStrategy
|
||||
|
@ -279,7 +299,11 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
|||
return nil, fmt.Errorf("load balancing strategy %s is not supported", strategy)
|
||||
}
|
||||
|
||||
namespace := namespaceOrFallback(svc, fallbackNamespace)
|
||||
namespace := namespaceOrFallback(svc, parentNamespace)
|
||||
|
||||
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, namespace) {
|
||||
return nil, fmt.Errorf("load balancer service %s/%s is not in the parent resource namespace %s", svc.Namespace, svc.Name, parentNamespace)
|
||||
}
|
||||
|
||||
// If the service uses explicitly the provider suffix
|
||||
sanitizedName := strings.TrimSuffix(svc.Name, providerNamespaceSeparator+providerName)
|
||||
|
@ -303,8 +327,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
|||
return nil, err
|
||||
}
|
||||
|
||||
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port)))
|
||||
|
||||
return append(servers, dynamic.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, svcPort.Port),
|
||||
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||
}), nil
|
||||
}
|
||||
|
||||
|
@ -338,8 +364,10 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
|||
}
|
||||
|
||||
for _, addr := range subset.Addresses {
|
||||
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
|
||||
|
||||
servers = append(servers, dynamic.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
|
||||
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -351,10 +379,14 @@ func (c configBuilder) loadServers(fallbackNamespace string, svc v1alpha1.LoadBa
|
|||
// In addition, if the service is a Kubernetes one,
|
||||
// it generates and returns the configuration part for such a service,
|
||||
// so that the caller can add it to the global config map.
|
||||
func (c configBuilder) nameAndService(ctx context.Context, namespaceService string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
|
||||
func (c configBuilder) nameAndService(ctx context.Context, parentNamespace string, service v1alpha1.LoadBalancerSpec) (string, *dynamic.Service, error) {
|
||||
svcCtx := log.With(ctx, log.Str(log.ServiceName, service.Name))
|
||||
|
||||
namespace := namespaceOrFallback(service, namespaceService)
|
||||
namespace := namespaceOrFallback(service, parentNamespace)
|
||||
|
||||
if !isNamespaceAllowed(c.allowCrossNamespace, parentNamespace, namespace) {
|
||||
return "", nil, fmt.Errorf("service %s/%s not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
|
||||
}
|
||||
|
||||
switch {
|
||||
case service.Kind == "" || service.Kind == "Service":
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
|
@ -53,7 +55,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
|||
serviceName := makeID(ingressRouteTCP.Namespace, key)
|
||||
|
||||
for _, service := range route.Services {
|
||||
balancerServerTCP, err := createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
|
||||
balancerServerTCP, err := p.createLoadBalancerServerTCP(client, ingressRouteTCP.Namespace, service)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("serviceName", service.Name).
|
||||
|
@ -123,9 +125,13 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
|||
return conf
|
||||
}
|
||||
|
||||
func createLoadBalancerServerTCP(client Client, namespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
|
||||
ns := namespace
|
||||
func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace string, service v1alpha1.ServiceTCP) (*dynamic.TCPService, error) {
|
||||
ns := parentNamespace
|
||||
if len(service.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, parentNamespace, service.Namespace) {
|
||||
return nil, fmt.Errorf("tcp service %s/%s is not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
|
||||
}
|
||||
|
||||
ns = service.Namespace
|
||||
}
|
||||
|
||||
|
@ -174,7 +180,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
|||
var servers []dynamic.TCPServer
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
servers = append(servers, dynamic.TCPServer{
|
||||
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, svcPort.Port),
|
||||
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))),
|
||||
})
|
||||
} else {
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||
|
@ -205,7 +211,7 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
|||
|
||||
for _, addr := range subset.Addresses {
|
||||
servers = append(servers, dynamic.TCPServer{
|
||||
Address: fmt.Sprintf("%s:%d", addr.IP, port),
|
||||
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,23 @@ package crd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
@ -1010,6 +1018,129 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress Route with IPv6 backends",
|
||||
paths: []string{
|
||||
"services.yml", "with_ipv6.yml",
|
||||
"tcp/services.yml", "tcp/with_ipv6.yml",
|
||||
"udp/services.yml", "udp/with_ipv6.yml",
|
||||
},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{
|
||||
"default-test.route-0": {
|
||||
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||
Servers: []dynamic.UDPServer{
|
||||
{
|
||||
Address: "[fd00:10:244:0:1::3]:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-673acf455cb2dab0b43a": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-673acf455cb2dab0b43a",
|
||||
Rule: "HostSNI(`*`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-673acf455cb2dab0b43a": {
|
||||
Weighted: &dynamic.TCPWeightedRoundRobin{
|
||||
Services: []dynamic.TCPWRRService{
|
||||
{
|
||||
Name: "default-test.route-673acf455cb2dab0b43a-whoamitcp-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-test.route-673acf455cb2dab0b43a-external.service.with.ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test.route-673acf455cb2dab0b43a-whoamitcp-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "[fd00:10:244:0:1::3]:8080",
|
||||
},
|
||||
{
|
||||
Address: "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test.route-673acf455cb2dab0b43a-external.service.with.ipv6-8080": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "[fe80::200:5aee:feaa:20a2]:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-whoami-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
"default-external-svc-with-ipv6-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:db8:85a3:8d3:1319:8a2e:370:7347]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-whoami-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-external-svc-with-ipv6-8080",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -1023,7 +1154,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
|
||||
p.SetDefaults()
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -3205,7 +3339,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
|
||||
p.SetDefaults()
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -3519,7 +3656,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
|||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...))
|
||||
p.SetDefaults()
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
|
@ -3738,3 +3878,572 @@ func TestGetServicePort(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossNamespace(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
allowCrossNamespace bool
|
||||
ingressClass string
|
||||
paths []string
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
{
|
||||
desc: "Empty",
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP middleware cross namespace disallowed",
|
||||
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
StripPrefix: &dynamic.StripPrefix{
|
||||
Prefixes: []string{"/stripit"},
|
||||
ForceSlash: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP middleware cross namespace allowed",
|
||||
paths: []string{"services.yml", "with_middleware_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{
|
||||
"cross-ns-stripprefix",
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-9313b71dbe6a649d5049",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bir`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
StripPrefix: &dynamic.StripPrefix{
|
||||
Prefixes: []string{"/stripit"},
|
||||
ForceSlash: false,
|
||||
},
|
||||
},
|
||||
"default-test-errorpage": {
|
||||
Errors: &dynamic.ErrorPage{
|
||||
Status: []string{"500-599"},
|
||||
Service: "default-test-errorpage-errorpage-service",
|
||||
Query: "/{status}.html",
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-9313b71dbe6a649d5049": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-errorpage-errorpage-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP cross namespace allowed",
|
||||
paths: []string{"services.yml", "with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-cross-ns-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-cross-ns-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-cross-ns-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-tr-svc-wrr1",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "cross-ns-tr-svc-wrr2",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-tr-svc-mirror1",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "cross-ns-tr-svc-mirror2",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-whoami-svc-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-tr-svc-wrr1": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-wrr2": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-tr-svc-mirror1": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "default-whoami-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "cross-ns-whoami-svc-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP cross namespace disallowed",
|
||||
paths: []string{"services.yml", "with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"cross-ns-tr-svc-wrr2": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"cross-ns-whoami-svc-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"cross-ns-tr-svc-mirror2": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "cross-ns-whoami-svc-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "cross-ns-whoami-svc-80",
|
||||
Percent: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP cross namespace allowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP cross namespace disallowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
Routers: map[string]*dynamic.TCPRouter{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||
Rule: "HostSNI(`foo.com`)",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP cross namespace allowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
|
||||
allowCrossNamespace: true,
|
||||
expected: &dynamic.Configuration{
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{
|
||||
"default-test.route-0": {
|
||||
LoadBalancer: &dynamic.UDPServersLoadBalancer{
|
||||
Servers: []dynamic.UDPServer{
|
||||
{
|
||||
Address: "10.10.0.1:8000",
|
||||
Port: "",
|
||||
},
|
||||
{
|
||||
Address: "10.10.0.2:8000",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP cross namespace disallowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_cross_namespace.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
// The router that references the invalid service will be discarded.
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{
|
||||
"default-test.route-0": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test.route-0",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var k8sObjects []runtime.Object
|
||||
var crdObjects []runtime.Object
|
||||
for _, path := range test.paths {
|
||||
yamlContent, err := ioutil.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch o := obj.(type) {
|
||||
case *corev1.Service, *corev1.Endpoints, *corev1.Secret:
|
||||
k8sObjects = append(k8sObjects, o)
|
||||
case *v1alpha1.IngressRoute:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteTCP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.IngressRouteUDP:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.Middleware:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TraefikService:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSOption:
|
||||
crdObjects = append(crdObjects, o)
|
||||
case *v1alpha1.TLSStore:
|
||||
crdObjects = append(crdObjects, o)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kubeClient := kubefake.NewSimpleClientset(k8sObjects...)
|
||||
crdClient := crdfake.NewSimpleClientset(crdObjects...)
|
||||
|
||||
client := newClientImpl(kubeClient, crdClient)
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
eventCh, err := client.WatchAll([]string{"default", "cross-ns"}, stopCh)
|
||||
require.NoError(t, err)
|
||||
|
||||
if k8sObjects != nil || crdObjects != nil {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{}
|
||||
p.SetDefaults()
|
||||
|
||||
p.AllowCrossNamespace = func(b bool) *bool { return &b }(test.allowCrossNamespace)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
|
@ -34,7 +36,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
|
|||
serviceName := makeID(ingressRouteUDP.Namespace, key)
|
||||
|
||||
for _, service := range route.Services {
|
||||
balancerServerUDP, err := createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
|
||||
balancerServerUDP, err := p.createLoadBalancerServerUDP(client, ingressRouteUDP.Namespace, service)
|
||||
if err != nil {
|
||||
logger.
|
||||
WithField("serviceName", service.Name).
|
||||
|
@ -75,9 +77,13 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
|
|||
return conf
|
||||
}
|
||||
|
||||
func createLoadBalancerServerUDP(client Client, namespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
|
||||
ns := namespace
|
||||
func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace string, service v1alpha1.ServiceUDP) (*dynamic.UDPService, error) {
|
||||
ns := parentNamespace
|
||||
if len(service.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(p.AllowCrossNamespace, parentNamespace, service.Namespace) {
|
||||
return nil, fmt.Errorf("udp service %s/%s is not in the parent resource namespace %s", service.Namespace, service.Name, ns)
|
||||
}
|
||||
|
||||
ns = service.Namespace
|
||||
}
|
||||
|
||||
|
@ -121,7 +127,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
|||
var servers []dynamic.UDPServer
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
servers = append(servers, dynamic.UDPServer{
|
||||
Address: fmt.Sprintf("%s:%d", service.Spec.ExternalName, portSpec.Port),
|
||||
Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port))),
|
||||
})
|
||||
} else {
|
||||
endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name)
|
||||
|
@ -152,7 +158,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
|||
|
||||
for _, addr := range subset.Addresses {
|
||||
servers = append(servers, dynamic.UDPServer{
|
||||
Address: fmt.Sprintf("%s:%d", addr.IP, port),
|
||||
Address: net.JoinHostPort(addr.IP, strconv.Itoa(int(port))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
kind: Endpoints
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service-bar
|
||||
namespace: testing
|
||||
|
||||
subsets:
|
||||
- addresses:
|
||||
- ip: "2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b"
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
|
@ -0,0 +1,18 @@
|
|||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: example.com
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
serviceName: service-bar
|
||||
servicePort: 8080
|
||||
- path: /foo
|
||||
backend:
|
||||
serviceName: service-foo
|
||||
servicePort: 8080
|
|
@ -0,0 +1,26 @@
|
|||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service-bar
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
clusterIp: "fc00:f853:ccd:e793::1"
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service-foo
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
type: ExternalName
|
||||
externalName: "2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b"
|
|
@ -5,8 +5,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -479,9 +481,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
|||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
protocol := getProtocol(portSpec, portSpec.Name, svcConfig)
|
||||
hostPort := net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port)))
|
||||
|
||||
svc.LoadBalancer.Servers = []dynamic.Server{
|
||||
{URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port)},
|
||||
{URL: fmt.Sprintf("%s://%s", protocol, hostPort)},
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
|
@ -516,8 +519,10 @@ func loadService(client Client, namespace string, backend networkingv1beta1.Ingr
|
|||
protocol := getProtocol(portSpec, portName, svcConfig)
|
||||
|
||||
for _, addr := range subset.Addresses {
|
||||
hostPort := net.JoinHostPort(addr.IP, strconv.Itoa(int(port)))
|
||||
|
||||
svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{
|
||||
URL: fmt.Sprintf("%s://%s:%d", protocol, addr.IP, port),
|
||||
URL: fmt.Sprintf("%s://%s", protocol, hostPort),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -661,6 +661,47 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with IPv6 endpoints",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"example-com-testing-bar": {
|
||||
Rule: "PathPrefix(`/bar`)",
|
||||
Service: "testing-service-bar-8080",
|
||||
},
|
||||
"example-com-testing-foo": {
|
||||
Rule: "PathPrefix(`/foo`)",
|
||||
Service: "testing-service-foo-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service-bar-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"testing-service-foo-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS support",
|
||||
expected: &dynamic.Configuration{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue