From 76f42a301382db52968c6ff7d1f4b3942dfcf50b Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Fri, 11 Sep 2020 15:40:03 +0200 Subject: [PATCH] add ServersTransport on services --- cmd/traefik/traefik.go | 7 +- docs/content/providers/kubernetes-crd.md | 2 +- .../reference/dynamic-configuration/file.toml | 36 ++ .../reference/dynamic-configuration/file.yaml | 34 ++ .../kubernetes-crd-definition.yml | 15 + .../kubernetes-crd-rbac.yml | 1 + .../kubernetes-crd-resource.yml | 22 ++ .../reference/dynamic-configuration/kv-ref.md | 25 ++ .../routing/providers/kubernetes-crd.md | 84 ++++- docs/content/routing/services/index.md | 322 ++++++++++++++++++ integration/fixtures/k8s/01-crd.yml | 15 + .../k8s/07-ingressroute-serverstransport.yml | 28 ++ integration/keepalive_test.go | 4 + integration/testdata/rawdata-crd.json | 61 +++- integration/testdata/rawdata-ingress.json | 8 +- pkg/config/dynamic/http_config.go | 40 ++- pkg/config/dynamic/zz_generated.deepcopy.go | 62 ++++ pkg/log/fields.go | 23 +- pkg/provider/file/file.go | 22 +- pkg/provider/http/http.go | 7 +- pkg/provider/http/http_test.go | 14 +- pkg/provider/kubernetes/crd/client.go | 17 + .../kubernetes/crd/client_mock_test.go | 7 + .../crd/fixtures/with_servers_transport.yml | 62 ++++ .../clientset/versioned/fake/register.go | 14 +- .../clientset/versioned/scheme/register.go | 14 +- .../v1alpha1/fake/fake_ingressroute.go | 1 + .../v1alpha1/fake/fake_ingressroutetcp.go | 1 + .../v1alpha1/fake/fake_ingressrouteudp.go | 1 + .../traefik/v1alpha1/fake/fake_middleware.go | 1 + .../v1alpha1/fake/fake_serverstransport.go | 138 ++++++++ .../traefik/v1alpha1/fake/fake_tlsoption.go | 1 + .../traefik/v1alpha1/fake/fake_tlsstore.go | 1 + .../v1alpha1/fake/fake_traefik_client.go | 4 + .../v1alpha1/fake/fake_traefikservice.go | 1 + .../traefik/v1alpha1/generated_expansion.go | 2 + .../traefik/v1alpha1/serverstransport.go | 186 ++++++++++ .../typed/traefik/v1alpha1/traefik_client.go | 5 + .../informers/externalversions/generic.go | 2 + .../traefik/v1alpha1/interface.go | 7 + .../traefik/v1alpha1/serverstransport.go | 98 ++++++ .../traefik/v1alpha1/expansion_generated.go | 8 + .../traefik/v1alpha1/serverstransport.go | 102 ++++++ pkg/provider/kubernetes/crd/kubernetes.go | 65 ++++ .../kubernetes/crd/kubernetes_http.go | 8 +- .../kubernetes/crd/kubernetes_test.go | 299 ++++++++++------ .../crd/traefik/v1alpha1/ingressroute.go | 1 + .../crd/traefik/v1alpha1/register.go | 2 + .../crd/traefik/v1alpha1/serverstransport.go | 48 +++ .../traefik/v1alpha1/zz_generated.deepcopy.go | 123 +++++++ pkg/provider/kubernetes/k8s/parser.go | 2 +- pkg/provider/traefik/internal.go | 32 +- pkg/server/aggregator.go | 12 +- pkg/server/aggregator_test.go | 15 +- pkg/server/router/router_test.go | 28 +- pkg/server/routerfactory_test.go | 12 +- pkg/server/service/managerfactory.go | 8 +- pkg/server/service/proxy.go | 4 +- pkg/server/service/roundtripper.go | 126 +++++-- pkg/server/service/roundtripper_test.go | 229 +++++++++++++ pkg/server/service/service.go | 24 +- pkg/server/service/service_test.go | 18 +- pkg/testhelpers/config.go | 3 +- pkg/tls/certificate.go | 37 ++ 64 files changed, 2359 insertions(+), 242 deletions(-) create mode 100644 integration/fixtures/k8s/07-ingressroute-serverstransport.yml create mode 100644 pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml create mode 100644 pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_serverstransport.go create mode 100644 pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/serverstransport.go create mode 100644 pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/serverstransport.go create mode 100644 pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/serverstransport.go create mode 100644 pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go create mode 100644 pkg/server/service/roundtripper_test.go diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index bb6ff11b2..b42c381f2 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -213,7 +213,8 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err metricsRegistry := metrics.NewMultiRegistry(metricRegistries) accessLog := setupAccessLog(staticConfiguration.AccessLog) chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog) - managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry) + roundTripperManager := service.NewRoundTripperManager() + managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager) client, plgs, devPlugin, err := initPlugins(staticConfiguration) if err != nil { @@ -259,6 +260,10 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix())) }) + watcher.AddListener(func(conf dynamic.Configuration) { + roundTripperManager.Update(conf.HTTP.ServersTransports) + }) + watcher.AddListener(switchRouter(routerFactory, acmeProviders, serverEntryPointsTCP, serverEntryPointsUDP, aviator)) watcher.AddListener(func(conf dynamic.Configuration) { diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 32538a861..a63af0e6e 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -57,7 +57,7 @@ Previous versions of Traefik used a [KV store](https://docs.traefik.io/v1.7/conf If you require LetsEncrypt with HA in a kubernetes environment, we recommend using [TraefikEE](https://containo.us/traefikee/) where distributed LetsEncrypt is a supported feature. -If you are wanting to continue to run Traefik Community Edition, LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html). +If you want to continue to run Traefik Community Edition, LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html). When using Cert-Manager to manage certificates, it will create secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls). When using the Traefik Kubernetes CRD Provider, unfortunately Cert-Manager cannot interface directly with the CRDs _yet_, but this is being worked on by our team. A workaround is to enable the [Kubernetes Ingress provider](./kubernetes-ingress.md) to allow Cert-Manager to create ingress objects to complete the challenges. diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 8bca1d3f3..061af180e 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -38,6 +38,7 @@ [http.services.Service01] [http.services.Service01.loadBalancer] passHostHeader = true + serversTransport = "foobar" [http.services.Service01.loadBalancer.sticky] [http.services.Service01.loadBalancer.sticky.cookie] name = "foobar" @@ -264,6 +265,41 @@ [http.middlewares.Middleware22] [http.middlewares.Middleware22.stripPrefixRegex] regex = ["foobar", "foobar"] + [http.serversTransports] + [http.serversTransports.ServersTransport0] + serverName = "foobar" + insecureSkipVerify = true + rootCAs = ["foobar", "foobar"] + maxIdleConnsPerHost = 42 + + [[http.serversTransports.ServersTransport0.certificates]] + certFile = "foobar" + keyFile = "foobar" + + [[http.serversTransports.ServersTransport0.certificates]] + certFile = "foobar" + keyFile = "foobar" + [http.serversTransports.ServersTransport0.forwardingTimeouts] + dialTimeout = "42s" + responseHeaderTimeout = "42s" + idleConnTimeout = "42s" + [http.serversTransports.ServersTransport1] + serverName = "foobar" + insecureSkipVerify = true + rootCAs = ["foobar", "foobar"] + maxIdleConnsPerHost = 42 + + [[http.serversTransports.ServersTransport1.certificates]] + certFile = "foobar" + keyFile = "foobar" + + [[http.serversTransports.ServersTransport1.certificates]] + certFile = "foobar" + keyFile = "foobar" + [http.serversTransports.ServersTransport1.forwardingTimeouts] + dialTimeout = "42s" + responseHeaderTimeout = "42s" + idleConnTimeout = "42s" [tcp] [tcp.routers] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index ba168964f..fa1a84ee7 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -70,6 +70,7 @@ http: passHostHeader: true responseForwarding: flushInterval: foobar + serversTransport: foobar Service02: mirroring: service: foobar @@ -301,6 +302,39 @@ http: regex: - foobar - foobar + serversTransports: + ServersTransport0: + serverName: foobar + insecureSkipVerify: true + rootCAs: + - foobar + - foobar + certificates: + - certFile: foobar + keyFile: foobar + - certFile: foobar + keyFile: foobar + maxIdleConnsPerHost: 42 + forwardingTimeouts: + dialTimeout: 42s + responseHeaderTimeout: 42s + idleConnTimeout: 42s + ServersTransport1: + serverName: foobar + insecureSkipVerify: true + rootCAs: + - foobar + - foobar + certificates: + - certFile: foobar + keyFile: foobar + - certFile: foobar + keyFile: foobar + maxIdleConnsPerHost: 42 + forwardingTimeouts: + dialTimeout: 42s + responseHeaderTimeout: 42s + idleConnTimeout: 42s tcp: routers: TCPRouter0: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml index 9b589da72..7cfd37b15 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition.yml @@ -101,3 +101,18 @@ spec: plural: traefikservices singular: traefikservice scope: Namespaced + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: serverstransports.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: ServersTransport + plural: serverstransports + singular: serverstransport + scope: Namespaced diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml index 875f0c03d..ddedbb7be 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml @@ -40,6 +40,7 @@ rules: - ingressrouteudps - tlsoptions - tlsstores + - serverstransports verbs: - get - list diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml index ae7752036..eb6294556 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-resource.yml @@ -186,3 +186,25 @@ spec: clientAuthType: foobar sniStrict: true preferServerCipherSuites: true + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + serverName: foobar + insecureSkipVerify: true + rootCAsSecrets: + - foobar + - foobar + certificatesSecrets: + - foobar + - foobar + maxIdleConnsPerHost: 1 + forwardingTimeouts: + dialTimeout: 42s + responseHeaderTimeout: 42s + idleConnTimeout: 42s diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index cc1ab5b72..7544c8be4 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -159,6 +159,30 @@ | `traefik/http/routers/Router1/tls/domains/1/sans/0` | `foobar` | | `traefik/http/routers/Router1/tls/domains/1/sans/1` | `foobar` | | `traefik/http/routers/Router1/tls/options` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/0/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/0/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/1/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/1/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/dialTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/idleConnTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/responseHeaderTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/insecureSkipVerify` | `true` | +| `traefik/http/serversTransports/ServersTransport0/maxIdleConnsPerHost` | `42` | +| `traefik/http/serversTransports/ServersTransport0/rootCAs/0` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/rootCAs/1` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/serverName` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/0/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/0/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/1/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/1/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/dialTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/idleConnTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/responseHeaderTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/insecureSkipVerify` | `true` | +| `traefik/http/serversTransports/ServersTransport1/maxIdleConnsPerHost` | `42` | +| `traefik/http/serversTransports/ServersTransport1/rootCAs/0` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/rootCAs/1` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/serverName` | `foobar` | | `traefik/http/services/Service01/loadBalancer/healthCheck/followRedirects` | `true` | | `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0` | `foobar` | | `traefik/http/services/Service01/loadBalancer/healthCheck/headers/name1` | `foobar` | @@ -172,6 +196,7 @@ | `traefik/http/services/Service01/loadBalancer/responseForwarding/flushInterval` | `foobar` | | `traefik/http/services/Service01/loadBalancer/servers/0/url` | `foobar` | | `traefik/http/services/Service01/loadBalancer/servers/1/url` | `foobar` | +| `traefik/http/services/Service01/loadBalancer/serversTransport` | `foobar` | | `traefik/http/services/Service01/loadBalancer/sticky/cookie/httpOnly` | `true` | | `traefik/http/services/Service01/loadBalancer/sticky/cookie/name` | `foobar` | | `traefik/http/services/Service01/loadBalancer/sticky/cookie/sameSite` | `foobar` | diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 64d22e2bc..790fd392f 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -1488,9 +1488,9 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres secretName: mySecret # [1] ``` -| Ref | Attribute | Purpose | -|-----|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [1] | `secretName` | The name of the referenced Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) that holds the default certificate for the store. | +| Ref | Attribute | Purpose | +|-----|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `secretName` | The name of the referenced Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) that holds the default certificate for the store. | ??? example "Declaring and referencing a TLSStore" @@ -1537,6 +1537,84 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= ``` +### Kind: `ServersTransport` + +`ServersTransport` is the CRD implementation of a [ServersTransport](../services/index.md#serverstransport). + +!!! important "Default serversTransport" + If no `serversTransport` is specified, the `default@internal` will be used. + The `default@internal` serversTransport is created from the [static configuration](../overview.md#transport-configuration). + +!!! info "ServersTransport Attributes" + + ```yaml tab="TLSStore" + apiVersion: traefik.containo.us/v1alpha1 + kind: ServersTransport + metadata: + name: mytransport + namespace: default + + spec: + serverName: foobar # [1] + insecureSkipVerify: true # [2] + rootCAsSecrets: # [3] + - foobar + - foobar + certificatesSecrets: # [4] + - foobar + - foobar + maxIdleConnsPerHost: 1 # [5] + forwardingTimeouts: # [6] + dialTimeout: 42s # [7] + responseHeaderTimeout: 42s # [8] + idleConnTimeout: 42s # [9] + ``` + +| Ref | Attribute | Purpose | +|-----|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| [1] | `serverName` | ServerName used to contact the server. | +| [2] | `insecureSkipVerify` | Disable SSL certificate verification. | +| [3] | `rootCAsSecrets` | Add cert file for self-signed certificate. | +| [4] | `certificatesSecrets` | Certificates for mTLS. | +| [5] | `maxIdleConnsPerHost` | If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, `defaultMaxIdleConnsPerHost` is used. | +| [6] | `forwardingTimeouts` | Timeouts for requests forwarded to the backend servers. | +| [7] | `dialTimeout` | The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. | +| [8] | `responseHeaderTimeout` | The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists. | +| [9] | `idleConnTimeout` | The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself. | + +??? example "Declaring and referencing a ServersTransport" + + ```yaml tab="ServersTransport" + apiVersion: traefik.containo.us/v1alpha1 + kind: ServersTransport + metadata: + name: mytransport + namespace: default + + spec: + serverName: example.org + insecureSkipVerify: true + ``` + + ```yaml tab="IngressRoute" + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + metadata: + name: testroute + namespace: default + + spec: + entryPoints: + - web + routes: + - match: Host(`example.com`) + kind: Rule + services: + - name: whoami + port: 80 + serversTransport: mytransport + ``` + ## Further Also see the [full example](../../user-guides/crd-acme/index.md) with Let's Encrypt. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index cbc271dfc..ef1fedf56 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -460,6 +460,33 @@ By default, `passHostHeader` is true. passHostHeader: false ``` +#### ServersTransport + +`serversTransport` allows to reference a ServersTransport configuration for the communication between Traefik and your servers. + +??? example "Specify a transport -- Using the [File Provider](../../providers/file.md)" + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.Service01] + [http.services.Service01.loadBalancer] + serversTransport = "mytransport" + ``` + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + Service01: + loadBalancer: + serversTransport = "mytransport" + ``` + +!!! info default serversTransport + If no serversTransport is specified, the `default@internal` will be used. + The `default@internal` serversTransport is created from the [static configuration](../overview.md#transport-configuration). + #### Response Forwarding This section is about configuring how Traefik forwards the response from the backend server to the client. @@ -492,6 +519,301 @@ Below are the available options for the Response Forwarding mechanism: flushInterval: 1s ``` +### ServersTransport + +ServersTransport allows to configure the transport between Traefik and your servers. + +#### `ServerName` + +_Optional_ + +`serverName` configure the server name that will be used for SNI. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport] + serverName = "myhost" +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + serverName: "myhost" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + serverName: "test" +``` + +#### `Certificates` + +_Optional_ + +`certificates` is the list of certificates (as file paths, or data bytes) +that will be set as client certificates for mTLS. + +```toml tab="File (TOML)" +## Dynamic configuration +[[http.serversTransports.mytransport.certificates]] + certFile = "foo.crt" + keyFile = "bar.crt" +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + certficates: + - certFile: foo.crt + keyFile: bar.crt +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + certificatesSecrets: + - mycert + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mycert + + data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= +``` + +#### `insecureSkipVerify` + +_Optional_ + +`insecureSkipVerify` disables SSL certificate verification. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport] + insecureSkipVerify = true +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + insecureSkipVerify: true +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + insecureSkipVerify: true +``` + +#### `rootCAs` + +_Optional_ + +`rootCAs` is the list of certificates (as file paths, or data bytes) +that will be set as Root Certificate Authorities when using a self-signed TLS certificate. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport] + rootCAs = ["foo.crt", "bar.crt"] +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + rootCAs: + - foo.crt + - bar.crt +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + rootCAsSecrets: + - myca +--- +apiVersion: v1 +kind: Secret +metadata: + name: myca + + data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= +``` + +#### `maxIdleConnsPerHost` + +_Optional, Default=2_ + +If non-zero, `maxIdleConnsPerHost` controls the maximum idle (keep-alive) connections to keep per-host. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport] + maxIdleConnsPerHost = 7 +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + maxIdleConnsPerHost: 7 +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + maxIdleConnsPerHost: 7 +``` + +#### `forwardingTimeouts` + +`forwardingTimeouts` is about a number of timeouts relevant to when forwarding requests to the backend servers. + +##### `forwardingTimeouts.dialTimeout` + +_Optional, Default=30s_ + +`dialTimeout` is the maximum duration allowed for a connection to a backend server to be established. +Zero means no timeout. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport.forwardingTimeouts] + dialTimeout = "1s" +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + forwardingTimeouts: + dialTimeout: "1s" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + forwardingTimeouts: + dialTimeout: "1s" +``` + +##### `forwardingTimeouts.responseHeaderTimeout` + +_Optional, Default=0s_ + +`responseHeaderTimeout`, if non-zero, specifies the amount of time to wait for a server's response headers +after fully writing the request (including its body, if any). +This time does not include the time to read the response body. +Zero means no timeout. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport.forwardingTimeouts] + responseHeaderTimeout = "1s" +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + forwardingTimeouts: + responseHeaderTimeout: "1s" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + forwardingTimeouts: + responseHeaderTimeout: "1s" +``` + +##### `forwardingTimeouts.idleConnTimeout` + +_Optional, Default=90s_ + +`idleConnTimeout`, is the maximum amount of time an idle (keep-alive) connection +will remain idle before closing itself. +Zero means no limit. + +```toml tab="File (TOML)" +## Dynamic configuration +[http.serversTransports.mytransport.forwardingTimeouts] + idleConnTimeout = "1s" +``` + +```yaml tab="File (YAML)" +## Dynamic configuration +http: + serversTransports: + mytransport: + forwardingTimeouts: + idleConnTimeout: "1s" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + forwardingTimeouts: + idleConnTimeout: "1s" +``` + ### Weighted Round Robin (service) The WRR is able to load balance the requests between multiple services based on weights. diff --git a/integration/fixtures/k8s/01-crd.yml b/integration/fixtures/k8s/01-crd.yml index 71a64b196..2bf53b37a 100644 --- a/integration/fixtures/k8s/01-crd.yml +++ b/integration/fixtures/k8s/01-crd.yml @@ -72,6 +72,21 @@ spec: singular: tlsoption scope: Namespaced +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: serverstransports.traefik.containo.us + +spec: + group: traefik.containo.us + version: v1alpha1 + names: + kind: ServersTransport + plural: serverstransports + singular: serverstransport + scope: Namespaced + --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/integration/fixtures/k8s/07-ingressroute-serverstransport.yml b/integration/fixtures/k8s/07-ingressroute-serverstransport.yml new file mode 100644 index 000000000..aa1ff9ecc --- /dev/null +++ b/integration/fixtures/k8s/07-ingressroute-serverstransport.yml @@ -0,0 +1,28 @@ +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: testst.route + namespace: default + +spec: + entryPoints: + - web + routes: + - match: Host(`foo.com`) && PathPrefix(`/serverstransport`) + kind: Rule + services: + - name: whoami + port: 80 + serversTransport: mytransport + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: mytransport + namespace: default + +spec: + serverName: "test" + insecureSkipVerify: true diff --git a/integration/keepalive_test.go b/integration/keepalive_test.go index 8e1838974..46719eb61 100644 --- a/integration/keepalive_test.go +++ b/integration/keepalive_test.go @@ -97,6 +97,10 @@ func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c * c.Check(err, checker.IsNil) defer cmd.Process.Kill() + // Wait for Traefik + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Duration(1)*time.Second, try.StatusCodeIs(200), try.BodyContains("PathPrefix(`/keepalive`)")) + c.Check(err, checker.IsNil) + err = try.GetRequest("http://127.0.0.1:8000/keepalive", time.Duration(1)*time.Second, try.StatusCodeIs(200)) c.Check(err, checker.IsNil) diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 570bee4d4..aeda65df9 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -50,6 +50,17 @@ "using": [ "web" ] + }, + "default-testst-route-60ad45fcb5fc1f5f3629@kubernetescrd": { + "entryPoints": [ + "web" + ], + "service": "default-testst-route-60ad45fcb5fc1f5f3629", + "rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/serverstransport`)", + "status": "enabled", + "using": [ + "web" + ] } }, "middlewares": { @@ -98,10 +109,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.6:80" }, { - "url": "http://10.42.0.3:80" + "url": "http://10.42.0.7:80" } ], "passHostHeader": true @@ -111,18 +122,18 @@ "default-test-route-6b204d94623b3df4370c@kubernetescrd" ], "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.3:80": "UP" + "http://10.42.0.6:80": "UP", + "http://10.42.0.7:80": "UP" } }, "default-test2-route-23c7f4c450289ee29016@kubernetescrd": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.6:80" }, { - "url": "http://10.42.0.3:80" + "url": "http://10.42.0.7:80" } ], "passHostHeader": true @@ -132,26 +143,48 @@ "default-test2-route-23c7f4c450289ee29016@kubernetescrd" ], "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.3:80": "UP" + "http://10.42.0.6:80": "UP", + "http://10.42.0.7:80": "UP" + } + }, + "default-testst-route-60ad45fcb5fc1f5f3629@kubernetescrd": { + "loadBalancer": { + "servers": [ + { + "url": "http://10.42.0.6:80" + }, + { + "url": "http://10.42.0.7:80" + } + ], + "passHostHeader": true, + "serversTransport": "mytransport@kubernetescrd" + }, + "status": "enabled", + "usedBy": [ + "default-testst-route-60ad45fcb5fc1f5f3629@kubernetescrd" + ], + "serverStatus": { + "http://10.42.0.6:80": "UP", + "http://10.42.0.7:80": "UP" } }, "default-whoami-80@kubernetescrd": { "loadBalancer": { "servers": [ { - "url": "http://10.42.0.2:80" + "url": "http://10.42.0.6:80" }, { - "url": "http://10.42.0.3:80" + "url": "http://10.42.0.7:80" } ], "passHostHeader": true }, "status": "enabled", "serverStatus": { - "http://10.42.0.2:80": "UP", - "http://10.42.0.3:80": "UP" + "http://10.42.0.6:80": "UP", + "http://10.42.0.7:80": "UP" } }, "default-wrr1@kubernetescrd": { @@ -210,7 +243,7 @@ "terminationDelay": 100, "servers": [ { - "address": "10.42.0.3:8080" + "address": "10.42.0.10:8080" }, { "address": "10.42.0.8:8080" @@ -255,7 +288,7 @@ "loadBalancer": { "servers": [ { - "address": "10.42.0.10:8090" + "address": "10.42.0.4:8090" }, { "address": "10.42.0.9:8090" diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index dbbfc239e..ffa2315ec 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -93,10 +93,10 @@ "loadBalancer": { "servers": [ { - "url": "http://10.42.0.3:80" + "url": "http://10.42.0.10:80" }, { - "url": "http://10.42.0.5:80" + "url": "http://10.42.0.8:80" } ], "passHostHeader": true @@ -107,8 +107,8 @@ "test-ingress-https-default-whoami-test-https-whoami@kubernetes" ], "serverStatus": { - "http://10.42.0.3:80": "UP", - "http://10.42.0.5:80": "UP" + "http://10.42.0.10:80": "UP", + "http://10.42.0.8:80": "UP" } }, "noop@internal": { diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index 6488af069..a7c202ddf 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -2,18 +2,22 @@ package dynamic import ( "reflect" + "time" + "github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/types" + ptypes "github.com/traefik/paerser/types" ) // +k8s:deepcopy-gen=true // HTTPConfiguration contains all the HTTP configuration parameters. type HTTPConfiguration struct { - Routers map[string]*Router `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty"` - Services map[string]*Service `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"` - Middlewares map[string]*Middleware `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"` - Models map[string]*Model `json:"models,omitempty" toml:"models,omitempty" yaml:"models,omitempty"` + Routers map[string]*Router `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty"` + Services map[string]*Service `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"` + Middlewares map[string]*Middleware `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty"` + Models map[string]*Model `json:"models,omitempty" toml:"models,omitempty" yaml:"models,omitempty"` + ServersTransports map[string]*ServersTransport `json:"serversTransports,omitempty" toml:"serversTransports,omitempty" yaml:"serversTransports,omitempty" label:"-"` } // +k8s:deepcopy-gen=true @@ -125,6 +129,7 @@ type ServersLoadBalancer struct { HealthCheck *HealthCheck `json:"healthCheck,omitempty" toml:"healthCheck,omitempty" yaml:"healthCheck,omitempty"` PassHostHeader *bool `json:"passHostHeader" toml:"passHostHeader" yaml:"passHostHeader"` ResponseForwarding *ResponseForwarding `json:"responseForwarding,omitempty" toml:"responseForwarding,omitempty" yaml:"responseForwarding,omitempty"` + ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty"` } // Mergeable tells if the given service is mergeable. @@ -192,3 +197,30 @@ func (h *HealthCheck) SetDefaults() { fr := true h.FollowRedirects = &fr } + +// +k8s:deepcopy-gen=true + +// ServersTransport options to configure communication between Traefik and the servers. +type ServersTransport struct { + ServerName string `description:"ServerName used to contact the server" json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty" export:"true"` + InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + RootCAs []tls.FileOrContent `description:"Add cert file for self-signed certificate." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` + Certificates tls.Certificates `description:"Certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` + ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` +} + +// +k8s:deepcopy-gen=true + +// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers. +type ForwardingTimeouts struct { + DialTimeout ptypes.Duration `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"` + ResponseHeaderTimeout ptypes.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"` + IdleConnTimeout ptypes.Duration `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"` +} + +// SetDefaults sets the default values. +func (f *ForwardingTimeouts) SetDefaults() { + f.DialTimeout = ptypes.Duration(30 * time.Second) + f.IdleConnTimeout = ptypes.Duration(90 * time.Second) +} diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 1c5a7cb81..d816e34d7 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -357,6 +357,22 @@ func (in *ForwardAuth) DeepCopy() *ForwardAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForwardingTimeouts) DeepCopyInto(out *ForwardingTimeouts) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForwardingTimeouts. +func (in *ForwardingTimeouts) DeepCopy() *ForwardingTimeouts { + if in == nil { + return nil + } + out := new(ForwardingTimeouts) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPConfiguration) DeepCopyInto(out *HTTPConfiguration) { *out = *in @@ -420,6 +436,21 @@ func (in *HTTPConfiguration) DeepCopyInto(out *HTTPConfiguration) { (*out)[key] = outVal } } + if in.ServersTransports != nil { + in, out := &in.ServersTransports, &out.ServersTransports + *out = make(map[string]*ServersTransport, len(*in)) + for key, val := range *in { + var outVal *ServersTransport + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(ServersTransport) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } return } @@ -1090,6 +1121,37 @@ func (in *ServersLoadBalancer) DeepCopy() *ServersLoadBalancer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServersTransport) DeepCopyInto(out *ServersTransport) { + *out = *in + if in.RootCAs != nil { + in, out := &in.RootCAs, &out.RootCAs + *out = make([]tls.FileOrContent, len(*in)) + copy(*out, *in) + } + if in.Certificates != nil { + in, out := &in.Certificates, &out.Certificates + *out = make(tls.Certificates, len(*in)) + copy(*out, *in) + } + if in.ForwardingTimeouts != nil { + in, out := &in.ForwardingTimeouts, &out.ForwardingTimeouts + *out = new(ForwardingTimeouts) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServersTransport. +func (in *ServersTransport) DeepCopy() *ServersTransport { + if in == nil { + return nil + } + out := new(ServersTransport) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Service) DeepCopyInto(out *Service) { *out = *in diff --git a/pkg/log/fields.go b/pkg/log/fields.go index c7ad31b08..ff113f6c9 100644 --- a/pkg/log/fields.go +++ b/pkg/log/fields.go @@ -2,15 +2,16 @@ package log // Log entry names. const ( - EntryPointName = "entryPointName" - RouterName = "routerName" - Rule = "rule" - MiddlewareName = "middlewareName" - MiddlewareType = "middlewareType" - ProviderName = "providerName" - ServiceName = "serviceName" - MetricsProviderName = "metricsProviderName" - TracingProviderName = "tracingProviderName" - ServerName = "serverName" - TLSStoreName = "tlsStoreName" + EntryPointName = "entryPointName" + RouterName = "routerName" + Rule = "rule" + MiddlewareName = "middlewareName" + MiddlewareType = "middlewareType" + ProviderName = "providerName" + ServiceName = "serviceName" + MetricsProviderName = "metricsProviderName" + TracingProviderName = "tracingProviderName" + ServerName = "serverName" + TLSStoreName = "tlsStoreName" + ServersTransportName = "serversTransport" ) diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index da59d5df3..987218010 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -205,9 +205,10 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st if configuration == nil { configuration = &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), @@ -274,6 +275,14 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st } } + for name, conf := range c.HTTP.ServersTransports { + if _, exists := configuration.HTTP.ServersTransports[name]; exists { + logger.WithField(log.ServersTransportName, name).Warn("HTTP servers transport already configured, skipping") + } else { + configuration.HTTP.ServersTransports[name] = conf + } + } + for name, conf := range c.TCP.Routers { if _, exists := configuration.TCP.Routers[name]; exists { logger.WithField(log.RouterName, name).Warn("TCP router already configured, skipping") @@ -398,9 +407,10 @@ func (p *Provider) DecodeConfiguration(filename string) (*dynamic.Configuration, func (p *Provider) decodeConfiguration(filePath, content string) (*dynamic.Configuration, error) { configuration := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index 067952b81..bdf4ba1d3 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -146,9 +146,10 @@ func (p *Provider) fetchConfigurationData() ([]byte, error) { func decodeConfiguration(data []byte) (*dynamic.Configuration, error) { configuration := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), diff --git a/pkg/provider/http/http_test.go b/pkg/provider/http/http_test.go index 9a11f5c1c..f4fdac7b4 100644 --- a/pkg/provider/http/http_test.go +++ b/pkg/provider/http/http_test.go @@ -134,9 +134,10 @@ func TestProvider_decodeConfiguration(t *testing.T) { configData: []byte("{\"tcp\":{\"routers\":{\"foo\":{}}}}"), expConfig: &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{ @@ -192,9 +193,10 @@ func TestProvider_Provide(t *testing.T) { expConfiguration := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), diff --git a/pkg/provider/kubernetes/crd/client.go b/pkg/provider/kubernetes/crd/client.go index e13e8a34c..4b45c8fd0 100644 --- a/pkg/provider/kubernetes/crd/client.go +++ b/pkg/provider/kubernetes/crd/client.go @@ -52,6 +52,7 @@ type Client interface { GetTraefikService(namespace, name string) (*v1alpha1.TraefikService, bool, error) GetTraefikServices() []*v1alpha1.TraefikService GetTLSOptions() []*v1alpha1.TLSOption + GetServersTransports() []*v1alpha1.ServersTransport GetTLSStores() []*v1alpha1.TLSStore GetService(namespace, name string) (*corev1.Service, bool, error) @@ -162,6 +163,7 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< factoryCrd.Traefik().V1alpha1().IngressRouteTCPs().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().IngressRouteUDPs().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().TLSOptions().Informer().AddEventHandler(eventHandler) + factoryCrd.Traefik().V1alpha1().ServersTransports().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().TLSStores().Informer().AddEventHandler(eventHandler) factoryCrd.Traefik().V1alpha1().TraefikServices().Informer().AddEventHandler(eventHandler) @@ -279,6 +281,21 @@ func (c *clientWrapper) GetTraefikServices() []*v1alpha1.TraefikService { return result } +// GetServersTransport returns all ServersTransport. +func (c *clientWrapper) GetServersTransports() []*v1alpha1.ServersTransport { + var result []*v1alpha1.ServersTransport + + for ns, factory := range c.factoriesCrd { + serversTransports, err := factory.Traefik().V1alpha1().ServersTransports().Lister().List(c.labelSelector) + if err != nil { + log.Errorf("Failed to list servers transport in namespace %s: %v", ns, err) + } + result = append(result, serversTransports...) + } + + return result +} + // GetTLSOptions returns all TLS options. func (c *clientWrapper) GetTLSOptions() []*v1alpha1.TLSOption { var result []*v1alpha1.TLSOption diff --git a/pkg/provider/kubernetes/crd/client_mock_test.go b/pkg/provider/kubernetes/crd/client_mock_test.go index 5097120a3..977c281f6 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 { tlsOptions []*v1alpha1.TLSOption tlsStores []*v1alpha1.TLSStore traefikServices []*v1alpha1.TraefikService + serversTransport []*v1alpha1.ServersTransport watchChan chan interface{} } @@ -69,6 +70,8 @@ func newClientMock(paths ...string) clientMock { c.traefikServices = append(c.traefikServices, o) case *v1alpha1.TLSOption: c.tlsOptions = append(c.tlsOptions, o) + case *v1alpha1.ServersTransport: + c.serversTransport = append(c.serversTransport, o) case *v1alpha1.TLSStore: c.tlsStores = append(c.tlsStores, o) case *corev1.Secret: @@ -120,6 +123,10 @@ func (c clientMock) GetTLSStores() []*v1alpha1.TLSStore { return c.tlsStores } +func (c clientMock) GetServersTransports() []*v1alpha1.ServersTransport { + return c.serversTransport +} + func (c clientMock) GetTLSOption(namespace, name string) (*v1alpha1.TLSOption, bool, error) { for _, option := range c.tlsOptions { if option.Namespace == namespace && option.Name == name { diff --git a/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml b/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml new file mode 100644 index 000000000..5bc2af3ee --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_servers_transport.yml @@ -0,0 +1,62 @@ +apiVersion: v1 +kind: Secret +metadata: + name: rootCas1 + namespace: foo + +data: + tls.ca: VEVTVFJPT1RDQVM= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: rootCas2 + namespace: foo + +data: + tls.ca: VEVTVFJPT1RDQVMy + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mtls1 + namespace: foo + +data: + tls.crt: VEVTVENFUlQx + tls.key: VEVTVEtFWTE= + +--- +apiVersion: v1 +kind: Secret +metadata: + name: mtls2 + namespace: foo + +data: + tls.crt: VEVTVENFUlQy + tls.key: VEVTVEtFWTI= + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: ServersTransport +metadata: + name: test + namespace: foo + +spec: + serverName: "test" + insecureSkipVerify: true + maxIdleConnsPerHost: 42 + rootCAsSecrets: + - rootCas1 + - rootCas2 + certificatesSecrets: + - mtls1 + - mtls2 + forwardingTimeouts: + dialTimeout: 42 + responseHeaderTimeout: 42s + idleConnTimeout: 42ms diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go index 18d2c5ea6..05425a4a2 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake/register.go @@ -35,14 +35,12 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) -var ( - scheme = runtime.NewScheme() - codecs = serializer.NewCodecFactory(scheme) - parameterCodec = runtime.NewParameterCodec(scheme) - localSchemeBuilder = runtime.SchemeBuilder{ - traefikv1alpha1.AddToScheme, - } -) +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) +var parameterCodec = runtime.NewParameterCodec(scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + traefikv1alpha1.AddToScheme, +} // AddToScheme adds all types of this clientset into the given scheme. This allows composition // of clientsets, like in: diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go index 3452dc7c3..154438378 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme/register.go @@ -35,14 +35,12 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) -var ( - Scheme = runtime.NewScheme() - Codecs = serializer.NewCodecFactory(Scheme) - ParameterCodec = runtime.NewParameterCodec(Scheme) - localSchemeBuilder = runtime.SchemeBuilder{ - traefikv1alpha1.AddToScheme, - } -) +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + traefikv1alpha1.AddToScheme, +} // AddToScheme adds all types of this clientset into the given scheme. This allows composition // of clientsets, like in: diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroute.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroute.go index 9b59b3913..7ebc6b1a7 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroute.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroute.go @@ -85,6 +85,7 @@ func (c *FakeIngressRoutes) List(ctx context.Context, opts v1.ListOptions) (resu func (c *FakeIngressRoutes) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. InvokesWatch(testing.NewWatchAction(ingressroutesResource, c.ns, opts)) + } // Create takes the representation of a ingressRoute and creates it. Returns the server's representation of the ingressRoute, and an error, if there is any. diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroutetcp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroutetcp.go index 423819c4c..3866e10c3 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressroutetcp.go @@ -85,6 +85,7 @@ func (c *FakeIngressRouteTCPs) List(ctx context.Context, opts v1.ListOptions) (r func (c *FakeIngressRouteTCPs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. InvokesWatch(testing.NewWatchAction(ingressroutetcpsResource, c.ns, opts)) + } // Create takes the representation of a ingressRouteTCP and creates it. Returns the server's representation of the ingressRouteTCP, and an error, if there is any. diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressrouteudp.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressrouteudp.go index 7eb8fd1d7..61517973d 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_ingressrouteudp.go @@ -85,6 +85,7 @@ func (c *FakeIngressRouteUDPs) List(ctx context.Context, opts v1.ListOptions) (r func (c *FakeIngressRouteUDPs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. InvokesWatch(testing.NewWatchAction(ingressrouteudpsResource, c.ns, opts)) + } // Create takes the representation of a ingressRouteUDP and creates it. Returns the server's representation of the ingressRouteUDP, and an error, if there is any. diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_middleware.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_middleware.go index 82bb6a8b3..fd0ab0481 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_middleware.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_middleware.go @@ -85,6 +85,7 @@ func (c *FakeMiddlewares) List(ctx context.Context, opts v1.ListOptions) (result func (c *FakeMiddlewares) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. InvokesWatch(testing.NewWatchAction(middlewaresResource, c.ns, opts)) + } // Create takes the representation of a middleware and creates it. Returns the server's representation of the middleware, and an error, if there is any. diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_serverstransport.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_serverstransport.go new file mode 100644 index 000000000..69b2fc453 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_serverstransport.go @@ -0,0 +1,138 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeServersTransports implements ServersTransportInterface +type FakeServersTransports struct { + Fake *FakeTraefikV1alpha1 + ns string +} + +var serverstransportsResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "serverstransports"} + +var serverstransportsKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "ServersTransport"} + +// Get takes name of the serversTransport, and returns the corresponding serversTransport object, and an error if there is any. +func (c *FakeServersTransports) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ServersTransport, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(serverstransportsResource, c.ns, name), &v1alpha1.ServersTransport{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ServersTransport), err +} + +// List takes label and field selectors, and returns the list of ServersTransports that match those selectors. +func (c *FakeServersTransports) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ServersTransportList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(serverstransportsResource, serverstransportsKind, c.ns, opts), &v1alpha1.ServersTransportList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.ServersTransportList{ListMeta: obj.(*v1alpha1.ServersTransportList).ListMeta} + for _, item := range obj.(*v1alpha1.ServersTransportList).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 serversTransports. +func (c *FakeServersTransports) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(serverstransportsResource, c.ns, opts)) + +} + +// Create takes the representation of a serversTransport and creates it. Returns the server's representation of the serversTransport, and an error, if there is any. +func (c *FakeServersTransports) Create(ctx context.Context, serversTransport *v1alpha1.ServersTransport, opts v1.CreateOptions) (result *v1alpha1.ServersTransport, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(serverstransportsResource, c.ns, serversTransport), &v1alpha1.ServersTransport{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ServersTransport), err +} + +// Update takes the representation of a serversTransport and updates it. Returns the server's representation of the serversTransport, and an error, if there is any. +func (c *FakeServersTransports) Update(ctx context.Context, serversTransport *v1alpha1.ServersTransport, opts v1.UpdateOptions) (result *v1alpha1.ServersTransport, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(serverstransportsResource, c.ns, serversTransport), &v1alpha1.ServersTransport{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ServersTransport), err +} + +// Delete takes name of the serversTransport and deletes it. Returns an error if one occurs. +func (c *FakeServersTransports) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(serverstransportsResource, c.ns, name), &v1alpha1.ServersTransport{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeServersTransports) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(serverstransportsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.ServersTransportList{}) + return err +} + +// Patch applies the patch and returns the patched serversTransport. +func (c *FakeServersTransports) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ServersTransport, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(serverstransportsResource, c.ns, name, pt, data, subresources...), &v1alpha1.ServersTransport{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.ServersTransport), err +} 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 index ca523a715..78bd634cb 100644 --- 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 @@ -85,6 +85,7 @@ func (c *FakeTLSOptions) List(ctx context.Context, opts v1.ListOptions) (result func (c *FakeTLSOptions) Watch(ctx context.Context, 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. diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go index f2d7b5e2b..1a26fc934 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_tlsstore.go @@ -85,6 +85,7 @@ func (c *FakeTLSStores) List(ctx context.Context, opts v1.ListOptions) (result * func (c *FakeTLSStores) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. InvokesWatch(testing.NewWatchAction(tlsstoresResource, c.ns, opts)) + } // Create takes the representation of a tLSStore and creates it. Returns the server's representation of the tLSStore, and an error, if there is any. 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 ff0fdea49..9060c2004 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go @@ -52,6 +52,10 @@ func (c *FakeTraefikV1alpha1) Middlewares(namespace string) v1alpha1.MiddlewareI return &FakeMiddlewares{c, namespace} } +func (c *FakeTraefikV1alpha1) ServersTransports(namespace string) v1alpha1.ServersTransportInterface { + return &FakeServersTransports{c, namespace} +} + func (c *FakeTraefikV1alpha1) TLSOptions(namespace string) v1alpha1.TLSOptionInterface { return &FakeTLSOptions{c, namespace} } diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go index b9d8d3842..deaecc27a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go @@ -85,6 +85,7 @@ func (c *FakeTraefikServices) List(ctx context.Context, opts v1.ListOptions) (re func (c *FakeTraefikServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { return c.Fake. InvokesWatch(testing.NewWatchAction(traefikservicesResource, c.ns, opts)) + } // Create takes the representation of a traefikService and creates it. Returns the server's representation of the traefikService, and an error, if there is any. 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 4568ff0ef..c1e761e6a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go @@ -34,6 +34,8 @@ type IngressRouteUDPExpansion interface{} type MiddlewareExpansion interface{} +type ServersTransportExpansion interface{} + type TLSOptionExpansion interface{} type TLSStoreExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/serverstransport.go new file mode 100644 index 000000000..23d9937d8 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/serverstransport.go @@ -0,0 +1,186 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + scheme "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/scheme" + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// ServersTransportsGetter has a method to return a ServersTransportInterface. +// A group's client should implement this interface. +type ServersTransportsGetter interface { + ServersTransports(namespace string) ServersTransportInterface +} + +// ServersTransportInterface has methods to work with ServersTransport resources. +type ServersTransportInterface interface { + Create(ctx context.Context, serversTransport *v1alpha1.ServersTransport, opts v1.CreateOptions) (*v1alpha1.ServersTransport, error) + Update(ctx context.Context, serversTransport *v1alpha1.ServersTransport, opts v1.UpdateOptions) (*v1alpha1.ServersTransport, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ServersTransport, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ServersTransportList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ServersTransport, err error) + ServersTransportExpansion +} + +// serversTransports implements ServersTransportInterface +type serversTransports struct { + client rest.Interface + ns string +} + +// newServersTransports returns a ServersTransports +func newServersTransports(c *TraefikV1alpha1Client, namespace string) *serversTransports { + return &serversTransports{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the serversTransport, and returns the corresponding serversTransport object, and an error if there is any. +func (c *serversTransports) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.ServersTransport, err error) { + result = &v1alpha1.ServersTransport{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serverstransports"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ServersTransports that match those selectors. +func (c *serversTransports) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.ServersTransportList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.ServersTransportList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serverstransports"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested serversTransports. +func (c *serversTransports) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("serverstransports"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a serversTransport and creates it. Returns the server's representation of the serversTransport, and an error, if there is any. +func (c *serversTransports) Create(ctx context.Context, serversTransport *v1alpha1.ServersTransport, opts v1.CreateOptions) (result *v1alpha1.ServersTransport, err error) { + result = &v1alpha1.ServersTransport{} + err = c.client.Post(). + Namespace(c.ns). + Resource("serverstransports"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(serversTransport). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a serversTransport and updates it. Returns the server's representation of the serversTransport, and an error, if there is any. +func (c *serversTransports) Update(ctx context.Context, serversTransport *v1alpha1.ServersTransport, opts v1.UpdateOptions) (result *v1alpha1.ServersTransport, err error) { + result = &v1alpha1.ServersTransport{} + err = c.client.Put(). + Namespace(c.ns). + Resource("serverstransports"). + Name(serversTransport.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(serversTransport). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the serversTransport and deletes it. Returns an error if one occurs. +func (c *serversTransports) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serverstransports"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *serversTransports) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("serverstransports"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched serversTransport. +func (c *serversTransports) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ServersTransport, err error) { + result = &v1alpha1.ServersTransport{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("serverstransports"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + 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 42bba3996..39346e15a 100644 --- a/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go +++ b/pkg/provider/kubernetes/crd/generated/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go @@ -38,6 +38,7 @@ type TraefikV1alpha1Interface interface { IngressRouteTCPsGetter IngressRouteUDPsGetter MiddlewaresGetter + ServersTransportsGetter TLSOptionsGetter TLSStoresGetter TraefikServicesGetter @@ -64,6 +65,10 @@ func (c *TraefikV1alpha1Client) Middlewares(namespace string) MiddlewareInterfac return newMiddlewares(c, namespace) } +func (c *TraefikV1alpha1Client) ServersTransports(namespace string) ServersTransportInterface { + return newServersTransports(c, namespace) +} + func (c *TraefikV1alpha1Client) TLSOptions(namespace string) TLSOptionInterface { return newTLSOptions(c, namespace) } diff --git a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go index 0513a3dff..10017716f 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/generic.go @@ -69,6 +69,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().IngressRouteUDPs().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("middlewares"): return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().Middlewares().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("serverstransports"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().ServersTransports().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tlsoptions"): return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TLSOptions().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tlsstores"): 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 31d683e23..94c22e6cd 100644 --- a/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/interface.go @@ -40,6 +40,8 @@ type Interface interface { IngressRouteUDPs() IngressRouteUDPInformer // Middlewares returns a MiddlewareInformer. Middlewares() MiddlewareInformer + // ServersTransports returns a ServersTransportInformer. + ServersTransports() ServersTransportInformer // TLSOptions returns a TLSOptionInformer. TLSOptions() TLSOptionInformer // TLSStores returns a TLSStoreInformer. @@ -79,6 +81,11 @@ func (v *version) Middlewares() MiddlewareInformer { return &middlewareInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// ServersTransports returns a ServersTransportInformer. +func (v *version) ServersTransports() ServersTransportInformer { + return &serversTransportInformer{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/serverstransport.go b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/serverstransport.go new file mode 100644 index 000000000..511f9382b --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/informers/externalversions/traefik/v1alpha1/serverstransport.go @@ -0,0 +1,98 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + versioned "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned" + internalinterfaces "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1" + traefikv1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ServersTransportInformer provides access to a shared informer and lister for +// ServersTransports. +type ServersTransportInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.ServersTransportLister +} + +type serversTransportInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewServersTransportInformer constructs a new informer for ServersTransport 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 NewServersTransportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredServersTransportInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredServersTransportInformer constructs a new informer for ServersTransport 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 NewFilteredServersTransportInformer(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().ServersTransports(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().ServersTransports(namespace).Watch(context.TODO(), options) + }, + }, + &traefikv1alpha1.ServersTransport{}, + resyncPeriod, + indexers, + ) +} + +func (f *serversTransportInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredServersTransportInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *serversTransportInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&traefikv1alpha1.ServersTransport{}, f.defaultInformer) +} + +func (f *serversTransportInformer) Lister() v1alpha1.ServersTransportLister { + return v1alpha1.NewServersTransportLister(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 f1137d956..4ef9ebd64 100644 --- a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/expansion_generated.go @@ -58,6 +58,14 @@ type MiddlewareListerExpansion interface{} // MiddlewareNamespaceLister. type MiddlewareNamespaceListerExpansion interface{} +// ServersTransportListerExpansion allows custom methods to be added to +// ServersTransportLister. +type ServersTransportListerExpansion interface{} + +// ServersTransportNamespaceListerExpansion allows custom methods to be added to +// ServersTransportNamespaceLister. +type ServersTransportNamespaceListerExpansion interface{} + // TLSOptionListerExpansion allows custom methods to be added to // TLSOptionLister. type TLSOptionListerExpansion interface{} diff --git a/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/serverstransport.go new file mode 100644 index 000000000..3879e4ef1 --- /dev/null +++ b/pkg/provider/kubernetes/crd/generated/listers/traefik/v1alpha1/serverstransport.go @@ -0,0 +1,102 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2020 Containous SAS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/containous/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ServersTransportLister helps list ServersTransports. +type ServersTransportLister interface { + // List lists all ServersTransports in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.ServersTransport, err error) + // ServersTransports returns an object that can list and get ServersTransports. + ServersTransports(namespace string) ServersTransportNamespaceLister + ServersTransportListerExpansion +} + +// serversTransportLister implements the ServersTransportLister interface. +type serversTransportLister struct { + indexer cache.Indexer +} + +// NewServersTransportLister returns a new ServersTransportLister. +func NewServersTransportLister(indexer cache.Indexer) ServersTransportLister { + return &serversTransportLister{indexer: indexer} +} + +// List lists all ServersTransports in the indexer. +func (s *serversTransportLister) List(selector labels.Selector) (ret []*v1alpha1.ServersTransport, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.ServersTransport)) + }) + return ret, err +} + +// ServersTransports returns an object that can list and get ServersTransports. +func (s *serversTransportLister) ServersTransports(namespace string) ServersTransportNamespaceLister { + return serversTransportNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ServersTransportNamespaceLister helps list and get ServersTransports. +type ServersTransportNamespaceLister interface { + // List lists all ServersTransports in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.ServersTransport, err error) + // Get retrieves the ServersTransport from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.ServersTransport, error) + ServersTransportNamespaceListerExpansion +} + +// serversTransportNamespaceLister implements the ServersTransportNamespaceLister +// interface. +type serversTransportNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all ServersTransports in the indexer for a given namespace. +func (s serversTransportNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.ServersTransport, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.ServersTransport)) + }) + return ret, err +} + +// Get retrieves the ServersTransport from the indexer for a given namespace and name. +func (s serversTransportNamespaceLister) Get(name string) (*v1alpha1.ServersTransport, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("serverstransport"), name) + } + return obj.(*v1alpha1.ServersTransport), nil +} diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 630be4239..b97e341e5 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -237,6 +237,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } cb := configBuilder{client} + for _, service := range client.GetTraefikServices() { err := cb.buildTraefikService(ctx, service, conf.HTTP.Services) if err != nil { @@ -246,6 +247,70 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } } + for _, serversTransport := range client.GetServersTransports() { + logger := log.FromContext(ctx).WithField(log.ServersTransportName, serversTransport.Name) + + var rootCAs []tls.FileOrContent + for _, secret := range serversTransport.Spec.RootCAsSecrets { + caSecret, err := loadCASecret(serversTransport.Namespace, secret, client) + if err != nil { + logger.Errorf("Error while loading rootCAs %s: %v", secret, err) + continue + } + + rootCAs = append(rootCAs, tls.FileOrContent(caSecret)) + } + + var certs tls.Certificates + for _, secret := range serversTransport.Spec.CertificatesSecrets { + tlsSecret, tlsKey, err := loadAuthTLSSecret(serversTransport.Namespace, secret, client) + if err != nil { + logger.Errorf("Error while loading certificates %s: %v", secret, err) + continue + } + + certs = append(certs, tls.Certificate{ + CertFile: tls.FileOrContent(tlsSecret), + KeyFile: tls.FileOrContent(tlsKey), + }) + } + + forwardingTimeout := &dynamic.ForwardingTimeouts{} + forwardingTimeout.SetDefaults() + + if serversTransport.Spec.ForwardingTimeouts != nil { + if serversTransport.Spec.ForwardingTimeouts.DialTimeout != nil { + err := forwardingTimeout.DialTimeout.Set(serversTransport.Spec.ForwardingTimeouts.DialTimeout.String()) + if err != nil { + logger.Errorf("Error while reading DialTimeout: %v", err) + } + } + + if serversTransport.Spec.ForwardingTimeouts.ResponseHeaderTimeout != nil { + err := forwardingTimeout.ResponseHeaderTimeout.Set(serversTransport.Spec.ForwardingTimeouts.ResponseHeaderTimeout.String()) + if err != nil { + logger.Errorf("Error while reading ResponseHeaderTimeout: %v", err) + } + } + + if serversTransport.Spec.ForwardingTimeouts.IdleConnTimeout != nil { + err := forwardingTimeout.IdleConnTimeout.Set(serversTransport.Spec.ForwardingTimeouts.IdleConnTimeout.String()) + if err != nil { + logger.Errorf("Error while reading IdleConnTimeout: %v", err) + } + } + } + + conf.HTTP.ServersTransports[serversTransport.Name] = &dynamic.ServersTransport{ + ServerName: serversTransport.Spec.ServerName, + InsecureSkipVerify: serversTransport.Spec.InsecureSkipVerify, + RootCAs: rootCAs, + Certificates: certs, + MaxIdleConnsPerHost: serversTransport.Spec.MaxIdleConnsPerHost, + ForwardingTimeouts: forwardingTimeout, + } + } + return conf } diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 26d024dc3..a0dff82bf 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -22,9 +22,10 @@ const ( func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Client, tlsConfigs map[string]*tls.CertAndStores) *dynamic.HTTPConfiguration { conf := &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, } for _, ingressRoute := range client.GetIngressRoutes() { @@ -264,6 +265,7 @@ func (c configBuilder) buildServersLB(namespace string, svc v1alpha1.LoadBalance lb.ResponseForwarding = conf.ResponseForwarding lb.Sticky = svc.Sticky + lb.ServersTransport = svc.ServersTransport return &dynamic.Service{LoadBalancer: lb}, nil } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index e8d68affb..7141de117 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -3,11 +3,13 @@ package crd import ( "context" "testing" + "time" "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/provider" "github.com/containous/traefik/v2/pkg/tls" "github.com/stretchr/testify/assert" + "github.com/traefik/paerser/types" corev1 "k8s.io/api/core/v1" ) @@ -35,9 +37,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -51,9 +54,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.UDPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{ @@ -136,9 +140,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -177,9 +182,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -242,9 +248,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -323,9 +330,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -344,9 +352,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -364,9 +373,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -416,9 +426,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -459,9 +470,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -523,9 +535,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -585,9 +598,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -646,9 +660,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -696,9 +711,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -746,9 +762,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -787,9 +804,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -830,9 +848,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -881,9 +900,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.UDPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -917,9 +937,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -954,9 +975,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -980,9 +1002,10 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -1025,9 +1048,10 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -1045,6 +1069,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"foo"}, @@ -1086,6 +1111,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test2-route-23c7f4c450289ee29016": { EntryPoints: []string{"web"}, @@ -1140,6 +1166,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test2-route-23c7f4c450289ee29016": { EntryPoints: []string{"web"}, @@ -1192,6 +1219,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -1253,6 +1281,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1321,6 +1350,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1372,8 +1402,9 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ "default-wrr1": { Weighted: &dynamic.WeightedRoundRobin{ @@ -1416,6 +1447,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1538,6 +1570,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1603,6 +1636,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1693,6 +1727,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1869,6 +1904,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -1931,6 +1967,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -2013,6 +2050,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-77c62dfe9517144aeeaa": { EntryPoints: []string{"web"}, @@ -2082,9 +2120,10 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -2102,9 +2141,10 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -2122,9 +2162,10 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, }, }, @@ -2151,6 +2192,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2212,6 +2254,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2258,6 +2301,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2321,6 +2365,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2383,6 +2428,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2444,6 +2490,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2494,6 +2541,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2544,6 +2592,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2588,6 +2637,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2630,6 +2680,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"foo"}, @@ -2671,6 +2722,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"foo"}, @@ -2712,7 +2764,8 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, Middlewares: map[string]*dynamic.Middleware{ "default-basicauth": { BasicAuth: &dynamic.BasicAuth{ @@ -2753,7 +2806,8 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, Middlewares: map[string]*dynamic.Middleware{ "default-errorpage": { Errors: &dynamic.ErrorPage{ @@ -2794,6 +2848,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"foo"}, @@ -2846,6 +2901,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2890,6 +2946,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6b204d94623b3df4370c": { EntryPoints: []string{"web"}, @@ -2934,6 +2991,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6f97418635c7e18853da": { EntryPoints: []string{"foo"}, @@ -2971,6 +3029,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6f97418635c7e18853da": { EntryPoints: []string{"foo"}, @@ -3008,6 +3067,7 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, Routers: map[string]*dynamic.Router{ "default-test-route-6f97418635c7e18853da": { EntryPoints: []string{"foo"}, @@ -3045,6 +3105,44 @@ func TestLoadIngressRoutes(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "ServersTransport", + paths: []string{"services.yml", "with_servers_transport.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{ + "test": { + ServerName: "test", + InsecureSkipVerify: true, + RootCAs: []tls.FileOrContent{"TESTROOTCAS", "TESTROOTCAS2"}, + Certificates: tls.Certificates{ + {CertFile: "TESTCERT1", KeyFile: "TESTKEY1"}, + {CertFile: "TESTCERT2", KeyFile: "TESTKEY2"}, + }, + MaxIdleConnsPerHost: 42, + ForwardingTimeouts: &dynamic.ForwardingTimeouts{ + DialTimeout: types.Duration(42 * time.Second), + ResponseHeaderTimeout: types.Duration(42 * time.Second), + IdleConnTimeout: types.Duration(42 * time.Millisecond), + }, + }, + }, Routers: map[string]*dynamic.Router{}, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{}, @@ -3056,7 +3154,6 @@ func TestLoadIngressRoutes(t *testing.T) { for _, test := range testCases { test := test - t.Run(test.desc, func(t *testing.T) { t.Parallel() @@ -3090,9 +3187,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -3126,9 +3224,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) { }, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{}, @@ -3188,9 +3287,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -3252,9 +3352,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -3332,9 +3433,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, @@ -3353,9 +3455,10 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ - Routers: map[string]*dynamic.Router{}, - Middlewares: map[string]*dynamic.Middleware{}, - Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, }, TLS: &dynamic.TLSConfiguration{}, }, diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index 0f3ba4f8b..2945aba3a 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -72,6 +72,7 @@ type LoadBalancerSpec struct { Strategy string `json:"strategy,omitempty"` PassHostHeader *bool `json:"passHostHeader,omitempty"` ResponseForwarding *dynamic.ResponseForwarding `json:"responseForwarding,omitempty"` + ServersTransport string `json:"serversTransport,omitempty"` // Weight should only be specified when Name references a TraefikService object // (and to be precise, one that embeds a Weighted Round Robin). diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go index 79c796129..2f5163604 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/register.go @@ -47,6 +47,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &TLSStoreList{}, &TraefikService{}, &TraefikServiceList{}, + &ServersTransport{}, + &ServersTransportList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go new file mode 100644 index 000000000..37f211ff1 --- /dev/null +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go @@ -0,0 +1,48 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServersTransport is a specification for a ServersTransport resource. +type ServersTransport struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec ServersTransportSpec `json:"spec"` +} + +// +k8s:deepcopy-gen=true + +// ServersTransportSpec options to configure communication between Traefik and the servers. +type ServersTransportSpec struct { + ServerName string `description:"ServerName used to contact the server" json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty" export:"true"` + InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` + RootCAsSecrets []string `description:"Add cert file for self-signed certificate." json:"rootCAsSecrets,omitempty" toml:"rootCAsSecrets,omitempty" yaml:"rootCAsSecrets,omitempty"` + CertificatesSecrets []string `description:"Certificates for mTLS." json:"certificatesSecrets,omitempty" toml:"certificatesSecrets,omitempty" yaml:"certificatesSecrets,omitempty"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` + ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` +} + +// +k8s:deepcopy-gen=true + +// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers. +type ForwardingTimeouts struct { + DialTimeout *intstr.IntOrString `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"` + ResponseHeaderTimeout *intstr.IntOrString `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"` + IdleConnTimeout *intstr.IntOrString `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ServersTransportList is a list of ServersTransport resources. +type ServersTransportList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []ServersTransport `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 6b580ac39..71a705908 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,7 @@ import ( dynamic "github.com/containous/traefik/v2/pkg/config/dynamic" types "github.com/containous/traefik/v2/pkg/types" runtime "k8s.io/apimachinery/pkg/runtime" + intstr "k8s.io/apimachinery/pkg/util/intstr" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -188,6 +189,37 @@ func (in *ForwardAuth) DeepCopy() *ForwardAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ForwardingTimeouts) DeepCopyInto(out *ForwardingTimeouts) { + *out = *in + if in.DialTimeout != nil { + in, out := &in.DialTimeout, &out.DialTimeout + *out = new(intstr.IntOrString) + **out = **in + } + if in.ResponseHeaderTimeout != nil { + in, out := &in.ResponseHeaderTimeout, &out.ResponseHeaderTimeout + *out = new(intstr.IntOrString) + **out = **in + } + if in.IdleConnTimeout != nil { + in, out := &in.IdleConnTimeout, &out.IdleConnTimeout + *out = new(intstr.IntOrString) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ForwardingTimeouts. +func (in *ForwardingTimeouts) DeepCopy() *ForwardingTimeouts { + if in == nil { + return nil + } + out := new(ForwardingTimeouts) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressRoute) DeepCopyInto(out *IngressRoute) { *out = *in @@ -827,6 +859,97 @@ func (in *RouteUDP) DeepCopy() *RouteUDP { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServersTransport) DeepCopyInto(out *ServersTransport) { + *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 ServersTransport. +func (in *ServersTransport) DeepCopy() *ServersTransport { + if in == nil { + return nil + } + out := new(ServersTransport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServersTransport) 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 *ServersTransportList) DeepCopyInto(out *ServersTransportList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ServersTransport, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServersTransportList. +func (in *ServersTransportList) DeepCopy() *ServersTransportList { + if in == nil { + return nil + } + out := new(ServersTransportList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ServersTransportList) 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 *ServersTransportSpec) DeepCopyInto(out *ServersTransportSpec) { + *out = *in + if in.RootCAsSecrets != nil { + in, out := &in.RootCAsSecrets, &out.RootCAsSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CertificatesSecrets != nil { + in, out := &in.CertificatesSecrets, &out.CertificatesSecrets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ForwardingTimeouts != nil { + in, out := &in.ForwardingTimeouts, &out.ForwardingTimeouts + *out = new(ForwardingTimeouts) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServersTransportSpec. +func (in *ServersTransportSpec) DeepCopy() *ServersTransportSpec { + if in == nil { + return nil + } + out := new(ServersTransportSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Service) DeepCopyInto(out *Service) { *out = *in diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index 75f67c5c1..85ae72736 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|Secret|TLSOption|TLSStore|TraefikService|IngressClass)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport)$`) files := strings.Split(string(content), "---") retVal := make([]runtime.Object, 0, len(files)) diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 4c341f56b..f4a392f17 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -49,10 +49,11 @@ func (i *Provider) Init() error { func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configuration { cfg := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), - Models: make(map[string]*dynamic.Model), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + Models: make(map[string]*dynamic.Model), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), @@ -70,6 +71,7 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati i.prometheusConfiguration(cfg) i.entryPointModels(cfg) i.redirection(ctx, cfg) + i.serverTransport(cfg) cfg.HTTP.Services["noop"] = &dynamic.Service{} @@ -274,3 +276,25 @@ func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) { cfg.HTTP.Services["prometheus"] = &dynamic.Service{} } + +func (i *Provider) serverTransport(cfg *dynamic.Configuration) { + if i.staticCfg.ServersTransport == nil { + return + } + + st := &dynamic.ServersTransport{ + InsecureSkipVerify: i.staticCfg.ServersTransport.InsecureSkipVerify, + RootCAs: i.staticCfg.ServersTransport.RootCAs, + MaxIdleConnsPerHost: i.staticCfg.ServersTransport.MaxIdleConnsPerHost, + } + + if i.staticCfg.ServersTransport.ForwardingTimeouts != nil { + st.ForwardingTimeouts = &dynamic.ForwardingTimeouts{ + DialTimeout: i.staticCfg.ServersTransport.ForwardingTimeouts.DialTimeout, + ResponseHeaderTimeout: i.staticCfg.ServersTransport.ForwardingTimeouts.ResponseHeaderTimeout, + IdleConnTimeout: i.staticCfg.ServersTransport.ForwardingTimeouts.IdleConnTimeout, + } + } + + cfg.HTTP.ServersTransports["default"] = st +} diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index e23ee8f10..db039218a 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -10,10 +10,11 @@ import ( func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration { conf := dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), - Models: make(map[string]*dynamic.Model), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + Models: make(map[string]*dynamic.Model), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, TCP: &dynamic.TCPConfiguration{ Routers: make(map[string]*dynamic.TCPRouter), @@ -52,6 +53,9 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint for modelName, model := range configuration.HTTP.Models { conf.HTTP.Models[provider.MakeQualifiedName(pvd, modelName)] = model } + for serversTransportName, serversTransport := range configuration.HTTP.ServersTransports { + conf.HTTP.ServersTransports[provider.MakeQualifiedName(pvd, serversTransportName)] = serversTransport + } } if configuration.TCP != nil { diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index 21d224916..5158ac354 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -18,10 +18,11 @@ func Test_mergeConfiguration(t *testing.T) { desc: "Nil returns an empty configuration", given: nil, expected: &dynamic.HTTPConfiguration{ - Routers: make(map[string]*dynamic.Router), - Middlewares: make(map[string]*dynamic.Middleware), - Services: make(map[string]*dynamic.Service), - Models: make(map[string]*dynamic.Model), + Routers: make(map[string]*dynamic.Router), + Middlewares: make(map[string]*dynamic.Middleware), + Services: make(map[string]*dynamic.Service), + Models: make(map[string]*dynamic.Model), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, }, { @@ -53,7 +54,8 @@ func Test_mergeConfiguration(t *testing.T) { Services: map[string]*dynamic.Service{ "service-1@provider-1": {}, }, - Models: make(map[string]*dynamic.Model), + Models: make(map[string]*dynamic.Model), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, }, { @@ -103,7 +105,8 @@ func Test_mergeConfiguration(t *testing.T) { "service-1@provider-1": {}, "service-1@provider-2": {}, }, - Models: make(map[string]*dynamic.Model), + Models: make(map[string]*dynamic.Model), + ServersTransports: make(map[string]*dynamic.ServersTransport), }, }, } diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index ed41ed1d0..fc7a10064 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -287,7 +287,9 @@ func TestRouterManager_Get(t *testing.T) { }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) @@ -391,7 +393,9 @@ func TestAccessLog(t *testing.T) { }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) @@ -678,7 +682,9 @@ func TestRuntimeConfiguration(t *testing.T) { }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) @@ -759,7 +765,9 @@ func TestProviderOnMiddlewares(t *testing.T) { }, }) - serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil) @@ -773,6 +781,14 @@ func TestProviderOnMiddlewares(t *testing.T) { assert.Equal(t, []string{"m1@docker", "m2@docker", "m1@file"}, rtConf.Middlewares["chain@docker"].Chain.Middlewares) } +type staticRoundTripperGetter struct { + res *http.Response +} + +func (s staticRoundTripperGetter) Get(name string) (http.RoundTripper, error) { + return &staticTransport{res: s.res}, nil +} + type staticTransport struct { res *http.Response } @@ -819,7 +835,7 @@ func BenchmarkRouterServe(b *testing.B) { }, }) - serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil) + serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) @@ -861,7 +877,7 @@ func BenchmarkService(b *testing.B) { }, }) - serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil) + serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index c5038e548..07f441076 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -48,7 +48,9 @@ func TestReuseService(t *testing.T) { ), ) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry()) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager) tlsManager := tls.NewManager() factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil) @@ -182,7 +184,9 @@ func TestServerResponseEmptyBackend(t *testing.T) { }, } - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry()) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager) tlsManager := tls.NewManager() factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil) @@ -221,7 +225,9 @@ func TestInternalServices(t *testing.T) { ), ) - managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry()) + roundTripperManager := service.NewRoundTripperManager() + roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) + managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager) tlsManager := tls.NewManager() factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil) diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go index e3d82b2a9..a4a70a83c 100644 --- a/pkg/server/service/managerfactory.go +++ b/pkg/server/service/managerfactory.go @@ -14,7 +14,7 @@ import ( type ManagerFactory struct { metricsRegistry metrics.Registry - defaultRoundTripper http.RoundTripper + roundTripperManager *RoundTripperManager api func(configuration *runtime.Configuration) http.Handler restHandler http.Handler @@ -26,11 +26,11 @@ type ManagerFactory struct { } // NewManagerFactory creates a new ManagerFactory. -func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry) *ManagerFactory { +func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager) *ManagerFactory { factory := &ManagerFactory{ metricsRegistry: metricsRegistry, - defaultRoundTripper: setupDefaultRoundTripper(staticConfiguration.ServersTransport), routinesPool: routinesPool, + roundTripperManager: roundTripperManager, } if staticConfiguration.API != nil { @@ -61,6 +61,6 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s // Build creates a service manager. func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers { - svcManager := NewManager(configuration.Services, f.defaultRoundTripper, f.metricsRegistry, f.routinesPool) + svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager) return NewInternalHandlers(f.api, configuration, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, svcManager) } diff --git a/pkg/server/service/proxy.go b/pkg/server/service/proxy.go index f6702d195..ae4f977a0 100644 --- a/pkg/server/service/proxy.go +++ b/pkg/server/service/proxy.go @@ -21,7 +21,7 @@ const StatusClientClosedRequest = 499 // StatusClientClosedRequestText non-standard HTTP status for client disconnection. const StatusClientClosedRequestText = "Client Closed Request" -func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) { +func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) { var flushInterval ptypes.Duration if responseForwarding != nil { err := flushInterval.Set(responseForwarding.FlushInterval) @@ -76,7 +76,7 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar delete(outReq.Header, "Sec-Websocket-Protocol") delete(outReq.Header, "Sec-Websocket-Version") }, - Transport: defaultRoundTripper, + Transport: roundTripper, FlushInterval: time.Duration(flushInterval), BufferPool: bufferPool, ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) { diff --git a/pkg/server/service/roundtripper.go b/pkg/server/service/roundtripper.go index 715d5d5c2..39623411e 100644 --- a/pkg/server/service/roundtripper.go +++ b/pkg/server/service/roundtripper.go @@ -4,11 +4,14 @@ import ( "crypto/tls" "crypto/x509" "errors" + "fmt" "net" "net/http" + "reflect" + "sync" "time" - "github.com/containous/traefik/v2/pkg/config/static" + "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/log" traefiktls "github.com/containous/traefik/v2/pkg/tls" "golang.org/x/net/http2" @@ -23,30 +26,100 @@ func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, erro return t.Transport.RoundTrip(req) } -// createRoundtripper creates an http.Roundtripper configured with the Transport configuration settings. +// NewRoundTripperManager creates a new RoundTripperManager. +func NewRoundTripperManager() *RoundTripperManager { + return &RoundTripperManager{ + roundTrippers: make(map[string]http.RoundTripper), + configs: make(map[string]*dynamic.ServersTransport), + } +} + +// RoundTripperManager handles roundtripper for the reverse proxy. +type RoundTripperManager struct { + rtLock sync.RWMutex + roundTrippers map[string]http.RoundTripper + configs map[string]*dynamic.ServersTransport +} + +// Update updates the roundtrippers configurations. +func (r *RoundTripperManager) Update(newConfigs map[string]*dynamic.ServersTransport) { + r.rtLock.Lock() + defer r.rtLock.Unlock() + + for configName, config := range r.configs { + newConfig, ok := newConfigs[configName] + if !ok { + delete(r.configs, configName) + delete(r.roundTrippers, configName) + continue + } + + if reflect.DeepEqual(newConfig, config) { + continue + } + + var err error + r.roundTrippers[configName], err = createRoundTripper(newConfig) + if err != nil { + log.WithoutContext().Errorf("Could not configure HTTP Transport %s, fallback on default transport: %v", configName, err) + r.roundTrippers[configName] = http.DefaultTransport + } + } + + for newConfigName, newConfig := range newConfigs { + if _, ok := r.configs[newConfigName]; ok { + continue + } + + var err error + r.roundTrippers[newConfigName], err = createRoundTripper(newConfig) + if err != nil { + log.WithoutContext().Errorf("Could not configure HTTP Transport %s, fallback on default transport: %v", newConfigName, err) + r.roundTrippers[newConfigName] = http.DefaultTransport + } + } + + r.configs = newConfigs +} + +// Get get a roundtripper by name. +func (r *RoundTripperManager) Get(name string) (http.RoundTripper, error) { + if len(name) == 0 { + name = "default@internal" + } + + r.rtLock.RLock() + defer r.rtLock.RUnlock() + + if rt, ok := r.roundTrippers[name]; ok { + return rt, nil + } + + return nil, fmt.Errorf("servers transport not found %s", name) +} + +// createRoundTripper creates an http.RoundTripper configured with the Transport configuration settings. // For the settings that can't be configured in Traefik it uses the default http.Transport settings. -// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost -// in Traefik at this point in time. Setting this value to the default of 100 could lead to confusing -// behavior and backwards compatibility issues. -func createRoundtripper(transportConfiguration *static.ServersTransport) (http.RoundTripper, error) { - if transportConfiguration == nil { +// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHostin Traefik at this point in time. +// Setting this value to the default of 100 could lead to confusing behavior and backwards compatibility issues. +func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) { + if cfg == nil { return nil, errors.New("no transport configuration given") } dialer := &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, - DualStack: true, } - if transportConfiguration.ForwardingTimeouts != nil { - dialer.Timeout = time.Duration(transportConfiguration.ForwardingTimeouts.DialTimeout) + if cfg.ForwardingTimeouts != nil { + dialer.Timeout = time.Duration(cfg.ForwardingTimeouts.DialTimeout) } transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: dialer.DialContext, - MaxIdleConnsPerHost: transportConfiguration.MaxIdleConnsPerHost, + MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, @@ -61,24 +134,21 @@ func createRoundtripper(transportConfiguration *static.ServersTransport) (http.R }, }) - if transportConfiguration.ForwardingTimeouts != nil { - transport.ResponseHeaderTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.ResponseHeaderTimeout) - transport.IdleConnTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.IdleConnTimeout) + if cfg.ForwardingTimeouts != nil { + transport.ResponseHeaderTimeout = time.Duration(cfg.ForwardingTimeouts.ResponseHeaderTimeout) + transport.IdleConnTimeout = time.Duration(cfg.ForwardingTimeouts.IdleConnTimeout) } - if transportConfiguration.InsecureSkipVerify || len(transportConfiguration.RootCAs) > 0 { + if cfg.InsecureSkipVerify || len(cfg.RootCAs) > 0 || len(cfg.ServerName) > 0 || len(cfg.Certificates) > 0 { transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: transportConfiguration.InsecureSkipVerify, - RootCAs: createRootCACertPool(transportConfiguration.RootCAs), + ServerName: cfg.ServerName, + InsecureSkipVerify: cfg.InsecureSkipVerify, + RootCAs: createRootCACertPool(cfg.RootCAs), + Certificates: cfg.Certificates.GetCertificates(), } } - smartTransport, err := newSmartRoundTripper(transport) - if err != nil { - return nil, err - } - - return smartTransport, nil + return newSmartRoundTripper(transport) } func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool { @@ -99,13 +169,3 @@ func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool { return roots } - -func setupDefaultRoundTripper(conf *static.ServersTransport) http.RoundTripper { - transport, err := createRoundtripper(conf) - if err != nil { - log.WithoutContext().Errorf("Could not configure HTTP Transport, fallbacking on default transport: %v", err) - return http.DefaultTransport - } - - return transport -} diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go new file mode 100644 index 000000000..44f562f50 --- /dev/null +++ b/pkg/server/service/roundtripper_test.go @@ -0,0 +1,229 @@ +package service + +import ( + "crypto/tls" + "crypto/x509" + "net" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + + "github.com/containous/traefik/v2/pkg/config/dynamic" + traefiktls "github.com/containous/traefik/v2/pkg/tls" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Int32(i int32) *int32 { + return &i +} + +// LocalhostCert is a PEM-encoded TLS cert +// for host example.com, www.example.com +// expiring at Jan 29 16:00:00 2084 GMT. +// go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host example.com,www.example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var LocalhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIICDDCCAXWgAwIBAgIQH20JmcOlcRWHNuf62SYwszANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQC0qINy3F4oq6viDnlpDDE5J08iSRGggg6EylJKBKZfphEG2ufgK78Dufl3 ++7b0LlEY2AeZHwviHODqC9a6ihj1ZYQk0/djAh+OeOhFEWu+9T/VP8gVFarFqT8D +Opy+hrG7YJivUIzwb4fmJQRI7FajzsnGyM6LiXLU+0qzb7ZO/QIDAQABo2EwXzAO +BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw +AwEB/zAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0G +CSqGSIb3DQEBCwUAA4GBAB+eluoQYzyyMfeEEAOtlldevx5MtDENT05NB0WI+91R +we7mX8lv763u0XuCWPxbHszhclI6FFjoQef0Z1NYLRm8ZRq58QqWDFZ3E6wdDK+B ++OWvkW+hRavo6R9LzIZPfbv8yBo4M9PK/DXw8hLqH7VkkI+Gh793iH7Ugd4A7wvT +-----END CERTIFICATE-----`) + +// LocalhostKey is the private key for localhostCert. +var LocalhostKey = []byte(`-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALSog3LcXiirq+IO +eWkMMTknTyJJEaCCDoTKUkoEpl+mEQba5+ArvwO5+Xf7tvQuURjYB5kfC+Ic4OoL +1rqKGPVlhCTT92MCH4546EURa771P9U/yBUVqsWpPwM6nL6GsbtgmK9QjPBvh+Yl +BEjsVqPOycbIzouJctT7SrNvtk79AgMBAAECgYB1wMT1MBgbkFIXpXGTfAP1id61 +rUTVBxCpkypx3ngHLjo46qRq5Hi72BN4FlTY8fugIudI8giP2FztkMvkiLDc4m0p +Gn+QMJzjlBjjTuNLvLy4aSmNRLIC3mtbx9PdU71DQswEpJHFj/vmsxbuSrG1I1YE +r1reuSo2ow6fOAjXLQJBANpz+RkOiPSPuvl+gi1sp2pLuynUJVDVqWZi386YRpfg +DiKCLpqwqYDkOozm/fwFALvwXKGmsyyL43HO8eI+2NsCQQDTtY32V+02GPecdsyq +msK06EPVTSaYwj9Mm+q709KsmYFHLXDqXjcKV4UgKYKRPz7my1fXodMmGmfuh1a3 +/HMHAkEAmOQKN0tA90mRJwUvvvMIyRBv0fq0kzq28P3KfiF9ZtZdjjFmxMVYHOmf +QPZ6VGR7+w1jB5BQXqEZcpHQIPSzeQJBAIy9tZJ/AYNlNbcegxEnsSjy/6VdlLsY +51vWi0Yym2uC4R6gZuBnoc+OP0ISVmqY0Qg9RjhjrCs4gr9f2ZaWjSECQCxqZMq1 +3viJ8BGCC0m/5jv1EHur3YgwphYCkf4Li6DKwIdMLk1WXkTcPIY3V2Jqj8rPEB5V +rqPRSAtd/h6oZbs= +-----END PRIVATE KEY-----`) + +// openssl req -newkey rsa:2048 \ +// -new -nodes -x509 \ +// -days 3650 \ +// -out cert.pem \ +// -keyout key.pem \ +// -subj "/CN=example.com" +// -addext "subjectAltName = DNS:example.com" +var mTLSCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIUYKnGcLnmMosOSKqTn4ydAMURE4gwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjAwODEzMDkyNzIwWhcNMzAw +ODExMDkyNzIwWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAOAe+QM1c9lZ2TPRgoiuPAq2A3Pfu+i82lmqrTJ0 +PR2Cx1fPbccCUTFJPlxSDiaMrwtvqw1yP9L2Pu/vJK5BY4YDVDtFGKjpRBau1otJ +iY50O5qMo3sfLqR4/1VsQGlLVZYLD3dyc4ZTmOp8+7tJ2SyGorojbIKfimZT7XD7 +dzrVr4h4Gn+SzzOnoKyx29uaNRP+XuMYHmHyQcJE03pUGhkTOvMwBlF96QdQ9WG0 +D+1CxRciEsZXE+imKBHoaTgrTkpnFHzsrIEw+OHQYf30zuT/k/lkgv1vqEwINHjz +W2VeTur5eqVvA7zZdoEXMRy7BUvh/nZk5AXkXAmZLn0eUg8CAwEAAaNrMGkwHQYD +VR0OBBYEFEDrbhPDt+hi3ZOzk6S/CFAVHwk0MB8GA1UdIwQYMBaAFEDrbhPDt+hi +3ZOzk6S/CFAVHwk0MA8GA1UdEwEB/wQFMAMBAf8wFgYDVR0RBA8wDYILZXhhbXBs +ZS5jb20wDQYJKoZIhvcNAQELBQADggEBAG/JRJWeUNx2mDJAk8W7Syq3gmQB7s9f ++yY/XVRJZGahOPilABqFpC6GVn2HWuvuOqy8/RGk9ja5abKVXqE6YKrljqo3XfzB +KQcOz4SFirpkHvNCiEcK3kggN3wJWqL2QyXAxWldBBBCO9yx7a3cux31C//sTUOG +xq4JZDg171U1UOpfN1t0BFMdt05XZFEM247N7Dcf7HoXwAa7eyLKgtKWqPDqGrFa +fvGDDKK9X/KVsU2x9V3pG+LsJg7ogUnSyD2r5G1F3Y8OVs2T/783PaN0M35fDL38 +09VbsxA2GasOHZrghUzT4UvZWWZbWEmG975hFYvdj6DlK9K0s5TdKIs= +-----END CERTIFICATE-----`) + +var mTLSKey = []byte(`-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDgHvkDNXPZWdkz +0YKIrjwKtgNz37vovNpZqq0ydD0dgsdXz23HAlExST5cUg4mjK8Lb6sNcj/S9j7v +7ySuQWOGA1Q7RRio6UQWrtaLSYmOdDuajKN7Hy6keP9VbEBpS1WWCw93cnOGU5jq +fPu7SdkshqK6I2yCn4pmU+1w+3c61a+IeBp/ks8zp6CssdvbmjUT/l7jGB5h8kHC +RNN6VBoZEzrzMAZRfekHUPVhtA/tQsUXIhLGVxPopigR6Gk4K05KZxR87KyBMPjh +0GH99M7k/5P5ZIL9b6hMCDR481tlXk7q+XqlbwO82XaBFzEcuwVL4f52ZOQF5FwJ +mS59HlIPAgMBAAECggEAAKLV3hZ2v7UrkqQTlMO50+X0WI3YAK8Yh4yedTgzPDQ0 +0KD8FMaC6HrmvGhXNfDMRmIIwD8Ew1qDjzbEieIRoD2+LXTivwf6c34HidmplEfs +K2IezKin/zuArgNio2ndUlGxt4sRnN373x5/sGZjQWcYayLSmgRN5kByuhFco0Qa +oSrXcXNUlb+KgRQXPDU4+M35tPHvLdyg+tko/m/5uK9dc9MNvGZHOMBKg0VNURJb +V1l3dR+evwvpqHzBvWiqN/YOiUUvIxlFKA35hJkfCl7ivFs4CLqqFNCKDao95fWe +s0UR9iMakT48jXV76IfwZnyX10OhIWzKls5trjDL8QKBgQD3thQJ8e0FL9y1W+Ph +mCdEaoffSPkgSn64wIsQ9bMmv4y+KYBK5AhqaHgYm4LgW4x1+CURNFu+YFEyaNNA +kNCXFyRX3Em3vxcShP5jIqg+f07mtXPKntWP/zBeKQWgdHX371oFTfaAlNuKX/7S +n0jBYjr4Iof1bnquMQvUoHCYWwKBgQDnntFU9/AQGaQIvhfeU1XKFkQ/BfhIsd27 +RlmiCi0ee9Ce74cMAhWr/9yg0XUxzrh+Ui1xnkMVTZ5P8tWIxROokznLUTGJA5rs +zB+ovCPFZcquTwNzn7SBnpHTR0OqJd8sd89P5ST2SqufeSF/gGi5sTs4EocOLCpZ +EPVIfm47XQKBgB4d5RHQeCDJUOw739jtxthqm1pqZN+oLwAHaOEG/mEXqOT15sM0 +NlG5oeBcB+1/M/Sj1t3gn8blrvmSBR00fifgiGqmPdA5S3TU9pjW/d2bXNxv80QP +S6fWPusz0ZtQjYc3cppygCXh808/nJu/AfmBF+pTSHRumjvTery/RPFBAoGBAMi/ +zCta4cTylEvHhqR5kiefePMu120aTFYeuV1KeKStJ7o5XNE5lVMIZk80e+D5jMpf +q2eIhhgWuBoPHKh4N3uqbzMbYlWgvEx09xOmTVKv0SWW8iTqzOZza2y1nZ4BSRcf +mJ1ku86EFZAYysHZp+saA3usA0ZzXRjpK87zVdM5AoGBAOSqI+t48PnPtaUDFdpd +taNNVDbcecJatm3w8VDWnarahfWe66FIqc9wUkqekqAgwZLa0AGdUalvXfGrHfNs +PtvuNc5EImfSkuPBYLBslNxtjbBvAYgacEdY+gRhn2TeIUApnND58lCWsKbNHLFZ +ajIPbTY+Fe9OTOFTN48ujXNn +-----END PRIVATE KEY-----`) + +func TestKeepConnectionWhenSameConfiguration(t *testing.T) { + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + connCount := Int32(0) + srv.Config.ConnState = func(conn net.Conn, state http.ConnState) { + if state == http.StateNew { + atomic.AddInt32(connCount, 1) + } + } + + cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey) + require.NoError(t, err) + + srv.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} + srv.StartTLS() + + rtManager := NewRoundTripperManager() + + dynamicConf := map[string]*dynamic.ServersTransport{ + "test": { + ServerName: "example.com", + RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + }, + } + + for i := 0; i < 10; i++ { + rtManager.Update(dynamicConf) + + tr, err := rtManager.Get("test") + require.NoError(t, err) + + client := http.Client{Transport: tr} + + resp, err := client.Get(srv.URL) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + } + + count := atomic.LoadInt32(connCount) + require.EqualValues(t, 1, count) + + dynamicConf = map[string]*dynamic.ServersTransport{ + "test": { + ServerName: "www.example.com", + RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + }, + } + + rtManager.Update(dynamicConf) + + tr, err := rtManager.Get("test") + require.NoError(t, err) + + client := http.Client{Transport: tr} + + resp, err := client.Get(srv.URL) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + count = atomic.LoadInt32(connCount) + assert.EqualValues(t, 2, count) +} + +func TestMTLS(t *testing.T) { + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + + cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey) + require.NoError(t, err) + + clientPool := x509.NewCertPool() + clientPool.AppendCertsFromPEM(mTLSCert) + + srv.TLS = &tls.Config{ + // For TLS + Certificates: []tls.Certificate{cert}, + + // For mTLS + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientPool, + } + srv.StartTLS() + + rtManager := NewRoundTripperManager() + + dynamicConf := map[string]*dynamic.ServersTransport{ + "test": { + ServerName: "example.com", + // For TLS + RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)}, + + // For mTLS + Certificates: traefiktls.Certificates{ + traefiktls.Certificate{ + CertFile: traefiktls.FileOrContent(mTLSCert), + KeyFile: traefiktls.FileOrContent(mTLSKey), + }, + }, + }, + } + + rtManager.Update(dynamicConf) + + tr, err := rtManager.Get("test") + require.NoError(t, err) + + client := http.Client{Transport: tr} + + resp, err := client.Get(srv.URL) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 37f02c4db..14df76f0d 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -35,13 +35,18 @@ const ( const defaultMaxBodySize int64 = -1 +// RoundTripperGetter is a roundtripper getter interface. +type RoundTripperGetter interface { + Get(name string) (http.RoundTripper, error) +} + // NewManager creates a new Manager. -func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool) *Manager { +func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { return &Manager{ routinePool: routinePool, metricsRegistry: metricsRegistry, bufferPool: newBufferPool(), - defaultRoundTripper: defaultRoundTripper, + roundTripperManager: roundTripperManager, balancers: make(map[string]healthcheck.Balancers), configs: configs, } @@ -52,7 +57,7 @@ type Manager struct { routinePool *safe.Pool metricsRegistry metrics.Registry bufferPool httputil.BufferPool - defaultRoundTripper http.RoundTripper + roundTripperManager RoundTripperGetter // balancers is the map of all Balancers, keyed by service name. // There is one Balancer per service handler, and there is one service handler per reference to a service // (e.g. if 2 routers refer to the same service name, 2 service handlers are created), @@ -168,7 +173,16 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName service.PassHostHeader = &defaultPassHostHeader } - fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool) + if len(service.ServersTransport) > 0 { + service.ServersTransport = provider.GetQualifiedName(ctx, service.ServersTransport) + } + + roundTripper, err := m.roundTripperManager.Get(service.ServersTransport) + if err != nil { + return nil, err + } + + fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, roundTripper, m.bufferPool) if err != nil { return nil, err } @@ -213,7 +227,7 @@ func (m *Manager) LaunchHealthCheck() { if hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck); hcOpts != nil { log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts) - hcOpts.Transport = m.defaultRoundTripper + hcOpts.Transport, _ = m.roundTripperManager.Get(service.ServersTransport) backendHealthCheck = healthcheck.NewBackendConfig(*hcOpts, serviceName) } diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 23b5f6b35..dff49a4bf 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -80,7 +80,11 @@ func TestGetLoadBalancer(t *testing.T) { } func TestGetLoadBalancerServiceHandler(t *testing.T) { - sm := NewManager(nil, http.DefaultTransport, nil, nil) + sm := NewManager(nil, nil, nil, &RoundTripperManager{ + roundTrippers: map[string]http.RoundTripper{ + "default@internal": http.DefaultTransport, + }, + }) server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-From", "first") @@ -332,7 +336,11 @@ func TestManager_Build(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - manager := NewManager(test.configs, http.DefaultTransport, nil, nil) + manager := NewManager(test.configs, nil, nil, &RoundTripperManager{ + roundTrippers: map[string]http.RoundTripper{ + "default@internal": http.DefaultTransport, + }, + }) ctx := context.Background() if len(test.providerName) > 0 { @@ -355,7 +363,11 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { }, } - manager := NewManager(services, http.DefaultTransport, nil, nil) + manager := NewManager(services, nil, nil, &RoundTripperManager{ + roundTrippers: map[string]http.RoundTripper{ + "default@internal": http.DefaultTransport, + }, + }) _, err := manager.BuildHTTP(context.Background(), "test@file") assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead") diff --git a/pkg/testhelpers/config.go b/pkg/testhelpers/config.go index 3a2c00ca6..e3ec7fdb5 100644 --- a/pkg/testhelpers/config.go +++ b/pkg/testhelpers/config.go @@ -7,7 +7,8 @@ import ( // BuildConfiguration is a helper to create a configuration. func BuildConfiguration(dynamicConfigBuilders ...func(*dynamic.HTTPConfiguration)) *dynamic.HTTPConfiguration { conf := &dynamic.HTTPConfiguration{ - Models: map[string]*dynamic.Model{}, + Models: map[string]*dynamic.Model{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, } for _, build := range dynamicConfigBuilders { diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go index de970cac9..0ae50c3ba 100644 --- a/pkg/tls/certificate.go +++ b/pkg/tls/certificate.go @@ -56,6 +56,23 @@ type Certificate struct { // Certs and Keys could be either a file path, or the file content itself. type Certificates []Certificate +// GetCertificates retrieves the certificates as slice of tls.Certificate. +func (c Certificates) GetCertificates() []tls.Certificate { + var certs []tls.Certificate + + for _, certificate := range c { + cert, err := certificate.GetCertificate() + if err != nil { + log.WithoutContext().Debugf("Error while getting certificate: %v", err) + continue + } + + certs = append(certs, cert) + } + + return certs +} + // FileOrContent hold a file path or content. type FileOrContent string @@ -190,6 +207,26 @@ func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certifi return err } +// GetCertificate retrieves Certificate as tls.Certificate. +func (c *Certificate) GetCertificate() (tls.Certificate, error) { + certContent, err := c.CertFile.Read() + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to read CertFile : %w", err) + } + + keyContent, err := c.KeyFile.Read() + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to read KeyFile : %w", err) + } + + cert, err := tls.X509KeyPair(certContent, keyContent) + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to generate TLS certificate : %w", err) + } + + return cert, nil +} + // GetTruncatedCertificateName truncates the certificate name. func (c *Certificate) GetTruncatedCertificateName() string { certName := c.CertFile.String()