]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/lua: support packages via luarocks
authorYuval Lifshitz <ylifshit@redhat.com>
Sun, 1 Nov 2020 10:49:26 +0000 (12:49 +0200)
committerYuval Lifshitz <ylifshit@redhat.com>
Fri, 4 Dec 2020 07:55:56 +0000 (09:55 +0200)
Signed-off-by: Yuval Lifshitz <ylifshit@redhat.com>
21 files changed:
CMakeLists.txt
ceph.spec.in
debian/control
doc/radosgw/lua-scripting.rst
src/common/options.cc
src/include/config-h.in.cmake
src/rgw/CMakeLists.txt
src/rgw/rgw_admin.cc
src/rgw/rgw_lua.cc
src/rgw/rgw_lua.h
src/rgw/rgw_lua_request.cc
src/rgw/rgw_lua_request.h
src/rgw/rgw_lua_utils.cc
src/rgw/rgw_lua_utils.h
src/rgw/rgw_lua_version.h [new file with mode: 0644]
src/rgw/rgw_main.cc
src/rgw/rgw_process.cc
src/test/cli/radosgw-admin/help.t
src/test/rgw/CMakeLists.txt
src/test/rgw/test_rgw_lua.cc
src/vstart.sh

index 65a81c9e100301ec25dc9a9bd7fe544db3d7431e..86510ac8c02fc7a34828b676abaaeeda169f07ff 100644 (file)
@@ -382,6 +382,7 @@ option(WITH_RADOSGW_BEAST_FRONTEND "Rados Gateway's Beast frontend is enabled" O
 option(WITH_RADOSGW_BEAST_OPENSSL "Rados Gateway's Beast frontend uses OpenSSL" ON)
 option(WITH_RADOSGW_AMQP_ENDPOINT "Rados Gateway's pubsub support for AMQP push endpoint" ON)
 option(WITH_RADOSGW_KAFKA_ENDPOINT "Rados Gateway's pubsub support for Kafka push endpoint" ON)
+option(WITH_RADOSGW_LUA_PACKAGES "Rados Gateway's support for dynamically adding lua packagess" ON)
 
 if(WITH_RADOSGW)
   find_package(EXPAT REQUIRED)
@@ -581,6 +582,10 @@ if(WITH_SEASTAR)
   list(APPEND BOOST_COMPONENTS filesystem timer)
 endif()
 
+if(WITH_RADOSGW AND WITH_RADOSGW_LUA_PACKAGES)
+  list(APPEND BOOST_COMPONENTS filesystem)
+endif()
+
 set(Boost_USE_MULTITHREADED ON)
 # require minimally the bundled version
 if(WITH_SYSTEM_BOOST)
index 9ac67112f4860d7427c79283eef44e7e81dea0c9..f2ec3c04c5a78572387b98c3613e0799cd7e1706 100644 (file)
 %endif
 %endif
 
+%if 0%{?suse_version}
+%if !0%{?is_opensuse}
+# SLE does not support luarocks
+%bcond_with lua_packages
+%else
+%global luarocks_package_name lua53-luarocks
+%bcond_without lua_packages
+%endif
+%else
+%global luarocks_package_name luarocks
+%bcond_without lua_packages
+%endif
+
 %{!?_udevrulesdir: %global _udevrulesdir /lib/udev/rules.d}
 %{!?tmpfiles_create: %global tmpfiles_create systemd-tmpfiles --create}
 %{!?python3_pkgversion: %global python3_pkgversion 3}
@@ -187,6 +200,9 @@ BuildRequires:  librabbitmq-devel
 %if 0%{with kafka_endpoint}
 BuildRequires:  librdkafka-devel
 %endif
+%if 0%{with lua_packages}
+BuildRequires:  %{luarocks_package_name}
+%endif
 %if 0%{with make_check}
 BuildRequires:  jq
 BuildRequires: libuuid-devel
@@ -1194,6 +1210,9 @@ ${CMAKE} .. \
 %else
     -DWITH_RADOSGW_KAFKA_ENDPOINT=OFF \
 %endif
+%if 0%{without lua_packages}
+    -DWITH_RADOSGW_LUA_PACKAGES=OFF
+%endif
 %if 0%{with zbd}
     -DWITH_ZBD=ON \
 %endif
index 4a89172aff9eb85e131a6d5ad0c3c575ac3c5771..370d7420ff6aa9a58c3ba961f9eb2b471704e36b 100644 (file)
@@ -63,6 +63,7 @@ Build-Depends: cmake (>= 3.10.2),
 # Crimson      libyaml-cpp-dev,
                librabbitmq-dev,
                librdkafka-dev,
+               luarocks,
 # Make-Check   libxmlsec1,
 # Make-Check   libxmlsec1-nss,
 # Make-Check   libxmlsec1-openssl,
index a5dbc8710e1656add1639e760d5a2c336032c075..81147f4e50081fd97822277c843ad945c5a64f08 100644 (file)
@@ -10,6 +10,15 @@ This feature allows users to upload Lua scripts to different context in the rado
 operation was taken, and "postRequest" that will execute after each operation is taken. Script may be uploaded to address requests for users of a specific tenant.
 The script can access fields in the request and modify some fields. All Lua language features can be used in the script.
 
+By default, all lua standard libraries are available in the script, however, in order to allow for other lua modules to be used in the script, we support adding packages to an allowlist:
+
+  - All packages in the allowlist are being re-installed using the luarocks package manager on radosgw restart. Therefore a restart is needed for adding or removing of packages to take effect 
+  - To add a package that contains C source code that needs to be compiled, use the `--allow-compilation` flag. In this case a C compiler needs to be available on the host
+  - Lua packages are installed in, and used from, a directory local to the radosgw. Meaning that lua packages in the allowlist are separated from any lua packages available on the host.
+    By default, this directory would be `/tmp/luarocks/`, and could be set to a different location via the `rgw_luarocks_location` configuration parameter. 
+    Note that this parameter should not be set to one of the default locations where luarocks install packages (e.g. `$HOME/.luarocks`, `/usr/lib64/lua`, `/usr/share/lua`)
+       
+
 .. toctree::
    :maxdepth: 1
 
@@ -38,6 +47,30 @@ To remove the script:
    # radosgw-admin script rm --context={preRequest|postRequest} [--tenant={tenant-name}]
 
 
+Package Management via CLI
+--------------------------
+
+To add a package to the allowlist:
+
+::
+
+  # radosgw-admin script-package add --package={package name} [--allow-compilation]
+
+
+To remove a package from the allowlist:
+
+::
+
+  # radosgw-admin script-package rm --package={package name}
+
+
+To print the list of packages in the allowlist:
+
+::
+
+  # radosgw-admin script-package list
+
+
 Context Free Functions
 ----------------------
 Debug Log
@@ -324,3 +357,37 @@ In the `postRequest` context we look at the metadata:
     RGWDebugLog("key=" .. k .. ", " .. "value=" .. v)
   end
  
+- Use modules to create Unix socket based, JSON encoded, "access log":
+
+First we should add the following packages to the allowlist:
+
+::
+
+  # radosgw-admin script-package add --package=luajson
+  # radosgw-admin script-package add --package=luasocket --allow-compilation
+
+
+Then, do a restart for the radosgw and upload the following script to the `postRequest` context:
+
+.. code-block:: lua
+
+  if Request.RGWOp == "get_obj" then
+    local json = require("json")
+    local socket = require("socket")
+    local unix = require("socket.unix")
+    local s = assert(unix())
+    E = {}
+
+    msg = {bucket = (Request.Bucket or (Request.CopyFrom or E).Bucket).Name,
+      time = Request.Time,
+      operation = Request.RGWOp,
+      http_status = Request.Response.HTTPStatusCode,
+      error_code = Request.Response.HTTPStatus,
+      object_size = Request.Object.Size,
+      trans_id = Request.TransactionId}
+
+    assert(s:connect("/tmp/socket"))
+    assert(s:send(json.encode(msg).."\n"))
+    assert(s:close())
+  end
+
index 7de01868ccfdf6b4f1a3a94e806c73f89731e3ef..2574f7ebd9a4a6d7c518d55277e9579e01b5af69 100644 (file)
@@ -7246,6 +7246,16 @@ std::vector<Option> get_rgw_options() {
        "but will default to FIFO if there isn't an existing log. Either of "
        "the explicit options will cause startup to fail if the other log is "
        "still around."),
+   
+   Option("rgw_luarocks_location", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+     .set_flag(Option::FLAG_STARTUP)
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+    .set_default("/tmp/luarocks")
+#else
+    // if set to empty string, system default luarocks package location (if exist) will be used
+    .set_default("")
+#endif
+    .set_description("Directory where luarocks install packages from allowlist"),
   });
 }
 
