From: mertsunacoglu Date: Tue, 10 Feb 2026 12:33:14 +0000 (+0100) Subject: rgw/lua: Add Lua functionality for blocking requests X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=68396f6337b5bb91b7e31593754d0dc95b5e9135;p=ceph.git rgw/lua: Add Lua functionality for blocking requests 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 --- diff --git a/doc/radosgw/lua-scripting.rst b/doc/radosgw/lua-scripting.rst index ef1729cb9ce..092d395b8d9 100644 --- a/doc/radosgw/lua-scripting.rst +++ b/doc/radosgw/lua-scripting.rst @@ -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. diff --git a/src/rgw/rgw_lua_request.cc b/src/rgw/rgw_lua_request.cc index 908bcfc4002..6f65a4c7ff0 100644 --- a/src/rgw/rgw_lua_request.cc +++ b/src/rgw/rgw_lua_request.cc @@ -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(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(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 diff --git a/src/rgw/rgw_lua_request.h b/src/rgw/rgw_lua_request.h index 911c660f2c6..d1491ba92b9 100644 --- a/src/rgw/rgw_lua_request.h +++ b/src/rgw/rgw_lua_request.h @@ -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 diff --git a/src/rgw/rgw_process.cc b/src/rgw/rgw_process.cc index bed3ff03d3c..d560446b135 100644 --- a/src/rgw/rgw_process.cc +++ b/src/rgw/rgw_process.cc @@ -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; + } } } } diff --git a/src/test/rgw/lua/test_lua.py b/src/test/rgw/lua/test_lua.py index f8131956b30..080637c3942 100644 --- a/src/test/rgw/lua/test_lua.py +++ b/src/test/rgw/lua/test_lua.py @@ -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 diff --git a/src/test/rgw/test_rgw_lua.cc b/src/test/rgw/test_rgw_lua.cc index 625db9c8436..7509b82d4f8 100644 --- a/src/test/rgw/test_rgw_lua.cc +++ b/src/test/rgw/test_rgw_lua.cc @@ -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