diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc989d2c..96f36e5ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +## [v2.0.4](https://github.com/containous/traefik/tree/v2.0.4) (2019-10-28) +[All Commits](https://github.com/containous/traefik/compare/v2.0.3...v2.0.4) + +Fixes releases system. +Same changelog as v2.0.3. + +## [v2.0.3](https://github.com/containous/traefik/tree/v2.0.3) (2019-10-28) +[All Commits](https://github.com/containous/traefik/compare/v2.0.2...v2.0.3) + +**Bug fixes:** +- **[acme,logs]** Use debug for log about skipping addition of cert ([#5641](https://github.com/containous/traefik/pull/5641) by [sylr](https://github.com/sylr)) +- **[file]** fix: add filename in the file provider logs. ([#5636](https://github.com/containous/traefik/pull/5636) by [ldez](https://github.com/ldez)) +- **[k8s,k8s/crd,k8s/ingress]** Remove unnecessary reload of the configuration. ([#5707](https://github.com/containous/traefik/pull/5707) by [ldez](https://github.com/ldez)) +- **[k8s,k8s/crd,k8s/ingress]** Fixing support for HTTPs backends with Kubernetes ExternalName services ([#5660](https://github.com/containous/traefik/pull/5660) by [kpeiruza](https://github.com/kpeiruza)) +- **[k8s,k8s/ingress]** Normalize service and router names for ingress. ([#5623](https://github.com/containous/traefik/pull/5623) by [ldez](https://github.com/ldez)) +- **[logs]** Set proxy protocol logger to DEBUG level ([#5712](https://github.com/containous/traefik/pull/5712) by [mmatur](https://github.com/mmatur)) +- **[middleware]** fix: add stacktrace when recover. ([#5654](https://github.com/containous/traefik/pull/5654) by [ldez](https://github.com/ldez)) +- **[tracing]** Let instana/go-sensor handle default agent host ([#5658](https://github.com/containous/traefik/pull/5658) by [sylr](https://github.com/sylr)) +- **[tracing]** fix: default tracing backend. ([#5717](https://github.com/containous/traefik/pull/5717) by [ldez](https://github.com/ldez)) +- fix: deep copy of passHostHeader on ServersLoadBalancer. ([#5720](https://github.com/containous/traefik/pull/5720) by [ldez](https://github.com/ldez)) + +**Documentation:** +- **[acme]** Fix acme storage file docker mounting example ([#5633](https://github.com/containous/traefik/pull/5633) by [jansauer](https://github.com/jansauer)) +- **[acme]** fix incorrect DNS reference ([#5666](https://github.com/containous/traefik/pull/5666) by [oskapt](https://github.com/oskapt)) +- **[logs]** Clarify unit of duration field in access log ([#5664](https://github.com/containous/traefik/pull/5664) by [Sarke](https://github.com/Sarke)) +- **[middleware]** Fix Security Headers Doc ([#5706](https://github.com/containous/traefik/pull/5706) by [FlorianPerrot](https://github.com/FlorianPerrot)) +- **[middleware]** Migration guide: pathprefixstrip migration ([#5600](https://github.com/containous/traefik/pull/5600) by [dduportal](https://github.com/dduportal)) +- **[middleware]** fix ForwardAuth tls.skipverify examples ([#5683](https://github.com/containous/traefik/pull/5683) by [remche](https://github.com/remche)) +- **[rules]** Add documentation about backtick for rule definition. ([#5714](https://github.com/containous/traefik/pull/5714) by [ldez](https://github.com/ldez)) +- **[webui]** Improve documentation of the router rules for API and dashboard ([#5625](https://github.com/containous/traefik/pull/5625) by [dduportal](https://github.com/dduportal)) +- doc: @ is not authorized in names definition. ([#5734](https://github.com/containous/traefik/pull/5734) by [ldez](https://github.com/ldez)) +- Remove obsolete v2 remark from README ([#5669](https://github.com/containous/traefik/pull/5669) by [dragetd](https://github.com/dragetd)) +- Fix spelling mistake: "founded" -> "found" ([#5674](https://github.com/containous/traefik/pull/5674) by [ocanty](https://github.com/ocanty)) +- fix typo for stripPrefix in tab File (YAML) ([#5694](https://github.com/containous/traefik/pull/5694) by [nalakawula](https://github.com/nalakawula)) +- Add example for changing the port used by traefik to connect to a service ([#5224](https://github.com/containous/traefik/pull/5224) by [robertbaker](https://github.com/robertbaker)) + +**Misc:** +- **[logs,middleware]** Cherry pick v1.7 into v2.0 ([#5735](https://github.com/containous/traefik/pull/5735) by [jbdoumenjou](https://github.com/jbdoumenjou)) + ## [v2.0.2](https://github.com/containous/traefik/tree/v2.0.2) (2019-10-09) [All Commits](https://github.com/containous/traefik/compare/v2.0.1...v2.0.2) @@ -70,6 +109,24 @@ - Add the router priority documentation ([#5481](https://github.com/containous/traefik/pull/5481) by [jbdoumenjou](https://github.com/jbdoumenjou)) - Improve the Migration Guide ([#5391](https://github.com/containous/traefik/pull/5391) by [jbdoumenjou](https://github.com/jbdoumenjou)) +## [v1.7.18](https://github.com/containous/traefik/tree/v1.7.18) (2019-09-23) +[All Commits](https://github.com/containous/traefik/compare/v1.7.17...v1.7.18) + +**Bug fixes:** +- **[go,security]** This version is compiled with [Go 1.12.10](https://groups.google.com/d/msg/golang-announce/cszieYyuL9Q/g4Z7pKaqAgAJ), which fixes a vulnerability in previous versions. See the [CVE](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16276) about it for more details. + +## [v1.7.17](https://github.com/containous/traefik/tree/v1.7.17) (2019-09-23) +[All Commits](https://github.com/containous/traefik/compare/v1.7.16...v1.7.17) + +**Bug fixes:** +- **[logs,middleware]** Avoid closing stdout when the accesslog handler is closed ([#5459](https://github.com/containous/traefik/pull/5459) by [nrwiersma](https://github.com/nrwiersma)) +- **[middleware]** Actually send header and code during WriteHeader, if needed ([#5404](https://github.com/containous/traefik/pull/5404) by [mpl](https://github.com/mpl)) + +**Documentation:** +- **[k8s]** Add note clarifying client certificate header ([#5362](https://github.com/containous/traefik/pull/5362) by [bradjones1](https://github.com/bradjones1)) +- **[webui]** Update docs links. ([#5412](https://github.com/containous/traefik/pull/5412) by [ldez](https://github.com/ldez)) +- Update Traefik image version. ([#5399](https://github.com/containous/traefik/pull/5399) by [ldez](https://github.com/ldez)) + ## [v2.0.0](https://github.com/containous/traefik/tree/v2.0.0) (2019-09-16) [All Commits](https://github.com/containous/traefik/compare/v2.0.0-alpha1...v2.0.0) @@ -414,6 +471,29 @@ - fix a service with one server .yaml example ([#5373](https://github.com/containous/traefik/pull/5373) by [zaverden](https://github.com/zaverden)) - fix: services configuration documentation. ([#5359](https://github.com/containous/traefik/pull/5359) by [ldez](https://github.com/ldez)) +## [v1.7.16](https://github.com/containous/traefik/tree/v1.7.16) (2019-09-13) +[All Commits](https://github.com/containous/traefik/compare/v1.7.15...v1.7.16) + +**Bug fixes:** +- **[middleware,websocket]** implement Flusher and Hijacker for codeCatcher ([#5376](https://github.com/containous/traefik/pull/5376) by [mpl](https://github.com/mpl)) + +## [v1.7.15](https://github.com/containous/traefik/tree/v1.7.15) (2019-09-12) +[All Commits](https://github.com/containous/traefik/compare/v1.7.14...v1.7.15) + +**Bug fixes:** +- **[authentication,k8s/ingress]** Kubernetes support for Auth.HeaderField ([#5235](https://github.com/containous/traefik/pull/5235) by [ErikWegner](https://github.com/ErikWegner)) +- **[k8s,k8s/ingress]** Finish kubernetes throttling refactoring ([#5269](https://github.com/containous/traefik/pull/5269) by [mpl](https://github.com/mpl)) +- **[k8s]** Throttle Kubernetes config refresh ([#4716](https://github.com/containous/traefik/pull/4716) by [benweissmann](https://github.com/benweissmann)) +- **[k8s]** Fix wrong handling of insecure tls auth forward ingress annotation ([#5319](https://github.com/containous/traefik/pull/5319) by [majkrzak](https://github.com/majkrzak)) +- **[middleware]** error pages: do not buffer response when it's not an error ([#5285](https://github.com/containous/traefik/pull/5285) by [mpl](https://github.com/mpl)) +- **[tls]** Consider default cert domain in certificate store ([#5353](https://github.com/containous/traefik/pull/5353) by [nrwiersma](https://github.com/nrwiersma)) +- **[tls]** Add TLS minversion constraint ([#5356](https://github.com/containous/traefik/pull/5356) by [dtomcej](https://github.com/dtomcej)) + +**Documentation:** +- **[acme]** Update Acme doc - Vultr Wildcard & Root ([#5320](https://github.com/containous/traefik/pull/5320) by [ddymko](https://github.com/ddymko)) +- **[consulcatalog]** Typo in basic auth usersFile label consul-catalog ([#5230](https://github.com/containous/traefik/pull/5230) by [pitan](https://github.com/pitan)) +- **[logs]** Improve Access Logs Documentation page ([#5238](https://github.com/containous/traefik/pull/5238) by [dduportal](https://github.com/dduportal)) + ## [v2.0.0-rc3](https://github.com/containous/traefik/tree/v2.0.0-rc3) (2019-09-10) [All Commits](https://github.com/containous/traefik/compare/v2.0.0-rc2...v2.0.0-rc3) diff --git a/Makefile b/Makefile index daf2cbae3..0608d8304 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ generate-crd: ## Create packages for the release release-packages: generate-webui build-dev-image rm -rf dist - $(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish + $(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish --timeout="60m" $(DOCKER_RUN_TRAEFIK_NOTTY) tar cfz dist/traefik-${VERSION}.src.tar.gz \ --exclude-vcs \ --exclude .idea \ diff --git a/README.md b/README.md index ac1ad3fbb..4c7574e8e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Pointing Traefik at your orchestrator should be the _only_ configuration step yo --- -:warning: Please be aware that the old configurations for Traefik v1.X are NOT compatible with the v2.X config as of now. If you're running v2, please ensure you are using a [v2 configuration](https://docs.traefik.io/). +:warning: Please be aware that the old configurations for Traefik v1.x are NOT compatible with the v2.x config as of now. If you're running v2, please ensure you are using a [v2 configuration](https://docs.traefik.io/). ## Overview @@ -87,10 +87,11 @@ You can access the simple HTML frontend of Traefik. ## Documentation -You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io). -A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io). +You can find the complete documentation of Traefik v2 at [https://docs.traefik.io](https://docs.traefik.io). -:warning: If you're testing out v2, please ensure you are using the [v2 documentation](https://docs.traefik.io/). +If you are using Traefik v1, you can find the complete documentation at [https://docs.traefik.io/v1.7/](https://docs.traefik.io/v1.7/) + +A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io). ## Support diff --git a/docs/content/getting-started/configuration-overview.md b/docs/content/getting-started/configuration-overview.md index 8a6b87a4c..581a60a03 100644 --- a/docs/content/getting-started/configuration-overview.md +++ b/docs/content/getting-started/configuration-overview.md @@ -16,8 +16,8 @@ The _dynamic configuration_ contains everything that defines how the requests ar This configuration can change and is seamlessly hot-reloaded, without any request interruption or connection loss. !!! warning "Incompatible Configuration" - Please be aware that the old configurations for Traefik v1.X are NOT compatible with the v2.X config as of now. - If you're testing out v2, please ensure you are using a v2 configuration. + Please be aware that the old configurations for Traefik v1.x are NOT compatible with the v2.x config as of now. + If you are running v2, please ensure you are using a v2 configuration. ## The Dynamic Configuration diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 1e3e35680..039c54dd2 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -382,7 +382,7 @@ ACME certificates can be stored in a JSON file that needs to have a `600` file m In Docker you can mount either the JSON file, or the folder containing it: ```bash -docker run -v "/my/host/acme.json:acme.json" traefik +docker run -v "/my/host/acme.json:/acme.json" traefik ``` ```bash diff --git a/docs/content/middlewares/forwardauth.md b/docs/content/middlewares/forwardauth.md index 39393e277..a20103af4 100644 --- a/docs/content/middlewares/forwardauth.md +++ b/docs/content/middlewares/forwardauth.md @@ -509,7 +509,8 @@ metadata: spec: forwardAuth: address: https://authserver.com/auth - insecureSkipVerify: true + tls: + insecureSkipVerify: true ``` ```yaml tab="Consul Catalog" @@ -531,7 +532,8 @@ labels: [http.middlewares] [http.middlewares.test-auth.forwardAuth] address = "https://authserver.com/auth" - insecureSkipVerify: true + [http.middlewares.test-auth.forwardAuth.tls] + insecureSkipVerify: true ``` ```yaml tab="File (YAML)" @@ -540,5 +542,6 @@ http: test-auth: forwardAuth: address: "https://authserver.com/auth" - insecureSkipVerify: true + tls: + insecureSkipVerify: true ``` diff --git a/docs/content/middlewares/headers.md b/docs/content/middlewares/headers.md index 708cf6d08..a5932c864 100644 --- a/docs/content/middlewares/headers.md +++ b/docs/content/middlewares/headers.md @@ -176,8 +176,8 @@ labels: ```toml tab="File (TOML)" [http.middlewares] [http.middlewares.testHeader.headers] - FrameDeny = true - SSLRedirect = true + frameDeny = true + sslRedirect = true ``` ```yaml tab="File (YAML)" @@ -185,8 +185,8 @@ http: middlewares: testHeader: headers: - FrameDeny: true - SSLRedirect: true + frameDeny: true + sslRedirect: true ``` ### CORS Headers diff --git a/docs/content/middlewares/passtlsclientcert.md b/docs/content/middlewares/passtlsclientcert.md index 6375da564..845be967a 100644 --- a/docs/content/middlewares/passtlsclientcert.md +++ b/docs/content/middlewares/passtlsclientcert.md @@ -245,7 +245,10 @@ PassTLSClientCert can add two headers to the request: - `X-Forwarded-Tls-Client-Cert-Info` that contains all the selected certificate information in an escaped string. !!! info - The headers are filled with escaped string so it can be safely placed inside a URL query. + + * The headers are filled with escaped string so it can be safely placed inside a URL query. + * These options only work accordingly to the [MutualTLS configuration](../https/tls.md#client-authentication-mtls). + That is to say, only the certificates that match the `clientAuth.clientAuthType` policy are passed. In the following example, you can see a complete certificate. We will use each part of it to explain the middleware options. diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md index 00fe99fe9..0a871e709 100644 --- a/docs/content/migration/v1-to-v2.md +++ b/docs/content/migration/v1-to-v2.md @@ -3,13 +3,13 @@ How to Migrate from Traefik v1 to Traefik v2. {: .subtitle } -The version 2 of Traefik introduces a number of breaking changes, +The version 2 of Traefik introduces a number of breaking changes, which require one to update their configuration when they migrate from v1 to v2. -The goal of this page is to recapitulate all of these changes, and in particular to give examples, +The goal of this page is to recapitulate all of these changes, and in particular to give examples, feature by feature, of how the configuration looked like in v1, and how it now looks like in v2. !!! info "Migration Helper" - + We created a tool to help during the migration: [traefik-migration-tool](https://github.com/containous/traefik-migration-tool) This tool allows to: @@ -32,7 +32,7 @@ Then any router can refer to an instance of the wanted middleware. !!! example "One frontend with basic auth and one backend, become one router, one service, and one basic auth middleware." !!! info "v1" - + ```yaml tab="Docker" labels: - "traefik.frontend.rule=Host:test.localhost;PathPrefix:/test" @@ -92,9 +92,9 @@ Then any router can refer to an instance of the wanted middleware. [backends.backend1.loadBalancer] method = "wrr" ``` - + !!! info "v2" - + ```yaml tab="Docker" labels: - "traefik.http.routers.router0.rule=Host(`bar.com`) && PathPrefix(`/test`)" @@ -103,7 +103,7 @@ Then any router can refer to an instance of the wanted middleware. ``` ```yaml tab="K8s IngressRoute" - # The definitions below require the definitions for the Middleware and IngressRoute kinds. + # The definitions below require the definitions for the Middleware and IngressRoute kinds. # https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition apiVersion: traefik.containo.us/v1alpha1 kind: Middleware @@ -155,7 +155,7 @@ Then any router can refer to an instance of the wanted middleware. [http.middlewares] [http.middlewares.auth.basicAuth] users = [ - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", ] ``` @@ -184,7 +184,7 @@ Then any router can refer to an instance of the wanted middleware. - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" ``` -## TLS configuration is now dynamic, per router. +## TLS Configuration Is Now Dynamic, per Router. TLS parameters used to be specified in the static configuration, as an entryPoint field. With Traefik v2, a new dynamic TLS section at the root contains all the desired TLS configurations. @@ -214,13 +214,13 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o certFile = "path/to/my.cert" keyFile = "path/to/my.key" ``` - + ```bash tab="CLI" --entryPoints='Name:web-secure Address::443 TLS:path/to/my.cert,path/to/my.key TLS.MinVersion:VersionTLS12 TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256' ``` - + !!! info "v2" - + ```toml tab="File (TOML)" # dynamic configuration [http.routers] @@ -250,7 +250,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", ] ``` - + ```yaml tab="File (YAML)" http: routers: @@ -275,9 +275,9 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ``` - + ```yaml tab="K8s IngressRoute" - # The definitions below require the definitions for the TLSOption and IngressRoute kinds. + # The definitions below require the definitions for the TLSOption and IngressRoute kinds. # https://docs.traefik.io/v2.0/providers/kubernetes-crd/#traefik-ingressroute-definition apiVersion: traefik.containo.us/v1alpha1 kind: TLSOption @@ -310,11 +310,11 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o - name: whoami port: 80 tls: - options: + options: name: mytlsoption namespace: default ``` - + ```yaml tab="Docker" labels: # myTLSOptions must be defined by another provider, in this instance in the File Provider. @@ -322,7 +322,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o - "traefik.http.routers.router0.tls.options=myTLSOptions@file" ``` -## HTTP to HTTPS Redirection is now configured on Routers +## HTTP to HTTPS Redirection Is Now Configured on Routers Previously on Traefik v1, the redirection was applied on an entry point or on a frontend. With Traefik v2 it is applied on a [Router](../routing/routers/index.md). @@ -350,14 +350,14 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd certFile = "examples/traefik.crt" keyFile = "examples/traefik.key" ``` - + ```bash tab="CLI" --entrypoints=Name:web Address::80 Redirect.EntryPoint:web-secure --entryPoints='Name:web-secure Address::443 TLS:path/to/my.cert,path/to/my.key' ``` - + !!! info "v2" - + ```yaml tab="Docker" labels: - traefik.http.routers.web.rule=Host(`foo.com`) @@ -454,9 +454,9 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd [[tls.certificates]] certFile = "/path/to/domain.cert" - keyFile = "/path/to/domain.key" + keyFile = "/path/to/domain.key" ``` - + ```yaml tab="File (YAML)" ## static configuration # traefik.yml @@ -506,7 +506,139 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd certificates: - certFile: /app/certs/server/server.pem keyFile: /app/certs/server/server.pem - ``` + ``` + +## Strip and Rewrite Path Prefixes + +With the new core notions of v2 (introduced earlier in the section +["Frontends and Backends Are Dead... Long Live Routers, Middlewares, and Services"](#frontends-and-backends-are-dead-long-live-routers-middlewares-and-services)), +transforming the URL path prefix of incoming requests is configured with [middlewares](../../middlewares/overview/), +after the routing step with [router rule `PathPrefix`](https://docs.traefik.io/v2.0/routing/routers/#rule). + +Use Case: Incoming requests to `http://company.org/admin` are forwarded to the webapplication "admin", +with the path `/admin` stripped, e.g. to `http://:/`. In this case, you must: + +* First, configure a router named `admin` with a rule matching at least the path prefix with the `PathPrefix` keyword, +* Then, define a middlware of type [`stripprefix`](../../middlewares/stripprefix/), which remove the prefix `/admin`, associated to the router `admin`. + +!!! example "Strip Path Prefix When Forwarding to Backend" + + !!! info "v1" + + ```yaml tab="Docker" + labels: + - "traefik.frontend.rule=Host:company.org;PathPrefixStrip:/admin" + ``` + + ```yaml tab="Kubernetes Ingress" + apiVersion: networking.k8s.io/v1beta1 + kind: Ingress + metadata: + name: traefik + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip + spec: + rules: + - host: company.org + http: + paths: + - path: /admin + backend: + serviceName: admin-svc + servicePort: admin + ``` + + ```toml tab="File (TOML)" + [frontends.admin] + [frontends.admin.routes.admin_1] + rule = "Host:company.org;PathPrefixStrip:/admin" + ``` + + !!! info "v2" + + ```yaml tab="Docker" + labels: + - "traefik.http.routers.admin.rule=Host(`company.org`) && PathPrefix(`/admin`)" + - "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin" + - "traefik.http.routers.web.middlewares=admin-stripprefix@docker" + ``` + + ```yaml tab="Kubernetes IngressRoute" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + metadata: + name: http-redirect-ingressRoute + namespace: admin-web + spec: + entryPoints: + - web + routes: + - match: Host(`company.org`) && PathPrefix(`/admin`) + kind: Rule + services: + - name: admin-svc + port: admin + middlewares: + - name: admin-stripprefix + --- + kind: Middleware + metadata: + name: admin-stripprefix + spec: + stripPrefix: + prefixes: + - /admin + ``` + + ```toml tab="File (TOML)" + ## Dynamic configuration + # dynamic-conf.toml + + [http.routers.router1] + rule = "Host(`company.org`) && PathPrefix(`/admin`)" + service = "admin-svc" + entrypoints = ["web"] + middlewares = ["admin-stripprefix"] + + [http.middlewares] + [http.middlewares.admin-stripprefix.stripPrefix] + prefixes = ["/admin"] + + # ... + ``` + + ```yaml tab="File (YAML)" + ## Dynamic Configuration + # dynamic-conf.yml + + # As YAML Configuration File + http: + routers: + admin: + service: admin-svc + middlewares: + - "admin-stripprefix" + rule: "Host(`company.org`) && PathPrefix(`/admin`)" + + middlewares: + admin-stripprefix: + stripPrefix: + prefixes: + - "/admin" + + # ... + ``` + +??? question "What About Other Path Transformations?" + + Instead of removing the path prefix with the [`stripprefix` middleware](../../middlewares/stripprefix/), you can also: + + * Add a path prefix with the [`addprefix` middleware](../../middlewares/addprefix/) + * Replace the complete path of the request with the [`replacepath` middleware](../../middlewares/replacepath/) + * ReplaceRewrite path using Regexp with the [`replacepathregex` middleware](../../middlewares/replacepathregex/) + * And a lot more on the [`middlewares` page](../../middlewares/overview/) ## ACME (LetsEncrypt) @@ -536,7 +668,7 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd [acme.httpChallenge] entryPoint = "web" ``` - + ```bash tab="CLI" --defaultentrypoints=web-secure,web --entryPoints=Name:web Address::80 Redirect.EntryPoint:web-secure @@ -547,9 +679,9 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd --acme.onHostRule=true --acme.httpchallenge.entrypoint=http ``` - + !!! info "v2" - + ```toml tab="File (TOML)" # static configuration [entryPoints] @@ -566,7 +698,7 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd # used during the challenge entryPoint = "web" ``` - + ```yaml tab="File (YAML)" entryPoints: web: @@ -583,8 +715,8 @@ To apply a redirection, one of the redirect middlewares, [RedirectRegex](../midd httpChallenge: # used during the challenge entryPoint: web - ``` - + ``` + ```bash tab="CLI" --entryPoints.web.address=":80" --entryPoints.websecure.address=":443" @@ -601,7 +733,7 @@ There is no more log configuration at the root level. !!! example "Simple log configuration" !!! info "v1" - + ```toml tab="File (TOML)" # static configuration logLevel = "DEBUG" @@ -610,15 +742,15 @@ There is no more log configuration at the root level. filePath = "/path/to/traefik.log" format = "json" ``` - + ```bash tab="CLI" --logLevel="DEBUG" --traefikLog.filePath="/path/to/traefik.log" --traefikLog.format="json" ``` - + !!! info "v2" - + ```toml tab="File (TOML)" # static configuration [log] @@ -626,15 +758,15 @@ There is no more log configuration at the root level. filePath = "/path/to/log-file.log" format = "json" ``` - + ```yaml tab="File (YAML)" # static configuration log: level: DEBUG filePath: /path/to/log-file.log format: json - ``` - + ``` + ```bash tab="CLI" --log.level="DEBUG" --log.filePath="/path/to/traefik.log" @@ -644,11 +776,11 @@ There is no more log configuration at the root level. ## Tracing Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is gone, you just have to set your [tracing configuration](../observability/tracing/overview.md). - + !!! example "Simple Jaeger tracing configuration" !!! info "v1" - + ```toml tab="File (TOML)" # static configuration [tracing] @@ -660,18 +792,18 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is samplingType = "const" localAgentHostPort = "12.0.0.1:6831" ``` - + ```bash tab="CLI" --tracing.backend="jaeger" --tracing.servicename="tracing" --tracing.jaeger.localagenthostport="12.0.0.1:6831" --tracing.jaeger.samplingparam="1.0" --tracing.jaeger.samplingserverurl="http://12.0.0.1:5778/sampling" - --tracing.jaeger.samplingtype="const" + --tracing.jaeger.samplingtype="const" ``` - + !!! info "v2" - + ```toml tab="File (TOML)" # static configuration [tracing] @@ -682,7 +814,7 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is samplingType = "const" localAgentHostPort = "12.0.0.1:6831" ``` - + ```yaml tab="File (YAML)" # static configuration tracing: @@ -692,8 +824,8 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is samplingServerURL: 'http://12.0.0.1:5778/sampling' samplingType: const localAgentHostPort: '12.0.0.1:6831' - ``` - + ``` + ```bash tab="CLI" --tracing.servicename="tracing" --tracing.jaeger.localagenthostport="12.0.0.1:6831" @@ -705,33 +837,33 @@ Traefik v2 retains OpenTracing support. The `backend` root option from the v1 is ## Metrics The v2 retains metrics tools and allows metrics to be configured for the entrypoints and/or services. -For a basic configuration, the [metrics configuration](../observability/metrics/overview.md) remains the same. +For a basic configuration, the [metrics configuration](../observability/metrics/overview.md) remains the same. !!! example "Simple Prometheus metrics configuration" !!! info "v1" - + ```toml tab="File (TOML)" # static configuration [metrics.prometheus] buckets = [0.1,0.3,1.2,5.0] entryPoint = "traefik" ``` - + ```bash tab="CLI" --metrics.prometheus.buckets=[0.1,0.3,1.2,5.0] --metrics.prometheus.entrypoint="traefik" ``` - + !!! info "v2" - + ```toml tab="File (TOML)" # static configuration [metrics.prometheus] buckets = [0.1,0.3,1.2,5.0] entryPoint = "metrics" ``` - + ```yaml tab="File (YAML)" # static configuration metrics: @@ -742,22 +874,22 @@ For a basic configuration, the [metrics configuration](../observability/metrics/ - 1.2 - 5 entryPoint: metrics - ``` - + ``` + ```bash tab="CLI" --metrics.prometheus.buckets=[0.1,0.3,1.2,5.0] --metrics.prometheus.entrypoint="metrics" ``` -## No more root level key/values +## No More Root Level Key/Values To avoid any source of confusion, there are no more configuration at the root level. Each root item has been moved to a related section or removed. !!! example "From root to dedicated section" - + !!! info "v1" - + ```toml tab="File (TOML)" # static configuration checkNewVersion = false @@ -772,7 +904,7 @@ Each root item has been moved to a related section or removed. defaultEntryPoints = ["web", "web-secure"] keepTrailingSlash = false ``` - + ```bash tab="CLI" --checknewversion=false --sendanonymoususage=true @@ -786,9 +918,9 @@ Each root item has been moved to a related section or removed. --defaultentrypoints="web","web-secure" --keeptrailingslash=true ``` - + !!! info "v2" - + ```toml tab="File (TOML)" # static configuration [global] @@ -804,9 +936,9 @@ Each root item has been moved to a related section or removed. maxIdleConnsPerHost = 42 [providers] - providersThrottleDuration = 42 + providersThrottleDuration = 42 ``` - + ```yaml tab="File (YAML)" # static configuration global: @@ -824,8 +956,8 @@ Each root item has been moved to a related section or removed. providers: providersThrottleDuration: 42 - ``` - + ``` + ```bash tab="CLI" --global.checknewversion=true --global.sendanonymoususage=true @@ -835,7 +967,7 @@ Each root item has been moved to a related section or removed. --serverstransport.maxidleconnsperhost=42 --providers.providersthrottleduration=42 ``` - + ## Dashboard You need to activate the API to access the [dashboard](../operations/dashboard.md). @@ -847,11 +979,11 @@ As the dashboard access is now secured by default you can either: !!! info "Dashboard with k8s and dedicated router" As `api@internal` is not a Kubernetes service, you have to use the file provider or the `insecure` API option. - + !!! example "Activate and access the dashboard" !!! info "v1" - + ```toml tab="File (TOML)" ## static configuration # traefik.toml @@ -868,14 +1000,14 @@ As the dashboard access is now secured by default you can either: [api] entryPoint = "web-secure" ``` - + ```bash tab="CLI" --entryPoints='Name:web-secure Address::443 TLS Auth.Basic.Users:test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/' --api ``` - + !!! info "v2" - + ```yaml tab="Docker" # dynamic configuration labels: @@ -916,7 +1048,7 @@ As the dashboard access is now secured by default you can either: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" ] ``` - + ```yaml tab="File (YAML)" ## static configuration # traefik.yaml @@ -952,8 +1084,8 @@ As the dashboard access is now secured by default you can either: basicAuth: users: - 'test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/' - ``` - + ``` + ## Providers Supported [providers](../providers/overview.md), for now: @@ -976,7 +1108,7 @@ Supported [providers](../providers/overview.md), for now: * [x] Rest * [ ] Zookeeper -## Some Tips You Should Known +## Some Tips You Should Know * Different sources of static configuration (file, CLI flags, ...) cannot be [mixed](../getting-started/configuration-overview.md#the-static-configuration). * Now, configuration elements can be referenced between different providers by using the provider namespace notation: `@`. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 418532f74..5a27efc17 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -180,7 +180,7 @@ accessLog: |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `StartUTC` | The time at which request processing started. | | `StartLocal` | The local time at which request processing started. | - | `Duration` | The total time taken by processing the response, including the origin server's time but not the log writing time. | + | `Duration` | The total time taken (in nanoseconds) by processing the response, including the origin server's time but not the log writing time. | | `FrontendName` | The name of the Traefik frontend. | | `BackendName` | The name of the Traefik backend. | | `BackendURL` | The URL of the Traefik backend. | diff --git a/docs/content/operations/.markdownlint.json b/docs/content/operations/.markdownlint.json new file mode 100644 index 000000000..3cd5e9a07 --- /dev/null +++ b/docs/content/operations/.markdownlint.json @@ -0,0 +1,4 @@ +{ + "extends": "../../.markdownlint.json", + "MD046": false +} diff --git a/docs/content/operations/api.md b/docs/content/operations/api.md index 51fc9859e..4b9391b39 100644 --- a/docs/content/operations/api.md +++ b/docs/content/operations/api.md @@ -14,7 +14,7 @@ In production, it should be at least secured by authentication and authorization A good sane default (non exhaustive) set of recommendations would be to apply the following protection mechanisms: -* At the transport level: +* At the transport level: NOT publicly exposing the API's port, keeping it restricted to internal networks (as in the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), applied to networks). @@ -23,13 +23,16 @@ would be to apply the following protection mechanisms: If you enable the API, a new special `service` named `api@internal` is created and can then be referenced in a router. -To enable the API handler: +To enable the API handler, use the following option on the +[static configuration](../getting-started/configuration-overview.md#the-static-configuration): ```toml tab="File (TOML)" +# Static Configuration [api] ``` ```yaml tab="File (YAML)" +# Static Configuration api: {} ``` @@ -37,11 +40,13 @@ api: {} --api=true ``` -And then you will be able to reference it like this: +And then define a routing configuration on Traefik itself with the +[dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration): ```yaml tab="Docker" +# Dynamic Configuration labels: - - "traefik.http.routers.api.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + - "traefik.http.routers.api.rule=Host(`traefik.domain.com`) - "traefik.http.routers.api.service=api@internal" - "traefik.http.routers.api.middlewares=auth" - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" @@ -57,40 +62,42 @@ labels: ```json tab="Marathon" "labels": { - "traefik.http.routers.api.rule": "PathPrefix(`/api`) || PathPrefix(`/dashboard`)" - "traefik.http.routers.api.service": "api@internal" - "traefik.http.routers.api.middlewares": "auth" + "traefik.http.routers.api.rule": "Host(`traefik.domain.com`)", + "traefik.http.routers.api.service": "api@internal", + "traefik.http.routers.api.middlewares": "auth", "traefik.http.middlewares.auth.basicauth.users": "test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" } ``` ```yaml tab="Rancher" -# Declaring the user list +# Dynamic Configuration labels: - - "traefik.http.routers.api.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + - "traefik.http.routers.api.rule=Host(`traefik.domain.com`) - "traefik.http.routers.api.service=api@internal" - "traefik.http.routers.api.middlewares=auth" - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" ``` ```toml tab="File (TOML)" +# Dynamic Configuration [http.routers.my-api] - rule="PathPrefix(`/api`) || PathPrefix(`/dashboard`)" + rule="Host(`traefik.domain.com`) service="api@internal" middlewares=["auth"] [http.middlewares.auth.basicAuth] users = [ - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", ] ``` ```yaml tab="File (YAML)" +# Dynamic Configuration http: routers: api: - rule: PathPrefix(`/api`) || PathPrefix(`/dashboard`) + rule: Host(`traefik.domain.com`) service: api@internal middlewares: - auth @@ -98,10 +105,32 @@ http: auth: basicAuth: users: - - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" ``` +??? warning "The router's [rule](../../routing/routers#rule) must catch requests for the URI path `/api`" + Using an "Host" rule is recommended, by catching all the incoming traffic on this host domain to the API. + However, you can also use "path prefix" rule or any combination or rules. + + ```bash tab="Host Rule" + # Matches http://traefik.domain.com, http://traefik.domain.com/api + # or http://traefik.domain.com/hello + rule = "Host(`traefik.domain.com`)" + ``` + + ```bash tab="Path Prefix Rule" + # Matches http://api.traefik.domain.com/api or http://domain.com/api + # but does not match http://api.traefik.domain.com/hello + rule = "PathPrefix(`/api`)" + ``` + + ```bash tab="Combination of Rules" + # Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard + # but does not match http://traefik.domain.com/hello + rule = "Host(`traefik.domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + ``` + ### `insecure` Enable the API in `insecure` mode, which means that the API will be available directly on the entryPoint named `traefik`. @@ -143,6 +172,9 @@ api: --api.dashboard=true ``` +!!! warning "With Dashboard enabled, the router [rule](../../routing/routers#rule) must catch requests for both `/api` and `/dashboard`" + Please check the [Dashboard documentation](./dashboard.md#dashboard-router-rule) to learn more about this and to get examples. + ### `debug` _Optional, Default=false_ diff --git a/docs/content/operations/dashboard.md b/docs/content/operations/dashboard.md index 9dc77bb5e..169e136f2 100644 --- a/docs/content/operations/dashboard.md +++ b/docs/content/operations/dashboard.md @@ -28,7 +28,8 @@ There are 2 ways to configure and access the dashboard: This is the **recommended** method. -Start by enabling the dashboard by using the following option from [Traefik's API](./api.md): +Start by enabling the dashboard by using the following option from [Traefik's API](./api.md) +on the [static configuration](../getting-started/configuration-overview.md#the-static-configuration): ```toml tab="File (TOML)" [api] @@ -59,17 +60,103 @@ api: --api.dashboard=true ``` -Then specify a router associated to the service `api@internal` to allow: +Then define a routing configuration on Traefik itself, +with a router attached to the service `api@internal` in the +[dynamic configuration](../getting-started/configuration-overview.md#the-dynamic-configuration), +to allow defining: -- Defining one or more security features through [middlewares](../middlewares/overview.md) +- One or more security features through [middlewares](../middlewares/overview.md) like authentication ([basicAuth](../middlewares/basicauth.md) , [digestAuth](../middlewares/digestauth.md), [forwardAuth](../middlewares/forwardauth.md)) or [whitelisting](../middlewares/ipwhitelist.md). -- Defining your own [HTTP routing rule](../../routing/routers/#rule) for accessing the dashboard, +- A [router rule](#dashboard-router-rule) for accessing the dashboard, through Traefik itself (sometimes referred as "Traefik-ception"). -Please visit the ["Configuration" section of the API documentation](./api.md#configuration) -to learn about configuring a router with the service `api@internal` and enabling the security features. +??? example "Dashboard Dynamic Configuration Examples" + + ```yaml tab="Docker" + # Dynamic Configuration + labels: + - "traefik.http.routers.api.rule=Host(`traefik.domain.com`) + - "traefik.http.routers.api.service=api@internal" + - "traefik.http.routers.api.middlewares=auth" + - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" + ``` + + ```json tab="Marathon" + "labels": { + "traefik.http.routers.api.rule": "Host(`traefik.domain.com`)", + "traefik.http.routers.api.service": "api@internal", + "traefik.http.routers.api.middlewares": "auth", + "traefik.http.middlewares.auth.basicauth.users": "test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" + } + ``` + + ```yaml tab="Rancher" + # Dynamic Configuration + labels: + - "traefik.http.routers.api.rule=Host(`traefik.domain.com`) + - "traefik.http.routers.api.service=api@internal" + - "traefik.http.routers.api.middlewares=auth" + - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" + ``` + + ```toml tab="File (TOML)" + # Dynamic Configuration + [http.routers.my-api] + rule="Host(`traefik.domain.com`) + service="api@internal" + middlewares=["auth"] + + [http.middlewares.auth.basicAuth] + users = [ + "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", + "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0", + ] + ``` + + ```yaml tab="File (YAML)" + # Dynamic Configuration + http: + routers: + api: + rule: Host(`traefik.domain.com`) + service: api@internal + middlewares: + - auth + middlewares: + auth: + basicAuth: + users: + - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" + - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0" + ``` + +### Dashboard Router Rule + +As underlined in the [documentation for the `api.dashboard` option](./api.md#dashboard), +the [router rule](../routing/routers/index.md#rule) defined for Traefik must match +the path prefixes `/api` and `/dashboard`. + +We recommend to use a "Host Based rule" as ```Host(`traefik.domain.com`)``` to match everything on the host domain, +or to make sure that the defined rule captures both prefixes: + +```bash tab="Host Rule" +# Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard +rule = "Host(`traefik.domain.com`)" +``` + +```bash tab="Path Prefix Rule" +# Matches http://traefik.domain.com/api , http://domain.com/api or http://traefik.domain.com/dashboard +# but does not match http://traefik.domain.com/hello +rule = "PathPrefix(`/api`) || PathPrefix(`/dashboard`)" +``` + +```bash tab="Combination of Rules" +# Matches http://traefik.domain.com/api or http://traefik.domain.com/dashboard +# but does not match http://traefik.domain.com/hello +rule = "Host(`traefik.domain.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" +``` ## Insecure Mode diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index 9e0715175..af39fc68b 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -128,7 +128,7 @@ Traefik requires access to the docker socket to get its dynamic configuration. ??? info "Resources about Docker's Security" - [KubeCon EU 2018 Keynote, Running with Scissors, from Liz Rice](https://www.youtube.com/watch?v=ltrV-Qmh3oY) - - [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container.html) + - [Don't expose the Docker socket (not even to a container)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container/) - [A thread on Stack Overflow about sharing the `/var/run/docker.sock` file](https://news.ycombinator.com/item?id=17983623) - [To DinD or not to DinD](https://blog.loof.fr/2018/01/to-dind-or-not-do-dind.html) diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 31939b3bb..f7456553c 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -580,7 +580,7 @@ Specifies the header name that will be used to store the trace ID. Settings for Instana. (Default: ```false```) `--tracing.instana.localagenthost`: -Set instana-agent's host that the reporter will used. (Default: ```localhost```) +Set instana-agent's host that the reporter will used. `--tracing.instana.localagentport`: Set instana-agent's port that the reporter will used. (Default: ```42699```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 976c9db10..91d8dcca5 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -580,7 +580,7 @@ Specifies the header name that will be used to store the trace ID. Settings for Instana. (Default: ```false```) `TRAEFIK_TRACING_INSTANA_LOCALAGENTHOST`: -Set instana-agent's host that the reporter will used. (Default: ```localhost```) +Set instana-agent's host that the reporter will used. `TRAEFIK_TRACING_INSTANA_LOCALAGENTPORT`: Set instana-agent's port that the reporter will used. (Default: ```42699```) diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index eade358c5..7af257d6b 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -12,16 +12,16 @@ Attach labels to your containers and let Traefik do the rest! ??? example "Configuring Docker & Deploying / Exposing Services" Enabling the docker provider - + ```toml tab="File (TOML)" [providers.docker] ``` - + ```yaml tab="File (YAML)" providers: docker: {} ``` - + ```bash tab="CLI" --providers.docker=true ``` @@ -37,6 +37,27 @@ Attach labels to your containers and let Traefik do the rest! - traefik.http.routers.my-container.rule=Host(`mydomain.com`) ``` +??? example "Specify a Custom Port for the Container" + + Forward requests for `http://mydomain.com` to `http://:12345`: + + ```yaml + version: "3" + services: + my-container: + # ... + labels: + - traefik.http.routers.my-container.rule=Host(`mydomain.com`) + # Tell Traefik to use the port 12345 to connect to `my-container` + - traefik.http.services.my-service.loadbalancer.server.port=12345 + ``` + + !!! important "Traefik Connecting to the Wrong Port: `HTTP/502 Gateway Error`" + By default, Traefik uses the first exposed port of a container. + + Setting the label `traefik.http.services.xxx.loadbalancer.server.port` + overrides that behavior. + ??? example "Configuring Docker Swarm & Deploying / Exposing Services" Enabling the docker provider (Swarm Mode) @@ -49,7 +70,7 @@ Attach labels to your containers and let Traefik do the rest! endpoint = "tcp://127.0.0.1:2377" swarmMode = true ``` - + ```yaml tab="File (YAML)" providers: docker: @@ -59,7 +80,7 @@ Attach labels to your containers and let Traefik do the rest! endpoint: "tcp://127.0.0.1:2375" swarmMode: true ``` - + ```bash tab="CLI" --providers.docker.endpoint="tcp://127.0.0.1:2375" --providers.docker.swarmMode=true @@ -85,7 +106,7 @@ Attach labels to your containers and let Traefik do the rest! ## Routing Configuration !!! info "Labels" - + - Labels are case insensitive. - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md). @@ -129,82 +150,84 @@ add labels starting with `traefik.http.routers..` and follo For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`mydomain.com`)```. +!!! warning "The character `@` is not authorized in the router name ``." + ??? info "`traefik.http.routers..rule`" - - See [rule](../routers/index.md#rule) for more information. - + + See [rule](../routers/index.md#rule) for more information. + ```yaml - "traefik.http.routers.myrouter.rule=Host(`mydomain.com`)" ``` ??? info "`traefik.http.routers..entrypoints`" - - See [entry points](../routers/index.md#entrypoints) for more information. - + + See [entry points](../routers/index.md#entrypoints) for more information. + ```yaml - "traefik.http.routers.myrouter.entrypoints=web,websecure" ``` ??? info "`traefik.http.routers..middlewares`" - - See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. - + + See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. + ```yaml - "traefik.http.routers.myrouter.middlewares=auth,prefix,cb" ``` ??? info "`traefik.http.routers..service`" - - See [rule](../routers/index.md#service) for more information. - + + See [rule](../routers/index.md#service) for more information. + ```yaml - "traefik.http.routers.myrouter.service=myservice" ``` ??? info "`traefik.http.routers..tls`" - + See [tls](../routers/index.md#tls) for more information. - + ```yaml - "traefik.http.routers.myrouter.tls=true" ``` ??? info "`traefik.http.routers..tls.certresolver`" - + See [certResolver](../routers/index.md#certresolver) for more information. - + ```yaml - "traefik.http.routers.myrouter.tls.certresolver=myresolver" ``` ??? info "`traefik.http.routers..tls.domains[n].main`" - + See [domains](../routers/index.md#domains) for more information. - + ```yaml - "traefik.http.routers.myrouter.tls.domains[0].main=foobar.com" ``` ??? info "`traefik.http.routers..tls.domains[n].sans`" - + See [domains](../routers/index.md#domains) for more information. - + ```yaml - "traefik.http.routers.myrouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com" ``` ??? info "`traefik.http.routers..tls.options`" - + See [options](../routers/index.md#options) for more information. - + ```yaml - "traefik.http.routers.myrouter.tls.options=foobar" ``` ??? info "`traefik.http.routers..priority`" - - See [options](../routers/index.md#priority) for more information. - + + See [priority](../routers/index.md#priority) for more information. + ```yaml - "traefik.http.routers.myrouter.priority=42" ``` @@ -217,125 +240,127 @@ add labels starting with `traefik.http.services..`, followe For example, to change the `passHostHeader` behavior, you'd add the label `traefik.http.services..loadbalancer.passhostheader=false`. +!!! warning "The character `@` is not authorized in the service name ``." + ??? info "`traefik.http.services..loadbalancer.server.port`" - + Registers a port. Useful when the container exposes multiples ports. - + Mandatory for Docker Swarm. - + ```yaml - "traefik.http.services.myservice.loadbalancer.server.port=8080" ``` ??? info "`traefik.http.services..loadbalancer.server.scheme`" - + Overrides the default scheme. - + ```yaml - "traefik.http.services.myservice.loadbalancer.server.scheme=http" ``` ??? info "`traefik.http.services..loadbalancer.passhostheader`" - + See [pass Host header](../services/index.md#pass-host-header) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.passhostheader=true" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.headers.`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.headers.X-Foo=foobar" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.hostname`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.hostname=foobar.com" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.interval`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.path=/foo" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.port`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.port=42" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.scheme`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.scheme=http" ``` ??? info "`traefik.http.services..loadbalancer.healthcheck.timeout`" - + See [health check](../services/index.md#health-check) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.healthcheck.timeout=10" ``` ??? info "`traefik.http.services..loadbalancer.sticky`" - + See [sticky sessions](../services/index.md#sticky-sessions) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.sticky=true" ``` ??? info "`traefik.http.services..loadbalancer.sticky.cookie.httponly`" - + See [sticky sessions](../services/index.md#sticky-sessions) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.httponly=true" ``` ??? info "`traefik.http.services..loadbalancer.sticky.cookie.name`" - + See [sticky sessions](../services/index.md#sticky-sessions) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.name=foobar" ``` ??? info "`traefik.http.services..loadbalancer.sticky.cookie.secure`" - + See [sticky sessions](../services/index.md#sticky-sessions) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.sticky.cookie.secure=true" ``` ??? info "`traefik.http.services..loadbalancer.responseforwarding.flushinterval`" - + See [response forwarding](../services/index.md#response-forwarding) for more information. - + ```yaml - "traefik.http.services.myservice.loadbalancer.responseforwarding.flushinterval=10" ``` @@ -350,6 +375,8 @@ you'd write `traefik.http.middlewares.my-redirect.redirectscheme.scheme=https`. More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). +!!! warning "The character `@` is not authorized in the middleware name." + ??? example "Declaring and Referencing a Middleware" ```yaml @@ -391,73 +418,73 @@ You can declare TCP Routers and/or Services using labels. #### TCP Routers ??? info "`traefik.tcp.routers..entrypoints`" - + See [entry points](../routers/index.md#entrypoints_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.entrypoints=ep1,ep2" ``` ??? info "`traefik.tcp.routers..rule`" - + See [rule](../routers/index.md#rule_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.rule=HostSNI(`myhost.com`)" ``` ??? info "`traefik.tcp.routers..service`" - + See [service](../routers/index.md#services) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.service=myservice" ``` ??? info "`traefik.tcp.routers..tls`" - + See [TLS](../routers/index.md#tls_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.tls=true" ``` ??? info "`traefik.tcp.routers..tls.certresolver`" - + See [certResolver](../routers/index.md#certresolver_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.tls.certresolver=myresolver" ``` ??? info "`traefik.tcp.routers..tls.domains[n].main`" - + See [domains](../routers/index.md#domains_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.tls.domains[0].main=foobar.com" ``` ??? info "`traefik.tcp.routers..tls.domains[n].sans`" - + See [domains](../routers/index.md#domains_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.tls.domains[0].sans=test.foobar.com,dev.foobar.com" ``` ??? info "`traefik.tcp.routers..tls.options`" - + See [options](../routers/index.md#options_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.tls.options=mysoptions" ``` ??? info "`traefik.tcp.routers..tls.passthrough`" - + See [TLS](../routers/index.md#tls_1) for more information. - + ```yaml - "traefik.tcp.routers.mytcprouter.tls.passthrough=true" ``` @@ -465,17 +492,17 @@ You can declare TCP Routers and/or Services using labels. #### TCP Services ??? info "`traefik.tcp.services..loadbalancer.server.port`" - + Registers a port of the application. - + ```yaml - "traefik.tcp.services.mytcpservice.loadbalancer.server.port=423" ``` ??? info "`traefik.tcp.services..loadbalancer.terminationdelay`" - + See [termination delay](../services/index.md#termination-delay) for more information. - + ```yaml - "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100" ``` diff --git a/docs/content/routing/providers/marathon.md b/docs/content/routing/providers/marathon.md index 10478c39e..f8a50ac5f 100644 --- a/docs/content/routing/providers/marathon.md +++ b/docs/content/routing/providers/marathon.md @@ -52,6 +52,8 @@ add labels starting with `traefik.http.routers.{router-name-of-your-choice}.` an For example, to change the routing rule, you could add the label ```"traefik.http.routers.routername.rule": "Host(`mydomain.com`)"```. +!!! warning "The character `@` is not authorized in the router name ``." + ??? info "`traefik.http.routers..rule`" See [rule](../routers/index.md#rule) for more information. @@ -126,7 +128,7 @@ For example, to change the routing rule, you could add the label ```"traefik.htt ??? info "`traefik.http.routers..priority`" - See [options](../routers/index.md#priority) for more information. + See [priority](../routers/index.md#priority) for more information. ```json "traefik.http.routers.myrouter.priority": "42" @@ -139,6 +141,8 @@ add labels starting with `traefik.http.services.{service-name-of-your-choice}.`, For example, to change the passHostHeader behavior, you'd add the label `"traefik.http.services.servicename.loadbalancer.passhostheader": "false"`. +!!! warning "The character `@` is not authorized in the service name ``." + ??? info "`traefik.http.services..loadbalancer.server.port`" Registers a port. @@ -268,6 +272,8 @@ For example, to declare a middleware [`redirectscheme`](../../middlewares/redire More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). +!!! warning "The character `@` is not authorized in the middleware name." + ??? example "Declaring and Referencing a Middleware" ```json diff --git a/docs/content/routing/providers/rancher.md b/docs/content/routing/providers/rancher.md index 4adb45614..f8255b0ac 100644 --- a/docs/content/routing/providers/rancher.md +++ b/docs/content/routing/providers/rancher.md @@ -57,6 +57,8 @@ To update the configuration of the Router automatically attached to the containe For example, to change the rule, you could add the label ```traefik.http.routers.my-container.rule=Host(`mydomain.com`)```. +!!! warning "The character `@` is not authorized in the router name ``." + ??? info "`traefik.http.routers..rule`" See [rule](../routers/index.md#rule) for more information. @@ -131,7 +133,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..priority`" - See [options](../routers/index.md#priority) for more information. + See [priority](../routers/index.md#priority) for more information. ```yaml - "traefik.http.routers.myrouter.priority=42" @@ -145,6 +147,8 @@ add labels starting with `traefik.http.services.{name-of-your-choice}.`, followe For example, to change the `passHostHeader` behavior, you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.passhostheader=false`. +!!! warning "The character `@` is not authorized in the service name ``." + ??? info "`traefik.http.services..loadbalancer.server.port`" Registers a port. @@ -274,6 +278,8 @@ For example, to declare a middleware [`redirectscheme`](../../middlewares/redire More information about available middlewares in the dedicated [middlewares section](../../middlewares/overview.md). +!!! warning "The character `@` is not authorized in the middleware name." + ??? example "Declaring and Referencing a Middleware" ```yaml diff --git a/docs/content/routing/providers/service-by-label.md b/docs/content/routing/providers/service-by-label.md index bd16fd43e..611f1ad2e 100644 --- a/docs/content/routing/providers/service-by-label.md +++ b/docs/content/routing/providers/service-by-label.md @@ -7,9 +7,8 @@ There are, however, exceptions when using label-based configurations: and a label defines a service (e.g. implicitly through a loadbalancer server port value), but the router does not specify any service, then that service is automatically assigned to the router. -1. If a label defines a router (e.g. through a router Rule) -but no service is defined, then a service is automatically created -and assigned to the router. +1. If a label defines a router (e.g. through a router Rule) but no service is defined, +then a service is automatically created and assigned to the router. !!! info "" As one would expect, in either of these cases, if in addition a service is specified for the router, diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 22186a5c0..4e00a72cc 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -84,6 +84,8 @@ In the process, routers may use pieces of [middleware](../../middlewares/overvie ## Configuring HTTP Routers +!!! warning "The character `@` is not authorized in the router name" + ### EntryPoints If not specified, HTTP routers will accept requests from all defined entry points. @@ -203,9 +205,14 @@ If you want to limit the router scope to a set of entry points, set the `entryPo ### Rule -Rules are a set of matchers that determine if a particular request matches specific criteria. +Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria. If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service. +??? tip "Backticks or Quotes?" + To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`. + + Single quotes `'` are not accepted as values are [Golang's String Literals](https://golang.org/ref/spec#String_literals). + !!! example "Host is traefik.io" ```toml @@ -337,6 +344,8 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul You can attach a list of [middlewares](../../middlewares/overview.md) to each HTTP router. The middlewares will take effect only if the rule matches, and before forwarding the request to the service. +!!! warning "The character `@` is not authorized in the middleware name." + !!! tip "Middlewares order" Middlewares are applied in the same order as their declaration in **router**. @@ -376,6 +385,8 @@ but there are exceptions for label-based providers. See the specific [docker](../providers/docker.md#service-definition), [rancher](../providers/rancher.md#service-definition), or [marathon](../providers/marathon.md#service-definition) documentation. +!!! warning "The character `@` is not authorized in the middleware name." + !!! important "HTTP routers can only target HTTP services (not TCP services)." ### TLS @@ -624,6 +635,8 @@ The [supported `provider` table](../../https/acme.md#providers) indicates if the ## Configuring TCP Routers +!!! warning "The character `@` is not authorized in the router name" + ### General If both HTTP routers and TCP routers listen to the same entry points, the TCP routers will apply *before* the HTTP routers. diff --git a/docs/content/routing/services/.markdownlint.json b/docs/content/routing/services/.markdownlint.json index e803880c0..dd98ee65a 100644 --- a/docs/content/routing/services/.markdownlint.json +++ b/docs/content/routing/services/.markdownlint.json @@ -1,4 +1,5 @@ { "extends": "../../../.markdownlint.json", - "MD024": false + "MD024": false, + "MD046": false } diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 2e8b4e53e..0b582918d 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -5,9 +5,9 @@ Configuring How to Reach the Services ![services](../../assets/img/services.png) -The `Services` are responsible for configuring how to reach the actual services that will eventually handle the incoming requests. +The `Services` are responsible for configuring how to reach the actual services that will eventually handle the incoming requests. -## Configuration Example +## Configuration Examples ??? example "Declaring an HTTP Service with Two Servers -- Using the [File Provider](../../providers/file.md)" @@ -17,11 +17,11 @@ The `Services` are responsible for configuring how to reach the actual services [http.services.my-service.loadBalancer] [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-1/" + url = "http://:/" [[http.services.my-service.loadBalancer.servers]] - url = "http://private-ip-server-2/" + url = "http://:/" ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -29,8 +29,8 @@ The `Services` are responsible for configuring how to reach the actual services my-service: loadBalancer: servers: - - url: "http://private-ip-server-1/" - - url: "http://private-ip-server-2/" + - url: "http://:/" + - url: "http://:/" ``` ??? example "Declaring a TCP Service with Two Servers -- Using the [File Provider](../../providers/file.md)" @@ -40,26 +40,28 @@ The `Services` are responsible for configuring how to reach the actual services [tcp.services] [tcp.services.my-service.loadBalancer] [[tcp.services.my-service.loadBalancer.servers]] - address = "xx.xx.xx.xx:xx" + address = ":" [[tcp.services.my-service.loadBalancer.servers]] - address = "xx.xx.xx.xx:xx" + address = ":" ``` - + ```yaml tab="YAML" tcp: services: my-service: - loadBalancer: + loadBalancer: servers: - - address: "xx.xx.xx.xx:xx" - - address: "xx.xx.xx.xx:xx" + - address: ":" + - address: ":" ``` ## Configuring HTTP Services ### Servers Load Balancer -The load balancers are able to load balance the requests between multiple instances of your programs. +The load balancers are able to load balance the requests between multiple instances of your programs. + +Each service has a load-balancer, even if there is only one server to forward traffic to. ??? example "Declaring a Service with Two Servers (with Load Balancing) -- Using the [File Provider](../../providers/file.md)" @@ -87,10 +89,10 @@ The load balancers are able to load balance the requests between multiple instan #### Servers Servers declare a single instance of your program. -The `url` option point to a specific instance. +The `url` option point to a specific instance. !!! info "" - Paths in the servers' `url` have no effect. + Paths in the servers' `url` have no effect. If you want the requests to be sent to a specific path on your servers, configure your [`routers`](../routers/index.md) to use a corresponding [middleware](../../middlewares/overview.md) (e.g. the [AddPrefix](../../middlewares/addprefix.md) or [ReplacePath](../../middlewares/replacepath.md)) middlewares. @@ -103,7 +105,7 @@ The `url` option point to a specific instance. [[http.services.my-service.loadBalancer.servers]] url = "http://private-ip-server-1/" ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -142,21 +144,21 @@ For now, only round robin load balancing is supported: ``` #### Sticky sessions - + When sticky sessions are enabled, a cookie is set on the initial request to track which server handles the first response. On subsequent requests, the client is forwarded to the same server. !!! info "Stickiness & Unhealthy Servers" - + If the server specified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server). -!!! info "Cookie Name" - +!!! info "Cookie Name" + The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`). !!! info "Secure & HTTPOnly flags" - By default, the affinity cookie is created without those flags. One however can change that through configuration. + By default, the affinity cookie is created without those flags. One however can change that through configuration. ??? example "Adding Stickiness -- Using the [File Provider](../../providers/file.md)" @@ -166,7 +168,7 @@ On subsequent requests, the client is forwarded to the same server. [http.services.my-service] [http.services.my-service.loadBalancer.sticky.cookie] ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -223,8 +225,8 @@ Below are the available options for the health check mechanism: The interval must be greater than the timeout. If configuration doesn't reflect this, the interval will be set to timeout + 1 second. !!! info "Recovering Servers" - - Traefik keeps monitoring the health of unhealthy servers. + + Traefik keeps monitoring the health of unhealthy servers. If a server has recovered (returning `2xx` -> `3xx` responses again), it will be added back to the load balacer rotation pool. ??? example "Custom Interval & Timeout -- Using the [File Provider](../../providers/file.md)" @@ -261,7 +263,7 @@ Below are the available options for the health check mechanism: path = "/health" port = 8080 ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -283,7 +285,7 @@ Below are the available options for the health check mechanism: path = "/health" scheme = "http" ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -308,7 +310,7 @@ Below are the available options for the health check mechanism: My-Custom-Header = "foo" My-Header = "bar" ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -337,7 +339,7 @@ By default, `passHostHeader` is true. [http.services.Service01.loadBalancer] passHostHeader = false ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -346,7 +348,7 @@ By default, `passHostHeader` is true. loadBalancer: passHostHeader: false ``` - + #### Response Forwarding This section is about configuring how Traefik forwards the response from the backend server to the client. @@ -358,7 +360,7 @@ Below are the available options for the Response Forwarding mechanism: A negative value means to flush immediately after each write to the client. The FlushInterval is ignored when ReverseProxy recognizes a response as a streaming response; for such responses, writes are flushed to the client immediately. - + ??? example "Using a custom FlushInterval -- Using the [File Provider](../../providers/file.md)" ```toml tab="TOML" @@ -368,7 +370,7 @@ Below are the available options for the Response Forwarding mechanism: [http.services.Service-1.loadBalancer.responseForwarding] flushInterval = "1s" ``` - + ```yaml tab="YAML" ## Dynamic configuration http: @@ -378,7 +380,7 @@ Below are the available options for the Response Forwarding mechanism: responseForwarding: flushInterval: 1s ``` - + ### Weighted Round Robin (service) The WRR is able to load balance the requests between multiple services based on weights. @@ -539,7 +541,7 @@ The `address` option (IP:Port) point to a specific instance. my-service: loadBalancer: servers: - address: "xx.xx.xx.xx:xx" + - address: "xx.xx.xx.xx:xx" ``` #### Termination Delay diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index c26946223..0eb47705b 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -101,4 +101,4 @@ curl [-k] https://your.domain.com/tls curl [-k] http://your.domain.com:8000/notls ``` -Note that you'll have to use `-k` as long as you're using the staging server of Let's Encrypt, since it is not in the root DNS servers. +Note that you'll have to use `-k` as long as you're using the staging server of Let's Encrypt, since it is not an authorized certificate authority on systems where it hasn't been manually added. diff --git a/docs/scripts/verify.sh b/docs/scripts/verify.sh index 5fe02c155..b7830b24c 100755 --- a/docs/scripts/verify.sh +++ b/docs/scripts/verify.sh @@ -21,7 +21,7 @@ find "${PATH_TO_SITE}" -type f -not -path "/app/site/theme/*" \ --check_external_hash \ --alt_ignore="/traefik.logo.png/" \ --http_status_ignore="0,500,501,503" \ - --url_ignore="/https://groups.google.com/a/traefik.io/forum/#!forum/security/,/localhost:/,/127.0.0.1:/,/fonts.gstatic.com/,/.minikube/,/github.com\/containous\/traefik\/*edit*/,/github.com\/containous\/traefik\/$/,/docs.traefik.io/,/github\.com\/golang\/oauth2\/blob\/36a7019397c4c86cf59eeab3bc0d188bac444277\/.+/" \ + --url_ignore="/https://groups.google.com/a/traefik.io/forum/#!forum/security/,/localhost:/,/127.0.0.1:/,/fonts.gstatic.com/,/.minikube/,/github.com\/containous\/traefik\/*edit*/,/github.com\/containous\/traefik\/$/,/docs.traefik.io/,/github\.com\/golang\/oauth2\/blob\/36a7019397c4c86cf59eeab3bc0d188bac444277\/.+/,/www.akamai.com/" \ '{}' 1>/dev/null ## HTML-proofer options at https://github.com/gjtorikian/html-proofer#configuration diff --git a/exp.Dockerfile b/exp.Dockerfile index b12c512b8..d13f64a7a 100644 --- a/exp.Dockerfile +++ b/exp.Dockerfile @@ -1,5 +1,5 @@ # WEBUI -FROM node:8.15.0 as webui +FROM node:12.11 as webui ENV WEBUI_DIR /src/webui RUN mkdir -p $WEBUI_DIR @@ -7,7 +7,7 @@ RUN mkdir -p $WEBUI_DIR COPY ./webui/ $WEBUI_DIR/ WORKDIR $WEBUI_DIR -RUN yarn install +RUN npm install RUN npm run build diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 36635949d..5e3285895 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -973,6 +973,11 @@ func (in *ServersLoadBalancer) DeepCopyInto(out *ServersLoadBalancer) { *out = new(HealthCheck) (*in).DeepCopyInto(*out) } + if in.PassHostHeader != nil { + in, out := &in.PassHostHeader, &out.PassHostHeader + *out = new(bool) + **out = **in + } if in.ResponseForwarding != nil { in, out := &in.ResponseForwarding, &out.ResponseForwarding *out = new(ResponseForwarding) diff --git a/pkg/hostresolver/hostresolver.go b/pkg/hostresolver/hostresolver.go deleted file mode 100644 index 2a7c7dcee..000000000 --- a/pkg/hostresolver/hostresolver.go +++ /dev/null @@ -1,123 +0,0 @@ -package hostresolver - -import ( - "fmt" - "net" - "sort" - "strings" - "time" - - "github.com/containous/traefik/v2/pkg/log" - "github.com/miekg/dns" - "github.com/patrickmn/go-cache" -) - -type cnameResolv struct { - TTL time.Duration - Record string -} - -type byTTL []*cnameResolv - -func (a byTTL) Len() int { return len(a) } -func (a byTTL) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byTTL) Less(i, j int) bool { return a[i].TTL > a[j].TTL } - -// Resolver used for host resolver -type Resolver struct { - CnameFlattening bool - ResolvConfig string - ResolvDepth int - cache *cache.Cache -} - -// CNAMEFlatten check if CNAME record exists, flatten if possible -func (hr *Resolver) CNAMEFlatten(host string) (string, string) { - if hr.cache == nil { - hr.cache = cache.New(30*time.Minute, 5*time.Minute) - } - - result := []string{host} - request := host - - value, found := hr.cache.Get(host) - if found { - result = strings.Split(value.(string), ",") - } else { - var cacheDuration = 0 * time.Second - - for depth := 0; depth < hr.ResolvDepth; depth++ { - resolv, err := cnameResolve(request, hr.ResolvConfig) - if err != nil { - log.Error(err) - break - } - if resolv == nil { - break - } - - result = append(result, resolv.Record) - if depth == 0 { - cacheDuration = resolv.TTL - } - request = resolv.Record - } - - if err := hr.cache.Add(host, strings.Join(result, ","), cacheDuration); err != nil { - log.Error(err) - } - } - - return result[0], result[len(result)-1] -} - -// cnameResolve resolves CNAME if exists, and return with the highest TTL -func cnameResolve(host string, resolvPath string) (*cnameResolv, error) { - config, err := dns.ClientConfigFromFile(resolvPath) - if err != nil { - return nil, fmt.Errorf("invalid resolver configuration file: %s", resolvPath) - } - - client := &dns.Client{Timeout: 30 * time.Second} - - m := &dns.Msg{} - m.SetQuestion(dns.Fqdn(host), dns.TypeCNAME) - - var result []*cnameResolv - for _, server := range config.Servers { - tempRecord, err := getRecord(client, m, server, config.Port) - if err != nil { - log.Errorf("Failed to resolve host %s: %v", host, err) - continue - } - result = append(result, tempRecord) - } - - if len(result) == 0 { - return nil, nil - } - - sort.Sort(byTTL(result)) - return result[0], nil -} - -func getRecord(client *dns.Client, msg *dns.Msg, server string, port string) (*cnameResolv, error) { - resp, _, err := client.Exchange(msg, net.JoinHostPort(server, port)) - if err != nil { - return nil, fmt.Errorf("exchange error for server %s: %v", server, err) - } - - if resp == nil || len(resp.Answer) == 0 { - return nil, fmt.Errorf("empty answer for server %s", server) - } - - rr, ok := resp.Answer[0].(*dns.CNAME) - if !ok { - return nil, fmt.Errorf("invalid response type for server %s", server) - } - - return &cnameResolv{ - TTL: time.Duration(rr.Hdr.Ttl) * time.Second, - Record: strings.TrimSuffix(rr.Target, "."), - }, nil -} diff --git a/pkg/hostresolver/hostresolver_test.go b/pkg/hostresolver/hostresolver_test.go deleted file mode 100644 index ee6029f7d..000000000 --- a/pkg/hostresolver/hostresolver_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package hostresolver - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCNAMEFlatten(t *testing.T) { - testCase := []struct { - desc string - resolvFile string - domain string - expectedDomain string - isCNAME bool - }{ - { - desc: "host request is CNAME record", - resolvFile: "/etc/resolv.conf", - domain: "www.github.com", - expectedDomain: "github.com", - isCNAME: true, - }, - { - desc: "resolve file not found", - resolvFile: "/etc/resolv.oops", - domain: "www.github.com", - expectedDomain: "www.github.com", - isCNAME: false, - }, - { - desc: "host request is not CNAME record", - resolvFile: "/etc/resolv.conf", - domain: "github.com", - expectedDomain: "github.com", - isCNAME: false, - }, - } - - for _, test := range testCase { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - hostResolver := &Resolver{ - ResolvConfig: test.resolvFile, - ResolvDepth: 5, - } - - reqH, flatH := hostResolver.CNAMEFlatten(test.domain) - assert.Equal(t, test.domain, reqH) - assert.Equal(t, test.expectedDomain, flatH) - - if test.isCNAME { - assert.NotEqual(t, test.expectedDomain, reqH) - } else { - assert.Equal(t, test.expectedDomain, reqH) - } - }) - } -} diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index 1f6b80b4b..53bebac6e 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -3,6 +3,7 @@ package accesslog import ( "context" "fmt" + "io" "net" "net/http" "net/url" @@ -31,6 +32,19 @@ const ( JSONFormat string = "json" ) +type noopCloser struct { + *os.File +} + +func (n noopCloser) Write(p []byte) (int, error) { + return n.File.Write(p) +} + +func (n noopCloser) Close() error { + // noop + return nil +} + type handlerParams struct { logDataTable *LogData crr *captureRequestReader @@ -41,7 +55,7 @@ type handlerParams struct { type Handler struct { config *types.AccessLog logger *logrus.Logger - file *os.File + file io.WriteCloser mu sync.Mutex httpCodeRanges types.HTTPCodeRanges logHandlerChan chan handlerParams @@ -59,7 +73,7 @@ func WrapHandler(handler *Handler) alice.Constructor { // NewHandler creates a new Handler. func NewHandler(config *types.AccessLog) (*Handler, error) { - file := os.Stdout + var file io.WriteCloser = noopCloser{os.Stdout} if len(config.FilePath) > 0 { f, err := openAccessLogFile(config.FilePath) if err != nil { @@ -213,14 +227,15 @@ func (h *Handler) Close() error { // Rotate closes and reopens the log file to allow for rotation by an external source. func (h *Handler) Rotate() error { - var err error - - if h.file != nil { - defer func(f *os.File) { - f.Close() - }(h.file) + if h.config.FilePath == "" { + return nil } + if h.file != nil { + defer func(f io.Closer) { _ = f.Close() }(h.file) + } + + var err error h.file, err = os.OpenFile(h.config.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0664) if err != nil { return err diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index 72c70a631..ee50f6dda 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -22,7 +22,10 @@ import ( ) // Compile time validation that the response recorder implements http interfaces correctly. -var _ middlewares.Stateful = &responseRecorderWithCloseNotify{} +var ( + _ middlewares.Stateful = &responseRecorderWithCloseNotify{} + _ middlewares.Stateful = &codeCatcherWithCloseNotify{} +) const ( typeName = "customError" @@ -80,25 +83,29 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } - recorder := newResponseRecorder(ctx, rw) - c.next.ServeHTTP(recorder, req) + catcher := newCodeCatcher(rw, c.httpCodeRanges) + c.next.ServeHTTP(catcher, req) + if !catcher.isFilteredCode() { + return + } // check the recorder code against the configured http status code ranges + code := catcher.getCode() for _, block := range c.httpCodeRanges { - if recorder.GetCode() >= block[0] && recorder.GetCode() <= block[1] { - logger.Errorf("Caught HTTP Status Code %d, returning error page", recorder.GetCode()) + if code >= block[0] && code <= block[1] { + logger.Errorf("Caught HTTP Status Code %d, returning error page", code) var query string if len(c.backendQuery) > 0 { query = "/" + strings.TrimPrefix(c.backendQuery, "/") - query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1) + query = strings.Replace(query, "{status}", strconv.Itoa(code), -1) } pageReq, err := newRequest(backendURL + query) if err != nil { logger.Error(err) - rw.WriteHeader(recorder.GetCode()) - _, err = fmt.Fprint(rw, http.StatusText(recorder.GetCode())) + rw.WriteHeader(code) + _, err = fmt.Fprint(rw, http.StatusText(code)) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) } @@ -111,7 +118,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { c.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context())) utils.CopyHeaders(rw.Header(), recorderErrorPage.Header()) - rw.WriteHeader(recorder.GetCode()) + rw.WriteHeader(code) if _, err = rw.Write(recorderErrorPage.GetBody().Bytes()); err != nil { logger.Error(err) @@ -119,14 +126,6 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } } - - // did not catch a configured status code so proceed with the request - utils.CopyHeaders(rw.Header(), recorder.Header()) - rw.WriteHeader(recorder.GetCode()) - _, err := rw.Write(recorder.GetBody().Bytes()) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - } } func newRequest(baseURL string) (*http.Request, error) { @@ -144,6 +143,132 @@ func newRequest(baseURL string) (*http.Request, error) { return req, nil } +type responseInterceptor interface { + http.ResponseWriter + http.Flusher + getCode() int + isFilteredCode() bool +} + +// codeCatcher is a response writer that detects as soon as possible whether the +// response is a code within the ranges of codes it watches for. If it is, it +// simply drops the data from the response. Otherwise, it forwards it directly to +// the original client (its responseWriter) without any buffering. +type codeCatcher struct { + headerMap http.Header + code int + httpCodeRanges types.HTTPCodeRanges + firstWrite bool + caughtFilteredCode bool + responseWriter http.ResponseWriter + headersSent bool +} + +type codeCatcherWithCloseNotify struct { + *codeCatcher +} + +// CloseNotify returns a channel that receives at most a +// single value (true) when the client connection has gone away. +func (cc *codeCatcherWithCloseNotify) CloseNotify() <-chan bool { + return cc.responseWriter.(http.CloseNotifier).CloseNotify() +} + +func newCodeCatcher(rw http.ResponseWriter, httpCodeRanges types.HTTPCodeRanges) responseInterceptor { + catcher := &codeCatcher{ + headerMap: make(http.Header), + code: http.StatusOK, // If backend does not call WriteHeader on us, we consider it's a 200. + responseWriter: rw, + httpCodeRanges: httpCodeRanges, + firstWrite: true, + } + if _, ok := rw.(http.CloseNotifier); ok { + return &codeCatcherWithCloseNotify{catcher} + } + return catcher +} + +func (cc *codeCatcher) Header() http.Header { + if cc.headerMap == nil { + cc.headerMap = make(http.Header) + } + + return cc.headerMap +} + +func (cc *codeCatcher) getCode() int { + return cc.code +} + +// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching, +// and for which the response should be deferred to the error handler. +func (cc *codeCatcher) isFilteredCode() bool { + return cc.caughtFilteredCode +} + +func (cc *codeCatcher) Write(buf []byte) (int, error) { + if !cc.firstWrite { + if cc.caughtFilteredCode { + // We don't care about the contents of the response, + // since we want to serve the ones from the error page, + // so we just drop them. + return len(buf), nil + } + return cc.responseWriter.Write(buf) + } + cc.firstWrite = false + + // If WriteHeader was already called from the caller, this is a NOOP. + // Otherwise, cc.code is actually a 200 here. + cc.WriteHeader(cc.code) + + if cc.caughtFilteredCode { + return len(buf), nil + } + return cc.responseWriter.Write(buf) +} + +func (cc *codeCatcher) WriteHeader(code int) { + if cc.headersSent || cc.caughtFilteredCode { + return + } + + cc.code = code + for _, block := range cc.httpCodeRanges { + if cc.code >= block[0] && cc.code <= block[1] { + cc.caughtFilteredCode = true + break + } + } + // it will be up to the other response recorder to send the headers, + // so it is out of our hands now. + if cc.caughtFilteredCode { + return + } + utils.CopyHeaders(cc.responseWriter.Header(), cc.Header()) + cc.responseWriter.WriteHeader(cc.code) + cc.headersSent = true +} + +// Hijack hijacks the connection +func (cc *codeCatcher) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hj, ok := cc.responseWriter.(http.Hijacker); ok { + return hj.Hijack() + } + return nil, nil, fmt.Errorf("%T is not a http.Hijacker", cc.responseWriter) +} + +// Flush sends any buffered data to the client. +func (cc *codeCatcher) Flush() { + // If WriteHeader was already called from the caller, this is a NOOP. + // Otherwise, cc.code is actually a 200 here. + cc.WriteHeader(cc.code) + + if flusher, ok := cc.responseWriter.(http.Flusher); ok { + flusher.Flush() + } +} + type responseRecorder interface { http.ResponseWriter http.Flusher diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 5dbb22f30..58a3a1673 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -33,6 +33,30 @@ func TestHandler(t *testing.T) { assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusOK)) }, }, + { + desc: "no error, but not a 200", + errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, + backendCode: http.StatusPartialContent, + backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "My error page.") + }), + validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusPartialContent, recorder.Code, "HTTP status") + assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusPartialContent)) + }, + }, + { + desc: "a 304, so no Write called", + errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, + backendCode: http.StatusNotModified, + backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "whatever, should not be called") + }), + validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { + assert.Equal(t, http.StatusNotModified, recorder.Code, "HTTP status") + assert.Contains(t, recorder.Body.String(), "") + }, + }, { desc: "in the range", errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, @@ -104,6 +128,9 @@ func TestHandler(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(test.backendCode) + if test.backendCode == http.StatusNotModified { + return + } fmt.Fprintln(w, http.StatusText(test.backendCode)) }) errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test") diff --git a/pkg/middlewares/recovery/recovery.go b/pkg/middlewares/recovery/recovery.go index 563d52e66..b7e83f673 100644 --- a/pkg/middlewares/recovery/recovery.go +++ b/pkg/middlewares/recovery/recovery.go @@ -3,6 +3,7 @@ package recovery import ( "context" "net/http" + "runtime" "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/middlewares" @@ -28,13 +29,30 @@ func New(ctx context.Context, next http.Handler, name string) (http.Handler, err } func (re *recovery) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - defer recoverFunc(middlewares.GetLoggerCtx(req.Context(), re.name, typeName), rw) + defer recoverFunc(middlewares.GetLoggerCtx(req.Context(), re.name, typeName), rw, req) re.next.ServeHTTP(rw, req) } -func recoverFunc(ctx context.Context, rw http.ResponseWriter) { +func recoverFunc(ctx context.Context, rw http.ResponseWriter, r *http.Request) { if err := recover(); err != nil { - log.FromContext(ctx).Errorf("Recovered from panic in http handler: %+v", err) + if !shouldLogPanic(err) { + log.FromContext(ctx).Debugf("Request has been aborted [%s - %s]: %v", r.RemoteAddr, r.URL, err) + return + } + + log.FromContext(ctx).Errorf("Recovered from panic in HTTP handler [%s - %s]: %+v", r.RemoteAddr, r.URL, err) + + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + log.FromContext(ctx).Errorf("Stack: %s", buf) + http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } } + +// https://github.com/golang/go/blob/a0d6420d8be2ae7164797051ec74fa2a2df466a1/src/net/http/server.go#L1761-L1775 +// https://github.com/golang/go/blob/c33153f7b416c03983324b3e8f869ce1116d84bc/src/net/http/httputil/reverseproxy.go#L284 +func shouldLogPanic(panicValue interface{}) bool { + return panicValue != nil && panicValue != http.ErrAbortHandler +} diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 300a81899..5a4278320 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -323,7 +323,7 @@ func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStor return } - log.FromContext(ctx).Debugf("Try to challenge certificate for domain %v founded in HostSNI rule", domains) + log.FromContext(ctx).Debugf("Try to challenge certificate for domain %v found in HostSNI rule", domains) var domain types.Domain if len(domains) > 0 { diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index f8125349d..862b9e1a2 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -199,8 +199,6 @@ func flattenCertificates(ctx context.Context, tlsConfig *dynamic.TLSConfiguratio } func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory string, configuration *dynamic.Configuration) (*dynamic.Configuration, error) { - logger := log.FromContext(ctx) - fileList, err := ioutil.ReadDir(directory) if err != nil { return configuration, fmt.Errorf("unable to read directory %s: %v", directory, err) @@ -227,6 +225,8 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st configTLSMaps := make(map[*tls.CertAndStores]struct{}) for _, item := range fileList { + logger := log.FromContext(log.With(ctx, log.Str("filename", item.Name()))) + if item.IsDir() { configuration, err = p.loadFileConfigFromDirectory(ctx, filepath.Join(directory, item.Name()), configuration) if err != nil { @@ -245,7 +245,7 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st var c *dynamic.Configuration c, err = p.loadFileConfig(ctx, filepath.Join(directory, item.Name()), true) if err != nil { - return configuration, err + return configuration, fmt.Errorf("%s: %v", filepath.Join(directory, item.Name()), err) } for name, conf := range c.HTTP.Routers { diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 7c7d59a74..73f9062fe 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "fmt" "os" - "reflect" "sort" "strconv" "strings" @@ -21,6 +20,7 @@ import ( "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/types" + "github.com/mitchellh/hashstructure" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" ) @@ -127,10 +127,14 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. // track more information about the dropped events. conf := p.loadConfigurationFromCRD(ctxLog, k8sClient) - if reflect.DeepEqual(p.lastConfiguration.Get(), conf) { + confHash, err := hashstructure.Hash(conf, nil) + switch { + case err != nil: + logger.Error("Unable to hash the configuration") + case p.lastConfiguration.Get() == confHash: logger.Debugf("Skipping Kubernetes event kind %T", event) - } else { - p.lastConfiguration.Set(conf) + default: + p.lastConfiguration.Set(confHash) configurationChan <- dynamic.Message{ ProviderName: "kubernetescrd", Configuration: conf, diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 2a6f13b13..bd894fe3d 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -209,8 +209,13 @@ func loadServers(client Client, namespace string, svc v1alpha1.Service) ([]dynam var servers []dynamic.Server if service.Spec.Type == corev1.ServiceTypeExternalName { + protocol := "http" + if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") { + protocol = "https" + } + servers = append(servers, dynamic.Server{ - URL: fmt.Sprintf("http://%s:%d", service.Spec.ExternalName, portSpec.Port), + URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port), }) } else { endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 863711cce..f81bf6369 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "os" - "reflect" "sort" "strconv" "strings" @@ -16,9 +15,11 @@ import ( "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/job" "github.com/containous/traefik/v2/pkg/log" + "github.com/containous/traefik/v2/pkg/provider" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/types" + "github.com/mitchellh/hashstructure" corev1 "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/labels" @@ -138,10 +139,14 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. // track more information about the dropped events. conf := p.loadConfigurationFromIngresses(ctxLog, k8sClient) - if reflect.DeepEqual(p.lastConfiguration.Get(), conf) { + confHash, err := hashstructure.Hash(conf, nil) + switch { + case err != nil: + logger.Error("Unable to hash the configuration") + case p.lastConfiguration.Get() == confHash: logger.Debugf("Skipping Kubernetes event kind %T", event) - } else { - p.lastConfiguration.Set(conf) + default: + p.lastConfiguration.Set(confHash) configurationChan <- dynamic.Message{ ProviderName: "kubernetes", Configuration: conf, @@ -202,8 +207,13 @@ func loadService(client Client, namespace string, backend v1beta1.IngressBackend } if service.Spec.Type == corev1.ServiceTypeExternalName { + protocol := "http" + if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") { + protocol = "https" + } + servers = append(servers, dynamic.Server{ - URL: fmt.Sprintf("http://%s:%d", service.Spec.ExternalName, portSpec.Port), + URL: fmt.Sprintf("%s://%s:%d", protocol, service.Spec.ExternalName, portSpec.Port), }) } else { endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, backend.ServiceName) @@ -324,8 +334,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl continue } - serviceName := ingress.Namespace + "-" + p.Backend.ServiceName + "-" + p.Backend.ServicePort.String() - serviceName = strings.ReplaceAll(serviceName, ".", "-") + serviceName := provider.Normalize(ingress.Namespace + "-" + p.Backend.ServiceName + "-" + p.Backend.ServicePort.String()) var rules []string if len(rule.Host) > 0 { rules = []string{"Host(`" + rule.Host + "`)"} @@ -335,10 +344,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl rules = append(rules, "PathPrefix(`"+p.Path+"`)") } - routerKey := strings.Replace(rule.Host, ".", "-", -1) + strings.Replace(p.Path, "/", "-", 1) - if strings.HasPrefix(routerKey, "-") { - routerKey = strings.Replace(routerKey, "-", "", 1) - } + routerKey := strings.TrimPrefix(provider.Normalize(rule.Host+p.Path), "-") conf.HTTP.Routers[routerKey] = &dynamic.Router{ Rule: strings.Join(rules, " && "), Service: serviceName, diff --git a/pkg/server/server.go b/pkg/server/server.go index 234d66e0d..b7bc15817 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -94,8 +94,9 @@ func setupTracing(conf *static.Tracing) tracing.Backend { if backend == nil { log.WithoutContext().Debug("Could not initialize tracing, use Jaeger by default") - backend := &jaeger.Config{} - backend.SetDefaults() + bcd := &jaeger.Config{} + bcd.SetDefaults() + backend = bcd } return backend diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 59db9ba85..51043403d 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -254,6 +254,15 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { return tc, nil } +type proxyProtocolLogger struct { + log.Logger +} + +// Printf force log level to debug. +func (p proxyProtocolLogger) Printf(format string, v ...interface{}) { + p.Debugf(format, v...) +} + func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) { var sourceCheck func(addr net.Addr) (bool, error) if entryPoint.ProxyProtocol.Insecure { @@ -280,7 +289,7 @@ func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoi return proxyprotocol.NewDefaultListener(listener). WithSourceChecker(sourceCheck). - WithLogger(log.FromContext(ctx)), nil + WithLogger(proxyProtocolLogger{Logger: log.FromContext(ctx)}), nil } func buildListener(ctx context.Context, entryPoint *static.EntryPoint) (net.Listener, error) { diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go index 4a43f2a2c..1e700f111 100644 --- a/pkg/tls/certificate.go +++ b/pkg/tls/certificate.go @@ -189,7 +189,7 @@ func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certifi } } if certExists { - log.Warnf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, ep) + log.Debugf("Skipping addition of certificate for domain(s) %q, to EntryPoint %s, as it already exists for this Entrypoint.", certKey, ep) } else { log.Debugf("Adding certificate for domain(s) %s", certKey) certs[ep][certKey] = &tlsCert diff --git a/pkg/tls/generate/generate.go b/pkg/tls/generate/generate.go index fd0744ade..8dbfa00aa 100644 --- a/pkg/tls/generate/generate.go +++ b/pkg/tls/generate/generate.go @@ -86,6 +86,7 @@ func derCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]by NotAfter: expiration, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement | x509.KeyUsageDataEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, DNSNames: []string{domain}, } diff --git a/pkg/tracing/instana/instana.go b/pkg/tracing/instana/instana.go index e2912bbdd..8bd86944e 100644 --- a/pkg/tracing/instana/instana.go +++ b/pkg/tracing/instana/instana.go @@ -20,7 +20,6 @@ type Config struct { // SetDefaults sets the default values. func (c *Config) SetDefaults() { - c.LocalAgentHost = "localhost" c.LocalAgentPort = 42699 c.LogLevel = "info" } diff --git a/script/binary b/script/binary index 82c6ebe3b..8d407c36c 100755 --- a/script/binary +++ b/script/binary @@ -28,7 +28,7 @@ fi # Build binaries # shellcheck disable=SC2086 CGO_ENABLED=0 GOGC=off go build ${FLAGS[*]} -ldflags "-s -w \ - -X github.com/containous/traefik/pkg/v2/version.Version=$VERSION \ - -X github.com/containous/traefik/pkg/v2/version.Codename=$CODENAME \ - -X github.com/containous/traefik/pkg/v2/version.BuildDate=$DATE" \ + -X github.com/containous/traefik/v2/pkg/version.Version=$VERSION \ + -X github.com/containous/traefik/v2/pkg/version.Codename=$CODENAME \ + -X github.com/containous/traefik/v2/pkg/version.BuildDate=$DATE" \ -a -installsuffix nocgo -o dist/traefik ./cmd/traefik diff --git a/webui/Dockerfile b/webui/Dockerfile index 091cb1909..67e491e5d 100644 --- a/webui/Dockerfile +++ b/webui/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.15.0 +FROM node:12.11 ENV WEBUI_DIR /src/webui RUN mkdir -p $WEBUI_DIR diff --git a/webui/readme.md b/webui/readme.md index f7e6ad98f..ca205bcef 100644 --- a/webui/readme.md +++ b/webui/readme.md @@ -20,7 +20,7 @@ make generate-webui # Generate static contents in `traefik/static/` folder. ## How to build (only for frontend developer) -- prerequisite: [Node 6+](https://nodejs.org) [Npm](https://www.npmjs.com/) +- prerequisite: [Node 12.11+](https://nodejs.org) [Npm](https://www.npmjs.com/) - Go to the directory `webui`