diff --git a/docs/content/contributing/submitting-security-issues.md b/docs/content/contributing/submitting-security-issues.md index 981c7fe4ce..b2d038b39c 100644 --- a/docs/content/contributing/submitting-security-issues.md +++ b/docs/content/contributing/submitting-security-issues.md @@ -21,3 +21,33 @@ We want to keep Traefik safe for everyone. If you've discovered a security vulnerability in Traefik, we appreciate your help in disclosing it to us in a responsible manner, by creating a [security advisory](https://github.com/traefik/traefik/security/advisories). + +## Submission Quality Guidelines + +We have been receiving an increasing number of low-quality vulnerability reports that are not actual security issues. +Many of these reports originate from AI/LLM tools and are submitted without any human validation or testing. +This wastes the time of our security team and delays the handling of legitimate vulnerabilities. + +Before submitting a security advisory, you **must**: + +- **Carefully test and validate** the vulnerability yourself before submitting. + You must be able to demonstrate a working proof of concept with clear reproduction steps. +- **Understand the impact** of the vulnerability and explain how it can be exploited in a realistic scenario. +- **Verify that the issue is not a false positive**. + Ensure the behavior you are reporting is actually a security concern and not expected behavior. + +### Policy on AI-Generated Reports + +Security reports that are **directly generated by AI/LLM tools without proper human validation** will be **closed immediately**. + +Indicators of unvalidated AI-generated reports include (but are not limited to): + +- No working proof of concept or reproduction steps. +- Generic or theoretical vulnerability descriptions with no evidence of actual testing. +- Misunderstanding of Traefik's architecture or threat model. +- Hallucinated code paths, configuration options, or behaviors that do not exist. + +**Contributors who repeatedly submit low-quality or unvalidated reports may have their accounts blocked.** + +We appreciate the work of security researchers who take the time to rigorously validate their findings. +Quality over quantity helps keep Traefik safe for everyone. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index c1d613d459..1352c60dec 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -281,8 +281,10 @@ accessLog: | `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). | | `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS) | | `TLSClientSubject` | The string representation of the TLS client certificate's Subject (e.g. `CN=username,O=organization`) | - | `TraceId` | A consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string | - | `SpanId` | A unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. | + | `TraceId` | (Deprecated) A consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string | + | `SpanId` | (Deprecated) A unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. | + | `trace_id` | OTel-conformant trace identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string | + | `span_id` | OTel-conformant span identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. | ## Log Rotation diff --git a/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md b/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md index 0291b9efe8..69712a373b 100644 --- a/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md +++ b/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md @@ -41,8 +41,8 @@ The `Header` and `HeaderRegexp` matchers allow matching requests that contain sp | Behavior | Rule | |-----------------------------------------------------------------|:------------------------------------------------------------------------| | Match requests with a `Content-Type` header set to `application/yaml`. | ```Header(`Content-Type`, `application/yaml`)``` | -| Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`. | ```HeaderRegexp(`Content-Type`, `^application/(json\|yaml)$`)``` | -| Match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HeaderRegexp(`Content-Type`, `(?i)^application/(json\|yaml)$`)``` | +| Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`. | ```HeaderRegexp(`Content-Type`, `^application/(json|yaml)$`)``` | +| Match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HeaderRegexp(`Content-Type`, `(?i)^application/(json|yaml)$`)``` | ### Host and HostRegexp @@ -58,8 +58,8 @@ These matchers will match the request's host in lowercase. |-----------------------------------------------------------------|:------------------------------------------------------------------------| | Match requests with `Host` set to `example.com`. | ```Host(`example.com`)``` | | Match requests sent to any subdomain of `example.com`. | ```HostRegexp(`^.+\.example\.com$`)``` | -| Match requests with `Host` set to either `example.com` or `example.org`. | ```HostRegexp(`^example\.(com\|org)$`)``` | -| Match `Host` [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HostRegexp(`(?i)^example\.(com\|org)$`)``` | +| Match requests with `Host` set to either `example.com` or `example.org`. | ```HostRegexp(`^example\.(com|org)$`)``` | +| Match `Host` [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HostRegexp(`(?i)^example\.(com|org)$`)``` | ### Method @@ -81,8 +81,8 @@ Path are always starting with a `/`, except for `PathRegexp`. |-----------------------------------------------------------------|:------------------------------------------------------------------------| | Match `/products` but neither `/products/shoes` nor `/products/`. | ```Path(`/products`)``` | | Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`. | ```PathPrefix(`/products`)``` | -| Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/31`. | ```PathRegexp(`^/products/(shoes\|socks)/[0-9]+$`)``` | -| Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`. | ```PathRegexp(`\.(jpeg\|jpg\|png)$`)``` | +| Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/31`. | ```PathRegexp(`^/products/(shoes|socks)/[0-9]+$`)``` | +| Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`. | ```PathRegexp(`\.(jpeg|jpg|png)$`)``` | | Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`, [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```PathRegexp(`(?i)^/products`)``` | ### Query and QueryRegexp @@ -93,9 +93,9 @@ The `Query` and `QueryRegexp` matchers allow matching requests based on query pa |-----------------------------------------------------------------|:------------------------------------------------------------------------| | Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`. | ```Query(`mobile`, `true`)``` | | Match requests with a query parameter `mobile` that has no value, such as in `/search?mobile`. | ```Query(`mobile`)``` | -| Match requests with a `mobile` query parameter set to either `true` or `yes`. | ```QueryRegexp(`mobile`, `^(true\|yes)$`)``` | +| Match requests with a `mobile` query parameter set to either `true` or `yes`. | ```QueryRegexp(`mobile`, `^(true|yes)$`)``` | | Match requests with a `mobile` query parameter set to any value (including the empty value). | ```QueryRegexp(`mobile`, `^.*$`)``` | -| Match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```QueryRegexp(`mobile`, `(?i)^(true\|yes)$`)``` | +| Match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```QueryRegexp(`mobile`, `(?i)^(true|yes)$`)``` | ### ClientIP diff --git a/pkg/middlewares/accesslog/logdata.go b/pkg/middlewares/accesslog/logdata.go index 451588b8a2..c9010fdd61 100644 --- a/pkg/middlewares/accesslog/logdata.go +++ b/pkg/middlewares/accesslog/logdata.go @@ -78,10 +78,15 @@ const ( // TLSClientSubject is the string representation of the TLS client certificate's Subject. TLSClientSubject = "TLSClientSubject" - // TraceID is the consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string. + // Deprecated: TraceID is the consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string. TraceID = "TraceId" - // SpanID is the unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. + // Deprecated: SpanID is the unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. SpanID = "SpanId" + + // OTelTraceID is the OTel-conformant log attribute for the trace identifier. + OTelTraceID = "trace_id" + // OTelSpanID is the OTel-conformant log attribute for the span identifier. + OTelSpanID = "span_id" ) // These are written out in the default case when no config is provided to specify keys of interest. @@ -126,6 +131,8 @@ func init() { allCoreKeys[TLSVersion] = struct{}{} allCoreKeys[TLSCipher] = struct{}{} allCoreKeys[TLSClientSubject] = struct{}{} + allCoreKeys[OTelTraceID] = struct{}{} + allCoreKeys[OTelSpanID] = struct{}{} } // CoreLogData holds the fields computed from the request/response. diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index a9461828c3..f0c8300910 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -209,6 +209,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http if spanContext.HasTraceID() && spanContext.HasSpanID() { logDataTable.Core[TraceID] = spanContext.TraceID().String() logDataTable.Core[SpanID] = spanContext.SpanID().String() + logDataTable.Core[OTelTraceID] = spanContext.TraceID().String() + logDataTable.Core[OTelSpanID] = spanContext.SpanID().String() } } diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 28bbb811b1..557f1ef0c4 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -622,6 +622,8 @@ func TestLoggerJSON(t *testing.T) { "StartUTC": assertNotEmpty(), TraceID: assertString("01000000000000000000000000000000"), SpanID: assertString("0100000000000000"), + OTelTraceID: assertString("01000000000000000000000000000000"), + OTelSpanID: assertString("0100000000000000"), }, }, { diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index d09e2e751b..6525a75c38 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -1093,24 +1093,13 @@ func findMatchingHostname(h1, h2 gatev1.Hostname) gatev1.Hostname { } trimmedH1 := strings.TrimPrefix(string(h1), "*") - // root domain doesn't match subdomain wildcard. - if trimmedH1 == string(h2) { - return "" - } if !strings.HasSuffix(string(h2), trimmedH1) { return "" } - return lessWildcards(h1, h2) -} - -func lessWildcards(h1, h2 gatev1.Hostname) gatev1.Hostname { - if strings.Count(string(h1), "*") > strings.Count(string(h2), "*") { - return h2 - } - - return h1 + // since h1 is a suffix of h2, we know h2 is the more specific host + return h2 } func allowRoute(listener gatewayListener, routeNamespace, routeKind string) bool { diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 7147419098..aebd3dd2cc 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -7702,6 +7702,13 @@ func Test_findMatchingHostnames(t *testing.T) { want: []gatev1.Hostname{"toto.foo.com", "test.foo.com"}, wantOk: true, }, + { + desc: "Matching wildcard subsubdomain with listener wildcard subdomain", + listenerHostname: ptr.To(gatev1.Hostname("*.foo.com")), + routeHostnames: []gatev1.Hostname{"*.bar.foo.com"}, + want: []gatev1.Hostname{"*.bar.foo.com"}, + wantOk: true, + }, } for _, test := range testCases {