index d952983c55b3b85c6420b18b97a134f50a14937a..3e3f48ae36268b9aaf17fc42154f31ab9a5b9839 100644 (file)
 /* Defined if libedkafka is available for rgw kafka push endpoint */
 #cmakedefine WITH_RADOSGW_KAFKA_ENDPOINT
 
+/* Defined if lua packages can be installed by radosgw */
+#cmakedefine WITH_RADOSGW_LUA_PACKAGES
+
 /* Defined if std::map::merge() is supported */
 #cmakedefine HAVE_STDLIB_MAP_SPLICING
 
index dc67f65c7a92a47427c1af3216a57fcb4401d3b2..65a77228b1b25d299b2f90363a41498cb38ac839 100644 (file)
@@ -270,6 +270,10 @@ endif()
 
 list(APPEND rgw_libs ${LUA_LIBRARIES})
 
+if(WITH_RADOSGW_LUA_PACKAGES)
+  list(APPEND rgw_libs Boost::filesystem)
+endif()
+
 set(rgw_schedulers_srcs
   rgw_dmclock_scheduler_ctx.cc
   rgw_dmclock_sync_scheduler.cc)
index 88cb5ee5a3bbecd8f8abf3b28e0c594cc1e50ccd..60e2e6d32756d88244289737f1b3fe08082c4a04 100644 (file)
@@ -283,6 +283,9 @@ void usage()
   cout << "  script put                 upload a lua script to a context\n";
   cout << "  script get                 get the lua script of a context\n";
   cout << "  script rm                  remove the lua scripts of a context\n";
