]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/lua: Add Lua functionality for blocking requests 66065/head
authormertsunacoglu <emin.sunacoglu@clyso.com>
Tue, 10 Feb 2026 12:33:14 +0000 (13:33 +0100)
committerEmin Mert Sunacoglu <emin.sunacoglu@clyso.com>
Fri, 13 Feb 2026 11:25:50 +0000 (12:25 +0100)
Implements the feature for RGW to abort requests when the LUA scripts return a certain error code. This code is implemented in LUA as RGW_ABORT_REQUEST, and in RGW it is interpreted as -EPERM.

Fixes: https://tracker.ceph.com/issues/73485
Signed-off-by: mertsunacoglu <emin.sunacoglu@clyso.com>
doc/radosgw/lua-scripting.rst
src/rgw/rgw_lua_request.cc
src/rgw/rgw_lua_request.h
src/rgw/rgw_process.cc
src/test/rgw/lua/test_lua.py
src/test/rgw/test_rgw_lua.cc

index ef1729cb9cee132c55fa2ff10bda726d24ed0013..092d395b8d960afeaec8aca9552c8a0ee82cdee8 100644 (file)
@@ -334,6 +334,23 @@ Tracing functions can be used only in the ``postrequest`` context.
   The function accepts one or two arguments: A string containing the event ``name`` should be the first argument, followed by the event ``attributes``, which is optional for events without attributes.
   An event's attributes must be a table of strings.
 
+Request Blocking and Error Handling
+-----------------------------------
+Script Execution Errors
+~~~~~~~~~~~~~~~~~~~~~~~
+If the Lua script fails with a syntax or runtime error, RGW will log the error. The request that triggered the script will still go through.
+
+Request Blocking and Return Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The script's return value determines how RGW proceeds with the request:
+- To block the request: The script must return the value ``RGW_ABORT_REQUEST``. RGW interprets this as ``-EPERM`` and will stop processing the request.
+- To continue the request: No return value, or any other return value or type will be treated as success.
+
+Return Value Context
+~~~~~~~~~~~~~~~~~~~~
+The Lua script’s return value is evaluated only during the prerequest context and is ignored in any other RGW request-processing context.
+The HTTP response status code is 403 (Forbidden) by default when a request is blocked by Lua. The response code can be changed using ``Request.Response.HTTPStatusCode`` and ``Request.Response.HTTPStatus``.
+If a request is aborted this way, the ``data`` and ``postrequest`` context will also be aborted.
 Background Context
 --------------------
 The ``background`` context may be used for purposes that include analytics, monitoring, caching data for other context executions.
index 908bcfc40024b892691984b6df59c56df4259a48..6f65a4c7ff04b30b2ce253450503ca464731b8e6 100644 (file)
@@ -788,7 +788,8 @@ int execute(
     OpsLogSink* olog,
     req_state* s, 
     RGWOp* op,
-    const std::string& script)
+    const std::string& script,
+    int& script_return_code )
 {
   lua_state_guard lguard(s->cct->_conf->rgw_lua_max_memory_per_state,
                          s->cct->_conf->rgw_lua_max_runtime_per_state, s);
@@ -808,6 +809,9 @@ int execute(
   
     create_top_metatable(L, s, const_cast<char*>(op_name));  
 
+    //Make special error code available to lua scripts
+    lua_pushinteger(L, -EPERM);
+    lua_setglobal(L, "RGW_ABORT_REQUEST");
     // add the ops log action
     pushstring(L, RequestLogAction);
     lua_pushlightuserdata(L, rest);
@@ -827,6 +831,12 @@ int execute(
       ldpp_dout(s, 1) << "Lua ERROR: " << err << dendl;
       rc = -1;
     }
+    if (lua_isinteger(L, -1)) {
+      script_return_code = static_cast<int>(lua_tointeger(L, -1));
+      ldpp_dout(s, 20) << "Lua script executed successfully and returned code: " << script_return_code << dendl;
+    } else {
+      ldpp_dout(s, 20) << "Lua script executed, but did not return an integer. Ignoring return code." << dendl;
+    }
   } catch (const std::runtime_error& e) {
     ldpp_dout(s, 1) << "Lua ERROR: " << e.what() << dendl;
     rc = -1;
@@ -838,5 +848,17 @@ int execute(
   return rc;
 }
 
+int execute(
+    RGWREST* rest,
+    OpsLogSink* olog,
+    req_state* s, 
+    RGWOp* op,
+    const std::string& script
+)
+{
+  int dummy_script_return_code = 0;
+  return execute(rest, olog, s, op, script, dummy_script_return_code);
+}
+
 } // namespace rgw::lua::request
 
index 911c660f2c6afd7141568313ec90bdc40e7cfcd9..d1491ba92b9f664caa70d0c30cf1c6cf4d363893 100644 (file)
@@ -22,5 +22,13 @@ int execute(
     req_state *s, 
     RGWOp* op,
     const std::string& script);
+
+int execute(
+    RGWREST* rest,
+    OpsLogSink* olog,
+    req_state *s, 
+    RGWOp* op,
+    const std::string& script,
+    int& script_return_code );
 } // namespace rgw::lua::request
 
