From a42d396ed212481f645b6b9cfc9c6591353dac85 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 27 Sep 2024 11:18:05 +0200 Subject: [PATCH 1/7] Clean connection headers for forward auth request only Co-authored-by: Kevin Pollet --- docs/content/migration/v2.md | 12 +++++++ pkg/middlewares/auth/connectionheader.go | 36 ++++++++----------- pkg/middlewares/auth/connectionheader_test.go | 8 +---- pkg/middlewares/auth/forward.go | 4 ++- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index e0c000cad..c0a58100b 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -637,3 +637,15 @@ Increasing the `readTimeout` value could be the solution notably if you are deal - TCP: `Error while handling TCP connection: readfrom tcp X.X.X.X:X->X.X.X.X:X: read tcp X.X.X.X:X->X.X.X.X:X: i/o timeout` - HTTP: `'499 Client Closed Request' caused by: context canceled` - HTTP: `ReverseProxy read error during body copy: read tcp X.X.X.X:X->X.X.X.X:X: use of closed network connection` + +## v2.11.3 + +### Connection headers + +In `v2.11.3`, the handling of the request Connection headers directives has changed to prevent any abuse. +Before, Traefik removed any header listed in the Connection header just before forwarding the request to the backends. +Now, Traefik removes the headers listed in the Connection header as soon as the request is handled. +As a consequence, middlewares do not have access to those Connection headers, +and a new option has been introduced to specify which ones could go through the middleware chain before being removed: `.forwardedHeaders.connection`. + +Please check out the [entrypoint forwarded headers connection option configuration](../routing/entrypoints.md#forwarded-headers) documentation. diff --git a/pkg/middlewares/auth/connectionheader.go b/pkg/middlewares/auth/connectionheader.go index 1cd1da81a..8b78b9430 100644 --- a/pkg/middlewares/auth/connectionheader.go +++ b/pkg/middlewares/auth/connectionheader.go @@ -13,34 +13,26 @@ const ( upgradeHeader = "Upgrade" ) -// Remover removes hop-by-hop headers listed in the "Connection" header. +// RemoveConnectionHeaders removes hop-by-hop headers listed in the "Connection" header. // See RFC 7230, section 6.1. -func Remover(next http.Handler) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - var reqUpType string - if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) { - reqUpType = req.Header.Get(upgradeHeader) - } - - removeConnectionHeaders(req.Header) - - if reqUpType != "" { - req.Header.Set(connectionHeader, upgradeHeader) - req.Header.Set(upgradeHeader, reqUpType) - } else { - req.Header.Del(connectionHeader) - } - - next.ServeHTTP(rw, req) +func RemoveConnectionHeaders(req *http.Request) { + var reqUpType string + if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) { + reqUpType = req.Header.Get(upgradeHeader) } -} -func removeConnectionHeaders(h http.Header) { - for _, f := range h[connectionHeader] { + for _, f := range req.Header[connectionHeader] { for _, sf := range strings.Split(f, ",") { if sf = textproto.TrimString(sf); sf != "" { - h.Del(sf) + req.Header.Del(sf) } } } + + if reqUpType != "" { + req.Header.Set(connectionHeader, upgradeHeader) + req.Header.Set(upgradeHeader, reqUpType) + } else { + req.Header.Del(connectionHeader) + } } diff --git a/pkg/middlewares/auth/connectionheader_test.go b/pkg/middlewares/auth/connectionheader_test.go index 00d719ef0..26854b858 100644 --- a/pkg/middlewares/auth/connectionheader_test.go +++ b/pkg/middlewares/auth/connectionheader_test.go @@ -50,19 +50,13 @@ func TestRemover(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}) - - h := Remover(next) - req := httptest.NewRequest(http.MethodGet, "https://localhost", nil) for k, v := range test.reqHeaders { req.Header.Set(k, v) } - rw := httptest.NewRecorder() - - h.ServeHTTP(rw, req) + RemoveConnectionHeaders(req) assert.Equal(t, test.expected, req.Header) }) diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index 27d42973c..6004a01fa 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -89,7 +89,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu fa.authResponseHeadersRegex = re } - return Remover(fa), nil + return fa, nil } func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) { @@ -195,6 +195,8 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowedHeaders []string) { utils.CopyHeaders(forwardReq.Header, req.Header) + + RemoveConnectionHeaders(forwardReq) utils.RemoveHeaders(forwardReq.Header, hopHeaders...) forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders) From 61bb3ab9912a0a7c8b59e50c90e49a84b37ef827 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 27 Sep 2024 11:34:05 +0200 Subject: [PATCH 2/7] Rework condition to not log on timeout --- pkg/server/router/tcp/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 0da33e10a..4790cd751 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -349,7 +349,7 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) { hdr, err := br.Peek(1) if err != nil { var opErr *net.OpError - if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || opErr.Timeout()) { + if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || !opErr.Timeout()) { log.WithoutContext().Errorf("Error while Peeking first byte: %s", err) } return nil, err From e485edbe9f19cb8532abd619b5c682753b653b19 Mon Sep 17 00:00:00 2001 From: lyrandy <42095565+lyrandy@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:00:06 -0400 Subject: [PATCH 3/7] Update API documentation to mention pagination --- docs/content/operations/api.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/operations/api.md b/docs/content/operations/api.md index f6786f6d5..a495ab4e5 100644 --- a/docs/content/operations/api.md +++ b/docs/content/operations/api.md @@ -136,6 +136,15 @@ api: All the following endpoints must be accessed with a `GET` HTTP request. +!!! info "Pagination" + + By default, up to 100 results are returned per page, and the next page can be checked using the `X-Next-Page` HTTP Header. + To control pagination, use the `page` and `per_page` query parameters. + + ```bash + curl https://traefik.example.com:8080/api/http/routers?page=2&per_page=20 + ``` + | Path | Description | |--------------------------------|---------------------------------------------------------------------------------------------| | `/api/http/routers` | Lists all the HTTP routers information. | From 14e5d4b4b36313cce6b38efcc3a6497e1e9dde3d Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:22:04 +0200 Subject: [PATCH 4/7] Remove unused boot files from webui --- webui/embed.go | 2 ++ webui/package.json | 3 --- webui/quasar.conf.js | 4 +--- webui/src/_directives/resize.js | 16 ---------------- webui/src/boot/_globals.js | 10 ---------- webui/src/boot/_hacks.js | 13 ------------- webui/src/boot/_init.js | 30 ------------------------------ webui/yarn.lock | 15 --------------- 8 files changed, 3 insertions(+), 90 deletions(-) delete mode 100644 webui/src/_directives/resize.js delete mode 100644 webui/src/boot/_globals.js delete mode 100644 webui/src/boot/_hacks.js delete mode 100644 webui/src/boot/_init.js diff --git a/webui/embed.go b/webui/embed.go index 155b0f9e6..23159dc1f 100644 --- a/webui/embed.go +++ b/webui/embed.go @@ -5,6 +5,8 @@ import ( "io/fs" ) +// Files starting with . and _ are excluded by default +// //go:embed static var assets embed.FS diff --git a/webui/package.json b/webui/package.json index f0521e8f0..0a8d12eac 100644 --- a/webui/package.json +++ b/webui/package.json @@ -20,16 +20,13 @@ "dependencies": { "@quasar/extras": "^1.16.12", "axios": "^1.7.4", - "bowser": "^2.11.0", "chart.js": "^4.4.1", "core-js": "^3.35.1", "dot-prop": "^8.0.2", - "iframe-resizer": "^4.3.9", "lodash.isequal": "4.5.0", "moment": "^2.30.1", "quasar": "^2.16.6", "query-string": "^8.1.0", - "vh-check": "^2.0.5", "vue": "^3.0.0", "vue-chartjs": "^5.3.0", "vue-router": "^4.0.12", diff --git a/webui/quasar.conf.js b/webui/quasar.conf.js index 58a8e2c39..c4a41a8c0 100644 --- a/webui/quasar.conf.js +++ b/webui/quasar.conf.js @@ -13,9 +13,7 @@ module.exports = configure(function (ctx) { // app boot file (/src/boot) // --> boot files are part of "main.js" boot: [ - 'api', - '_hacks', - '_init' + 'api' ], css: [ diff --git a/webui/src/_directives/resize.js b/webui/src/_directives/resize.js deleted file mode 100644 index 3a0500a44..000000000 --- a/webui/src/_directives/resize.js +++ /dev/null @@ -1,16 +0,0 @@ -import iframeResize from 'iframe-resizer/js/iframeResizer' - -const resize = { - mounted (el, binding) { - const options = binding.value || {} - el.addEventListener('load', () => iframeResize(options, el)) - }, - unmounted (el) { - const resizableEl = el - if (resizableEl.iFrameResizer) { - resizableEl.iFrameResizer.removeListeners() - } - } -} - -export default resize diff --git a/webui/src/boot/_globals.js b/webui/src/boot/_globals.js deleted file mode 100644 index f099d03cd..000000000 --- a/webui/src/boot/_globals.js +++ /dev/null @@ -1,10 +0,0 @@ -import { APP } from '../_helpers/APP' -import Boot from '../_middleware/Boot' - -export default async ({ app, router, store }) => { - app.use(Boot) - - APP.root = app - APP.router = router - APP.store = store -} diff --git a/webui/src/boot/_hacks.js b/webui/src/boot/_hacks.js deleted file mode 100644 index 2d63bbdd0..000000000 --- a/webui/src/boot/_hacks.js +++ /dev/null @@ -1,13 +0,0 @@ -import Bowser from 'bowser' -import vhCheck from 'vh-check' - -const browser = Bowser.getParser(window.navigator.userAgent) - -// In Mobile -if (browser.getPlatform().type === 'mobile') { - vhCheck() -} - -export default async ({ app, Vue }) => { - -} diff --git a/webui/src/boot/_init.js b/webui/src/boot/_init.js deleted file mode 100644 index 5ec3caf4e..000000000 --- a/webui/src/boot/_init.js +++ /dev/null @@ -1,30 +0,0 @@ -import { APP } from '../_helpers/APP' -import errors from '../_helpers/Errors' -import resize from '../_directives/resize' - -export default async ({ app, router }) => { - // Directives - app.directive('resize', resize) - - // Router - // ---------------------------------------------- - router.beforeEach(async (to, from, next) => { - // Set APP - APP.routeTo = to - APP.routeFrom = from - next() - }) - - // Api (axios) - // ---------------------------------------------- - APP.api.interceptors.request.use((config) => { - console.log('interceptors -> config', config) - // config.headers['Accept'] = '*/*' - return config - }) - - APP.api.interceptors.response.use((response) => { - console.log('interceptors -> response', response) - return response - }, errors.handleResponse) -} diff --git a/webui/yarn.lock b/webui/yarn.lock index 88b6bac4f..c9eb68233 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -2267,11 +2267,6 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3864,11 +3859,6 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -iframe-resizer@^4.3.9: - version "4.4.5" - resolved "https://registry.yarnpkg.com/iframe-resizer/-/iframe-resizer-4.4.5.tgz#f5048636e7f2fb5d9a09cc2ae78eb2da55ad555c" - integrity sha512-U8bCywf/Gh07O69RXo6dXAzTtODQrxaHGHRI7Nt4ipXsuq6EMxVsOP/jjaP43YtXz/ibESS0uSVDN3sOGCzSmw== - ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" @@ -6007,11 +5997,6 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -vh-check@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/vh-check/-/vh-check-2.0.5.tgz#1b70610461e9776176f23d172daae3c4761aed09" - integrity sha512-vHtIYWt9uLl2P2tLlatVpMwv9+ezuJCtMNjUVIpzd5Pa/dJXN8AtqkKmVRcNSlmXyCjkCkbMQX/Vs9axmdlfgg== - vite-jsconfig-paths@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/vite-jsconfig-paths/-/vite-jsconfig-paths-2.0.1.tgz#d66e36d67596dd8a8e4a6ed6e6db20debc50b45e" From 2bb712135df1b5c7b1f9d6b7c04bbd6ca3a83add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20BUISSON?= Date: Fri, 27 Sep 2024 15:34:04 +0200 Subject: [PATCH 5/7] Specify default format value for access log --- docs/content/observability/access-logs.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index f96473a95..1049e6fc5 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -47,6 +47,8 @@ accessLog: ### `format` +_Optional, Default="common"_ + By default, logs are written using the Common Log Format (CLF). To write logs in JSON, use `json` in the `format` option. If the given format is unsupported, the default (CLF) is used instead. From 9eb804a689c9bdfd0e9cb969a7df7cc746be3de9 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 30 Sep 2024 11:56:04 +0200 Subject: [PATCH 6/7] Bump github.com/klauspost/compress to 8e14b1b5a913 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a32b04ba6..4833b4267 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo. github.com/instana/go-sensor v1.38.3 - github.com/klauspost/compress v1.17.9 + github.com/klauspost/compress v1.17.11-0.20240927175842-8e14b1b5a913 // Required to have the content-type fix: https://github.com/klauspost/compress/pull/1011 github.com/kvtools/consul v1.0.2 github.com/kvtools/etcdv3 v1.0.2 github.com/kvtools/redis v1.1.0 diff --git a/go.sum b/go.sum index 4d4a05089..2faf7fb35 100644 --- a/go.sum +++ b/go.sum @@ -741,8 +741,8 @@ github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11-0.20240927175842-8e14b1b5a913 h1:7s7Xd7zVElAw1qh/eh+tXDNfDNXXj38Tpq54eeG6/BM= +github.com/klauspost/compress v1.17.11-0.20240927175842-8e14b1b5a913/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From 4d6cb6af030688a9e341438020210b8d3cdb3012 Mon Sep 17 00:00:00 2001 From: Mathieu <45506907+Lamatte@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:10:05 +0200 Subject: [PATCH 7/7] Ensure defaultGeneratedCert.main as Subject's CN --- pkg/provider/acme/provider.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 3b4c6d845..47e3d731d 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -552,8 +552,11 @@ func (p *Provider) resolveDefaultCertificate(ctx context.Context, domains []stri p.resolvingDomainsMutex.Lock() - sort.Strings(domains) - domainKey := strings.Join(domains, ",") + sortedDomains := make([]string, len(domains)) + copy(sortedDomains, domains) + sort.Strings(sortedDomains) + + domainKey := strings.Join(sortedDomains, ",") if _, ok := p.resolvingDomains[domainKey]; ok { p.resolvingDomainsMutex.Unlock() @@ -947,12 +950,14 @@ func (p *Provider) certExists(validDomains []string) bool { p.certificatesMu.RLock() defer p.certificatesMu.RUnlock() - sort.Strings(validDomains) + sortedDomains := make([]string, len(validDomains)) + copy(sortedDomains, validDomains) + sort.Strings(sortedDomains) for _, cert := range p.certificates { domains := cert.Certificate.Domain.ToStrArray() sort.Strings(domains) - if reflect.DeepEqual(domains, validDomains) { + if reflect.DeepEqual(domains, sortedDomains) { return true } }