+  cout << "  script-package add         add a lua package to the scripts allowlist\n";
+  cout << "  script-package rm          remove a lua package from the scripts allowlist\n";
+  cout << "  script-package list        get the lua packages allowlist\n";
   cout << "options:\n";
   cout << "   --tenant=<tenant>         tenant name\n";
   cout << "   --user_ns=<namespace>     namespace of user (oidc in case of users authenticated with oidc provider)\n";
@@ -434,6 +437,8 @@ void usage()
   cout << "   --event-id                event id in a pubsub subscription\n";
   cout << "\nScript options:\n";
   cout << "   --context                 context in which the script runs. one of: preRequest, postRequest\n";
+  cout << "   --package                 name of the lua package that should be added/removed to/from the allowlist\n";
+  cout << "   --allow-compilation       package is allowed to compile C code as part of its installation\n";
   cout << "\n";
   generic_client_usage();
 }
@@ -768,6 +773,9 @@ enum class OPT {
   SCRIPT_PUT,
   SCRIPT_GET,
   SCRIPT_RM,
+  SCRIPT_PACKAGE_ADD,
+  SCRIPT_PACKAGE_RM,
+  SCRIPT_PACKAGE_LIST
 };
 
 }
@@ -984,6 +992,9 @@ static SimpleCmd::Commands all_cmds = {
   { "script put", OPT::SCRIPT_PUT },
   { "script get", OPT::SCRIPT_GET },
   { "script rm", OPT::SCRIPT_RM },
+  { "script-package add", OPT::SCRIPT_PACKAGE_ADD },
+  { "script-package rm", OPT::SCRIPT_PACKAGE_RM },
+  { "script-package list", OPT::SCRIPT_PACKAGE_LIST },
 };
 
 static SimpleCmd::Aliases cmd_aliases = {
@@ -3173,6 +3184,8 @@ int main(int argc, const char **argv)
   string event_id;
 
   std::optional<std::string> str_script_ctx;
+  std::optional<std::string> script_package;
+  int allow_compilation = false;
 
   std::optional<string> opt_group_id;
   std::optional<string> opt_status;
@@ -3634,6 +3647,10 @@ int main(int argc, const char **argv)
       // do nothing
     } else if (ceph_argparse_witharg(args, i, &val, "--context", (char*)NULL)) {
       str_script_ctx = val;
+    } else if (ceph_argparse_witharg(args, i, &val, "--package", (char*)NULL)) {
+      script_package = val;
+    } else if (ceph_argparse_binary_flag(args, i, &allow_compilation, NULL, "--allow-compilation", (char*)NULL)) {
+      // do nothing
     } else if (strncmp(*i, "-", 1) == 0) {
       cerr << "ERROR: invalid flag " << *i << std::endl;
       return EINVAL;
@@ -9285,7 +9302,7 @@ next:
     auto rc = read_input(infile, bl);
     if (rc < 0) {
       cerr << "ERROR: failed to read script: '" << infile << "'. error: " << rc << std::endl;
-      return rc;
+      return -rc;
     }
     const std::string script = bl.to_str();
     std::string err_msg;
@@ -9301,7 +9318,7 @@ next:
     rc = rgw::lua::write_script(store, tenant, null_yield, script_ctx, script);
     if (rc < 0) {
       cerr << "ERROR: failed to put script. error: " << rc << std::endl;
-      return rc;
+      return -rc;
     }
   }
 
@@ -9322,7 +9339,7 @@ next:
         (tenant.empty() ? "" : (" in tenant: " + tenant)) << std::endl;
     } else if (rc < 0) {
       cerr << "ERROR: failed to read script. error: " << rc << std::endl;
-      return rc;
+      return -rc;
     } else {
       std::cout << script << std::endl;
     }
