You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ez...@apache.org on 2022/07/29 17:22:14 UTC

[trafficserver] branch master updated: Add anonymous mmdb support (#8982)

This is an automated email from the ASF dual-hosted git repository.

eze 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 8d7802fb3 Add anonymous mmdb support (#8982)
8d7802fb3 is described below

commit 8d7802fb3ad4961f7a7fc80e41cd2d05951885d1
Author: Evan Zelkowitz <ez...@apache.org>
AuthorDate: Fri Jul 29 11:22:08 2022 -0600

    Add anonymous mmdb support (#8982)
    
    * Add anonymous mmdb support
---
 doc/admin-guide/plugins/maxmind_acl.en.rst |  29 +++++
 plugins/experimental/maxmind_acl/mmdb.cc   | 182 +++++++++++++++++++++++++++--
 plugins/experimental/maxmind_acl/mmdb.h    |  12 ++
 3 files changed, 216 insertions(+), 7 deletions(-)

diff --git a/doc/admin-guide/plugins/maxmind_acl.en.rst b/doc/admin-guide/plugins/maxmind_acl.en.rst
index 6fa1a8d94..bf56a86d9 100644
--- a/doc/admin-guide/plugins/maxmind_acl.en.rst
+++ b/doc/admin-guide/plugins/maxmind_acl.en.rst
@@ -78,3 +78,32 @@ Optional
 ========
 
 There is an optional ``html`` field which takes a html file that will be used as the body of the response for any denied requests if you wish to use a custom one.
+
+Anonymous
+=========
+
+There is also an optional ``anonymous`` field. This allows you to use the MaxMind GeoIP2 Anonymous IP database and reference it's optional fields. These are ``ip``, ``vpn``,
+``hosting``, ``public``, ``tor``, and ``residential``. Currently anonymous blocking cannot be combined with GeoIP blocking since it is considered a separate database.
+However if you custom generate your own database that includes anonymous data then you could use them at the same time. Another solution is to have two instances
+of the maxmind_acl plugin on a remap, one to handle geo blocking and another for anonymous blocking. A setting of ``true`` will cause that type to be blocked, ``false``
+will not block and is effectively a no-op.
+
+An example configuration ::
+
+   maxmind:
+    database: GeoIP2-Anonymous-IP.mmdb
+    anonymous:
+     hosting: false
+     ip: true
+     vpn: true
+     tor: false
+    allow:
+     ip:
+      - 127.0.0.1  # Can insert known anonymous IP you wish to allow here
+
+This would block anonymous IPs and VPNs while allowing hosting and tor IPs. However if an IP has multiple designations then having a ``false`` set will not stop a ``true`` entry from being blocked.
+For example in the above if an IP had both vpn and hosting true in the database then it would still be blocked due to the vpn designation.
+
+The allow IP and deny IP fields also will work while using the anonymous blocking if you wish to allow specific known IPs or block specific IPs. Keep in mind that the same rule about reversing the logic
+applies, so that even if you are only doing anonymous IP blocking, and then set allowable IPs to allow certain anonymous IP through (if desired), this will reverse the logic and default to blocking all
+IPs unless they fall into a range in the allow list.
diff --git a/plugins/experimental/maxmind_acl/mmdb.cc b/plugins/experimental/maxmind_acl/mmdb.cc
index 27e66e577..1f80e0d74 100644
--- a/plugins/experimental/maxmind_acl/mmdb.cc
+++ b/plugins/experimental/maxmind_acl/mmdb.cc
@@ -61,12 +61,12 @@ Acl::init(char const *filename)
     // Get our root maxmind node
     maxmind = _config["maxmind"];
 #if 0
-      // Test junk
-      for (YAML::const_iterator it = maxmind.begin(); it != maxmind.end(); ++it) {
-        const std::string &name    = it->first.as<std::string>();
-        YAML::NodeType::value type = it->second.Type();
-        TSDebug(PLUGIN_NAME, "name: %s, value: %d", name.c_str(), type);
-      }
+    // Test junk
+    for (YAML::const_iterator it = maxmind.begin(); it != maxmind.end(); ++it) {
+      const std::string &name    = it->first.as<std::string>();
+      YAML::NodeType::value type = it->second.Type();
+      TSDebug(PLUGIN_NAME, "name: %s, value: %d", name.c_str(), type);
+    }
 #endif
   } catch (const YAML::Exception &e) {
     TSError("[%s] YAML::Exception %s when parsing YAML config file %s for maxmind", PLUGIN_NAME, e.what(), configloc.c_str());
@@ -95,7 +95,14 @@ Acl::init(char const *filename)
   allow_regex.clear();
   deny_regex.clear();
   _html.clear();
-  default_allow = false;
+  default_allow       = false;
+  _anonymous_blocking = false;
+  _anonymous_ip       = false;
+  _anonymous_vpn      = false;
+  _hosting_provider   = false;
+  _tor_exit_node      = false;
+  _residential_proxy  = false;
+  _public_proxy       = false;
 
   if (loadallow(maxmind["allow"])) {
     TSDebug(PLUGIN_NAME, "Loaded Allow ruleset");
@@ -113,6 +120,8 @@ Acl::init(char const *filename)
 
   loadhtml(maxmind["html"]);
 
+  _anonymous_blocking = loadanonymous(maxmind["anonymous"]);
+
   if (!status) {
     TSDebug(PLUGIN_NAME, "Failed to load any rulesets, none specified");
     status = false;
@@ -121,6 +130,68 @@ Acl::init(char const *filename)
   return status;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// Parse the anonymous blocking settings
+bool
+Acl::loadanonymous(const YAML::Node &anonNode)
+{
+  if (!anonNode) {
+    TSDebug(PLUGIN_NAME, "No anonymous rules set");
+    return false;
+  }
+  if (anonNode.IsNull()) {
+    TSDebug(PLUGIN_NAME, "Anonymous rules are NULL");
+    return false;
+  }
+
+#if 0
+  // Test junk
+  for (YAML::const_iterator it = anonNode.begin(); it != anonNode.end(); ++it) {
+    const std::string &name    = it->first.as<std::string>();
+    YAML::NodeType::value type = it->second.Type();
+    TSDebug(PLUGIN_NAME, "name: %s, value: %d", name.c_str(), type);
+  }
+#endif
+
+  try {
+    if (anonNode["ip"].as<bool>(false)) {
+      TSDebug(PLUGIN_NAME, "saw ip true");
+      _anonymous_ip = true;
+    }
+
+    if (anonNode["vpn"].as<bool>(false)) {
+      TSDebug(PLUGIN_NAME, "saw vpn true");
+      _anonymous_vpn = true;
+    }
+
+    if (anonNode["hosting"].as<bool>(false)) {
+      TSDebug(PLUGIN_NAME, "saw hosting true");
+      _hosting_provider = true;
+    }
+
+    if (anonNode["public"].as<bool>(false)) {
+      TSDebug(PLUGIN_NAME, "saw public proxy true");
+      _public_proxy = true;
+    }
+
+    if (anonNode["tor"].as<bool>(false)) {
+      TSDebug(PLUGIN_NAME, "saw tor exit node true");
+      _tor_exit_node = true;
+    }
+
+    if (anonNode["residential"].as<bool>(false)) {
+      TSDebug(PLUGIN_NAME, "saw residential proxy true");
+      _residential_proxy = true;
+    }
+
+  } catch (const YAML::Exception &e) {
+    TSDebug(PLUGIN_NAME, "YAML::Exception %s when parsing YAML config file anonymous list", e.what());
+    return false;
+  }
+
+  return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Parse the deny list country codes and IPs
 bool
@@ -465,6 +536,15 @@ Acl::eval(TSRemapRequestInfo *rri, TSHttpTxn txnp)
         // Country map is empty as well as regexes, use our default rejection
         ret = default_allow;
       }
+
+      // We have mmdb data, check if we want anonymous blocking checked
+      // If blocked here, then block as well
+      if (_anonymous_blocking) {
+        if (!eval_anonymous(&result.entry)) {
+          TSDebug(PLUGIN_NAME, "Blocking Anonymous IP");
+          ret = false;
+        }
+      }
     }
   } else {
     TSDebug(PLUGIN_NAME, "No Country Code entry for this IP was found");
@@ -497,6 +577,94 @@ Acl::eval(TSRemapRequestInfo *rri, TSHttpTxn txnp)
   return ret;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// Returns true if entry data contains an
+// allowable non-anonymous IP.
+// False otherwise
+bool
+Acl::eval_anonymous(MMDB_entry_s *entry)
+{
+  MMDB_entry_data_s entry_data;
+  int status;
+
+  // For each value we only care if it was successful and if there was data.
+  // We can have unsuccessful gets in the instance an IP is not one of the things
+  // we are asking for, so its not an error
+  if (_anonymous_ip) {
+    status = MMDB_get_value(entry, &entry_data, "is_anonymous", NULL);
+    if ((MMDB_SUCCESS == status) && (entry_data.has_data)) {
+      if (entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
+        if (entry_data.boolean == true) {
+          TSDebug(PLUGIN_NAME, "saw is_anonymous set to true bool");
+          return false;
+        }
+      }
+    }
+  }
+
+  if (_anonymous_vpn) {
+    status = MMDB_get_value(entry, &entry_data, "is_anonymous_vpn", NULL);
+    if ((MMDB_SUCCESS == status) && (entry_data.has_data)) {
+      if (entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
+        if (entry_data.boolean == true) {
+          TSDebug(PLUGIN_NAME, "saw is_anonymous vpn set to true bool");
+          return false;
+        }
+      }
+    }
+  }
+
+  if (_hosting_provider) {
+    status = MMDB_get_value(entry, &entry_data, "is_hosting_provider", NULL);
+    if ((MMDB_SUCCESS == status) && (entry_data.has_data)) {
+      if (entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
+        if (entry_data.boolean == true) {
+          TSDebug(PLUGIN_NAME, "saw is_hosting set to true bool");
+          return false;
+        }
+      }
+    }
+  }
+
+  if (_public_proxy) {
+    status = MMDB_get_value(entry, &entry_data, "is_public_proxy", NULL);
+    if ((MMDB_SUCCESS == status) && (entry_data.has_data)) {
+      if (entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
+        if (entry_data.boolean == true) {
+          TSDebug(PLUGIN_NAME, "saw public_proxy set to true bool");
+          return false;
+        }
+      }
+    }
+  }
+
+  if (_tor_exit_node) {
+    status = MMDB_get_value(entry, &entry_data, "is_tor_exit_node", NULL);
+    if ((MMDB_SUCCESS == status) && (entry_data.has_data)) {
+      if (entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
+        if (entry_data.boolean == true) {
+          TSDebug(PLUGIN_NAME, "saw is_tor_exit_node set to true bool");
+          return false;
+        }
+      }
+    }
+  }
+
+  if (_residential_proxy) {
+    status = MMDB_get_value(entry, &entry_data, "is_residential_proxy", NULL);
+    if ((MMDB_SUCCESS == status) && (entry_data.has_data)) {
+      if (entry_data.type == MMDB_DATA_TYPE_BOOLEAN) {
+        if (entry_data.boolean == true) {
+          TSDebug(PLUGIN_NAME, "saw is_residential set to true bool");
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Returns true if entry data contains an
 // allowable country code from our map.
diff --git a/plugins/experimental/maxmind_acl/mmdb.h b/plugins/experimental/maxmind_acl/mmdb.h
index 989832c01..e406d88e6 100644
--- a/plugins/experimental/maxmind_acl/mmdb.h
+++ b/plugins/experimental/maxmind_acl/mmdb.h
@@ -94,6 +94,16 @@ protected:
   IpMap allow_ip_map;
   IpMap deny_ip_map;
 
+  // Anonymous blocking default to off
+  bool _anonymous_ip      = false;
+  bool _anonymous_vpn     = false;
+  bool _hosting_provider  = false;
+  bool _public_proxy      = false;
+  bool _tor_exit_node     = false;
+  bool _residential_proxy = false;
+
+  bool _anonymous_blocking = false;
+
   // Do we want to allow by default or not? Useful
   // for deny only rules
   bool default_allow = false;
@@ -103,7 +113,9 @@ protected:
   bool loadallow(const YAML::Node &allowNode);
   bool loaddeny(const YAML::Node &denyNode);
   void loadhtml(const YAML::Node &htmlNode);
+  bool loadanonymous(const YAML::Node &anonNode);
   bool eval_country(MMDB_entry_data_s *entry_data, const char *path, int path_len);
+  bool eval_anonymous(MMDB_entry_s *entry_data);
   void parseregex(const YAML::Node &regex, bool allow);
   ipstate eval_ip(const sockaddr *sock) const;
 };