diff --git a/src/changelog/.2.x.x/4181_routing_appender_security_docs.xml b/src/changelog/.2.x.x/4181_routing_appender_security_docs.xml new file mode 100644 index 00000000000..acd8bb3e3b1 --- /dev/null +++ b/src/changelog/.2.x.x/4181_routing_appender_security_docs.xml @@ -0,0 +1,8 @@ + + + + Document security considerations for high-cardinality keys with the `Routing` Appender + diff --git a/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc b/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc index 6076fafa71d..287ba4dcfac 100644 --- a/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/appenders/delegating.adoc @@ -872,7 +872,7 @@ The appender can be: -- * A previously declared appender, from the xref:manual/configuration.adoc#main-configuration-elements[`Appenders` section] of the configuration file. * A new appender that is instantiated based on a nested appender definition, when the route becomes active. -See also <> to learn more about the lifecycle of such an appender. +See also <> to learn more about the lifecycle of such an appender, and <> before deriving the key from request data. -- + [#Route-attributes] @@ -945,6 +945,57 @@ If the `Route` element contains an appender definition, the appender will be ins * once for each value of the key, if the `Route` has the default key. ==== +[#RoutingAppender-security] +=== Security considerations + +When a default <> embeds an appender definition, the `Routing` Appender creates **one subordinate appender per distinct routing key value**. +Unlike most appenders, which are fully built when the configuration is loaded, those subordinate appenders are created **at runtime** when a new key appears. +Lookups used in the route (for example `${ctx:userId}`) can therefore still carry attacker-controlled data when appender attributes are resolved. + +Review two aspects of any dynamic routing configuration: + +[#RoutingAppender-security-resources] +==== Resource allocation + +The `Routing` Appender is intentionally powerful: a separate log file per tenant or per long-running job, or round-robin routing between a fixed set of appenders, are all supported designs. +The responsibility that comes with that power is provisioning: the system must be able to allocate the resources required by every appender that can be created. + +If the key is derived from untrusted or high-cardinality data (for example `${ctx:userId}`, a client IP, or a free-form request header), an attacker or a busy system can force creation of an unbounded number of appenders. + +That growth commonly leads to: + +* exhaustion of file descriptors (when each route opens a `File` or rolling file appender) +* elevated memory use for appender state, buffers, and managers +* difficulty shutting down or reconfiguring the application cleanly + +[#RoutingAppender-security-threat-model] +==== Threat model + +An untrusted key is not only a resource problem: it is also substituted into the subordinate appender's configuration. +Attributes such as `fileName` therefore inherit whatever the lookup returns. + +For example, with `fileName="logs/${ctx:userId}.log"`, a Thread Context value of `../../../../tmp/x` (as a whole path segment) can open `/tmp/x.log` without an error. +Embedding the lookup inside a longer fixed segment, such as `logs/user-${ctx:userId}.log`, usually fails to open the file instead of escaping the directory, so the exact `fileName` pattern matters. +That failure is a side effect of how paths are resolved, not a mitigation: it is loud rather than safe, and it does not hold for every value a lookup can return. +The appender is also re-attempted, and the event lost, on every subsequent event with that key. + +This matches the project's {logging-services-url}/security.html#threat-common-sources-configuration[threat model for configuration sources]: operators are responsible for ensuring that appender configuration attributes come from trusted data. +Only the application developer knows which Thread Context keys carry validated values and which are entirely attacker-controlled. +See also {logging-services-url}/security/faq.html#path-traversal[path traversal in the security FAQ]. + +The same substitution reaches non-file sinks: a `Route` that builds an xref:manual/appenders/network.adoc#HttpAppender[HTTP Appender] interpolates the key into its `url`, turning an untrusted routing key into a server-side request forgery (SSRF) vector. + +[#RoutingAppender-security-mitigations] +==== Mitigations + +* Prefer the <> of a `Route`, pointing at a fixed, predeclared set of appenders, when the set of destinations is known. +* When dynamic routes are required, always configure a <> (typically <>) so idle route appenders are stopped and released. ++ +Note that a purge policy bounds how long an unused appender survives, not the rate at which new ones are created. +* Constrain routing keys to a low-cardinality, validated domain (allow-lists, enums, hashed buckets) instead of raw user input. +* Avoid routing on pure user-controlled identifiers when each value would create a new file-backed appender, or when the key is interpolated into paths, URLs, or other sink configuration. +* Do not create an appender per web request. xref:manual/api.adoc#fish-tagging[Fish tagging] the events and filtering on the tag is a far better use of resources. + [#PurgePolicy] === Purge Policy