@@ -9341,8 +9358,66 @@ next:
     const auto rc = rgw::lua::delete_script(store, tenant, null_yield, script_ctx);
     if (rc < 0) {
       cerr << "ERROR: failed to remove script. error: " << rc << std::endl;
+      return -rc;
+    }
+  }
+
+  if (opt_cmd == OPT::SCRIPT_PACKAGE_ADD) {
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+    if (!script_package) {
+      cerr << "ERROR: lua package name was not provided (via --package)" << std::endl;
+      return EINVAL;
+    }
+    const auto rc = rgw::lua::add_package(store, null_yield, *script_package, bool(allow_compilation));
+    if (rc < 0) {
+      cerr << "ERROR: failed to add lua package: " << script_package << " .error: " << rc << std::endl;
+      return -rc;
+    }
+#else
+    cerr << "ERROR: adding lua packages in not permitted" << std::endl;
+    return EPERM;
+#endif
+  }
+
+  if (opt_cmd == OPT::SCRIPT_PACKAGE_RM) {
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+    if (!script_package) {
+      cerr << "ERROR: lua package name was not provided (via --package)" << std::endl;
+      return EINVAL;
+    }
+    const auto rc = rgw::lua::remove_package(store, null_yield, *script_package);
+    if (rc == -ENOENT) {
+      cerr << "WARNING: package " << script_package << " did not exists or already removed" << std::endl;
+      return 0;
+    }
+    if (rc < 0) {
+      cerr << "ERROR: failed to remove lua package: " << script_package << " .error: " << rc << std::endl;
+      return -rc;
+    }
+#else
+    cerr << "ERROR: removing lua packages in not permitted" << std::endl;
+    return EPERM;
+#endif
+  }
+
+  if (opt_cmd == OPT::SCRIPT_PACKAGE_LIST) {
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+    rgw::lua::packages_t packages;
+    const auto rc = rgw::lua::list_packages(store, null_yield, packages);
+    if (rc == -ENOENT) {
+      std::cout << "no lua packages in allowlist" << std::endl;
+    } else if (rc < 0) {
+      cerr << "ERROR: failed to read lua packages allowlist. error: " << rc << std::endl;
       return rc;
+    } else {
+      for (const auto& package : packages) {
+          std::cout << package << std::endl;
+      }
     }
+#else
+    cerr << "ERROR: listing lua packages in not permitted" << std::endl;
+    return EPERM;
+#endif
   }
 
   return 0;
index 18a4135a58fe67ca19118207cd9b2ed16849921b..c43d8faf523d9d9f5364693f67e2a4be99b80d7f 100644 (file)
@@ -5,6 +5,11 @@
 #include "rgw_lua_utils.h"
 #include "rgw_sal_rados.h"
 #include "rgw_lua.h"
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+#include <boost/process.hpp>
+#include <boost/filesystem.hpp>
+#include "rgw_lua_version.h"
+#endif
 
 #define dout_subsys ceph_subsys_rgw
 
@@ -38,7 +43,7 @@ bool verify(const std::string& script, std::string& err_msg)
 {
   lua_State *L = luaL_newstate();
   lua_state_guard guard(L);
-  luaL_openlibs(L);
+  open_standard_libs(L);
   try {
     if (luaL_loadstring(L, script.c_str()) != LUA_OK) {
       err_msg.assign(lua_tostring(L, -1));
@@ -52,15 +57,18 @@ bool verify(const std::string& script, std::string& err_msg)
   return true;
 }
 
-static const std::string SCRIPT_OID_PREFIX("script.");
+std::string script_oid(context ctx, const std::string& tenant) {
+  static const std::string SCRIPT_OID_PREFIX("script.");
+  return SCRIPT_OID_PREFIX + to_string(ctx) + "." + tenant;
+}
+
 
 int read_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx, std::string& script)
 {
   RGWSysObjectCtx obj_ctx(store->svc()->sysobj->init_obj_ctx());
   RGWObjVersionTracker objv_tracker;
 
-  const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
-  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid(ctx, tenant));
 
   bufferlist bl;
   
@@ -94,8 +102,7 @@ int write_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, opti
   RGWSysObjectCtx obj_ctx(store->svc()->sysobj->init_obj_ctx());
   RGWObjVersionTracker objv_tracker;
 
-  const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
-  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid(ctx, tenant));
 
   bufferlist bl;
   ceph::encode(script, bl);
@@ -121,8 +128,7 @@ int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, opt
 {
   RGWObjVersionTracker objv_tracker;
 
-  const auto script_oid = SCRIPT_OID_PREFIX + to_string(ctx) + tenant;
-  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid);
+  rgw_raw_obj obj(store->svc()->zone->get_zone_params().log_pool, script_oid(ctx, tenant));
 
   const auto rc = rgw_delete_system_obj(
       store->svc()->sysobj, 
@@ -137,5 +143,146 @@ int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, opt
 
   return 0;
 }
