diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml index f696ac4cf..35c6029a2 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml @@ -57,6 +57,21 @@ spec: singular: tlsoption scope: Namespaced +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsstores.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSStore + plural: tlsstores + singular: tlsstore + scope: Namespaced + --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd.yml index 01fede4fc..58f5f6bb4 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd.yml @@ -41,6 +41,21 @@ spec: singular: tlsoption scope: Namespaced +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsstores.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSStore + plural: tlsstores + singular: tlsstore + scope: Namespaced + --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 1e96cb5d6..9b51b8070 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -890,6 +890,82 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre just as in the [middleware case](../../middlewares/overview.md#provider-namespace). Specifying a namespace attribute in this case would not make any sense, and will be ignored. +### Kind: `TLSStore` + +`TLSStore` is the CRD implementation of a [Traefik "TLS Store"](../../https/tls.md#certificates-stores). + +Register the `TLSStore` kind in the Kubernetes cluster before creating `TLSStore` objects +or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`IngressRouteTCP`](#kind-ingressroutetcp) objects. + +!!! important "Default TLS Store" + + Traefik currently only uses the [TLS Store named "default"](../../https/tls.md#certificates-stores). + This means that if you have two stores that are named default in different kubernetes namespaces, + they may be randomly chosen. + For the time being, please only configure one TLSSTore named default. + +!!! info "TLSStore Attributes" + + ```yaml tab="TLSStore" + apiVersion: traefik.containo.us/v1alpha1 + kind: TLSStore + metadata: + name: default + namespace: default + + spec: + defaultCertificate: + secretName: mySecret # [1] + ``` + +| Ref | Attribute | Purpose | +|-----|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `secretName` | The name of the referenced Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) that holds the default certificate for the store. | + +??? example "Declaring and referencing a TLSStore" + + ```yaml tab="TLSStore" + apiVersion: traefik.containo.us/v1alpha1 + kind: TLSStore + metadata: + name: default + namespace: default + + spec: + defaultCertificate: + secretName: supersecret + ``` + + ```yaml tab="IngressRoute" + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + metadata: + name: ingressroutebar + + spec: + entryPoints: + - web + routes: + - match: Host(`bar.com`) && PathPrefix(`/stripit`) + kind: Rule + services: + - name: whoami + port: 80 + tls: + store: + name: default + ``` + + ```yaml tab="Secret" + apiVersion: v1 + kind: Secret + metadata: + name: supersecret + + data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + ``` ## Further Also see the [full example](../../user-guides/crd-acme/index.md) with Let's Encrypt. diff --git a/integration/fixtures/k8s/01-crd.yml b/integration/fixtures/k8s/01-crd.yml index f696ac4cf..f96f95c09 100644 --- a/integration/fixtures/k8s/01-crd.yml +++ b/integration/fixtures/k8s/01-crd.yml @@ -57,6 +57,22 @@ spec: singular: tlsoption scope: Namespaced +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsstores.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSStore + plural: tlsstores + singular: tlsstore + scope: Namespaced + + --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/integration/fixtures/k8s/03-ingressroute.yml b/integration/fixtures/k8s/03-ingressroute.yml index 03f458333..4c48254a0 100644 --- a/integration/fixtures/k8s/03-ingressroute.yml +++ b/integration/fixtures/k8s/03-ingressroute.yml @@ -19,3 +19,5 @@ spec: tls: options: name: mytlsoption + store: + name: mytlsstore diff --git a/integration/fixtures/k8s/03-tlsstore.yml b/integration/fixtures/k8s/03-tlsstore.yml new file mode 100644 index 000000000..4ff03107d --- /dev/null +++ b/integration/fixtures/k8s/03-tlsstore.yml @@ -0,0 +1,9 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSStore +metadata: + name: mytlsstore + namespace: default + +spec: + defaultCertificate: + secretName: tls-cert diff --git a/integration/fixtures/k8s/05-ingressroutetcp.yml b/integration/fixtures/k8s/05-ingressroutetcp.yml index 32bf76864..ee4eedc51 100644 --- a/integration/fixtures/k8s/05-ingressroutetcp.yml +++ b/integration/fixtures/k8s/05-ingressroutetcp.yml @@ -16,3 +16,5 @@ spec: tls: options: name: mytlsoption + store: + name: mytlsstore diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index ed0c71ebb..b244643ea 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -51,6 +51,7 @@ type Client interface { GetTraefikService(namespace, name string) (*v1alpha1.TraefikService, bool, error) GetTraefikServices() []*v1alpha1.TraefikService GetTLSOptions() []*v1alpha1.TLSOption + GetTLSStores() []*v1alpha1.TLSStore GetService(namespace, name string) (*corev1.Service, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error) @@ -159,6 +160,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< factoryCrd.Traefik().V1alpha1().Middlewares().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().TLSOptions().Informer().AddEventHandler(eventHandler) + factoryCrd.Traefik().V1alpha1().TLSStores().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().TraefikServices().Informer().AddEventHandler(eventHandler) factoryKube := informers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, informers.WithNamespace(ns)) @@ -284,6 +286,21 @@ func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption { return result } +// GetTLSStores returns all TLS stores. +func (c *clientWrapper) GetTLSStores() []*v1alpha1.TLSStore { + var result []*v1alpha1.TLSStore + + for ns, factory := range c.factoriesCrd { + stores, err := factory.Traefik().V1alpha1().TLSStores().Lister().List(c.labelSelector) + if err != nil { + log.Errorf("Failed to list tls stores in namespace %s: %v", ns, err) + } + result = append(result, stores...) + } + + return result +} + // GetService returns the named service from the given namespace. func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, bool, error) { if !c.isWatchedNamespace(namespace) { @@ -343,6 +360,8 @@ func (c *clientWrapper) newResourceEventHandler(events chan<- interface{}) cache return c.labelSelector.Matches(labels.Set(v.GetLabels())) case *v1alpha1.TLSOption: return c.labelSelector.Matches(labels.Set(v.GetLabels())) + case *v1alpha1.TLSStore: + return c.labelSelector.Matches(labels.Set(v.GetLabels())) case *v1alpha1.Middleware: return c.labelSelector.Matches(labels.Set(v.GetLabels())) default: diff --git a/pkg/provider/kubernetes/crd/client_mock_test.go b/pkg/provider/kubernetes/crd/client_mock_test.go index e01230753..8eb0538bf 100644 --- a/pkg/provider/kubernetes/crd/client_mock_test.go +++ b/pkg/provider/kubernetes/crd/client_mock_test.go @@ -34,6 +34,7 @@ type clientMock struct { ingressRouteTCPs []*v1alpha1.IngressRouteTCP middlewares []*v1alpha1.Middleware tlsOptions []*v1alpha1.TLSOption + tlsStores []*v1alpha1.TLSStore traefikServices []*v1alpha1.TraefikService watchChan chan interface{} @@ -65,6 +66,8 @@ func newClientMock(paths ...string) clientMock { c.traefikServices = append(c.traefikServices, o) case *v1alpha1.TLSOption: c.tlsOptions = append(c.tlsOptions, o) + case *v1alpha1.TLSStore: + c.tlsStores = append(c.tlsStores, o) case *corev1.Secret: c.secrets = append(c.secrets, o) default: @@ -106,6 +109,10 @@ func (c clientMock) GetTLSOptions() []*v1alpha1.TLSOption { return c.tlsOptions } +func (c clientMock) GetTLSStores() []*v1alpha1.TLSStore { + return c.tlsStores +} + func (c clientMock) GetTLSOption(namespace, name string) (*v1alpha1.TLSOption, bool, error) { for _, option := range c.tlsOptions { if option.Namespace == namespace && option.Name == name { diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_store.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_store.yml new file mode 100644 index 000000000..24894e300 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_store.yml @@ -0,0 +1,40 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSStore +metadata: + name: default + namespace: default + +spec: + defaultCertificate: + secretName: supersecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + store: + name: default diff --git a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options.yml new file mode 100644 index 000000000..fb0a6a840 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options.yml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCAdefault1 + namespace: foo + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCAdefault2 + namespace: foo + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: default + namespace: foo + +spec: + minVersion: VersionTLS12 + sniStrict: true + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientAuth: + secretNames: + - secretCAdefault1 + - secretCAdefault2 + clientAuthType: VerifyClientCertIfGiven + preferServerCipherSuites: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml new file mode 100644 index 000000000..d1c1d3885 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_options_default_namespace.yml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: default + namespace: default + +spec: + minVersion: VersionTLS12 + sniStrict: true + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientAuth: + secretNames: + - secretCA1 + - secretCA2 + clientAuthType: VerifyClientCertIfGiven + preferServerCipherSuites: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml new file mode 100644 index 000000000..07f1f982c --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_default_tls_store.yml @@ -0,0 +1,43 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSStore +metadata: + name: default + namespace: foo + +spec: + defaultCertificate: + secretName: supersecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: foo + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + store: + name: default diff --git a/pkg/provider/kubernetes/crd/fixtures/with_tls_store.yml b/pkg/provider/kubernetes/crd/fixtures/with_tls_store.yml new file mode 100644 index 000000000..ba1acc4cd --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_tls_store.yml @@ -0,0 +1,43 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSStore +metadata: + name: default + namespace: default + +spec: + defaultCertificate: + secretName: supersecret + +--- +apiVersion: v1 +kind: Secret +metadata: + name: supersecret + namespace: default + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + store: + name: default diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go new file mode 100644 index 000000000..051c07541 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTLSStores implements TLSStoreInterface +type FakeTLSStores struct { + Fake *FakeTraefikV1alpha1 + ns string +} + +var tlsstoresResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "tlsstores"} + +var tlsstoresKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "TLSStore"} + +// Get takes name of the tLSStore, and returns the corresponding tLSStore object, and an error if there is any. +func (c *FakeTLSStores) Get(name string, options v1.GetOptions) (result *v1alpha1.TLSStore, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tlsstoresResource, c.ns, name), &v1alpha1.TLSStore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSStore), err +} + +// List takes label and field selectors, and returns the list of TLSStores that match those selectors. +func (c *FakeTLSStores) List(opts v1.ListOptions) (result *v1alpha1.TLSStoreList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tlsstoresResource, tlsstoresKind, c.ns, opts), &v1alpha1.TLSStoreList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TLSStoreList{ListMeta: obj.(*v1alpha1.TLSStoreList).ListMeta} + for _, item := range obj.(*v1alpha1.TLSStoreList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tLSStores. +func (c *FakeTLSStores) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tlsstoresResource, c.ns, opts)) + +} + +// Create takes the representation of a tLSStore and creates it. Returns the server's representation of the tLSStore, and an error, if there is any. +func (c *FakeTLSStores) Create(tLSStore *v1alpha1.TLSStore) (result *v1alpha1.TLSStore, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tlsstoresResource, c.ns, tLSStore), &v1alpha1.TLSStore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSStore), err +} + +// Update takes the representation of a tLSStore and updates it. Returns the server's representation of the tLSStore, and an error, if there is any. +func (c *FakeTLSStores) Update(tLSStore *v1alpha1.TLSStore) (result *v1alpha1.TLSStore, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tlsstoresResource, c.ns, tLSStore), &v1alpha1.TLSStore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSStore), err +} + +// Delete takes name of the tLSStore and deletes it. Returns an error if one occurs. +func (c *FakeTLSStores) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(tlsstoresResource, c.ns, name), &v1alpha1.TLSStore{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTLSStores) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tlsstoresResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.TLSStoreList{}) + return err +} + +// Patch applies the patch and returns the patched tLSStore. +func (c *FakeTLSStores) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSStore, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tlsstoresResource, c.ns, name, pt, data, subresources...), &v1alpha1.TLSStore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSStore), err +} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go index 617a9b332..f07c74323 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go @@ -52,6 +52,10 @@ func (c *FakeTraefikV1alpha1) TLSOptions(namespace string) v1alpha1.TLSOptionInt return &FakeTLSOptions{c, namespace} } +func (c *FakeTraefikV1alpha1) TLSStores(namespace string) v1alpha1.TLSStoreInterface { + return &FakeTLSStores{c, namespace} +} + func (c *FakeTraefikV1alpha1) TraefikServices(namespace string) v1alpha1.TraefikServiceInterface { return &FakeTraefikServices{c, namespace} } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go index 195a27148..10c2f1332 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go @@ -34,4 +34,6 @@ type MiddlewareExpansion interface{} type TLSOptionExpansion interface{} +type TLSStoreExpansion interface{} + type TraefikServiceExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsstore.go new file mode 100644 index 000000000..6babb0a8b --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsstore.go @@ -0,0 +1,182 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "time" + + scheme "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme" + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TLSStoresGetter has a method to return a TLSStoreInterface. +// A group's client should implement this interface. +type TLSStoresGetter interface { + TLSStores(namespace string) TLSStoreInterface +} + +// TLSStoreInterface has methods to work with TLSStore resources. +type TLSStoreInterface interface { + Create(*v1alpha1.TLSStore) (*v1alpha1.TLSStore, error) + Update(*v1alpha1.TLSStore) (*v1alpha1.TLSStore, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.TLSStore, error) + List(opts v1.ListOptions) (*v1alpha1.TLSStoreList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSStore, err error) + TLSStoreExpansion +} + +// tLSStores implements TLSStoreInterface +type tLSStores struct { + client rest.Interface + ns string +} + +// newTLSStores returns a TLSStores +func newTLSStores(c *TraefikV1alpha1Client, namespace string) *tLSStores { + return &tLSStores{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tLSStore, and returns the corresponding tLSStore object, and an error if there is any. +func (c *tLSStores) Get(name string, options v1.GetOptions) (result *v1alpha1.TLSStore, err error) { + result = &v1alpha1.TLSStore{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tlsstores"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TLSStores that match those selectors. +func (c *tLSStores) List(opts v1.ListOptions) (result *v1alpha1.TLSStoreList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.TLSStoreList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tlsstores"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tLSStores. +func (c *tLSStores) Watch(opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tlsstores"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch() +} + +// Create takes the representation of a tLSStore and creates it. Returns the server's representation of the tLSStore, and an error, if there is any. +func (c *tLSStores) Create(tLSStore *v1alpha1.TLSStore) (result *v1alpha1.TLSStore, err error) { + result = &v1alpha1.TLSStore{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tlsstores"). + Body(tLSStore). + Do(). + Into(result) + return +} + +// Update takes the representation of a tLSStore and updates it. Returns the server's representation of the tLSStore, and an error, if there is any. +func (c *tLSStores) Update(tLSStore *v1alpha1.TLSStore) (result *v1alpha1.TLSStore, err error) { + result = &v1alpha1.TLSStore{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tlsstores"). + Name(tLSStore.Name). + Body(tLSStore). + Do(). + Into(result) + return +} + +// Delete takes name of the tLSStore and deletes it. Returns an error if one occurs. +func (c *tLSStores) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tlsstores"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tLSStores) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + var timeout time.Duration + if listOptions.TimeoutSeconds != nil { + timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("tlsstores"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Timeout(timeout). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched tLSStore. +func (c *tLSStores) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSStore, err error) { + result = &v1alpha1.TLSStore{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tlsstores"). + SubResource(subresources...). + Name(name). + Body(data). + Do(). + Into(result) + return +} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go index 5528ecae7..f9063c83a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go @@ -38,6 +38,7 @@ type TraefikV1alpha1Interface interface { IngressRouteTCPsGetter MiddlewaresGetter TLSOptionsGetter + TLSStoresGetter TraefikServicesGetter } @@ -62,6 +63,10 @@ func (c *TraefikV1alpha1Client) TLSOptions(namespace string) TLSOptionInterface return newTLSOptions(c, namespace) } +func (c *TraefikV1alpha1Client) TLSStores(namespace string) TLSStoreInterface { + return newTLSStores(c, namespace) +} + func (c *TraefikV1alpha1Client) TraefikServices(namespace string) TraefikServiceInterface { return newTraefikServices(c, namespace) } diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go index 896197afe..13c31bbb4 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go @@ -69,6 +69,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().Middlewares().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tlsoptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TLSOptions().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("tlsstores"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TLSStores().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("traefikservices"): return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TraefikServices().Informer()}, nil diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go index 5b036f34f..13fb193a4 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go @@ -40,6 +40,8 @@ type Interface interface { Middlewares() MiddlewareInformer // TLSOptions returns a TLSOptionInformer. TLSOptions() TLSOptionInformer + // TLSStores returns a TLSStoreInformer. + TLSStores() TLSStoreInformer // TraefikServices returns a TraefikServiceInformer. TraefikServices() TraefikServiceInformer } @@ -75,6 +77,11 @@ func (v *version) TLSOptions() TLSOptionInformer { return &tLSOptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// TLSStores returns a TLSStoreInformer. +func (v *version) TLSStores() TLSStoreInformer { + return &tLSStoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TraefikServices returns a TraefikServiceInformer. func (v *version) TraefikServices() TraefikServiceInformer { return &traefikServiceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsstore.go new file mode 100644 index 000000000..5ce0fbf1d --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsstore.go @@ -0,0 +1,97 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + + versioned "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned" + internalinterfaces "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1" + traefikv1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TLSStoreInformer provides access to a shared informer and lister for +// TLSStores. +type TLSStoreInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TLSStoreLister +} + +type tLSStoreInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTLSStoreInformer constructs a new informer for TLSStore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTLSStoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTLSStoreInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTLSStoreInformer constructs a new informer for TLSStore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTLSStoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TLSStores(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TLSStores(namespace).Watch(options) + }, + }, + &traefikv1alpha1.TLSStore{}, + resyncPeriod, + indexers, + ) +} + +func (f *tLSStoreInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTLSStoreInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tLSStoreInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&traefikv1alpha1.TLSStore{}, f.defaultInformer) +} + +func (f *tLSStoreInformer) Lister() v1alpha1.TLSStoreLister { + return v1alpha1.NewTLSStoreLister(f.Informer().GetIndexer()) +} diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go index 9e937313e..d42d1051a 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go @@ -58,6 +58,14 @@ type TLSOptionListerExpansion interface{} // TLSOptionNamespaceLister. type TLSOptionNamespaceListerExpansion interface{} +// TLSStoreListerExpansion allows custom methods to be added to +// TLSStoreLister. +type TLSStoreListerExpansion interface{} + +// TLSStoreNamespaceListerExpansion allows custom methods to be added to +// TLSStoreNamespaceLister. +type TLSStoreNamespaceListerExpansion interface{} + // TraefikServiceListerExpansion allows custom methods to be added to // TraefikServiceLister. type TraefikServiceListerExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsstore.go new file mode 100644 index 000000000..c7983fff4 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsstore.go @@ -0,0 +1,102 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TLSStoreLister helps list TLSStores. +type TLSStoreLister interface { + // List lists all TLSStores in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.TLSStore, err error) + // TLSStores returns an object that can list and get TLSStores. + TLSStores(namespace string) TLSStoreNamespaceLister + TLSStoreListerExpansion +} + +// tLSStoreLister implements the TLSStoreLister interface. +type tLSStoreLister struct { + indexer cache.Indexer +} + +// NewTLSStoreLister returns a new TLSStoreLister. +func NewTLSStoreLister(indexer cache.Indexer) TLSStoreLister { + return &tLSStoreLister{indexer: indexer} +} + +// List lists all TLSStores in the indexer. +func (s *tLSStoreLister) List(selector labels.Selector) (ret []*v1alpha1.TLSStore, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TLSStore)) + }) + return ret, err +} + +// TLSStores returns an object that can list and get TLSStores. +func (s *tLSStoreLister) TLSStores(namespace string) TLSStoreNamespaceLister { + return tLSStoreNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TLSStoreNamespaceLister helps list and get TLSStores. +type TLSStoreNamespaceLister interface { + // List lists all TLSStores in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.TLSStore, err error) + // Get retrieves the TLSStore from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.TLSStore, error) + TLSStoreNamespaceListerExpansion +} + +// tLSStoreNamespaceLister implements the TLSStoreNamespaceLister +// interface. +type tLSStoreNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TLSStores in the indexer for a given namespace. +func (s tLSStoreNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TLSStore, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TLSStore)) + }) + return ret, err +} + +// Get retrieves the TLSStore from the indexer for a given namespace and name. +func (s tLSStoreNamespaceLister) Get(name string) (*v1alpha1.TLSStore, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tlsstore"), name) + } + return obj.(*v1alpha1.TLSStore), nil +} diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 98d4c2336..148acb307 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -171,6 +171,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) TLS: &dynamic.TLSConfiguration{ Certificates: getTLSConfig(tlsConfigs), Options: buildTLSOptions(ctx, client), + Stores: buildTLSStores(ctx, client), }, } @@ -466,6 +467,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options return tlsOptions } tlsOptions = make(map[string]tls.Options) + var nsDefault []string for _, tlsOption := range tlsOptionsCRD { logger := log.FromContext(log.With(ctx, log.Str("tlsOption", tlsOption.Name), log.Str("namespace", tlsOption.Namespace))) @@ -492,7 +494,13 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options clientCAs = append(clientCAs, tls.FileOrContent(cert)) } - tlsOptions[makeID(tlsOption.Namespace, tlsOption.Name)] = tls.Options{ + id := makeID(tlsOption.Namespace, tlsOption.Name) + // If the name is default, we override the default config. + if tlsOption.Name == "default" { + id = tlsOption.Name + nsDefault = append(nsDefault, tlsOption.Namespace) + } + tlsOptions[id] = tls.Options{ MinVersion: tlsOption.Spec.MinVersion, MaxVersion: tlsOption.Spec.MaxVersion, CipherSuites: tlsOption.Spec.CipherSuites, @@ -505,9 +513,68 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options PreferServerCipherSuites: tlsOption.Spec.PreferServerCipherSuites, } } + + if len(nsDefault) > 1 { + delete(tlsOptions, "default") + log.FromContext(ctx).Errorf("Default TLS Options defined in multiple namespaces: %v", nsDefault) + } + return tlsOptions } +func buildTLSStores(ctx context.Context, client Client) map[string]tls.Store { + tlsStoreCRD := client.GetTLSStores() + var tlsStores map[string]tls.Store + + if len(tlsStoreCRD) == 0 { + return tlsStores + } + tlsStores = make(map[string]tls.Store) + var nsDefault []string + + for _, tlsStore := range tlsStoreCRD { + namespace := tlsStore.Namespace + secretName := tlsStore.Spec.DefaultCertificate.SecretName + logger := log.FromContext(log.With(ctx, log.Str("tlsStore", tlsStore.Name), log.Str("namespace", namespace), log.Str("secretName", secretName))) + + secret, exists, err := client.GetSecret(namespace, secretName) + if err != nil { + logger.Errorf("Failed to fetch secret %s/%s: %v", namespace, secretName, err) + continue + } + if !exists { + logger.Errorf("Secret %s/%s does not exist", namespace, secretName) + continue + } + + cert, key, err := getCertificateBlocks(secret, namespace, secretName) + if err != nil { + logger.Errorf("Could not get certificate blocks: %v", err) + continue + } + + id := makeID(tlsStore.Namespace, tlsStore.Name) + // If the name is default, we override the default config. + if tlsStore.Name == "default" { + id = tlsStore.Name + nsDefault = append(nsDefault, tlsStore.Namespace) + } + tlsStores[id] = tls.Store{ + DefaultCertificate: &tls.Certificate{ + CertFile: tls.FileOrContent(cert), + KeyFile: tls.FileOrContent(key), + }, + } + } + + if len(nsDefault) > 1 { + delete(tlsStores, "default") + log.FromContext(ctx).Errorf("Default TLS Stores defined in multiple namespaces: %v", nsDefault) + } + + return tlsStores +} + func checkStringQuoteValidity(value string) error { _, err := strconv.Unquote(`"` + value + `"`) return err diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 0bb8ce165..63e56d84c 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -746,6 +746,53 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, }, + { + desc: "TLS with tls Store", + paths: []string{"tcp/services.yml", "tcp/with_tls_store.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{ + "default": { + DefaultCertificate: &tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-test.route-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &dynamic.RouterTCPTLSConfig{}, + }, + }, + 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: "", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + }, + }, } for _, test := range testCases { @@ -1937,6 +1984,107 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "TLS with two default tls options", + paths: []string{"services.yml", "with_default_tls_options.yml", "with_default_tls_options_default_namespace.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Options: map[string]tls.Options{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &dynamic.RouterTLSConfig{ + Options: "default-foo", + }, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-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), + }, + }, + }, + }, + }, + }, + { + desc: "TLS with default tls options", + paths: []string{"services.yml", "with_default_tls_options.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Options: map[string]tls.Options{ + "default": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientAuth: tls.ClientAuth{ + CAFiles: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + ClientAuthType: "VerifyClientCertIfGiven", + }, + SniStrict: true, + PreferServerCipherSuites: true, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &dynamic.RouterTLSConfig{ + Options: "default-foo", + }, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-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), + }, + }, + }, + }, + }, + }, { desc: "TLS with tls options and specific namespace", paths: []string{"services.yml", "with_tls_options_and_specific_namespace.yml"}, @@ -2369,6 +2517,93 @@ func TestLoadIngressRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "TLS with tls store", + paths: []string{"services.yml", "with_tls_store.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{ + "default": { + DefaultCertificate: &tls.Certificate{ + CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"), + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-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), + }, + }, + }, + }, + }, + }, + { + desc: "TLS with tls store default two times", + paths: []string{"services.yml", "with_tls_store.yml", "with_default_tls_store.yml"}, + expected: &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test-route-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default-test-route-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-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), + }, + }, + }, + }, + }, + }, { desc: "port selected by name (TODO)", }, diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index 3cb2705c1..a8b6eae22 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -38,7 +38,9 @@ type TLS struct { // certificate details. SecretName string `json:"secretName"` // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. - Options *TLSOptionRef `json:"options,omitempty"` + Options *TLSOptionRef `json:"options,omitempty"` + // Store is a reference to a TLSStore, that specifies the parameters of the TLS store. + Store *TLSStoreRef `json:"store,omitempty"` CertResolver string `json:"certResolver,omitempty"` Domains []types.Domain `json:"domains,omitempty"` } @@ -49,6 +51,12 @@ type TLSOptionRef struct { Namespace string `json:"namespace"` } +// TLSStoreRef is a ref to the TLSStore resource. +type TLSStoreRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + // LoadBalancerSpec can reference either a Kubernetes Service object (a load-balancer of servers), // or a TraefikService object (a traefik load-balancer of services). type LoadBalancerSpec struct { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index b6b47b22f..a29386bf3 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -32,9 +32,11 @@ type TLSTCP struct { SecretName string `json:"secretName"` Passthrough bool `json:"passthrough"` // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. - Options *TLSOptionTCPRef `json:"options"` - CertResolver string `json:"certResolver"` - Domains []types.Domain `json:"domains,omitempty"` + Options *TLSOptionTCPRef `json:"options"` + // Store is a reference to a TLSStore, that specifies the parameters of the TLS store. + Store *TLSStoreTCPRef `json:"store"` + CertResolver string `json:"certResolver"` + Domains []types.Domain `json:"domains,omitempty"` } // TLSOptionTCPRef is a ref to the TLSOption resources. @@ -43,6 +45,12 @@ type TLSOptionTCPRef struct { Namespace string `json:"namespace"` } +// TLSStoreTCPRef is a ref to the TLSStore resources. +type TLSStoreTCPRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + // ServiceTCP defines an upstream to proxy traffic. type ServiceTCP struct { Name string `json:"name"` diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go index ef3341e8c..b8de2058b 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go @@ -41,6 +41,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &MiddlewareList{}, &TLSOption{}, &TLSOptionList{}, + &TLSStore{}, + &TLSStoreList{}, &TraefikService{}, &TraefikServiceList{}, ) diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go new file mode 100644 index 000000000..25dee994a --- /dev/null +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go @@ -0,0 +1,42 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TLSStore is a specification for a TLSStore resource. +type TLSStore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec TLSStoreSpec `json:"spec"` +} + +// +k8s:deepcopy-gen=true + +// TLSStoreSpec configures a TLSStore resource. +type TLSStoreSpec struct { + DefaultCertificate DefaultCertificate `json:"defaultCertificate"` +} + +// +k8s:deepcopy-gen=true + +// DefaultCertificate holds a secret name for the TLSOption resource. +type DefaultCertificate struct { + // SecretName is the name of the referenced Kubernetes Secret to specify the + // certificate details. + SecretName string `json:"secretName,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TLSStoreList is a list of TLSStore resources. +type TLSStoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []TLSStore `json:"items"` +} diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go index ed60cd8cb..5590ee372 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -108,6 +108,22 @@ func (in *ClientTLS) DeepCopy() *ClientTLS { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultCertificate) DeepCopyInto(out *DefaultCertificate) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCertificate. +func (in *DefaultCertificate) DeepCopy() *DefaultCertificate { + if in == nil { + return nil + } + out := new(DefaultCertificate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DigestAuth) DeepCopyInto(out *DigestAuth) { *out = *in @@ -793,6 +809,11 @@ func (in *TLS) DeepCopyInto(out *TLS) { *out = new(TLSOptionRef) **out = **in } + if in.Store != nil { + in, out := &in.Store, &out.Store + *out = new(TLSStoreRef) + **out = **in + } if in.Domains != nil { in, out := &in.Domains, &out.Domains *out = make([]types.Domain, len(*in)) @@ -932,6 +953,115 @@ func (in *TLSOptionTCPRef) DeepCopy() *TLSOptionTCPRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSStore) DeepCopyInto(out *TLSStore) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSStore. +func (in *TLSStore) DeepCopy() *TLSStore { + if in == nil { + return nil + } + out := new(TLSStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TLSStore) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSStoreList) DeepCopyInto(out *TLSStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TLSStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSStoreList. +func (in *TLSStoreList) DeepCopy() *TLSStoreList { + if in == nil { + return nil + } + out := new(TLSStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TLSStoreList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSStoreRef) DeepCopyInto(out *TLSStoreRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSStoreRef. +func (in *TLSStoreRef) DeepCopy() *TLSStoreRef { + if in == nil { + return nil + } + out := new(TLSStoreRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSStoreSpec) DeepCopyInto(out *TLSStoreSpec) { + *out = *in + out.DefaultCertificate = in.DefaultCertificate + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSStoreSpec. +func (in *TLSStoreSpec) DeepCopy() *TLSStoreSpec { + if in == nil { + return nil + } + out := new(TLSStoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSStoreTCPRef) DeepCopyInto(out *TLSStoreTCPRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSStoreTCPRef. +func (in *TLSStoreTCPRef) DeepCopy() *TLSStoreTCPRef { + if in == nil { + return nil + } + out := new(TLSStoreTCPRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSTCP) DeepCopyInto(out *TLSTCP) { *out = *in @@ -940,6 +1070,11 @@ func (in *TLSTCP) DeepCopyInto(out *TLSTCP) { *out = new(TLSOptionTCPRef) **out = **in } + if in.Store != nil { + in, out := &in.Store, &out.Store + *out = new(TLSStoreTCPRef) + **out = **in + } if in.Domains != nil { in, out := &in.Domains, &out.Domains *out = make([]types.Domain, len(*in)) diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index 3b7be6e51..f3aa25666 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|Middleware|Secret|TLSOption|TraefikService)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|Middleware|Secret|TLSOption|TLSStore|TraefikService)$`) files := strings.Split(string(content), "---") retVal := make([]runtime.Object, 0, len(files)) diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 41142d6be..c2e00c5f9 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -29,6 +29,7 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura } var defaultTLSOptionProviders []string + var defaultTLSStoreProviders []string for pvd, configuration := range configurations { if configuration.HTTP != nil { for routerName, router := range configuration.HTTP.Routers { @@ -64,6 +65,11 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura conf.TLS.Certificates = append(conf.TLS.Certificates, configuration.TLS.Certificates...) for key, store := range configuration.TLS.Stores { + if key != "default" { + key = provider.MakeQualifiedName(pvd, key) + } else { + defaultTLSStoreProviders = append(defaultTLSStoreProviders, pvd) + } conf.TLS.Stores[key] = store } @@ -79,6 +85,11 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura } } + if len(defaultTLSStoreProviders) > 1 { + log.WithoutContext().Errorf("Default TLS Stores defined multiple times in %v", defaultTLSOptionProviders) + delete(conf.TLS.Stores, "default") + } + if len(defaultTLSOptionProviders) == 0 { conf.TLS.Options["default"] = tls.DefaultTLSOptions } else if len(defaultTLSOptionProviders) > 1 { diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index 9bfb1310d..98c5b00f9 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -294,3 +294,95 @@ func TestAggregator_tlsoptions(t *testing.T) { }) } } + +func TestAggregator_tlsStore(t *testing.T) { + testCases := []struct { + desc string + given dynamic.Configurations + expected map[string]tls.Store + }{ + { + desc: "Create a valid default tls store when appears only in one provider", + given: dynamic.Configurations{ + "provider-1": &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{ + "default": { + DefaultCertificate: &tls.Certificate{ + CertFile: "foo", + KeyFile: "bar", + }, + }, + }, + }, + }, + "provider-2": &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{ + "foo": { + DefaultCertificate: &tls.Certificate{ + CertFile: "foo", + KeyFile: "bar", + }, + }, + }, + }, + }, + }, + expected: map[string]tls.Store{ + "default": { + DefaultCertificate: &tls.Certificate{ + CertFile: "foo", + KeyFile: "bar", + }, + }, + "foo@provider-2": { + DefaultCertificate: &tls.Certificate{ + CertFile: "foo", + KeyFile: "bar", + }, + }, + }, + }, + { + desc: "Don't default tls store when appears two times", + given: dynamic.Configurations{ + "provider-1": &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{ + "default": { + DefaultCertificate: &tls.Certificate{ + CertFile: "foo", + KeyFile: "bar", + }, + }, + }, + }, + }, + "provider-2": &dynamic.Configuration{ + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{ + "default": { + DefaultCertificate: &tls.Certificate{ + CertFile: "foo", + KeyFile: "bar", + }, + }, + }, + }, + }, + }, + expected: map[string]tls.Store{}, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := mergeConfiguration(test.given) + assert.Equal(t, test.expected, actual.TLS.Stores) + }) + } +}