diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index ddca68a3b..936c8402f 100644 --- a/docs/content/middlewares/overview.md +++ b/docs/content/middlewares/overview.md @@ -71,6 +71,38 @@ labels: - "traefik.http.middlewares.foo-add-prefix.addprefix.prefix=/foo" ``` +```toml tab="File" +[tlsOptions] + [tlsOptions.default] + minVersion = "VersionTLS12" +``` + +```yaml tab="Kubernetes" +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: mytlsoption + namespace: default + +spec: + minversion: VersionTLS12 +``` + ```toml tab="File" # As Toml Configuration File [providers] diff --git a/docs/content/providers/crd_tls_option.yml b/docs/content/providers/crd_tls_option.yml new file mode 100644 index 000000000..1495e0d0b --- /dev/null +++ b/docs/content/providers/crd_tls_option.yml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 19745dfc1..53f6617a2 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -230,6 +230,51 @@ spec: More information about available middlewares in the dedicated [middlewares section](../middlewares/overview.md). +### Traefik TLS Option Definition + +Additionally, to allow for the use of tls options in an IngressRoute, we defined the CRD below for the TLSOption kind. +More information about TLS Options is available in the dedicated [TLS Configuration Options](../../https/tls/#tls-options). + +```yaml +--8<-- "content/providers/crd_tls_option.yml" +``` + +Once the TLSOption kind has been registered with the Kubernetes cluster or defined in the File Provider, it can then be used in IngressRoute definitions, such as: + +```yaml +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: mytlsoption + namespace: default + +spec: + minversion: VersionTLS12 + +--- +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: + options: + name: mytlsoption + namespace: default +``` + +!!! note "TLS Option reference and namespace" + If the optional `namespace` attribute is not set, the configuration will be applied with the namespace of the IngressRoute. + ### TLS To allow for TLS, we made use of the `Secret` kind, as it was already defined, and it can be directly used in an `IngressRoute`: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd.yml index 2850d669c..bb016b6d7 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd.yml @@ -26,6 +26,21 @@ spec: singular: middleware scope: Namespaced +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced + --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition @@ -85,6 +100,9 @@ spec: # use an empty tls object for TLS with Let's Encrypt tls: secretName: supersecret + options: + name: myTLSOption + namespace: default --- apiVersion: traefik.containo.us/v1alpha1 @@ -104,3 +122,6 @@ spec: tls: secretName: foosecret passthrough: false + options: + name: myTLSOption + namespace: default diff --git a/integration/fixtures/https/https_tls_options.toml b/integration/fixtures/https/https_tls_options.toml index c0072efcd..680db609c 100644 --- a/integration/fixtures/https/https_tls_options.toml +++ b/integration/fixtures/https/https_tls_options.toml @@ -44,7 +44,6 @@ level = "DEBUG" [[http.services.service2.LoadBalancer.Servers]] URL = "http://127.0.0.1:9020" - [[tls]] [tls.certificate] certFile = "fixtures/https/snitest.com.cert" diff --git a/integration/fixtures/k8s/01-crd.yml b/integration/fixtures/k8s/01-crd.yml index f3992350d..70b00f662 100644 --- a/integration/fixtures/k8s/01-crd.yml +++ b/integration/fixtures/k8s/01-crd.yml @@ -41,3 +41,18 @@ spec: plural: ingressroutetcps singular: ingressroutetcp scope: Namespaced + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: tlsoptions.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: TLSOption + plural: tlsoptions + singular: tlsoption + scope: Namespaced diff --git a/integration/fixtures/k8s/03-ingressroute.yml b/integration/fixtures/k8s/03-ingressroute.yml index 1342e3f4e..fc4a9230f 100644 --- a/integration/fixtures/k8s/03-ingressroute.yml +++ b/integration/fixtures/k8s/03-ingressroute.yml @@ -15,3 +15,7 @@ spec: services: - name: whoami port: 80 + + tls: + options: + name: mytlsoption diff --git a/integration/fixtures/k8s/03-tlsoption.yml b/integration/fixtures/k8s/03-tlsoption.yml new file mode 100644 index 000000000..dea75d7e9 --- /dev/null +++ b/integration/fixtures/k8s/03-tlsoption.yml @@ -0,0 +1,12 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: mytlsoption + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 diff --git a/integration/fixtures/k8s/05-ingressroutetcp.yml b/integration/fixtures/k8s/05-ingressroutetcp.yml index ec84bd21b..f7b1896a8 100644 --- a/integration/fixtures/k8s/05-ingressroutetcp.yml +++ b/integration/fixtures/k8s/05-ingressroutetcp.yml @@ -12,3 +12,6 @@ spec: services: - name: whoamitcp port: 8080 + tls: + options: + name: mytlsoption diff --git a/integration/https_test.go b/integration/https_test.go index 2b43ae673..6923f076d 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -191,7 +191,7 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) { c.Assert(err.Error(), checker.Contains, "protocol version not supported") // with unknown tls option - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("unknown TLS options: unknown@file")) c.Assert(err, checker.IsNil) } diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 1a2aade82..abab62087 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -6,7 +6,10 @@ ], "service": "default/test-crd-6b204d94623b3df4370c", "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/bar`)", - "priority": 12 + "priority": 12, + "tls": { + "options": "default/mytlsoption" + } }, "default/test2-crd-23c7f4c450289ee29016@kubernetescrd": { "entryPoints": [ @@ -36,10 +39,10 @@ "loadbalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.6:80" } ], "passHostHeader": true @@ -48,18 +51,18 @@ "default/test-crd-6b204d94623b3df4370c@kubernetescrd" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.6:80": "UP" } }, "default/test2-crd-23c7f4c450289ee29016@kubernetescrd": { "loadbalancer": { "servers": [ { - "url": "http://10.42.0.4:80" + "url": "http://10.42.0.2:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.6:80" } ], "passHostHeader": true @@ -68,8 +71,8 @@ "default/test2-crd-23c7f4c450289ee29016@kubernetescrd" ], "serverStatus": { - "http://10.42.0.4:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.2:80": "UP", + "http://10.42.0.6:80": "UP" } } }, @@ -79,7 +82,11 @@ "footcp" ], "service": "default/test3-crd-673acf455cb2dab0b43a", - "rule": "HostSNI(`*`)" + "rule": "HostSNI(`*`)", + "tls": { + "passthrough": false, + "options": "default/mytlsoption" + } } }, "tcpServices": { @@ -87,10 +94,10 @@ "loadbalancer": { "servers": [ { - "address": "10.42.0.2:8080" + "address": "10.42.0.3:8080" }, { - "address": "10.42.0.3:8080" + "address": "10.42.0.4:8080" } ] }, diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index de37b2446..ab5fb3f0e 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -49,6 +49,7 @@ type Client interface { GetIngressRoutes() []*v1alpha1.IngressRoute GetIngressRouteTCPs() []*v1alpha1.IngressRouteTCP GetMiddlewares() []*v1alpha1.Middleware + GetTLSOptions() []*v1alpha1.TLSOption GetIngresses() []*extensionsv1beta1.Ingress GetService(namespace, name string) (*corev1.Service, bool, error) @@ -158,6 +159,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< factoryCrd.Traefik().V1alpha1().IngressRoutes().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().Middlewares().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler) + factoryCrd.Traefik().V1alpha1().TLSOptions().Informer().AddEventHandler(eventHandler) factoryKube := informers.NewFilteredSharedInformerFactory(c.csKube, resyncPeriod, ns, nil) factoryKube.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler) @@ -241,6 +243,21 @@ func (c *clientWrapper) GetMiddlewares() []*v1alpha1.Middleware { return result } +// GetTLSOptions +func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption { + var result []*v1alpha1.TLSOption + + for ns, factory := range c.factoriesCrd { + options, err := factory.Traefik().V1alpha1().TLSOptions().Lister().List(c.labelSelector) + if err != nil { + log.Errorf("Failed to list tls options in namespace %s: %s", ns, err) + } + result = append(result, options...) + } + + return result +} + // GetIngresses returns all Ingresses for observed namespaces in the cluster. func (c *clientWrapper) GetIngresses() []*extensionsv1beta1.Ingress { var result []*extensionsv1beta1.Ingress diff --git a/pkg/provider/kubernetes/crd/client_mock_test.go b/pkg/provider/kubernetes/crd/client_mock_test.go index 70ff381fe..9d0478bf7 100644 --- a/pkg/provider/kubernetes/crd/client_mock_test.go +++ b/pkg/provider/kubernetes/crd/client_mock_test.go @@ -37,6 +37,7 @@ type clientMock struct { ingressRoutes []*v1alpha1.IngressRoute ingressRouteTCPs []*v1alpha1.IngressRouteTCP middlewares []*v1alpha1.Middleware + tlsOptions []*v1alpha1.TLSOption watchChan chan interface{} } @@ -63,6 +64,8 @@ func newClientMock(paths ...string) clientMock { c.ingressRouteTCPs = append(c.ingressRouteTCPs, o) case *v1alpha1.Middleware: c.middlewares = append(c.middlewares, o) + case *v1alpha1.TLSOption: + c.tlsOptions = append(c.tlsOptions, o) case *v1beta12.Ingress: c.ingresses = append(c.ingresses, o) case *corev1.Secret: @@ -88,6 +91,20 @@ func (c clientMock) GetMiddlewares() []*v1alpha1.Middleware { return c.middlewares } +func (c clientMock) GetTLSOptions() []*v1alpha1.TLSOption { + return c.tlsOptions +} + +func (c clientMock) GetTLSOption(namespace, name string) (*v1alpha1.TLSOption, bool, error) { + for _, option := range c.tlsOptions { + if option.Namespace == namespace && option.Name == name { + return option, true, nil + } + } + + return nil, false, nil +} + func (c clientMock) GetIngresses() []*extensionsv1beta1.Ingress { return c.ingresses } diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_bad_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_bad_tls_options.yml new file mode 100644 index 000000000..81c08b434 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_bad_tls_options.yml @@ -0,0 +1,70 @@ +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: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretUnknown + - emptySecret + optional: true + +--- +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.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options.yml new file mode 100644 index 000000000..8666918bc --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options.yml @@ -0,0 +1,69 @@ +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: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +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.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options_and_specific_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options_and_specific_namespace.yml new file mode 100644 index 000000000..49c1b4bb5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_tls_options_and_specific_namespace.yml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: myns + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +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.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo + namespace: myns diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options.yml new file mode 100644 index 000000000..d42471762 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options.yml @@ -0,0 +1,30 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: unknown diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options_namespace.yml new file mode 100644 index 000000000..743ab8072 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_unknown_tls_options_namespace.yml @@ -0,0 +1,31 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + tls: + options: + name: foo + namespace: unknown diff --git a/pkg/provider/kubernetes/crd/fixtures/with_bad_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_bad_tls_options.yml new file mode 100644 index 000000000..a1db6f972 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_bad_tls_options.yml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: default + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: badSecret + namespace: default + +data: + tls.ca: + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretUnknown + - emptySecret + optional: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + 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_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_tls_options.yml new file mode 100644 index 000000000..10839d487 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_tls_options.yml @@ -0,0 +1,60 @@ +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: foo + namespace: default + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + 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_tls_options_and_specific_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_tls_options_and_specific_namespace.yml new file mode 100644 index 000000000..c378a82f3 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_tls_options_and_specific_namespace.yml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secretCA1 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: secretCA2 + namespace: myns + +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: myns + +spec: + minversion: VersionTLS12 + snistrict: true + ciphersuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + clientca: + secretnames: + - secretCA1 + - secretCA2 + optional: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + 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 + namespace: myns \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options.yml b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options.yml new file mode 100644 index 000000000..d39f40468 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options.yml @@ -0,0 +1,31 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/bar`) + kind: Rule + priority: 12 + services: + - name: whoami + port: 80 + + tls: + options: + name: unknown diff --git a/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options_namespace.yml b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options_namespace.yml new file mode 100644 index 000000000..7b2b3111a --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_unknown_tls_options_namespace.yml @@ -0,0 +1,32 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: TLSOption +metadata: + name: foo + namespace: default + +spec: + minversion: VersionTLS12 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test.crd + 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 + namespace: unknown \ No newline at end of file diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsoption.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsoption.go new file mode 100644 index 000000000..f7450621a --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsoption.go @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 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/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" +) + +// FakeTLSOptions implements TLSOptionInterface +type FakeTLSOptions struct { + Fake *FakeTraefikV1alpha1 + ns string +} + +var tlsoptionsResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "tlsoptions"} + +var tlsoptionsKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "TLSOption"} + +// Get takes name of the tLSOption, and returns the corresponding tLSOption object, and an error if there is any. +func (c *FakeTLSOptions) Get(name string, options v1.GetOptions) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tlsoptionsResource, c.ns, name), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} + +// List takes label and field selectors, and returns the list of TLSOptions that match those selectors. +func (c *FakeTLSOptions) List(opts v1.ListOptions) (result *v1alpha1.TLSOptionList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tlsoptionsResource, tlsoptionsKind, c.ns, opts), &v1alpha1.TLSOptionList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TLSOptionList{ListMeta: obj.(*v1alpha1.TLSOptionList).ListMeta} + for _, item := range obj.(*v1alpha1.TLSOptionList).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 tLSOptions. +func (c *FakeTLSOptions) Watch(opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tlsoptionsResource, c.ns, opts)) + +} + +// Create takes the representation of a tLSOption and creates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *FakeTLSOptions) Create(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tlsoptionsResource, c.ns, tLSOption), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} + +// Update takes the representation of a tLSOption and updates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *FakeTLSOptions) Update(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tlsoptionsResource, c.ns, tLSOption), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), err +} + +// Delete takes name of the tLSOption and deletes it. Returns an error if one occurs. +func (c *FakeTLSOptions) Delete(name string, options *v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(tlsoptionsResource, c.ns, name), &v1alpha1.TLSOption{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTLSOptions) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tlsoptionsResource, c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1alpha1.TLSOptionList{}) + return err +} + +// Patch applies the patch and returns the patched tLSOption. +func (c *FakeTLSOptions) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSOption, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tlsoptionsResource, c.ns, name, data, subresources...), &v1alpha1.TLSOption{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TLSOption), 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 2a4094a68..92998021f 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 @@ -48,6 +48,10 @@ func (c *FakeTraefikV1alpha1) Middlewares(namespace string) v1alpha1.MiddlewareI return &FakeMiddlewares{c, namespace} } +func (c *FakeTraefikV1alpha1) TLSOptions(namespace string) v1alpha1.TLSOptionInterface { + return &FakeTLSOptions{c, namespace} +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeTraefikV1alpha1) RESTClient() rest.Interface { 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 2a9108930..30a0f1a1b 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 @@ -31,3 +31,5 @@ type IngressRouteExpansion interface{} type IngressRouteTCPExpansion interface{} type MiddlewareExpansion interface{} + +type TLSOptionExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..800b1f6cc --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,165 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 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 ( + scheme "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme" + v1alpha1 "github.com/containous/traefik/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" +) + +// TLSOptionsGetter has a method to return a TLSOptionInterface. +// A group's client should implement this interface. +type TLSOptionsGetter interface { + TLSOptions(namespace string) TLSOptionInterface +} + +// TLSOptionInterface has methods to work with TLSOption resources. +type TLSOptionInterface interface { + Create(*v1alpha1.TLSOption) (*v1alpha1.TLSOption, error) + Update(*v1alpha1.TLSOption) (*v1alpha1.TLSOption, error) + Delete(name string, options *v1.DeleteOptions) error + DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error + Get(name string, options v1.GetOptions) (*v1alpha1.TLSOption, error) + List(opts v1.ListOptions) (*v1alpha1.TLSOptionList, error) + Watch(opts v1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSOption, err error) + TLSOptionExpansion +} + +// tLSOptions implements TLSOptionInterface +type tLSOptions struct { + client rest.Interface + ns string +} + +// newTLSOptions returns a TLSOptions +func newTLSOptions(c *TraefikV1alpha1Client, namespace string) *tLSOptions { + return &tLSOptions{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tLSOption, and returns the corresponding tLSOption object, and an error if there is any. +func (c *tLSOptions) Get(name string, options v1.GetOptions) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tlsoptions"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TLSOptions that match those selectors. +func (c *tLSOptions) List(opts v1.ListOptions) (result *v1alpha1.TLSOptionList, err error) { + result = &v1alpha1.TLSOptionList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tlsoptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tLSOptions. +func (c *tLSOptions) Watch(opts v1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tlsoptions"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch() +} + +// Create takes the representation of a tLSOption and creates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *tLSOptions) Create(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tlsoptions"). + Body(tLSOption). + Do(). + Into(result) + return +} + +// Update takes the representation of a tLSOption and updates it. Returns the server's representation of the tLSOption, and an error, if there is any. +func (c *tLSOptions) Update(tLSOption *v1alpha1.TLSOption) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tlsoptions"). + Name(tLSOption.Name). + Body(tLSOption). + Do(). + Into(result) + return +} + +// Delete takes name of the tLSOption and deletes it. Returns an error if one occurs. +func (c *tLSOptions) Delete(name string, options *v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tlsoptions"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tLSOptions) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tlsoptions"). + VersionedParams(&listOptions, scheme.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Patch applies the patch and returns the patched tLSOption. +func (c *tLSOptions) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.TLSOption, err error) { + result = &v1alpha1.TLSOption{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tlsoptions"). + 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 e1b0f47b5..bb050537e 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 { IngressRoutesGetter IngressRouteTCPsGetter MiddlewaresGetter + TLSOptionsGetter } // TraefikV1alpha1Client is used to interact with features provided by the traefik.containo.us group. @@ -57,6 +58,10 @@ func (c *TraefikV1alpha1Client) Middlewares(namespace string) MiddlewareInterfac return newMiddlewares(c, namespace) } +func (c *TraefikV1alpha1Client) TLSOptions(namespace string) TLSOptionInterface { + return newTLSOptions(c, namespace) +} + // NewForConfig creates a new TraefikV1alpha1Client for the given config. func NewForConfig(c *rest.Config) (*TraefikV1alpha1Client, error) { config := *c diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go index fcd191a3c..70060f80a 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go @@ -67,6 +67,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().IngressRouteTCPs().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("middlewares"): 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 } 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 80c6a617d..54b02039a 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 @@ -38,6 +38,8 @@ type Interface interface { IngressRouteTCPs() IngressRouteTCPInformer // Middlewares returns a MiddlewareInformer. Middlewares() MiddlewareInformer + // TLSOptions returns a TLSOptionInformer. + TLSOptions() TLSOptionInformer } type version struct { @@ -65,3 +67,8 @@ func (v *version) IngressRouteTCPs() IngressRouteTCPInformer { func (v *version) Middlewares() MiddlewareInformer { return &middlewareInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// TLSOptions returns a TLSOptionInformer. +func (v *version) TLSOptions() TLSOptionInformer { + return &tLSOptionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..4bbf1d981 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,97 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 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/pkg/provider/kubernetes/crd/generated/clientset/versioned" + internalinterfaces "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/containous/traefik/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1" + traefikv1alpha1 "github.com/containous/traefik/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" +) + +// TLSOptionInformer provides access to a shared informer and lister for +// TLSOptions. +type TLSOptionInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TLSOptionLister +} + +type tLSOptionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTLSOptionInformer constructs a new informer for TLSOption 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 NewTLSOptionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTLSOptionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTLSOptionInformer constructs a new informer for TLSOption 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 NewFilteredTLSOptionInformer(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().TLSOptions(namespace).List(options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TLSOptions(namespace).Watch(options) + }, + }, + &traefikv1alpha1.TLSOption{}, + resyncPeriod, + indexers, + ) +} + +func (f *tLSOptionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTLSOptionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tLSOptionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&traefikv1alpha1.TLSOption{}, f.defaultInformer) +} + +func (f *tLSOptionInformer) Lister() v1alpha1.TLSOptionLister { + return v1alpha1.NewTLSOptionLister(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 1463476eb..dc40e3ba8 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 @@ -49,3 +49,11 @@ type MiddlewareListerExpansion interface{} // MiddlewareNamespaceListerExpansion allows custom methods to be added to // MiddlewareNamespaceLister. type MiddlewareNamespaceListerExpansion interface{} + +// TLSOptionListerExpansion allows custom methods to be added to +// TLSOptionLister. +type TLSOptionListerExpansion interface{} + +// TLSOptionNamespaceListerExpansion allows custom methods to be added to +// TLSOptionNamespaceLister. +type TLSOptionNamespaceListerExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..d8e2ca80b --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,102 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2019 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/pkg/provider/kubernetes/crd/traefik/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TLSOptionLister helps list TLSOptions. +type TLSOptionLister interface { + // List lists all TLSOptions in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) + // TLSOptions returns an object that can list and get TLSOptions. + TLSOptions(namespace string) TLSOptionNamespaceLister + TLSOptionListerExpansion +} + +// tLSOptionLister implements the TLSOptionLister interface. +type tLSOptionLister struct { + indexer cache.Indexer +} + +// NewTLSOptionLister returns a new TLSOptionLister. +func NewTLSOptionLister(indexer cache.Indexer) TLSOptionLister { + return &tLSOptionLister{indexer: indexer} +} + +// List lists all TLSOptions in the indexer. +func (s *tLSOptionLister) List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TLSOption)) + }) + return ret, err +} + +// TLSOptions returns an object that can list and get TLSOptions. +func (s *tLSOptionLister) TLSOptions(namespace string) TLSOptionNamespaceLister { + return tLSOptionNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TLSOptionNamespaceLister helps list and get TLSOptions. +type TLSOptionNamespaceLister interface { + // List lists all TLSOptions in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) + // Get retrieves the TLSOption from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.TLSOption, error) + TLSOptionNamespaceListerExpansion +} + +// tLSOptionNamespaceLister implements the TLSOptionNamespaceLister +// interface. +type tLSOptionNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TLSOptions in the indexer for a given namespace. +func (s tLSOptionNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TLSOption, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TLSOption)) + }) + return ret, err +} + +// Get retrieves the TLSOption from the indexer for a given namespace and name. +func (s tLSOptionNamespaceLister) Get(name string) (*v1alpha1.TLSOption, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("tlsoption"), name) + } + return obj.(*v1alpha1.TLSOption), nil +} diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 389a0485e..c950b29ea 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -118,7 +118,7 @@ func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.P case <-stop: return nil case event := <-eventsChan: - conf := p.loadConfigurationFromIngresses(ctxLog, k8sClient) + conf := p.loadConfigurationFromCRD(ctxLog, k8sClient) if reflect.DeepEqual(p.lastConfiguration.Get(), conf) { logger.Debugf("Skipping Kubernetes event kind %T", event) @@ -293,19 +293,59 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]confi return servers, nil } -func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Client) *config.Configuration { - conf := &config.Configuration{ - HTTP: &config.HTTPConfiguration{ - Routers: map[string]*config.Router{}, - Middlewares: map[string]*config.Middleware{}, - Services: map[string]*config.Service{}, - }, - TCP: &config.TCPConfiguration{ - Routers: map[string]*config.TCPRouter{}, - Services: map[string]*config.TCPService{}, - }, +func buildTLSOptions(ctx context.Context, client Client) map[string]tls.TLS { + tlsOptionsCRD := client.GetTLSOptions() + var tlsOptions map[string]tls.TLS + + if len(tlsOptionsCRD) == 0 { + return tlsOptions + } + tlsOptions = make(map[string]tls.TLS) + + for _, tlsOption := range tlsOptionsCRD { + logger := log.FromContext(log.With(ctx, log.Str("tlsOption", tlsOption.Name), log.Str("namespace", tlsOption.Namespace))) + var clientCAs []tls.FileOrContent + + for _, secretName := range tlsOption.Spec.ClientCA.SecretNames { + secret, exists, err := client.GetSecret(tlsOption.Namespace, secretName) + if err != nil { + logger.Errorf("Failed to fetch secret %s/%s: %v", tlsOption.Namespace, secretName, err) + continue + } + + if !exists { + logger.Warnf("Secret %s/%s does not exist", tlsOption.Namespace, secretName) + continue + } + + cert, err := getCABlocks(secret, tlsOption.Namespace, secretName) + if err != nil { + logger.Errorf("Failed to extract CA from secret %s/%s: %v", tlsOption.Namespace, secretName, err) + continue + } + + clientCAs = append(clientCAs, tls.FileOrContent(cert)) + } + + tlsOptions[makeID(tlsOption.Namespace, tlsOption.Name)] = tls.TLS{ + MinVersion: tlsOption.Spec.MinVersion, + CipherSuites: tlsOption.Spec.CipherSuites, + ClientCA: tls.ClientCA{ + Files: clientCAs, + Optional: tlsOption.Spec.ClientCA.Optional, + }, + SniStrict: tlsOption.Spec.SniStrict, + } + } + return tlsOptions +} + +func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.Configuration) *config.HTTPConfiguration { + conf := &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, } - tlsConfigs := make(map[string]*tls.Configuration) for _, ingressRoute := range client.GetIngressRoutes() { logger := log.FromContext(log.With(ctx, log.Str("ingress", ingressRoute.Name), log.Str("namespace", ingressRoute.Namespace))) @@ -377,17 +417,33 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl serviceName := makeID(ingressRoute.Namespace, key) - conf.HTTP.Routers[serviceName] = &config.Router{ + conf.Routers[serviceName] = &config.Router{ Middlewares: mds, Priority: route.Priority, EntryPoints: ingressRoute.Spec.EntryPoints, Rule: route.Match, Service: serviceName, } + if ingressRoute.Spec.TLS != nil { - conf.HTTP.Routers[serviceName].TLS = &config.RouterTLSConfig{} + tlsConf := &config.RouterTLSConfig{} + if ingressRoute.Spec.TLS.Options != nil && len(ingressRoute.Spec.TLS.Options.Name) > 0 { + tlsOptionsName := ingressRoute.Spec.TLS.Options.Name + // Is a Kubernetes CRD reference, (i.e. not a cross-provider default) + if !strings.Contains(tlsOptionsName, "@") { + ns := ingressRoute.Spec.TLS.Options.Namespace + if len(ns) == 0 { + ns = ingressRoute.Namespace + } + tlsOptionsName = makeID(ns, tlsOptionsName) + } + + tlsConf.Options = tlsOptionsName + } + conf.Routers[serviceName].TLS = tlsConf } - conf.HTTP.Services[serviceName] = &config.Service{ + + conf.Services[serviceName] = &config.Service{ LoadBalancer: &config.LoadBalancerService{ Servers: allServers, // TODO: support other strategies. @@ -397,8 +453,13 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } } - for _, middleware := range client.GetMiddlewares() { - conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec + return conf +} + +func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.Configuration) *config.TCPConfiguration { + conf := &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, } for _, ingressRouteTCP := range client.GetIngressRouteTCPs() { @@ -452,19 +513,34 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } serviceName := makeID(ingressRouteTCP.Namespace, key) - conf.TCP.Routers[serviceName] = &config.TCPRouter{ + conf.Routers[serviceName] = &config.TCPRouter{ EntryPoints: ingressRouteTCP.Spec.EntryPoints, Rule: route.Match, Service: serviceName, } if ingressRouteTCP.Spec.TLS != nil { - conf.TCP.Routers[serviceName].TLS = &config.RouterTCPTLSConfig{ + conf.Routers[serviceName].TLS = &config.RouterTCPTLSConfig{ Passthrough: ingressRouteTCP.Spec.TLS.Passthrough, } + + if ingressRouteTCP.Spec.TLS.Options != nil && len(ingressRouteTCP.Spec.TLS.Options.Name) > 0 { + tlsOptionsName := ingressRouteTCP.Spec.TLS.Options.Name + // Is a Kubernetes CRD reference (i.e. not a cross-provider reference) + if !strings.Contains(tlsOptionsName, "@") { + ns := ingressRouteTCP.Spec.TLS.Options.Namespace + if len(ns) == 0 { + ns = ingressRouteTCP.Namespace + } + tlsOptionsName = makeID(ns, tlsOptionsName) + } + + conf.Routers[serviceName].TLS.Options = tlsOptionsName + + } } - conf.TCP.Services[serviceName] = &config.TCPService{ + conf.Services[serviceName] = &config.TCPService{ LoadBalancer: &config.TCPLoadBalancerService{ Servers: allServers, }, @@ -472,7 +548,21 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } } - conf.TLS = getTLSConfig(tlsConfigs) + return conf +} + +func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) *config.Configuration { + tlsConfigs := make(map[string]*tls.Configuration) + conf := &config.Configuration{ + HTTP: p.loadIngressRouteConfiguration(ctx, client, tlsConfigs), + TCP: p.loadIngressRouteTCPConfiguration(ctx, client, tlsConfigs), + TLSOptions: buildTLSOptions(ctx, client), + TLS: getTLSConfig(tlsConfigs), + } + + for _, middleware := range client.GetMiddlewares() { + conf.HTTP.Middlewares[makeID(middleware.Namespace, middleware.Name)] = &middleware.Spec + } return conf } @@ -618,3 +708,19 @@ func getCertificateBlocks(secret *corev1.Secret, namespace, secretName string) ( return cert, key, nil } + +func getCABlocks(secret *corev1.Secret, namespace, secretName string) (string, error) { + tlsCrtData, tlsCrtExists := secret.Data["tls.ca"] + if !tlsCrtExists { + return "", fmt.Errorf("the tls.ca entry is missing from secret %s/%s", + namespace, secretName) + } + + cert := string(tlsCrtData) + if cert == "" { + return "", fmt.Errorf("the tls.ca entry in secret %s/%s is empty", + namespace, secretName) + } + + return cert, nil +} diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 159b65f2b..37ae2da3f 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -297,6 +297,261 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, }, + { + desc: "TLS with tls options", + paths: []string{"tcp/services.yml", "tcp/with_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "default/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with tls options and specific namespace", + paths: []string{"tcp/services.yml", "tcp/with_tls_options_and_specific_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "myns/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "myns/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with bad tls options", + paths: []string{"tcp/services.yml", "tcp/with_bad_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "default/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with unknown tls options", + paths: []string{"tcp/services.yml", "tcp/with_unknown_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "default/unknown", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, + { + desc: "TLS with unknown tls options namespace", + paths: []string{"tcp/services.yml", "tcp/with_unknown_tls_options_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{ + "default/test-crd-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default/test-crd-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + TLS: &config.RouterTCPTLSConfig{ + Options: "unknown/foo", + }, + }, + }, + Services: map[string]*config.TCPService{ + "default/test-crd-fdd3e9338e47a45efefc": { + LoadBalancer: &config.TCPLoadBalancerService{ + Servers: []config.TCPServer{ + { + Address: "10.10.0.1:8000", + Port: "", + }, + { + Address: "10.10.0.2:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{}, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{}, + }, + }, + }, { desc: "TLS with ACME", paths: []string{"tcp/services.yml", "tcp/with_tls_acme.yml"}, @@ -338,6 +593,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { for _, test := range testCases { test := test + t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -346,7 +602,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { } p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } @@ -660,6 +916,261 @@ func TestLoadIngressRoutes(t *testing.T) { }, }, }, + { + desc: "TLS with tls options", + paths: []string{"services.yml", "with_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "default/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with tls options and specific namespace", + paths: []string{"services.yml", "with_tls_options_and_specific_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "myns/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "myns/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with bad tls options", + paths: []string{"services.yml", "with_bad_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + CipherSuites: []string{ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + }, + ClientCA: tls.ClientCA{ + Files: []tls.FileOrContent{ + tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"), + }, + Optional: true, + }, + SniStrict: true, + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "default/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with unknown tls options", + paths: []string{"services.yml", "with_unknown_tls_options.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "default/unknown", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, + { + desc: "TLS with unknown tls options namespace", + paths: []string{"services.yml", "with_unknown_tls_options_namespace.yml"}, + expected: &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "default/foo": { + MinVersion: "VersionTLS12", + }, + }, + TCP: &config.TCPConfiguration{ + Routers: map[string]*config.TCPRouter{}, + Services: map[string]*config.TCPService{}, + }, + HTTP: &config.HTTPConfiguration{ + Routers: map[string]*config.Router{ + "default/test-crd-6b204d94623b3df4370c": { + EntryPoints: []string{"web"}, + Service: "default/test-crd-6b204d94623b3df4370c", + Rule: "Host(`foo.com`) && PathPrefix(`/bar`)", + Priority: 12, + TLS: &config.RouterTLSConfig{ + Options: "unknown/foo", + }, + }, + }, + Middlewares: map[string]*config.Middleware{}, + Services: map[string]*config.Service{ + "default/test-crd-6b204d94623b3df4370c": { + LoadBalancer: &config.LoadBalancerService{ + Servers: []config.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: true, + }, + }, + }, + }, + }, + }, { desc: "TLS with ACME", paths: []string{"services.yml", "with_tls_acme.yml"}, @@ -740,6 +1251,7 @@ func TestLoadIngressRoutes(t *testing.T) { for _, test := range testCases { test := test + t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -748,7 +1260,7 @@ func TestLoadIngressRoutes(t *testing.T) { } p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromCRD(context.Background(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index 9d37e876c..26f534ad8 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -31,7 +31,14 @@ type TLS struct { // SecretName is the name of the referenced Kubernetes Secret to specify the // certificate details. SecretName string `json:"secretName"` - // TODO MinimumProtocolVersion string `json:"minimumProtocolVersion,omitempty"` + // Options is a reference to a TLSOption, that specifies the parameters of the TLS connection. + Options *TLSOptionRef `json:"options"` +} + +// TLSOptionRef is a ref to the TLSOption resources. +type TLSOptionRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` } // Service defines an upstream to proxy traffic. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index 0a3ec20c4..4b844722c 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -29,6 +29,14 @@ type TLSTCP struct { // certificate details. 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"` +} + +// TLSOptionTCPRef is a ref to the TLSOption resources. +type TLSOptionTCPRef struct { + Name string `json:"name"` + Namespace string `json:"namespace"` } // ServiceTCP defines an upstream to proxy traffic. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go index b64fdb3ce..15278474a 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go @@ -39,6 +39,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &IngressRouteTCPList{}, &Middleware{}, &MiddlewareList{}, + &TLSOption{}, + &TLSOptionList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go new file mode 100644 index 000000000..22e04e3e5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go @@ -0,0 +1,48 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TLSOption is a specification for a TLSOption resource. +type TLSOption struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec TLSOptionSpec `json:"spec"` +} + +// +k8s:deepcopy-gen=true + +// TLSOptionSpec configures TLS for an entry point +type TLSOptionSpec struct { + MinVersion string `json:"minversion"` + CipherSuites []string `json:"ciphersuites"` + ClientCA ClientCA `json:"clientca"` + SniStrict bool `json:"snistrict"` +} + +// +k8s:deepcopy-gen=true + +// ClientCA defines traefik CA files for an entryPoint +// and it indicates if they are mandatory or have just to be analyzed if provided +type ClientCA struct { + // SecretName is the name of the referenced Kubernetes Secret to specify the + // certificate details. + SecretNames []string `json:"secretnames"` + // Optional indicates if ClientCA are mandatory or have just to be analyzed if provided + Optional bool `json:"optional"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TLSOptionList is a list of TLSOption resources. +type TLSOptionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []TLSOption `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 afed89fc6..e1d756c35 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/zz_generated.deepcopy.go @@ -32,6 +32,27 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientCA) DeepCopyInto(out *ClientCA) { + *out = *in + if in.SecretNames != nil { + in, out := &in.SecretNames, &out.SecretNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientCA. +func (in *ClientCA) DeepCopy() *ClientCA { + if in == nil { + return nil + } + out := new(ClientCA) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthCheck) DeepCopyInto(out *HealthCheck) { *out = *in @@ -133,7 +154,7 @@ func (in *IngressRouteSpec) DeepCopyInto(out *IngressRouteSpec) { if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLS) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -226,7 +247,7 @@ func (in *IngressRouteTCPSpec) DeepCopyInto(out *IngressRouteTCPSpec) { if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSTCP) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -406,6 +427,11 @@ func (in *ServiceTCP) DeepCopy() *ServiceTCP { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLS) DeepCopyInto(out *TLS) { *out = *in + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = new(TLSOptionRef) + **out = **in + } return } @@ -419,9 +445,128 @@ func (in *TLS) DeepCopy() *TLS { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOption) DeepCopyInto(out *TLSOption) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOption. +func (in *TLSOption) DeepCopy() *TLSOption { + if in == nil { + return nil + } + out := new(TLSOption) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TLSOption) 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 *TLSOptionList) DeepCopyInto(out *TLSOptionList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TLSOption, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionList. +func (in *TLSOptionList) DeepCopy() *TLSOptionList { + if in == nil { + return nil + } + out := new(TLSOptionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TLSOptionList) 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 *TLSOptionRef) DeepCopyInto(out *TLSOptionRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionRef. +func (in *TLSOptionRef) DeepCopy() *TLSOptionRef { + if in == nil { + return nil + } + out := new(TLSOptionRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) { + *out = *in + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.ClientCA.DeepCopyInto(&out.ClientCA) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionSpec. +func (in *TLSOptionSpec) DeepCopy() *TLSOptionSpec { + if in == nil { + return nil + } + out := new(TLSOptionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSOptionTCPRef) DeepCopyInto(out *TLSOptionTCPRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSOptionTCPRef. +func (in *TLSOptionTCPRef) DeepCopy() *TLSOptionTCPRef { + if in == nil { + return nil + } + out := new(TLSOptionTCPRef) + 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 + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = new(TLSOptionTCPRef) + **out = **in + } return } diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index e8359df62..0c4ae4cd1 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|Middleware|Secret)`) + acceptedK8sTypes := regexp.MustCompile(`(Deployment|Endpoints|Service|Ingress|IngressRoute|Middleware|Secret|TLSOption)`) 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 09684dd1c..60db24001 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -2,6 +2,7 @@ package server import ( "github.com/containous/traefik/pkg/config" + "github.com/containous/traefik/pkg/log" "github.com/containous/traefik/pkg/server/internal" "github.com/containous/traefik/pkg/tls" ) @@ -21,6 +22,7 @@ func mergeConfiguration(configurations config.Configurations) config.Configurati TLSStores: make(map[string]tls.Store), } + var defaultTLSOptionProviders []string for provider, configuration := range configurations { if configuration.HTTP != nil { for routerName, router := range configuration.HTTP.Routers { @@ -48,10 +50,25 @@ func mergeConfiguration(configurations config.Configurations) config.Configurati conf.TLSStores[key] = store } - for key, config := range configuration.TLSOptions { - conf.TLSOptions[key] = config + for tlsOptionsName, config := range configuration.TLSOptions { + if tlsOptionsName != "default" { + tlsOptionsName = internal.MakeQualifiedName(provider, tlsOptionsName) + } else { + defaultTLSOptionProviders = append(defaultTLSOptionProviders, provider) + } + + conf.TLSOptions[tlsOptionsName] = config } } + if len(defaultTLSOptionProviders) == 0 { + conf.TLSOptions["default"] = tls.TLS{} + } else if len(defaultTLSOptionProviders) > 1 { + log.WithoutContext().Errorf("Default TLS Options defined multiple times in %v", defaultTLSOptionProviders) + // We do not set an empty tls.TLS{} as above so that we actually get a "cascading failure" later on, + // i.e. routers depending on this missing TLS option will fail to initialize as well. + delete(conf.TLSOptions, "default") + } + return conf } diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index e6fe7e032..6267b819a 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/containous/traefik/pkg/config" + "github.com/containous/traefik/pkg/tls" "github.com/stretchr/testify/assert" ) @@ -108,3 +109,170 @@ func TestAggregator(t *testing.T) { }) } } + +func TestAggregator_tlsoptions(t *testing.T) { + testCases := []struct { + desc string + given config.Configurations + expected map[string]tls.TLS + }{ + { + desc: "Nil returns an empty configuration", + given: nil, + expected: map[string]tls.TLS{ + "default": {}, + }, + }, + { + desc: "Returns fully qualified elements from a mono-provider configuration map", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": {}, + "foo@provider-1": { + MinVersion: "VersionTLS12", + }, + }, + }, + { + desc: "Returns fully qualified elements from a multi-provider configuration map", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": {}, + "foo@provider-1": { + MinVersion: "VersionTLS13", + }, + "foo@provider-2": { + MinVersion: "VersionTLS12", + }, + }, + }, + { + desc: "Create a valid default tls option when appears only in one provider", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + "default": { + MinVersion: "VersionTLS11", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": { + MinVersion: "VersionTLS11", + }, + "foo@provider-1": { + MinVersion: "VersionTLS13", + }, + "foo@provider-2": { + MinVersion: "VersionTLS12", + }, + }, + }, + { + desc: "No default tls option if it is defined in multiple providers", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + "default": { + MinVersion: "VersionTLS11", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + "default": { + MinVersion: "VersionTLS12", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "foo@provider-1": { + MinVersion: "VersionTLS12", + }, + "foo@provider-2": { + MinVersion: "VersionTLS13", + }, + }, + }, + { + desc: "Create a default TLS Options configuration if none was provided", + given: config.Configurations{ + "provider-1": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS12", + }, + }, + }, + "provider-2": &config.Configuration{ + TLSOptions: map[string]tls.TLS{ + "foo": { + MinVersion: "VersionTLS13", + }, + }, + }, + }, + expected: map[string]tls.TLS{ + "default": {}, + "foo@provider-1": { + MinVersion: "VersionTLS12", + }, + "foo@provider-2": { + MinVersion: "VersionTLS13", + }, + }, + }, + } + + 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.TLSOptions) + }) + } +} diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 18299b793..9ea4995f7 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -81,8 +81,9 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) m func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*config.TCPRouterInfo, configsHTTP map[string]*config.RouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) { router := &tcp.Router{} router.HTTPHandler(handlerHTTP) + const defaultTLSConfigName = "default" - defaultTLSConf, err := m.tlsManager.Get("default", "default") + defaultTLSConf, err := m.tlsManager.Get("default", defaultTLSConfigName) if err != nil { return nil, err } @@ -90,7 +91,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string router.HTTPSHandler(handlerHTTPS, defaultTLSConf) for routerHTTPName, routerHTTPConfig := range configsHTTP { - if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == "default" { + if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName { continue } @@ -111,7 +112,12 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string for _, domain := range domains { if routerHTTPConfig.TLS != nil { - tlsConf, err := m.tlsManager.Get("default", routerHTTPConfig.TLS.Options) + tlsOptionsName := routerHTTPConfig.TLS.Options + if tlsOptionsName != defaultTLSConfigName { + tlsOptionsName = internal.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options) + } + + tlsConf, err := m.tlsManager.Get("default", tlsOptionsName) if err != nil { routerHTTPConfig.Err = err.Error() logger.Debug(err) @@ -149,12 +155,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string if routerConfig.TLS.Passthrough { router.AddRoute(domain, handler) } else { - configName := "default" - if len(routerConfig.TLS.Options) > 0 { - configName = routerConfig.TLS.Options + tlsOptionsName := routerConfig.TLS.Options + + if len(tlsOptionsName) == 0 { + tlsOptionsName = defaultTLSConfigName } - tlsConf, err := m.tlsManager.Get("default", configName) + if tlsOptionsName != defaultTLSConfigName { + tlsOptionsName = internal.GetQualifiedName(ctxRouter, tlsOptionsName) + } + + tlsConf, err := m.tlsManager.Get("default", tlsOptionsName) if err != nil { routerConfig.Err = err.Error() logger.Debug(err) diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index b8657b985..1050e7938 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -204,6 +204,9 @@ func TestRuntimeConfiguration(t *testing.T) { tlsManager.UpdateConfigs( map[string]tls.Store{}, map[string]tls.TLS{ + "default": { + MinVersion: "VersionTLS10", + }, "foo": { MinVersion: "VersionTLS12", }, diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 9bb0f12de..60791ace0 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -74,7 +74,7 @@ func (m *Manager) Get(storeName string, configName string) (*tls.Config, error) defer m.lock.RUnlock() config, ok := m.configs[configName] - if !ok && configName != "default" { + if !ok { return nil, fmt.Errorf("unknown TLS options: %s", configName) } diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index f73cfe051..848d55389 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -3,6 +3,8 @@ package tls import ( "crypto/tls" "testing" + + "github.com/stretchr/testify/assert" ) // LocalhostCert is a PEM-encoded TLS cert with SAN IPs @@ -89,3 +91,67 @@ func TestTLSInvalidStore(t *testing.T) { t.Fatal("got error: default store must have TLS certificates.") } } + +func TestManager_Get(t *testing.T) { + dynamicConfigs := + []*Configuration{ + { + Certificate: &Certificate{ + CertFile: localhostCert, + KeyFile: localhostKey, + }, + }, + } + tlsConfigs := map[string]TLS{ + "foo": {MinVersion: "VersionTLS12"}, + "bar": {MinVersion: "VersionTLS11"}, + } + + testCases := []struct { + desc string + tlsOptionsName string + expectedMinVersion uint16 + expectedError bool + }{ + { + desc: "Get a tls config from a valid name", + tlsOptionsName: "foo", + expectedMinVersion: uint16(tls.VersionTLS12), + }, + { + desc: "Get another tls config from a valid name", + tlsOptionsName: "bar", + expectedMinVersion: uint16(tls.VersionTLS11), + }, + { + desc: "Get an tls config from an invalid name", + tlsOptionsName: "unknown", + expectedError: true, + }, + { + desc: "Get an tls config from unexisting 'default' name", + tlsOptionsName: "default", + expectedError: true, + }, + } + + tlsManager := NewManager() + tlsManager.UpdateConfigs(nil, tlsConfigs, dynamicConfigs) + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + config, err := tlsManager.Get("default", test.tlsOptionsName) + if test.expectedError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, config.MinVersion, test.expectedMinVersion) + }) + } + +} diff --git a/script/update-generated-crd-code.sh b/script/update-generated-crd-code.sh index 9fddab5ba..f6178477d 100755 --- a/script/update-generated-crd-code.sh +++ b/script/update-generated-crd-code.sh @@ -11,4 +11,4 @@ REPO_ROOT=${HACK_DIR}/.. --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl \ "$@" -deepcopy-gen --input-dirs github.com/containous/traefik/pkg/config -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl +deepcopy-gen --input-dirs github.com/containous/traefik/pkg/config -O zz_generated.deepcopy --go-header-file "${HACK_DIR}"/boilerplate.go.tmpl