+
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+
+const std::string PACKAGE_LIST_OBJECT_NAME = "lua_package_allowlist";
+
+namespace bp = boost::process;
+
+int add_package(rgw::sal::RGWRadosStore* store, optional_yield y, const std::string& package_name, bool allow_compilation) {
+  // verify that luarocks can load this oackage
+  const auto p = bp::search_path("luarocks");
+  if (p.empty()) {
+    return -ECHILD;
+  }
+  bp::ipstream is;
+  const auto cmd = p.string() + " search --porcelain" + (allow_compilation ? " " : " --binary ") + package_name;
+  bp::child c(cmd,
+      bp::std_in.close(),
+      bp::std_err > bp::null,
+      bp::std_out > is);
+
+  std::string line;
+  bool package_found = false;
+  // TODO: yield on reading the output
+  while (c.running() && std::getline(is, line) && !line.empty()) {
+    package_found = true;
+  }
+  c.wait();
+  auto ret = c.exit_code();
+  if (ret) {
+    return -ret;
+  }
+
+  if (!package_found) {
+    return -EINVAL;
+  }
+  
+  // add package to list
+  const bufferlist empty_bl;
+  std::map<std::string, bufferlist> new_package{{package_name, empty_bl}};
+  librados::ObjectWriteOperation op;
+  op.omap_set(new_package);
+  ret = rgw_rados_operate(*(store->getRados()->get_lc_pool_ctx()), 
+      PACKAGE_LIST_OBJECT_NAME, &op, y);
+
+  if (ret < 0) {
+    return ret;
+  } 
+  return 0;
+}
+
+int remove_package(rgw::sal::RGWRadosStore* store, optional_yield y, const std::string& package_name) {
+  librados::ObjectWriteOperation op;
+  op.omap_rm_keys(std::set<std::string>({package_name}));
+  const auto ret = rgw_rados_operate(*(store->getRados()->get_lc_pool_ctx()), 
+    PACKAGE_LIST_OBJECT_NAME, &op, y);
+
+  if (ret < 0) {
+    return ret;
+  }
+
+  return 0;
+}
+
+int list_packages(rgw::sal::RGWRadosStore* store, optional_yield y, packages_t& packages) {
+  constexpr auto max_chunk = 1024U;
+  std::string start_after;
+  bool more = true;
+  int rval;
+  while (more) {
+    librados::ObjectReadOperation op;
+    packages_t packages_chunk;
+    op.omap_get_keys2(start_after, max_chunk, &packages_chunk, &more, &rval);
+    const auto ret = rgw_rados_operate(*(store->getRados()->get_lc_pool_ctx()),
+      PACKAGE_LIST_OBJECT_NAME, &op, nullptr, y);
+  
+    if (ret < 0) {
+      return ret;
+    }
+
+    packages.merge(packages_chunk);
+  }
+  return 0;
+}
+
+int install_packages(rgw::sal::RGWRadosStore* store, optional_yield y, packages_t& failed_packages, std::string& output) {
+  // luarocks directory cleanup
+  const auto& luarocks_location = g_conf().get_val<std::string>("rgw_luarocks_location");
+  boost::system::error_code ec;
+  boost::filesystem::remove_all(luarocks_location, ec);
+  if (ec.value() != 0 && ec.value() != ENOENT) {
+    output.append("failed to clear luarock directory: ");
+    output.append(ec.message());
+    output.append("\n");
+    return ec.value();
+  }
+
+  packages_t packages;
+  auto ret = list_packages(store, y, packages);
+  if (ret == -ENOENT) {
+    // allowlist is empty 
+    return 0;
+  }
+  if (ret < 0) {
+    return ret;
+  }
+  // verify that luarocks exists
+  const auto p = bp::search_path("luarocks");
+  if (p.empty()) {
+    return -ECHILD;
+  }
+
+  // the lua rocks install dir will be created by luarocks the first time it is called
+  for (const auto& package : packages) {
+    bp::ipstream is;
+    bp::child c(p, "install", "--lua-version", CEPH_LUA_VERSION, "--tree", luarocks_location, "--deps-mode", "one", package, 
+        bp::std_in.close(),
+        (bp::std_err & bp::std_out) > is);
+
+    // TODO: yield until wait returns
+    std::string line = "CMD: luarocks install --lua-version " + std::string(CEPH_LUA_VERSION) + std::string(" --tree ") + 
+      luarocks_location + " --deps-mode one " + package;
+
+    do {
+      if (!line.empty()) {
+        output.append(line);
+        output.append("\n");
+      }
+    } while (c.running() && std::getline(is, line));
+
+    c.wait();
+    if (c.exit_code()) {
+      failed_packages.insert(package);
+    }
+  }
+
+  return 0;
+}
+
+#endif
+
 }
 
index 9ba9171bd3a77b6c0b44f403c30eb8b7c03974a5..8241622b246cab8ab4f0a4e7dafc5650797f2387 100644 (file)
@@ -34,5 +34,23 @@ int read_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optio
 // delete the stored lua script from a context
 int delete_script(rgw::sal::RGWRadosStore* store, const std::string& tenant, optional_yield y, context ctx);
 
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+#include <set>
+
+using packages_t = std::set<std::string>;
+
+// add a lua package to the allowlist
+int add_package(rgw::sal::RGWRadosStore* store, optional_yield y, const std::string& package_name, bool allow_compilation);
+
+// remove a lua package from the allowlist
+int remove_package(rgw::sal::RGWRadosStore* store, optional_yield y, const std::string& package_name);
+
+// list lua packages in the allowlist
+int list_packages(rgw::sal::RGWRadosStore* store, optional_yield y, packages_t& packages);
+
+// install all packages from the allowlist
+// return the list of packages that failed to install and the output of the install command
+int install_packages(rgw::sal::RGWRadosStore* store, optional_yield y, packages_t& failed_packages, std::string& output);
+#endif
 }
 
index 1d28812496c34213dae7af6216fd2f8736b4ba37..81e41851b167b01f88216b926694039e4d0fe3f5 100644 (file)
@@ -4,6 +4,7 @@
 #include "common/dout.h"
 #include "services/svc_zone.h"
 #include "rgw_lua_utils.h"
