This is an automated email from the ASF dual-hosted git repository.
traeak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new f891fe9c44 add bypass header config option to maxmind_acl plugin
(#13160)
f891fe9c44 is described below
commit f891fe9c440b1e7676f699f6df82dcffed936e51
Author: Brian Olsen <[email protected]>
AuthorDate: Mon Jun 8 16:07:35 2026 -0600
add bypass header config option to maxmind_acl plugin (#13160)
* add bypass header config option to maxmind_acl plugin
* fix for header value check, update docs with warning about usage of this
new feature
* require both header and value. clean up review suggestions
* more explicit documentation about value match, limit debug log
* maxmind: tighten up checks on yaml node
---
doc/admin-guide/plugins/maxmind_acl.en.rst | 47 ++++++++++++-
plugins/experimental/maxmind_acl/maxmind_acl.cc | 4 ++
plugins/experimental/maxmind_acl/mmdb.cc | 92 +++++++++++++++++++++++++
plugins/experimental/maxmind_acl/mmdb.h | 7 ++
4 files changed, 149 insertions(+), 1 deletion(-)
diff --git a/doc/admin-guide/plugins/maxmind_acl.en.rst
b/doc/admin-guide/plugins/maxmind_acl.en.rst
index d0a4aacb97..32fc35d199 100644
--- a/doc/admin-guide/plugins/maxmind_acl.en.rst
+++ b/doc/admin-guide/plugins/maxmind_acl.en.rst
@@ -113,4 +113,49 @@ The plugin also supports optional fields from GeoGuard
databases which includes:
``vpn_datacenter``
``relay_proxy``
``proxy_over_vpn``
-``smart_dns_proxy``
\ No newline at end of file
+``smart_dns_proxy``
+
+Bypass
+======
+
+An optional ``bypass`` field allows a request to skip all geo checks entirely
and pass through
+unmodified. Both a header name and an expected value must be configured; when
the named header
+is present in the request **and** its value matches exactly, the plugin
returns immediately
+without performing any country, IP, regex, or anonymous evaluation.
+
+``header``
+ Required sub-key. The name of the HTTP request header to look for, e.g.
``@GeoBypass``.
+
+``value``
+ Required sub-key. The header field value must match this string exactly for
the bypass to
+ trigger. Both ``header`` and ``value`` must be present and non-empty;
omitting either
+ disables the bypass entirely and a warning is emitted to the ATS error log.
+
+The comparison uses the complete, raw field value of the first occurrence of
the named header.
+Duplicate headers with the same name (repeated lines) are ignored — only the
first is evaluated.
+Within that first field, the entire value must match exactly, so a
comma-separated multi-value
+(e.g. ``@GeoBypass: 1, extra``) in a single header line will not match a
simple configured value.
+
+An example configuration ::
+
+ maxmind:
+ database: GeoIP2-City.mmdb
+ bypass:
+ header: "@GeoBypass"
+ value: "1"
+ allow:
+ country:
+ - US
+
+This is useful for internal or trusted upstream services that should not be
subject to geo
+restrictions. If ``bypass`` is absent from the configuration, or if either
``header`` or
+``value`` is missing, bypass is disabled and all requests are evaluated
normally.
+
+.. warning::
+
+ Because the bypass skips **all** ACL checks, the configured header must be
+ unforgeable by external clients. Use an internal ``@``-prefixed header (e.g.
+ ``@GeoBypass``) that is set by ATS itself or a trusted upstream, or
+ ensure the edge strips/overwrites the header before it reaches this plugin.
+ Configuring a normal client-supplied header allows end users to opt out of
+ geo restrictions by simply sending the header in their request.
\ No newline at end of file
diff --git a/plugins/experimental/maxmind_acl/maxmind_acl.cc
b/plugins/experimental/maxmind_acl/maxmind_acl.cc
index a6c6a26948..b05aa50dc4 100644
--- a/plugins/experimental/maxmind_acl/maxmind_acl.cc
+++ b/plugins/experimental/maxmind_acl/maxmind_acl.cc
@@ -68,6 +68,10 @@ TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo
*rri)
Dbg(dbg_ctl, "No ACLs configured");
} else {
Acl *a = static_cast<Acl *>(ih);
+ if (a->check_bypass(rh)) {
+ Dbg(dbg_ctl, "bypassing geo check due to bypass header");
+ return TSREMAP_NO_REMAP;
+ }
if (!a->eval(rri, rh)) {
Dbg(dbg_ctl, "denying request");
TSHttpTxnStatusSet(rh, TS_HTTP_STATUS_FORBIDDEN, PLUGIN_NAME);
diff --git a/plugins/experimental/maxmind_acl/mmdb.cc
b/plugins/experimental/maxmind_acl/mmdb.cc
index 8600172f05..26aa807047 100644
--- a/plugins/experimental/maxmind_acl/mmdb.cc
+++ b/plugins/experimental/maxmind_acl/mmdb.cc
@@ -121,6 +121,9 @@ Acl::init(char const *filename)
_proxy_over_vpn = false;
_smart_dns_proxy = false;
+ _bypass_header.clear();
+ _bypass_header_value.clear();
+
if (loadallow(maxmind["allow"])) {
Dbg(dbg_ctl, "Loaded Allow ruleset");
status = true;
@@ -139,6 +142,8 @@ Acl::init(char const *filename)
_anonymous_blocking = loadanonymous(maxmind["anonymous"]);
+ loadbypass(maxmind["bypass"]);
+
if (!status) {
Dbg(dbg_ctl, "Failed to load any rulesets, none specified");
status = false;
@@ -429,6 +434,58 @@ Acl::parseregex(const YAML::Node ®ex, bool allow)
}
}
+void
+Acl::loadbypass(const YAML::Node &bypassNode)
+{
+ if (!bypassNode) {
+ Dbg(dbg_ctl, "No bypass set");
+ return;
+ }
+ if (bypassNode.IsNull()) {
+ TSWarning("[%s] bypass node is NULL — bypass disabled", PLUGIN_NAME);
+ return;
+ }
+
+ try {
+ if (bypassNode["header"]) {
+ const YAML::Node &headerNode = bypassNode["header"];
+ if (headerNode.IsNull() || !headerNode.IsScalar()) {
+ TSWarning("[%s] bypass 'header' is null or non-scalar — bypass
disabled", PLUGIN_NAME);
+ return;
+ }
+
+ if (!bypassNode["value"]) {
+ TSWarning("[%s] bypass 'header' set without 'value' — bypass disabled;
both are required", PLUGIN_NAME);
+ return;
+ }
+ const YAML::Node &valueNode = bypassNode["value"];
+ if (valueNode.IsNull() || !valueNode.IsScalar()) {
+ TSWarning("[%s] bypass 'value' is null or non-scalar — bypass
disabled", PLUGIN_NAME);
+ return;
+ }
+
+ _bypass_header_value = valueNode.as<std::string>();
+ if (_bypass_header_value.empty()) {
+ TSWarning("[%s] bypass 'value' is empty — bypass disabled; a non-empty
value is required", PLUGIN_NAME);
+ return;
+ }
+ _bypass_header = headerNode.as<std::string>();
+ if (_bypass_header.empty()) {
+ TSWarning("[%s] bypass 'header' is empty — bypass disabled; a
non-empty header is required", PLUGIN_NAME);
+ return;
+ }
+ Dbg(dbg_ctl, "bypass header set to: %s", _bypass_header.c_str());
+ Dbg(dbg_ctl, "bypass value set to: %s", _bypass_header_value.c_str());
+ } else {
+ TSWarning("[%s] bypass is set but missing 'header' key — bypass
disabled", PLUGIN_NAME);
+ return;
+ }
+ } catch (const YAML::Exception &e) {
+ TSError("[%s] YAML::Exception %s when parsing bypass config", PLUGIN_NAME,
e.what());
+ return;
+ }
+}
+
void
Acl::loadhtml(const YAML::Node &htmlNode)
{
@@ -503,6 +560,41 @@ Acl::loaddb(const YAML::Node &dbNode)
return true;
}
+bool
+Acl::check_bypass(TSHttpTxn txnp) const
+{
+ if (_bypass_header.empty()) {
+ return false;
+ }
+
+ TSMBuffer mbuf;
+ TSMLoc hdr_loc;
+ if (TS_SUCCESS != TSHttpTxnClientReqGet(txnp, &mbuf, &hdr_loc)) {
+ Dbg(dbg_ctl, "check_bypass: failed to get client request headers");
+ return false;
+ }
+
+ TSMLoc field_loc = TSMimeHdrFieldFind(mbuf, hdr_loc, _bypass_header.c_str(),
static_cast<int>(_bypass_header.size()));
+ if (TS_NULL_MLOC == field_loc) {
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, hdr_loc);
+ return false;
+ }
+
+ bool bypassed = false;
+ int val_len = 0;
+ const char *val = TSMimeHdrFieldValueStringGet(mbuf, hdr_loc,
field_loc, -1, &val_len);
+ if (val != nullptr && 0 < val_len && std::string_view(val, val_len) ==
_bypass_header_value) {
+ Dbg(dbg_ctl, "check_bypass: bypass triggered");
+ bypassed = true;
+ } else {
+ Dbg(dbg_ctl, "check_bypass: bypass header present but value did not
match");
+ }
+
+ TSHandleMLocRelease(mbuf, hdr_loc, field_loc);
+ TSHandleMLocRelease(mbuf, TS_NULL_MLOC, hdr_loc);
+ return bypassed;
+}
+
bool
Acl::eval(TSRemapRequestInfo * /* rri ATS_UNUSED */, TSHttpTxn txnp)
{
diff --git a/plugins/experimental/maxmind_acl/mmdb.h
b/plugins/experimental/maxmind_acl/mmdb.h
index 9b1d622a20..bdcb5ad550 100644
--- a/plugins/experimental/maxmind_acl/mmdb.h
+++ b/plugins/experimental/maxmind_acl/mmdb.h
@@ -26,6 +26,7 @@
#include <ts/ts.h>
#include <ts/remap.h>
#include <string>
+#include <string_view>
#include <cstring>
#include <iostream>
#include <fstream>
@@ -69,6 +70,7 @@ public:
}
bool eval(TSRemapRequestInfo *rri, TSHttpTxn txnp);
+ bool check_bypass(TSHttpTxn txnp) const;
bool init(char const *filename);
void
@@ -111,6 +113,10 @@ protected:
bool _anonymous_blocking = false;
+ // Bypass header fields
+ std::string _bypass_header;
+ std::string _bypass_header_value;
+
// Do we want to allow by default or not? Useful
// for deny only rules
bool default_allow = false;
@@ -121,6 +127,7 @@ protected:
bool loaddeny(const YAML::Node &denyNode);
void loadhtml(const YAML::Node &htmlNode);
bool loadanonymous(const YAML::Node &anonNode);
+ void loadbypass(const YAML::Node &bypassNode);
bool eval_country(MMDB_entry_data_s *entry_data, const std::string &url);
bool eval_anonymous(MMDB_entry_s *entry_data);
void parseregex(const YAML::Node ®ex, bool allow);