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>
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.
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);
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);
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;
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
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
"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;
+ }
}
}
}
get_secret_key
)
-
# configure logging for the tests module
log = logging.getLogger(__name__)
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
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