+#include "rgw_lua.h"
 #include "rgw_common.h"
 #include "rgw_log.h"
 #include "rgw_process.h"
@@ -773,12 +774,15 @@ int execute(
     OpsLogSocket* olog,
     req_state* s, 
     const char* op_name,
-    const std::string& script) 
+    const std::string& script,
+    const std::string& package_path)
+
 {
   auto L = luaL_newstate();
   lua_state_guard lguard(L);
 
-  luaL_openlibs(L);
+  open_standard_libs(L);
+  set_package_path(L, package_path);
 
   create_debug_action(L, s->cct);  
 
index 5ace5bf157fc442c3d437cac6316c6a620c8c8a1..be3ed4853b30863d3e0605eef773647b3f968c5f 100644 (file)
@@ -19,7 +19,8 @@ int execute(
     OpsLogSocket* olog,
     req_state *s, 
     const char* op_name,
-    const std::string& script);
+    const std::string& script,
+    const std::string& package_path);
 
 }
 
index caf33a23ac34f9b7639c91b742c6c286e8b8af48..6af87d2c045a8878e0721a5694d93e8ade79b31c 100644 (file)
@@ -3,6 +3,7 @@
 #include "common/ceph_context.h"
 #include "common/dout.h"
 #include "rgw_lua_utils.h"
+#include "rgw_lua_version.h"
 
 #define dout_subsys ceph_subsys_rgw
 
@@ -41,4 +42,35 @@ void stack_dump(lua_State* L) {
   std::cout << "--------------- Stack Dump Finished ---------------" << std::endl;
 }
 
+void set_package_path(lua_State* L, const std::string& install_dir) {
+  if (install_dir.empty()) {
+    return;
+  }
+  lua_getglobal(L, "package");
+  if (!lua_istable(L, -1)) {
+    return;
+  }
+  const auto path = install_dir+"/share/lua/"+CEPH_LUA_VERSION+"/?.lua";  
+  pushstring(L, path);
+  lua_setfield(L, -2, "path");
+  
+  const auto cpath = install_dir+"/lib/lua/"+CEPH_LUA_VERSION+"/?.so";
+  pushstring(L, cpath);
+  lua_setfield(L, -2, "cpath");
+}
+
+void open_standard_libs(lua_State* L) {
+  luaL_openlibs(L);
+  unsetglobal(L, "load");
+  unsetglobal(L, "loadfile");
+  unsetglobal(L, "loadstring");
+  unsetglobal(L, "dofile");
+  unsetglobal(L, "debug");
+  // remove os.exit()
+  lua_getglobal(L, "os");
+  lua_pushstring(L, "exit");
+  lua_pushnil(L);
+  lua_settable(L, -3);
+}
+
 }
index e3a7a132cb18e5206f1fca32457e1920a5525d87..00f3cfdefb5a9d1c89dfff14cc00f8da24f8761d 100644 (file)
@@ -26,6 +26,12 @@ static inline void pushstring(lua_State* L, std::string_view str)
   lua_pushlstring(L, str.data(), str.size());
 }
 
+static inline void unsetglobal(lua_State* L, const char* name) 
+{
+  lua_pushnil(L);
+  lua_setglobal(L, name);
+}
+
 // dump the lua stack to stdout
 void stack_dump(lua_State* L);
 
@@ -175,5 +181,19 @@ struct EmptyMetaTable {
 //
 void create_debug_action(lua_State* L, CephContext* cct);
 
+// set the packages search path according to:
+// package.path = "<install_dir>/share/lua/5.3/?.lua"                                                                         │                         LuaRocks.
+// package.cpath= "<install_dir>/lib/lua/5.3/?.so"
+void set_package_path(lua_State* L, const std::string& install_dir);
+
+// open standard lua libs and remove the following functions:
+// os.exit()
+// load()
+// loadfile()
+// loadstring()
+// dofile()
+// and the "debug" library
+void open_standard_libs(lua_State* L);
+
 }
 
diff --git a/src/rgw/rgw_lua_version.h b/src/rgw/rgw_lua_version.h
new file mode 100644 (file)
index 0000000..ff09633
--- /dev/null
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <lua.hpp>
+#include <string>
+
+namespace rgw::lua {
+
+const std::string CEPH_LUA_VERSION(LUA_VERSION_MAJOR "." LUA_VERSION_MINOR);
+
+}
+
index d8803416eb68e9240d09e774d00b27c4632d23d3..ffe6c6047cc352f7f16691f479c1480dd49c270a 100644 (file)
@@ -49,6 +49,9 @@
 #include "rgw_asio_frontend.h"
 #endif /* WITH_RADOSGW_BEAST_FRONTEND */
 #include "rgw_dmclock_scheduler_ctx.h"
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+#include "rgw_lua.h"
+#endif
 
 #include "services/svc_zone.h"
 
@@ -404,6 +407,21 @@ int radosgw_Main(int argc, const char **argv)
 #endif
   }
 