index bed3ff03d3ce41b1ec112c0c40e3fe354ca9aada..d560446b13512285f95b7351c03ba7de9c0c4c90 100644 (file)
@@ -402,12 +402,17 @@ int process_request(const RGWProcessEnv& penv,
           "WARNING: failed to execute pre request script. "
           "error: " << rc << dendl;
       } else {
-        rc = rgw::lua::request::execute(rest, penv.olog.get(), s, op, script);
+        int script_return_code = 0;
+        rc = rgw::lua::request::execute(rest, penv.olog.get(), s, op, script, script_return_code);
         if (rc < 0) {
           ldpp_dout(op, 5) <<
             "WARNING: failed to execute pre request script. "
             "error: " << rc << dendl;
         }
+        if (script_return_code == -EPERM) {
+          abort_early(s, op, script_return_code, handler, yield);
+          goto done;
+        }
       }
     }
   }
index f8131956b30f103cc1ad1d6a682822171e56c1e0..080637c3942d34b64b0f9a3e10677a7b3ac839ef 100644 (file)
@@ -20,7 +20,6 @@ from . import(
     get_secret_key
     )
 
-
 # configure logging for the tests module
 log = logging.getLogger(__name__)
 
@@ -474,3 +473,35 @@ end
             result = admin(['script', 'rm', '--context', context])
             assert result[1] == 0
 
+
+@pytest.mark.example_test
+def test_interrupt_request():
+    script = '''
+        return RGW_ABORT_REQUEST
+    '''
+
+    conn = connection()
+    bucket_name = gen_bucket_name()
+    conn.create_bucket(Bucket=bucket_name)
+    
+    result = put_script(script, "prerequest")
+    assert result[1] == 0
+    key = "hello"
+    
+    try:
+        conn.put_object(Body="this should be blocked".encode("ascii"), Bucket=bucket_name, Key=key)
+        pytest.fail("The put_object operation was not blocked by the Lua script.")
+    except Exception as e:
+        pass
+
+    out, err = admin(['script', 'rm', '--context', 'prerequest'])
+    assert err == 0
+
+    try:
+        conn.get_object(Bucket=bucket_name, Key=key)
+        pytest.fail("The object was written to the bucket despite the error.")
+    except Exception as e:
+        assert e.response['Error']['Code'] == 'NoSuchKey'
+        log.info("Successfully confirmed that the request was interrupted.")
+
+    conn.delete_bucket(Bucket=bucket_name)
\ No newline at end of file
index 625db9c8436c13a4d48322948d4f026f8e722e71..7509b82d4f8ed73dd9423f3dbd2daedce509a9a9 100644 (file)
@@ -1674,3 +1674,53 @@ TEST(TestRGWLua, NestedLoop)
   ASSERT_EQ(rc, 0);
 }
 
+TEST(TestRGWLua, ReturnError)
+{
+  const std::string script = R"(
+  return RGW_ABORT_REQUEST
+  )";
+  int return_code = 0;
+  DEFINE_REQ_STATE;
+  const auto rc = lua::request::execute(nullptr, nullptr, &s, nullptr, script, return_code);
+  EXPECT_EQ(rc, 0);
+  EXPECT_EQ(return_code, -EPERM);
+}
+
+TEST(TestRGWLua, ReturnString)
+{
+  const std::string script = R"(
+  return "NoSuchBucket"
+  )";
+
+  int return_code = 0;
+  DEFINE_REQ_STATE;
+  const auto rc = lua::request::execute(nullptr, nullptr, &s, nullptr, script, return_code);
+  ASSERT_EQ(rc, 0);
+  EXPECT_NE(return_code, -EPERM);
+}
+
+TEST(TestRGWLua, SuccessNoReturn)
+{
+  const std::string script = R"(
+  -- do nothing and return nothing
+  )";
+
+  int return_code = 0;
+  DEFINE_REQ_STATE;
+  const auto rc = lua::request::execute(nullptr, nullptr, &s, nullptr, script, return_code);
+  ASSERT_EQ(rc, 0);
+  EXPECT_NE(return_code, -EPERM);
+}
+
+TEST(TestRGWLua, NotValidLua)
+{
+  const std::string script = R"(
+  this is not valid lua code
+  )";
+
+  int return_code = 0;
+  DEFINE_REQ_STATE;
+  const auto rc = lua::request::execute(nullptr, nullptr, &s, nullptr, script, return_code);
+  ASSERT_EQ(rc, -1);
+  EXPECT_NE(return_code, -EPERM);
+}
\ No newline at end of file