From e9f3089e9045812bcf1b410a9d40568917b26c3d Mon Sep 17 00:00:00 2001 From: LBF38 Date: Thu, 8 Jan 2026 16:16:05 +0100 Subject: [PATCH 1/2] Add timeout to ACME-TLS/1 challenge handshake Co-authored-by: Kevin Pollet --- pkg/server/router/tcp/router.go | 13 ++++++- pkg/server/router/tcp/router_test.go | 58 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index f8d657265..ae110c3c3 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -3,6 +3,7 @@ package tcp import ( "bufio" "bytes" + "context" "crypto/tls" "errors" "io" @@ -206,7 +207,17 @@ func (r *Router) acmeTLSALPNHandler() tcp.Handler { } return tcp.HandlerFunc(func(conn tcp.WriteCloser) { - _ = tls.Server(conn, r.httpsTLSConfig).Handshake() + tlsConn := tls.Server(conn, r.httpsTLSConfig) + defer tlsConn.Close() + + // This avoids stale connections when validating the ACME challenge, + // as we expect a validation request to complete in a short period of time. + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + if err := tlsConn.HandshakeContext(ctx); err != nil { + log.FromContext(ctx).WithError(err).Debug("Error during ACME-TLS/1 handshake") + } }) } diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 55b4b4f11..ecf556b23 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -679,6 +679,64 @@ func Test_Routing(t *testing.T) { } } +func Test_Router_acmeTLSALPNHandlerTimeout(t *testing.T) { + router, err := NewRouter() + require.NoError(t, err) + + router.httpsTLSConfig = &tls.Config{} + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + acceptCh := make(chan struct{}, 1) + go func() { + close(acceptCh) + + conn, err := listener.Accept() + require.NoError(t, err) + + defer listener.Close() + + router.acmeTLSALPNHandler(). + ServeTCP(conn.(*net.TCPConn)) + }() + + <-acceptCh + + conn, err := net.DialTimeout("tcp", listener.Addr().String(), 2*time.Second) + require.NoError(t, err) + + // This is a minimal truncated Client Hello message + // to simulate a hanging connection during TLS handshake. + clientHello := []byte{ + // TLS Record Header + 0x16, // Content Type: Handshake + 0x03, 0x01, // Version: TLS 1.0 (for compatibility) + 0x00, 0x50, // Length: 80 bytes + } + + _, err = conn.Write(clientHello) + require.NoError(t, err) + + errCh := make(chan error, 1) + go func() { + // This will return an EOF as the acmeTLSALPNHandler will close the connection + // after a timeout during the TLS handshake. + b := make([]byte, 256) + _, err = conn.Read(b) + + errCh <- err + }() + + select { + case err := <-errCh: + assert.ErrorIs(t, err, io.EOF) + + case <-time.After(3 * time.Second): + t.Fatal("Error: Timeout waiting for acmeTLSALPNHandler to close the connection") + } +} + // routerTCPCatchAll configures a TCP CatchAll No TLS - HostSNI(`*`) router. func routerTCPCatchAll(conf *runtime.Configuration) { conf.TCPRouters["tcp-catchall"] = &runtime.TCPRouterInfo{ From e8067f4e01e29f5bbd62e58ae3b0c9383a467184 Mon Sep 17 00:00:00 2001 From: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:24:04 +0100 Subject: [PATCH 2/2] Refactor CI on documentation --- .github/workflows/check_doc.yaml | 63 +++++++++++++++++++ .github/workflows/check_doc.yml | 25 -------- .../{documentation.yml => documentation.yaml} | 0 docs/content/deprecation/releases.md | 2 +- docs/mkdocs.yml | 8 +-- docs/readme.md | 12 ++++ docs/scripts/lint.sh | 10 +-- 7 files changed, 85 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/check_doc.yaml delete mode 100644 .github/workflows/check_doc.yml rename .github/workflows/{documentation.yml => documentation.yaml} (100%) diff --git a/.github/workflows/check_doc.yaml b/.github/workflows/check_doc.yaml new file mode 100644 index 000000000..5fea9809c --- /dev/null +++ b/.github/workflows/check_doc.yaml @@ -0,0 +1,63 @@ +name: Check Documentation + +on: + pull_request: + branches: + - '*' + paths: + - '.github/workflows/check_doc.yaml' + - 'docs/**' + +jobs: + + docs: + name: lint, build and verify + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - name: Install markdownlint + run: | + npm install --global markdownlint@0.29.0 markdownlint-cli@0.35.0 + + - name: Lint + run: ./docs/scripts/lint.sh docs + + - name: Setup python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + cache-dependency-path: "./docs/requirements.txt" + + - name: Build documentation + working-directory: ./docs + run: | + pip install -r requirements.txt + mkdocs build --strict + + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4' + + - name: Install html-proofer + run: | + gem install nokogiri --version 1.18.6 --no-document -- --use-system-libraries + gem install html-proofer --version 5.0.10 --no-document -- --use-system-libraries + env: + NOKOGIRI_USE_SYSTEM_LIBRARIES: "true" + + # Comes from https://github.com/gjtorikian/html-proofer?tab=readme-ov-file#caching-with-continuous-integration + - name: Cache HTMLProofer + uses: actions/cache@v4 + with: + path: tmp/.htmlproofer + key: ${{ runner.os }}-htmlproofer + + - name: Verify + run: ./docs/scripts/verify.sh docs/site diff --git a/.github/workflows/check_doc.yml b/.github/workflows/check_doc.yml deleted file mode 100644 index 48afa3298..000000000 --- a/.github/workflows/check_doc.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Check Documentation - -on: - pull_request: - branches: - - '*' - -jobs: - - docs: - name: Check, verify and build documentation - runs-on: ubuntu-latest - - steps: - - name: Check out code - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Check documentation - run: make docs-pull-images docs - env: - # These variables are not passed to workflows that are triggered by a pull request from a fork. - DOCS_VERIFY_SKIP: ${{ vars.DOCS_VERIFY_SKIP }} - DOCS_LINT_SKIP: ${{ vars.DOCS_LINT_SKIP }} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yaml similarity index 100% rename from .github/workflows/documentation.yml rename to .github/workflows/documentation.yaml diff --git a/docs/content/deprecation/releases.md b/docs/content/deprecation/releases.md index 6bfcc984e..fe34b8be8 100644 --- a/docs/content/deprecation/releases.md +++ b/docs/content/deprecation/releases.md @@ -34,7 +34,7 @@ Below is a non-exhaustive list of versions and their maintenance status: This page is maintained and updated periodically to reflect our roadmap and any decisions affecting the end of support for Traefik Proxy. -Please refer to our migration guides for specific instructions on upgrading between versions, an example is the [v2 to v3 migration guide](../migration/v2-to-v3.md). +Please refer to our migration guides for specific instructions on upgrading between versions. !!! important "All target dates for end of support or feature removal announcements may be subject to change." diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e39aac44d..9e6fe7d3c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -2,18 +2,19 @@ site_name: Traefik site_description: Traefik Documentation site_author: traefik.io site_url: https://doc.traefik.io/traefik -dev_addr: 0.0.0.0:8000 +dev_addr: localhost:8000 repo_name: 'GitHub' repo_url: 'https://github.com/traefik/traefik' docs_dir: 'content' -product: proxy -# https://squidfunk.github.io/mkdocs-material/ +# Use custom version of mkdocs-material +# See https://github.com/traefik/mkdocs-material theme: name: 'traefik-labs' + product: proxy language: en include_sidebar: true favicon: assets/img/traefikproxy-icon-color.png @@ -165,7 +166,6 @@ nav: - 'Instana': 'observability/tracing/instana.md' - 'Haystack': 'observability/tracing/haystack.md' - 'Elastic': 'observability/tracing/elastic.md' - - 'OpenTelemetry': 'observability/tracing/opentelemetry.md' - 'Security': - 'Request Path': 'security/request-path.md' - 'Content-Length': 'security/content-length.md' diff --git a/docs/readme.md b/docs/readme.md index de1d8b677..822c1cd6a 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -16,3 +16,15 @@ [pymdown-extensions]: https://facelessuser.github.io/pymdown-extensions "PyMdown Extensions" [pymdown-extensions-src]: https://github.com/facelessuser/pymdown-extensions "PyMdown Extensions - Sources" + +## Build locally without docker + +```sh +# Pre-requisite: python3, pip and virtualenv +DOCS="/tmp/traefik-docs" +mkdir "$DOCS" +virtualenv "$DOCS" +source "$DOCS/bin/activate" +pip install -r requirements.txt +mkdocs serve # or mkdocs build +``` diff --git a/docs/scripts/lint.sh b/docs/scripts/lint.sh index a46066df8..39b83836f 100755 --- a/docs/scripts/lint.sh +++ b/docs/scripts/lint.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # This script will run a couple of linter on the documentation set -eu @@ -6,14 +6,14 @@ set -eu # We want to run all linters before returning success (exit 0) or failure (exit 1) # So this variable holds the global exit code EXIT_CODE=0 -readonly BASE_DIR=/app +readonly BASE_DIR="${1:-/app}" echo "== Linting Markdown" # Uses the file ".markdownlint.json" for setup cd "${BASE_DIR}" || exit 1 -LINTER_EXCLUSIONS="$(find "${BASE_DIR}/content" -type f -name '.markdownlint.json')" -GLOBAL_LINT_OPTIONS="--config ${BASE_DIR}/.markdownlint.json" +LINTER_EXCLUSIONS="$(find "content" -type f -name '.markdownlint.json')" +GLOBAL_LINT_OPTIONS="--config .markdownlint.json" # Lint the specific folders (containing linter specific rulesets) for LINTER_EXCLUSION in ${LINTER_EXCLUSIONS} @@ -24,6 +24,6 @@ do done # Lint all the content, excluding the previously done` -eval markdownlint "${GLOBAL_LINT_OPTIONS}" "${BASE_DIR}/content/**/*.md" || EXIT_CODE=1 +eval markdownlint "${GLOBAL_LINT_OPTIONS}" "content/**/*.md" || EXIT_CODE=1 exit "${EXIT_CODE}"