+#ifdef WITH_RADOSGW_LUA_PACKAGES
+  rgw::lua::packages_t failed_packages;
+  std::string output;
+  r = rgw::lua::install_packages(store, null_yield, failed_packages, output);
+  if (r < 0) {
+    dout(1) << "ERROR: failed to install lua packages from allowlist" << dendl;
+  }
+  if (!output.empty()) {
+    dout(10) << "INFO: lua packages installation output: \n" << output << dendl; 
+  }
+  for (const auto& p : failed_packages) {
+    dout(5) << "WARNING: failed to install lua package: " << p << " from allowlist" << dendl;
+  }
+#endif
+
   if (apis_map.count("swift") > 0) {
     RGWRESTMgr_SWIFT* const swift_resource = new RGWRESTMgr_SWIFT;
 
index b95ed15082cc059d8b13ba8bc2ef8b83cce870aa..3fba27d2d44fd9aa20d260543637acb6a942b5d0 100644 (file)
@@ -225,6 +225,8 @@ int process_request(rgw::sal::RGWRadosStore* const store,
                                                frontend_prefix,
                                                client_io, &mgr, &init_error);
   rgw::dmclock::SchedulerCompleter c;
+  const auto& lua_package_path = g_conf().get_val<std::string>("rgw_luarocks_location"); 
+
   if (init_error != 0) {
     abort_early(s, nullptr, init_error, nullptr, yield);
     goto done;
@@ -247,7 +249,7 @@ int process_request(rgw::sal::RGWRadosStore* const store,
     } else if (rc < 0) {
       ldpp_dout(op, 5) << "WARNING: failed to read pre request script. error: " << rc << dendl;
     } else {
-      rc = rgw::lua::request::execute(store, rest, olog, s, op->name(), script);
+      rc = rgw::lua::request::execute(store, rest, olog, s, op->name(), script, lua_package_path);
       if (rc < 0) {
         ldpp_dout(op, 5) << "WARNING: failed to execute pre request script. error: " << rc << dendl;
       }
@@ -315,7 +317,7 @@ done:
     } else if (rc < 0) {
       ldpp_dout(op, 5) << "WARNING: failed to read post request script. error: " << rc << dendl;
     } else {
-      rc = rgw::lua::request::execute(store, rest, olog, s, op->name(), script);
+      rc = rgw::lua::request::execute(store, rest, olog, s, op->name(), script, lua_package_path);
       if (rc < 0) {
         ldpp_dout(op, 5) << "WARNING: failed to execute post request script. error: " << rc << dendl;
       }
index 7db2aee332f949834bc80b3826c2ab5eab2e2b95..86e6110d0bb720fb3d9dbc9b733893474c21b857 100644 (file)
     script put                 upload a lua script to a context
     script get                 get the lua script of a context
     script rm                  remove the lua scripts of a context
+    script-package add         add a lua package to the scripts allowlist
+    script-package rm          remove a lua package from the scripts allowlist
+    script-package list        get the lua packages allowlist
   options:
      --tenant=<tenant>         tenant name
      --user_ns=<namespace>     namespace of user (oidc in case of users authenticated with oidc provider)
   
   Script options:
      --context                 context in which the script runs. one of: preRequest, postRequest
+     --package                 name of the lua package that should be added/removed to/from the allowlist
+     --allow-compilation       package is allowed to compile C code as part of its installation
   
     --conf/-c FILE    read configuration from the given configuration file
     --id ID           set ID portion of my name
index 78598f737dbd4628dde856cec635a0a0d628f65e..7817a42ef9ab8d8bbc882ed8c46bf5f8efa8d08e 100644 (file)
@@ -13,6 +13,10 @@ if(WITH_RADOSGW_KAFKA_ENDPOINT)
   add_library(kafka_stub STATIC ${kafka_stub_src})
 endif()
 
+if(WITH_RADOSGW_LUA_PACKAGES)
+  list(APPEND rgw_libs Boost::filesystem)
+endif()
+
 #unittest_rgw_bencode
 add_executable(unittest_rgw_bencode test_rgw_bencode.cc)
 add_ceph_unittest(unittest_rgw_bencode)
index 270b8a460ee825564320d5afeae1fb5970016cdb..bf14e1941035760ce10778df772e98bcdda9d6cd 100644 (file)
@@ -80,7 +80,7 @@ TEST(TestRGWLua, EmptyScript)
   uint64_t id = 0;
   req_state s(cct, &e, id); 
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -95,7 +95,7 @@ TEST(TestRGWLua, SyntaxError)
 
   DEFINE_REQ_STATE;
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script, "");
   ASSERT_EQ(rc, -1);
 }
 
@@ -107,7 +107,7 @@ TEST(TestRGWLua, Hello)
 
   DEFINE_REQ_STATE;
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -119,7 +119,7 @@ TEST(TestRGWLua, RGWDebugLogNumber)
 
   DEFINE_REQ_STATE;
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -131,7 +131,7 @@ TEST(TestRGWLua, RGWDebugNil)
 
   DEFINE_REQ_STATE;
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script, "");
   ASSERT_EQ(rc, -1);
 }
 
