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 &regex, 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 &regex, bool allow);

Reply via email to