From: Christopher Faulet Date: Wed, 30 Sep 2026 16:36:55 +0000 (+0200) Subject: MINOR: filters: Restrict defaults inheritance with explicit filter directives X-Git-Tag: v3.5-dev9~83 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=db18ddaa2441d47cb23205f6dae474f12301c3d2;p=haproxy.git MINOR: filters: Restrict defaults inheritance with explicit filter directives In proxy_ref_defaults(), reject listen and defaults sections inheriting from a defaults section containing explicit filter instances, enable/disable entries or sequence directives. Frontends and backends must not share such a defaults section either; record the first inheriting proxy's type in the defaults capabilities, as already done for TCP/HTTP rules. Inspect instances on both request and response sides, ignoring those marked FLT_INST_F_IMPLICIT. Class references alone do not imply an explicit filter configuration. Multiple proxies of the same type remain allowed, and implicit instances alone do not restrict inheritance. Perform these checks before copying instances. Update the named-defaults acceptance test to use two frontends. Add native VTC coverage for explicit instances and each directive independently, including response-only instances, both frontend/backend declaration orders, listen and defaults rejection, and allowed inheritance. --- diff --git a/reg-tests/filters/defaults-inheritance.vtc b/reg-tests/filters/defaults-inheritance.vtc new file mode 100644 index 000000000..a8795b912 --- /dev/null +++ b/reg-tests/filters/defaults-inheritance.vtc @@ -0,0 +1,419 @@ +varnishtest "Filter directives restrict defaults inheritance to one proxy type" + +feature ignore_unknown_macro + +# Each explicit directive independently restricts inheritance. The rejection +# cases are intentionally minimal: -conf-BAD checks both exit status and the +# specific parsing diagnostic, before listener or filter finalization checks. + +# filter-config (bidirectional) +# Frontend first, then backend. +haproxy h1 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-config trace id inherited + + frontend fe from filters + backend be from filters +} + +# Backend first, then frontend. +haproxy h2 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-config trace id inherited + + backend be from filters + frontend fe from filters +} + +# Listen sections must not inherit these directives. +haproxy h3 -conf-BAD {a listen section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-config trace id inherited + + listen li from filters +} + +# Defaults-to-defaults inheritance must fail at the defaults declaration. +haproxy h4 -conf-BAD {a defaults section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-config trace id inherited + + defaults derived from filters +} + +# filter-config (response-only) +# Frontend first, then backend. +haproxy h5 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-config cache-store id store + + frontend fe from filters + backend be from filters +} + +# Backend first, then frontend. +haproxy h6 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-config cache-store id store + + backend be from filters + frontend fe from filters +} + +# Listen sections must not inherit these directives. +haproxy h7 -conf-BAD {a listen section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-config cache-store id store + + listen li from filters +} + +# Defaults-to-defaults inheritance must fail at the defaults declaration. +haproxy h8 -conf-BAD {a defaults section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-config cache-store id store + + defaults derived from filters +} + +# filter-enable +# Frontend first, then backend. +haproxy h9 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-enable trace/local + + frontend fe from filters + backend be from filters +} + +# Backend first, then frontend. +haproxy h10 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-enable trace/local + + backend be from filters + frontend fe from filters +} + +# Listen sections must not inherit these directives. +haproxy h11 -conf-BAD {a listen section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-enable trace/local + + listen li from filters +} + +# Defaults-to-defaults inheritance must fail at the defaults declaration. +haproxy h12 -conf-BAD {a defaults section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-enable trace/local + + defaults derived from filters +} + +# filter-disable +# Frontend first, then backend. +haproxy h13 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-disable trace/local + + frontend fe from filters + backend be from filters +} + +# Backend first, then frontend. +haproxy h14 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-disable trace/local + + backend be from filters + frontend fe from filters +} + +# Listen sections must not inherit these directives. +haproxy h15 -conf-BAD {a listen section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-disable trace/local + + listen li from filters +} + +# Defaults-to-defaults inheritance must fail at the defaults declaration. +haproxy h16 -conf-BAD {a defaults section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-disable trace/local + + defaults derived from filters +} + +# filter-sequence +# Frontend first, then backend. +haproxy h17 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-sequence request trace/local:before(trace) + + frontend fe from filters + backend be from filters +} + +# Backend first, then frontend. +haproxy h18 -conf-BAD {frontends and backends cannot inherit from the same defaults section if it defines filter directives} { + defaults filters + filter-sequence request trace/local:before(trace) + + backend be from filters + frontend fe from filters +} + +# Listen sections must not inherit these directives. +haproxy h19 -conf-BAD {a listen section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-sequence request trace/local:before(trace) + + listen li from filters +} + +# Defaults-to-defaults inheritance must fail at the defaults declaration. +haproxy h20 -conf-BAD {a defaults section cannot inherit from a defaults section defining filter directives} { + defaults filters + filter-sequence request trace/local:before(trace) + + defaults derived from filters +} + +# Multiple frontends or multiple backends may still share their own defaults. +# For enable/disable/sequence-only defaults, the referenced filters are local. +# The common ancestor contains no filter directives and remains unrestricted. + +# filter-config (bidirectional): same-type inheritance remains valid. +haproxy h21 -conf-OK { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults plain + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + defaults filtered_fe from plain + filter-config trace id inherited + + frontend fe1 from filtered_fe + bind "fd@${fe1}" + filter-config trace id local + + frontend fe2 from filtered_fe + bind "fd@${fe2}" + filter-config trace id local + + defaults filtered_be from plain + filter-config trace id inherited + + backend be1 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 + + backend be2 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 +} + +# filter-config (response-only): same-type inheritance remains valid. +haproxy h22 -conf-OK { + global + .if feature(THREAD) + thread-groups 1 + .endif + + cache store + total-max-size 1 + + defaults plain + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + defaults filtered_fe from plain + filter-config cache-store id store + + frontend fe1 from filtered_fe + bind "fd@${fe1}" + filter-config trace id local + + frontend fe2 from filtered_fe + bind "fd@${fe2}" + filter-config trace id local + + defaults filtered_be from plain + filter-config cache-store id store + + backend be1 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 + + backend be2 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 +} + +# filter-enable: same-type inheritance remains valid. +haproxy h23 -conf-OK { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults plain + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + defaults filtered_fe from plain + filter-enable trace/local + + frontend fe1 from filtered_fe + bind "fd@${fe1}" + filter-config trace id local + + frontend fe2 from filtered_fe + bind "fd@${fe2}" + filter-config trace id local + + defaults filtered_be from plain + filter-enable trace/local + + backend be1 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 + + backend be2 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 +} + +# filter-disable: same-type inheritance remains valid. +haproxy h24 -conf-OK { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults plain + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + defaults filtered_fe from plain + filter-disable trace/local + + frontend fe1 from filtered_fe + bind "fd@${fe1}" + filter-config trace id local + + frontend fe2 from filtered_fe + bind "fd@${fe2}" + filter-config trace id local + + defaults filtered_be from plain + filter-disable trace/local + + backend be1 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 + + backend be2 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 +} + +# filter-sequence: same-type inheritance remains valid. +haproxy h25 -conf-OK { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults plain + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + defaults filtered_fe from plain + filter-sequence request trace/local:before(trace) + + frontend fe1 from filtered_fe + bind "fd@${fe1}" + filter-config trace id local + + frontend fe2 from filtered_fe + bind "fd@${fe2}" + filter-config trace id local + + defaults filtered_be from plain + filter-sequence request trace/local:before(trace) + + backend be1 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 + + backend be2 from filtered_be + filter-config trace id local + server s1 127.0.0.1:80 +} + +# Empty defaults remain shareable, even when their users add local instances. +haproxy h26 -conf-OK { + global + .if feature(THREAD) + thread-groups 1 + .endif + + defaults plain + mode http + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend fe1 from plain + bind "fd@${fe1}" + filter-config trace id local + + backend be1 from plain + filter-config trace id local + server s1 127.0.0.1:80 + + frontend fe2 from plain + bind "fd@${fe2}" + filter-config trace id local + + backend be2 from plain + filter-config trace id local + server s1 127.0.0.1:80 + + listen li from plain + bind "fd@${li}" + filter-config trace id local + server s1 127.0.0.1:80 + + defaults derived from plain + + backend be3 from derived + filter-config trace id local + server s1 127.0.0.1:80 + + frontend fe3 from derived + bind "fd@${fe3}" + filter-config trace id local + + listen li2 from derived + bind "fd@${li2}" + filter-config trace id local + server s1 127.0.0.1:80 +} diff --git a/reg-tests/filters/defaults.vtc b/reg-tests/filters/defaults.vtc index 47cbc7b1b..baf655086 100644 --- a/reg-tests/filters/defaults.vtc +++ b/reg-tests/filters/defaults.vtc @@ -70,7 +70,8 @@ haproxy h5 -conf-OK { filter-sequence response trace/local:before(trace/b) http-request return status 200 - backend be from filters + frontend fe2 from filters + bind "fd@${fe2}" filter-config trace id local enabled filter-enable trace/b filter-disable trace/a diff --git a/src/proxy.c b/src/proxy.c index 1b0bc3425..ca7ef8c5e 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -3163,6 +3163,52 @@ int proxy_ref_defaults(struct proxy *px, struct proxy *defpx, char **errmsg) defaults_px_ref(defpx, px); } + /* Explicit filter instances and enable/sequence directives may only be + * inherited by proxies of one type, never by listen or defaults sections. + * Class references are always present, so check their instances rather + * than the refs lists. Implicit instances do not restrict inheritance. + */ + { + struct filter_class_ref *ref; + struct filter_instance *inst; + int has_filters = (!LIST_ISEMPTY(&defpx->conf.filter_enabled) || + !LIST_ISEMPTY(&defpx->conf.filter_sequences)); + + list_for_each_entry(ref, &defpx->conf.filter_classes_req, list) { + list_for_each_entry(inst, &ref->instances, req.list) + has_filters |= !(inst->flags & FLT_INST_F_IMPLICIT); + } + list_for_each_entry(ref, &defpx->conf.filter_classes_res, list) { + list_for_each_entry(inst, &ref->instances, res.list) + has_filters |= !(inst->flags & FLT_INST_F_IMPLICIT); + } + + if (has_filters) { + if (px->cap & PR_CAP_DEF) { + memprintf(errmsg, "a defaults section cannot inherit from a defaults section defining" + " filter directives (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + else if ((px->cap & PR_CAP_LISTEN) == PR_CAP_LISTEN) { + memprintf(errmsg, "a listen section cannot inherit from a defaults section defining" + " filter directives (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + else if ((defcap == PR_CAP_BE || defcap == PR_CAP_FE) && (px->cap & PR_CAP_LISTEN) != defcap) { + memprintf(errmsg, "frontends and backends cannot inherit from the same defaults section" + " if it defines filter directives (defaults section at %s:%d)", + defpx->conf.file, defpx->conf.line); + err_code |= ERR_ALERT | ERR_ABORT; + goto out; + } + defpx->cap = (defpx->cap & ~PR_CAP_LISTEN) | (px->cap & PR_CAP_LISTEN); + } + } + /* The proxy inherits the filter instances of its defaults section. It * happens before its own directives are parsed, so that locally defined * ones can replace the inherited ones.