@@ -145,7 +145,7 @@ TEST(TestRGWLua, URI)
   DEFINE_REQ_STATE;
   s.decoded_uri = "http://hello.world/";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -164,7 +164,7 @@ TEST(TestRGWLua, Response)
   s.err.err_code = "Bad Request";
   s.err.message = "This is a bad request";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -179,7 +179,7 @@ TEST(TestRGWLua, SetResponse)
   DEFINE_REQ_STATE;
   s.err.message = "this is a bad request";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -193,7 +193,7 @@ TEST(TestRGWLua, SetRGWId)
   DEFINE_REQ_STATE;
   s.host_id = "foo";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_NE(rc, 0);
 }
 
@@ -206,7 +206,7 @@ TEST(TestRGWLua, InvalidField)
   DEFINE_REQ_STATE;
   s.host_id = "foo";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script, "");
   ASSERT_EQ(rc, -1);
 }
 
@@ -218,7 +218,7 @@ TEST(TestRGWLua, InvalidSubField)
 
   DEFINE_REQ_STATE;
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "kaboom", script, "");
   ASSERT_EQ(rc, -1);
 }
 
@@ -252,7 +252,7 @@ TEST(TestRGWLua, Bucket)
   b.bucket_id = "myid"; 
   s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -275,7 +275,7 @@ TEST(TestRGWLua, GenericAttributes)
   s.generic_attrs["goodbye"] = "cruel world";
   s.generic_attrs["ka"] = "boom";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -298,7 +298,7 @@ TEST(TestRGWLua, Environment)
   s.env["goodbye"] = "cruel world";
   s.env["ka"] = "boom";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -319,7 +319,7 @@ TEST(TestRGWLua, Tags)
   s.tagset.add_tag("goodbye", "cruel world");
   s.tagset.add_tag("ka", "boom");
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -332,7 +332,7 @@ TEST(TestRGWLua, TagsNotWriteable)
   DEFINE_REQ_STATE;
   s.tagset.add_tag("hello", "world");
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_NE(rc, 0);
 }
 
@@ -358,7 +358,7 @@ TEST(TestRGWLua, Metadata)
   s.info.x_meta_map["foo"] = "bar";
   s.info.x_meta_map["ka"] = "boom";
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -409,7 +409,7 @@ TEST(TestRGWLua, Acl)
   s.user_acl->get_acl().add_grant(&grant3);
   s.user_acl->get_acl().add_grant(&grant4);
   s.user_acl->get_acl().add_grant(&grant5);
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -453,7 +453,7 @@ TEST(TestRGWLua, UseFunction)
   s.object_acl->get_owner().set_name("user five");
   s.object_acl->get_owner().set_id(rgw_user("tenant5", "user5"));
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
@@ -474,10 +474,22 @@ TEST(TestRGWLua, WithLib)
   b.name = "my-bucket-name-is-fish";
   s.bucket.reset(new sal::RGWRadosBucket(nullptr, b));
 
-  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script);
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
   ASSERT_EQ(rc, 0);
 }
 
+TEST(TestRGWLua, NotAllowedInLib)
+{
+  const std::string script = R"(
+    os.clock() -- this should be ok
+    os.exit()  -- this should fail (os.exit() is removed)
+  )";
+
+  DEFINE_REQ_STATE;
+
+  const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script, "");
+  ASSERT_NE(rc, 0);
+}
 #include <sys/socket.h>
 #include <stdlib.h>
 
@@ -551,11 +563,11 @@ TEST(TestRGWLua, OpsLog)
   s.cio = &ac; 
        s.cct->_conf->rgw_ops_log_rados = false;
 
-  auto rc = lua::request::execute(store.get(), nullptr, olog.get(), &s, "put_obj", script);
+  auto rc = lua::request::execute(store.get(), nullptr, olog.get(), &s, "put_obj", script, "");
   EXPECT_EQ(rc, 0);
  
        s.err.http_ret = 400;
-  rc = lua::request::execute(store.get(), nullptr, olog.get(), &s, "put_obj", script);
+  rc = lua::request::execute(store.get(), nullptr, olog.get(), &s, "put_obj", script, "");
   EXPECT_EQ(rc, 0);
 
        // give the socket client time to read
index 749a125d4617d79669b103683af326a31c907706..067a57d5151aa6ba4b77445f334276f04b44d206 100755 (executable)
@@ -1536,6 +1536,7 @@ do_rgw()
             --log-file=${CEPH_OUT_DIR}/radosgw.${current_port}.log \
             --admin-socket=${CEPH_OUT_DIR}/radosgw.${current_port}.asok \
             --pid-file=${CEPH_OUT_DIR}/radosgw.${current_port}.pid \
+            --rgw_luarocks_location=${CEPH_OUT_DIR}/luarocks \
             ${RGWDEBUG} \
             -n ${rgw_name} \
             "--rgw_frontends=${rgw_frontend} port=${current_port}${CEPH_RGW_HTTPS}"