Merge current v2.4 into v2.5
This commit is contained in:
commit
3a180e2afc
28 changed files with 847 additions and 166 deletions
|
@ -0,0 +1,14 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRouteUDP
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- services:
|
||||
- name: external.service.with.port
|
||||
port: 80
|
|
@ -41,20 +41,16 @@ const (
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
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)
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
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"`
|
||||
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,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
|
||||
}
|
||||
|
||||
func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
||||
|
@ -106,10 +102,14 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
return err
|
||||
}
|
||||
|
||||
if p.AllowCrossNamespace == nil || *p.AllowCrossNamespace {
|
||||
if p.AllowCrossNamespace {
|
||||
logger.Warn("Cross-namespace reference between IngressRoutes and resources is enabled, please ensure that this is expected (see AllowCrossNamespace option)")
|
||||
}
|
||||
|
||||
if p.AllowExternalNameServices {
|
||||
logger.Warn("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
|
@ -274,7 +274,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
|||
}
|
||||
}
|
||||
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
cb := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}
|
||||
|
||||
for _, service := range client.GetTraefikServices() {
|
||||
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
||||
|
@ -450,7 +450,7 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
|
|||
Query: errorPage.Query,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := configBuilder{client, p.AllowCrossNamespace}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
balancerServerHTTP, err := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -919,7 +919,7 @@ func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *s
|
|||
return eventsChanBuffered
|
||||
}
|
||||
|
||||
func isNamespaceAllowed(allowCrossNamespace *bool, parentNamespace, namespace string) bool {
|
||||
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
|
||||
return allowCrossNamespace || parentNamespace == namespace
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
ingressName = ingressRoute.GenerateName
|
||||
}
|
||||
|
||||
cb := configBuilder{client, p.AllowCrossNamespace}
|
||||
cb := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}
|
||||
|
||||
for _, route := range ingressRoute.Spec.Routes {
|
||||
if route.Kind != "Rule" {
|
||||
|
@ -173,8 +173,9 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
|||
}
|
||||
|
||||
type configBuilder struct {
|
||||
client Client
|
||||
allowCrossNamespace *bool
|
||||
client Client
|
||||
allowCrossNamespace bool
|
||||
allowExternalNameServices bool
|
||||
}
|
||||
|
||||
// buildTraefikService creates the configuration for the traefik service defined in tService,
|
||||
|
@ -323,6 +324,10 @@ func (c configBuilder) loadServers(parentNamespace string, svc v1alpha1.LoadBala
|
|||
|
||||
var servers []dynamic.Server
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
if !c.allowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, sanitizedName)
|
||||
}
|
||||
|
||||
protocol, err := parseServiceProtocol(svc.Scheme, svcPort.Name, svcPort.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -172,7 +172,7 @@ func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace st
|
|||
ns = service.Namespace
|
||||
}
|
||||
|
||||
servers, err := loadTCPServers(client, ns, service)
|
||||
servers, err := p.loadTCPServers(client, ns, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ func (p *Provider) createLoadBalancerServerTCP(client Client, parentNamespace st
|
|||
return tcpService, nil
|
||||
}
|
||||
|
||||
func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([]dynamic.TCPServer, error) {
|
||||
func (p *Provider) loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([]dynamic.TCPServer, error) {
|
||||
service, exists, err := client.GetService(namespace, svc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -209,6 +209,10 @@ func loadTCPServers(client Client, namespace string, svc v1alpha1.ServiceTCP) ([
|
|||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName && !p.AllowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, svc.Name)
|
||||
}
|
||||
|
||||
svcPort, err := getServicePort(service, svc.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1294,8 +1294,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.SetDefaults()
|
||||
p := Provider{IngressClass: test.ingressClass, AllowCrossNamespace: true, AllowExternalNameServices: true}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
|
@ -3476,8 +3475,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.SetDefaults()
|
||||
p := Provider{IngressClass: test.ingressClass, AllowCrossNamespace: true, AllowExternalNameServices: true}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
|
@ -3894,8 +3892,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.SetDefaults()
|
||||
p := Provider{IngressClass: test.ingressClass, AllowCrossNamespace: true, AllowExternalNameServices: true}
|
||||
|
||||
clientMock := newClientMock(test.paths...)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), clientMock)
|
||||
|
@ -4895,10 +4892,299 @@ func TestCrossNamespace(t *testing.T) {
|
|||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{}
|
||||
p.SetDefaults()
|
||||
p := Provider{AllowCrossNamespace: test.allowCrossNamespace}
|
||||
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalNameService(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
allowExternalNameService 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{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
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 ExternalName services allowed",
|
||||
paths: []string{"services.yml", "with_externalname_with_http.yml"},
|
||||
allowExternalNameService: 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{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-6f97418635c7e18853da": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-route-6f97418635c7e18853da",
|
||||
Rule: "Host(`foo.com`)",
|
||||
Priority: 0,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-route-6f97418635c7e18853da": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://external.domain:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HTTP Externalname services disallowed",
|
||||
paths: []string{"services.yml", "with_externalname_with_http.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{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
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: "TCP ExternalName services allowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_externalname_with_port.yml"},
|
||||
allowExternalNameService: 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`)",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{
|
||||
"default-test.route-fdd3e9338e47a45efefc": {
|
||||
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||
Servers: []dynamic.TCPServer{
|
||||
{
|
||||
Address: "external.domain:80",
|
||||
Port: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP ExternalName services disallowed",
|
||||
paths: []string{"tcp/services.yml", "tcp/with_externalname_with_port.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`)",
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
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 ExternalName services allowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_externalname_service.yml"},
|
||||
allowExternalNameService: 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: "external.domain:80",
|
||||
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{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "UDP ExternalName service disallowed",
|
||||
paths: []string{"udp/services.yml", "udp/with_externalname_service.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{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
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 := os.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{AllowExternalNameServices: test.allowExternalNameService}
|
||||
|
||||
p.AllowCrossNamespace = Bool(test.allowCrossNamespace)
|
||||
conf := p.loadConfigurationFromCRD(context.Background(), client)
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
|
|
|
@ -87,7 +87,7 @@ func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace st
|
|||
ns = service.Namespace
|
||||
}
|
||||
|
||||
servers, err := loadUDPServers(client, ns, service)
|
||||
servers, err := p.loadUDPServers(client, ns, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ func (p *Provider) createLoadBalancerServerUDP(client Client, parentNamespace st
|
|||
return udpService, nil
|
||||
}
|
||||
|
||||
func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([]dynamic.UDPServer, error) {
|
||||
func (p *Provider) loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([]dynamic.UDPServer, error) {
|
||||
service, exists, err := client.GetService(namespace, svc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -111,6 +111,10 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
|
|||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
if service.Spec.Type == corev1.ServiceTypeExternalName && !p.AllowExternalNameServices {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, svc.Name)
|
||||
}
|
||||
|
||||
svcPort, err := getServicePort(service, svc.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: example.com
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /foo
|
||||
backend:
|
||||
serviceName: service-foo
|
||||
servicePort: 8080
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
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"
|
|
@ -0,0 +1,15 @@
|
|||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- host: traefik.tchouk
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 8080
|
|
@ -0,0 +1,13 @@
|
|||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
clusterIP: 10.0.0.1
|
||||
type: ExternalName
|
||||
externalName: traefik.wtf
|
||||
|
|
@ -36,16 +36,17 @@ const (
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress 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 or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress 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 or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||
AllowExternalNameServices bool `description:"Allow ExternalName services." json:"allowExternalNameServices,omitempty" toml:"allowExternalNameServices,omitempty" yaml:"allowExternalNameServices,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
}
|
||||
|
||||
// EndpointIngress holds the endpoint information for the Kubernetes provider.
|
||||
|
@ -107,6 +108,10 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
return err
|
||||
}
|
||||
|
||||
if p.AllowExternalNameServices {
|
||||
logger.Warn("ExternalName service loading is enabled, please ensure that this is expected (see AllowExternalNameServices option)")
|
||||
}
|
||||
|
||||
pool.GoCtx(func(ctxPool context.Context) {
|
||||
operation := func() error {
|
||||
eventsChan, err := k8sClient.WatchAll(p.Namespaces, ctxPool.Done())
|
||||
|
@ -232,7 +237,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
continue
|
||||
}
|
||||
|
||||
service, err := loadService(client, ingress.Namespace, *ingress.Spec.DefaultBackend)
|
||||
service, err := p.loadService(client, ingress.Namespace, *ingress.Spec.DefaultBackend)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).
|
||||
WithField("serviceName", ingress.Spec.DefaultBackend.Service.Name).
|
||||
|
@ -277,7 +282,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
}
|
||||
|
||||
for _, pa := range rule.HTTP.Paths {
|
||||
service, err := loadService(client, ingress.Namespace, pa.Backend)
|
||||
service, err := p.loadService(client, ingress.Namespace, pa.Backend)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).
|
||||
WithField("serviceName", pa.Backend.Service.Name).
|
||||
|
@ -486,7 +491,7 @@ func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores
|
|||
return configs
|
||||
}
|
||||
|
||||
func loadService(client Client, namespace string, backend networkingv1.IngressBackend) (*dynamic.Service, error) {
|
||||
func (p *Provider) loadService(client Client, namespace string, backend networkingv1.IngressBackend) (*dynamic.Service, error) {
|
||||
service, exists, err := client.GetService(namespace, backend.Service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -496,6 +501,10 @@ func loadService(client Client, namespace string, backend networkingv1.IngressBa
|
|||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
if !p.AllowExternalNameServices && service.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
return nil, fmt.Errorf("externalName services not allowed: %s/%s", namespace, backend.Service.Name)
|
||||
}
|
||||
|
||||
var portName string
|
||||
var portSpec corev1.ServicePort
|
||||
var match bool
|
||||
|
|
|
@ -746,33 +746,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with service with externalName",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-traefik-tchouk-bar": {
|
||||
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://traefik.wtf:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with port invalid for one service",
|
||||
expected: &dynamic.Configuration{
|
||||
|
@ -800,47 +773,6 @@ 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{
|
||||
|
@ -1702,7 +1634,6 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
}
|
||||
|
||||
clientMock := newClientMock(serverVersion, paths...)
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass, AllowEmptyServices: test.allowEmptyServices}
|
||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||
|
||||
|
@ -1711,6 +1642,154 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ingressClass string
|
||||
serverVersion string
|
||||
allowExternalNameServices bool
|
||||
expected *dynamic.Configuration
|
||||
}{
|
||||
{
|
||||
desc: "Ingress with service with externalName",
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with service with externalName enabled",
|
||||
allowExternalNameServices: true,
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"testing-traefik-tchouk-bar": {
|
||||
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||
Service: "testing-service1-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://traefik.wtf:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
},
|
||||
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),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Ingress with IPv6 endpoints externalname enabled",
|
||||
allowExternalNameServices: true,
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"example-com-testing-foo": {
|
||||
Rule: "PathPrefix(`/foo`)",
|
||||
Service: "testing-service-foo-8080",
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service-foo-8080": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://[2001:0db8:3c4d:0015:0000:0000:1a2f:2a3b]:8080",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var paths []string
|
||||
_, err := os.Stat(generateTestFilename("_ingress", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_ingress", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_endpoint", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_endpoint", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_service", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_service", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_secret", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_secret", test.desc))
|
||||
}
|
||||
_, err = os.Stat(generateTestFilename("_ingressclass", test.desc))
|
||||
if err == nil {
|
||||
paths = append(paths, generateTestFilename("_ingressclass", test.desc))
|
||||
}
|
||||
|
||||
serverVersion := test.serverVersion
|
||||
if serverVersion == "" {
|
||||
serverVersion = "v1.17"
|
||||
}
|
||||
|
||||
clientMock := newClientMock(serverVersion, paths...)
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p.AllowExternalNameServices = test.allowExternalNameServices
|
||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||
|
||||
assert.Equal(t, test.expected, conf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateTestFilename(suffix, desc string) string {
|
||||
return "./fixtures/" + strings.ReplaceAll(desc, " ", "-") + suffix + ".yml"
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue