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

cmcfarlen pushed a commit to branch 10.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 8d0d375e64e11947a2911fe02877de497b288e46
Author: Leif Hedstrom <[email protected]>
AuthorDate: Mon May 6 14:03:28 2024 -0600

    Adds a run-plugin operator to HRW (#11320)
    
    (cherry picked from commit aff07ef3ab25681d9a84539d25509709a4d17b5f)
---
 doc/admin-guide/plugins/header_rewrite.en.rst | 18 +++++++
 plugins/header_rewrite/CMakeLists.txt         |  4 +-
 plugins/header_rewrite/factory.cc             |  2 +
 plugins/header_rewrite/header_rewrite.cc      | 34 +++++++++----
 plugins/header_rewrite/lulu.h                 |  7 ++-
 plugins/header_rewrite/operator.h             |  2 +
 plugins/header_rewrite/operators.cc           | 73 +++++++++++++++++++++++++++
 plugins/header_rewrite/operators.h            | 32 ++++++++++++
 plugins/header_rewrite/parser.h               | 22 ++++++--
 9 files changed, 177 insertions(+), 17 deletions(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst 
b/doc/admin-guide/plugins/header_rewrite.en.rst
index 4068140c83..fe4db67e71 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -774,6 +774,14 @@ changing the remapped destination, ``<part>`` should be 
used to indicate the
 component that is being modified (see `URL Parts`_). Currently the only valid
 parts for rm-destination are QUERY, PATH, and PORT.
 
+run-plugin
+~~~~~~~~~~~~~~
+::
+
+  run-plugin <plugin-name>.so "<plugin-argument> ..."
+
+This allows to run an existing remap plugin, conditionally, from within a
+header rewrite rule.
 
 set-header
 ~~~~~~~~~~
@@ -1349,3 +1357,13 @@ the client where the requested data was served from.::
    cond %{HEADER:ATS-SRVR-UUID} ="" [OR]
    cond %{CACHE} ="hit-fresh"
    set-header ATS-SRVR-UUID %{ID:UNIQUE}
+
+Apply rate limiting for some select requests
+------------------------------------
+
+This rule will conditiionally, based on the client request headers, apply rate
+limiting to the request.::
+
+   cond %{REMAP_PSEUDO_HOOK} [AND]
+   cond %{CLIENT-HEADER:Some-Special-Header} ="yes"
+   run-plugin rate_limit.so "--limit=300 --error=429"
diff --git a/plugins/header_rewrite/CMakeLists.txt 
b/plugins/header_rewrite/CMakeLists.txt
index b60db87226..fbc919f58d 100644
--- a/plugins/header_rewrite/CMakeLists.txt
+++ b/plugins/header_rewrite/CMakeLists.txt
@@ -38,7 +38,7 @@ target_link_libraries(header_rewrite_parser PUBLIC 
libswoc::libswoc)
 
 target_link_libraries(
   header_rewrite
-  PRIVATE PCRE::PCRE
+  PRIVATE ts::tscore PCRE::PCRE
   PUBLIC libswoc::libswoc
 )
 
@@ -52,7 +52,7 @@ if(BUILD_TESTING)
   add_executable(test_header_rewrite header_rewrite_test.cc)
   add_test(NAME test_header_rewrite COMMAND $<TARGET_FILE:test_header_rewrite>)
 
-  target_link_libraries(test_header_rewrite PRIVATE header_rewrite_parser)
+  target_link_libraries(test_header_rewrite PRIVATE header_rewrite_parser 
ts::inkevent ts::tscore)
 
   if(maxminddb_FOUND)
     target_link_libraries(test_header_rewrite PRIVATE maxminddb::maxminddb)
diff --git a/plugins/header_rewrite/factory.cc 
b/plugins/header_rewrite/factory.cc
index 0c2b55e2ec..531f390f24 100644
--- a/plugins/header_rewrite/factory.cc
+++ b/plugins/header_rewrite/factory.cc
@@ -75,6 +75,8 @@ operator_factory(const std::string &op)
     o = new OperatorSetBody();
   } else if (op == "set-http-cntl") {
     o = new OperatorSetHttpCntl();
+  } else if (op == "run-plugin") {
+    o = new OperatorRunPlugin();
 
   } else {
     TSError("[%s] Unknown operator: %s", PLUGIN_NAME, op.c_str());
diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index 476bd57599..e6d2197788 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -26,6 +26,8 @@
 #include "ts/remap.h"
 #include "ts/remap_version.h"
 
+#include "proxy/http/remap/PluginFactory.h"
+
 #include "parser.h"
 #include "ruleset.h"
 #include "resources.h"
@@ -35,19 +37,27 @@
 // Debugs
 const char PLUGIN_NAME[]     = "header_rewrite";
 const char PLUGIN_NAME_DBG[] = "dbg_header_rewrite";
-
 namespace header_rewrite_ns
 {
 DbgCtl dbg_ctl{PLUGIN_NAME_DBG};
 DbgCtl pi_dbg_ctl{PLUGIN_NAME};
+
+PluginFactory plugin_factory;
 } // namespace header_rewrite_ns
 
-static std::once_flag initGeoLibs;
+static std::once_flag initHRWLibs;
 
 static void
-initGeoLib(const std::string &dbPath)
+initHRWLibraries(const std::string &dbPath)
 {
+  
header_rewrite_ns::plugin_factory.setRuntimeDir(RecConfigReadRuntimeDir()).addSearchDir(RecConfigReadPluginDir());
+
+  if (dbPath.empty()) {
+    return;
+  }
+
   Dbg(pi_dbg_ctl, "Loading geo db %s", dbPath.c_str());
+
 #if TS_USE_HRW_GEOIP
   GeoIPConditionGeo::initLibrary(dbPath);
 #elif TS_USE_HRW_MAXMINDDB
@@ -99,7 +109,7 @@ public:
     return _rules[hook];
   }
 
-  bool parse_config(const std::string &fname, TSHttpHookID default_hook);
+  bool parse_config(const std::string &fname, TSHttpHookID default_hook, char 
*from_url = nullptr, char *to_url = nullptr);
 
 private:
   bool add_rule(RuleSet *rule);
@@ -133,7 +143,7 @@ RulesConfig::add_rule(RuleSet *rule)
 // anyways (or reload for remap.config), so not really in the critical path.
 //
 bool
-RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook)
+RulesConfig::parse_config(const std::string &fname, TSHttpHookID default_hook, 
char *from_url, char *to_url)
 {
   std::unique_ptr<RuleSet> rule(nullptr);
   std::string              filename;
@@ -177,7 +187,7 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook)
       continue;
     }
 
-    Parser p;
+    Parser p(from_url, to_url);
 
     // Tokenize and parse this line
     if (!p.parse_line(line)) {
@@ -356,7 +366,7 @@ TSPluginInit(int argc, const char *argv[])
 
   Dbg(pi_dbg_ctl, "Global geo db %s", geoDBpath.c_str());
 
-  std::call_once(initGeoLibs, [&geoDBpath]() { initGeoLib(geoDBpath); });
+  std::call_once(initHRWLibs, [&geoDBpath]() { initHRWLibraries(geoDBpath); });
 
   // Parse the global config file(s). All rules are just appended
   // to the "global" Rules configuration.
@@ -414,6 +424,9 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char 
* /* errbuf ATS_UNUSE
     return TS_ERROR;
   }
 
+  char *from_url = argv[0];
+  char *to_url   = argv[1];
+
   // argv contains the "to" and "from" URLs. Skip the first so that the
   // second one poses as the program name.
   --argc;
@@ -439,15 +452,15 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, 
char * /* errbuf ATS_UNUSE
     }
 
     Dbg(pi_dbg_ctl, "Remap geo db %s", geoDBpath.c_str());
-
-    std::call_once(initGeoLibs, [&geoDBpath]() { initGeoLib(geoDBpath); });
   }
 
+  std::call_once(initHRWLibs, [&geoDBpath]() { initHRWLibraries(geoDBpath); });
+
   RulesConfig *conf = new RulesConfig;
 
   for (int i = optind; i < argc; ++i) {
     Dbg(pi_dbg_ctl, "Loading remap configuration file %s", argv[i]);
-    if (!conf->parse_config(argv[i], TS_REMAP_PSEUDO_HOOK)) {
+    if (!conf->parse_config(argv[i], TS_REMAP_PSEUDO_HOOK, from_url, to_url)) {
       TSError("[%s] Unable to create remap instance", PLUGIN_NAME);
       delete conf;
       return TS_ERROR;
@@ -473,6 +486,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char 
* /* errbuf ATS_UNUSE
 void
 TSRemapDeleteInstance(void *ih)
 {
+  Dbg(pi_dbg_ctl, "Deleting RulesConfig");
   delete static_cast<RulesConfig *>(ih);
 }
 
diff --git a/plugins/header_rewrite/lulu.h b/plugins/header_rewrite/lulu.h
index 2d23a9ac94..f12339346e 100644
--- a/plugins/header_rewrite/lulu.h
+++ b/plugins/header_rewrite/lulu.h
@@ -27,6 +27,8 @@
 #include "tscore/ink_defs.h"
 #include "tscore/ink_platform.h"
 
+#include "proxy/http/remap/PluginFactory.h"
+
 #define TS_REMAP_PSEUDO_HOOK TS_HTTP_LAST_HOOK // Ugly, but use the "last 
hook" for remap instances.
 
 std::string getIP(sockaddr const *s_sockaddr);
@@ -38,7 +40,8 @@ extern const char PLUGIN_NAME_DBG[];
 
 namespace header_rewrite_ns
 {
-extern DbgCtl dbg_ctl;
-extern DbgCtl pi_dbg_ctl;
+extern DbgCtl        dbg_ctl;
+extern DbgCtl        pi_dbg_ctl;
+extern PluginFactory plugin_factory;
 } // namespace header_rewrite_ns
 using namespace header_rewrite_ns;
diff --git a/plugins/header_rewrite/operator.h 
b/plugins/header_rewrite/operator.h
index 84855e9a38..27e800fa62 100644
--- a/plugins/header_rewrite/operator.h
+++ b/plugins/header_rewrite/operator.h
@@ -45,6 +45,8 @@ class Operator : public Statement
 public:
   Operator() { Dbg(dbg_ctl, "Calling CTOR for Operator"); }
 
+  virtual ~Operator() = default; // Very uncommon for an Operator to have a 
custom DTOR, but happens.
+
   // noncopyable
   Operator(const Operator &)       = delete;
   void operator=(const Operator &) = delete;
diff --git a/plugins/header_rewrite/operators.cc 
b/plugins/header_rewrite/operators.cc
index f4b652f939..f6f96b15f1 100644
--- a/plugins/header_rewrite/operators.cc
+++ b/plugins/header_rewrite/operators.cc
@@ -22,8 +22,10 @@
 #include <arpa/inet.h>
 #include <cstring>
 #include <algorithm>
+#include <iomanip>
 
 #include "ts/ts.h"
+#include "swoc/swoc_file.h"
 
 #include "operators.h"
 #include "ts/apidefs.h"
@@ -1092,6 +1094,7 @@ OperatorSetHttpCntl::initialize_hooks()
 static const char *const HttpCntls[] = {
   "LOGGING", "INTERCEPT_RETRY", "RESP_CACHEABLE", "REQ_CACHEABLE", 
"SERVER_NO_STORE", "TXN_DEBUG", "SKIP_REMAP",
 };
+
 void
 OperatorSetHttpCntl::exec(const Resources &res) const
 {
@@ -1103,3 +1106,73 @@ OperatorSetHttpCntl::exec(const Resources &res) const
     Dbg(pi_dbg_ctl, "   Turning OFF %s for transaction", 
HttpCntls[static_cast<size_t>(_cntl_qual)]);
   }
 }
+
+void
+OperatorRunPlugin::initialize(Parser &p)
+{
+  Operator::initialize(p);
+
+  auto plugin_name = p.get_arg();
+  auto plugin_args = p.get_value();
+
+  if (plugin_name.empty()) {
+    TSError("[%s] missing plugin name", PLUGIN_NAME);
+    return;
+  }
+
+  std::vector<std::string> tokens;
+  std::istringstream       iss(plugin_args);
+  std::string              token;
+
+  while (iss >> std::quoted(token)) {
+    tokens.push_back(token);
+  }
+
+  // Create argc and argv
+  int    argc = tokens.size() + 2;
+  char **argv = new char *[argc];
+
+  argv[0] = p.from_url();
+  argv[1] = p.to_url();
+
+  for (int i = 0; i < argc; ++i) {
+    argv[i + 2] = const_cast<char *>(tokens[i].c_str());
+  }
+
+  std::string error;
+
+  // We have to escalate access while loading these plugins, just as done when 
loading remap.config
+  {
+    uint32_t elevate_access = 0;
+
+    REC_ReadConfigInteger(elevate_access, "proxy.config.plugin.load_elevated");
+    ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0);
+
+    _plugin = plugin_factory.getRemapPlugin(swoc::file::path(plugin_name), 
argc, const_cast<char **>(argv), error,
+                                            isPluginDynamicReloadEnabled());
+  } // done elevating access
+
+  delete[] argv;
+
+  if (!_plugin) {
+    TSError("[%s] Unable to load plugin '%s': %s", PLUGIN_NAME, 
plugin_name.c_str(), error.c_str());
+  }
+}
+
+void
+OperatorRunPlugin::initialize_hooks()
+{
+  add_allowed_hook(TS_REMAP_PSEUDO_HOOK);
+
+  require_resources(RSRC_CLIENT_REQUEST_HEADERS); // Need this for the txnp
+}
+
+void
+OperatorRunPlugin::exec(const Resources &res) const
+{
+  TSReleaseAssert(_plugin != nullptr);
+
+  if (res._rri && res.txnp) {
+    _plugin->doRemap(res.txnp, res._rri);
+  }
+}
diff --git a/plugins/header_rewrite/operators.h 
b/plugins/header_rewrite/operators.h
index 8fea20bef2..3f30d2654d 100644
--- a/plugins/header_rewrite/operators.h
+++ b/plugins/header_rewrite/operators.h
@@ -443,3 +443,35 @@ private:
   bool           _flag = false;
   TSHttpCntlType _cntl_qual;
 };
+
+class RemapPluginInst; // Opaque to the HRW operator, but needed in the 
implementation.
+
+class OperatorRunPlugin : public Operator
+{
+public:
+  OperatorRunPlugin() { Dbg(dbg_ctl, "Calling CTOR for OperatorRunPlugin"); }
+
+  // This one is special, since we have to remove the old plugin from the 
factory.
+  ~OperatorRunPlugin() override
+  {
+    Dbg(dbg_ctl, "Calling DTOR for OperatorRunPlugin");
+
+    if (_plugin) {
+      _plugin->done();
+      _plugin = nullptr;
+    }
+  }
+
+  // noncopyable
+  OperatorRunPlugin(const OperatorRunPlugin &) = delete;
+  void operator=(const OperatorRunPlugin &)    = delete;
+
+  void initialize(Parser &p) override;
+
+protected:
+  void initialize_hooks() override;
+  void exec(const Resources &res) const override;
+
+private:
+  RemapPluginInst *_plugin = nullptr;
+};
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index aef6cb4b4d..25bfdd0a70 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -33,12 +33,26 @@
 class Parser
 {
 public:
-  Parser(){};
+  Parser() = default; // No from/to URLs for this parser
+  Parser(char *from_url, char *to_url) : _from_url(from_url), _to_url(to_url) 
{}
 
   // noncopyable
   Parser(const Parser &)         = delete;
   void operator=(const Parser &) = delete;
 
+  // These are not const char *, because, you know, everything else with argv 
is a char *
+  char *
+  from_url() const
+  {
+    return _from_url;
+  }
+
+  char *
+  to_url() const
+  {
+    return _to_url;
+  }
+
   bool
   empty() const
   {
@@ -95,8 +109,10 @@ public:
 private:
   bool preprocess(std::vector<std::string> tokens);
 
-  bool                     _cond  = false;
-  bool                     _empty = false;
+  bool                     _cond     = false;
+  bool                     _empty    = false;
+  char                    *_from_url = nullptr;
+  char                    *_to_url   = nullptr;
   std::vector<std::string> _mods;
   std::string              _op;
   std::string              _arg;

Reply via email to