From: Emin Mert Sunacoglu Date: Tue, 24 Feb 2026 18:13:39 +0000 (+0100) Subject: rgw: Add new Lua postAuth hook that is being run after authorization X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=fba30067e93cbb538a30cb5c2dfde470c253f567;p=ceph.git rgw: Add new Lua postAuth hook that is being run after authorization Currently, the prerequest Lua context runs after authentication is done, but before the authorization. Because of this, bucket specific data like bucket tags and tenant info are not loaded yet into the environment when the prerequest script runs. PR implements a new Lua hook after user is authorized and bucket context is ready, but before we handle the actual request. Fixes: https://tracker.ceph.com/issues/75195 Signed-off-by: Emin Sunacoglu --- diff --git a/doc/radosgw/lua-scripting.rst b/doc/radosgw/lua-scripting.rst index e71aa6a59dd9..e925d2a79853 100644 --- a/doc/radosgw/lua-scripting.rst +++ b/doc/radosgw/lua-scripting.rst @@ -8,11 +8,12 @@ Lua Scripting This feature allows users to assign execution context to Lua scripts. The supported contexts are: -- ``prerequest`` which will execute a script before each operation is performed -- ``postrequest`` which will execute after each operation is performed -- ``background`` which will execute within a specified time interval -- ``getdata`` which will execute on objects' data when objects are downloaded -- ``putdata`` which will execute on objects' data when objects are uploaded + - ``prerequest`` which will execute a script before each operation is performed + - ``postauth`` which will execute a script after each operation is authorized but before it is performed + - ``postrequest`` which will execute after each operation is performed + - ``background`` which will execute within a specified time interval + - ``getdata`` which will execute on objects' data when objects are downloaded + - ``putdata`` which will execute on objects' data when objects are uploaded A request (pre or post) or data (get or put) context script may be constrained to operations belonging to a specific tenant's users. The request context script can also access fields in the request and modify certain fields, as well as the `Global RGW Table`_. @@ -49,7 +50,7 @@ To upload a script: :: - # radosgw-admin script put --infile={lua-file-path} --context={prerequest|postrequest|background|getdata|putdata} [--tenant={tenant-name}] + # radosgw-admin script put --infile={lua-file-path} --context={prerequest|postauth|postrequest|background|getdata|putdata} [--tenant={tenant-name}] * When uploading a script with the ``background`` context, a tenant name should not be specified. @@ -62,14 +63,14 @@ To print the content of the script to standard output: :: - # radosgw-admin script get --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}] + # radosgw-admin script get --context={preRequest|postAuth|postRequest|background|getdata|putdata} [--tenant={tenant-name}] To remove the script: :: - # radosgw-admin script rm --context={preRequest|postRequest|background|getdata|putdata} [--tenant={tenant-name}] + # radosgw-admin script rm --context={preRequest|postAuth|postRequest|background|getdata|putdata} [--tenant={tenant-name}] Package Management via CLI @@ -351,7 +352,7 @@ The script's return value determines how RGW proceeds with the request: 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 Lua script’s return value is evaluated only during the prerequest and postauth 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. diff --git a/src/rgw/radosgw-admin/radosgw-admin.cc b/src/rgw/radosgw-admin/radosgw-admin.cc index 4e2c39d6a9cf..daa83e4db0f9 100644 --- a/src/rgw/radosgw-admin/radosgw-admin.cc +++ b/src/rgw/radosgw-admin/radosgw-admin.cc @@ -135,7 +135,7 @@ inline int posix_errortrans(int r) return ERR_NO_SUCH_BUCKET == r ? ENOENT : r; } -static const std::string LUA_CONTEXT_LIST("prerequest, postrequest, background, getdata, putdata"); +static const std::string LUA_CONTEXT_LIST("prerequest, postauth, postrequest, background, getdata, putdata"); void usage() { diff --git a/src/rgw/rgw_lua.cc b/src/rgw/rgw_lua.cc index 3b3899bf2976..a51b8d6fee5c 100644 --- a/src/rgw/rgw_lua.cc +++ b/src/rgw/rgw_lua.cc @@ -26,6 +26,9 @@ context to_context(const std::string& s) if (strcasecmp(s.c_str(), "prerequest") == 0) { return context::preRequest; } + if (strcasecmp(s.c_str(), "postauth") == 0) { + return context::postAuth; + } if (strcasecmp(s.c_str(), "postrequest") == 0) { return context::postRequest; } @@ -46,6 +49,8 @@ std::string to_string(context ctx) switch (ctx) { case context::preRequest: return "prerequest"; + case context::postAuth: + return "postauth"; case context::postRequest: return "postrequest"; case context::background: diff --git a/src/rgw/rgw_lua.h b/src/rgw/rgw_lua.h index a4ee24ff8318..8a1b9d386e0f 100644 --- a/src/rgw/rgw_lua.h +++ b/src/rgw/rgw_lua.h @@ -21,6 +21,7 @@ namespace rgw::lua { enum class context { preRequest, + postAuth, postRequest, background, getData, diff --git a/src/rgw/rgw_process.cc b/src/rgw/rgw_process.cc index 507d4e3ff7b1..28214296c50a 100644 --- a/src/rgw/rgw_process.cc +++ b/src/rgw/rgw_process.cc @@ -261,6 +261,34 @@ int rgw_process_authenticated(RGWHandler_REST * const handler, if (rate_limit(driver, s)) { return -ERR_RATE_LIMITED; } + + bool is_health_request = (op->get_type() == RGW_OP_GET_HEALTH_CHECK); + { + if (!is_health_request) { + std::string script; + auto rc = rgw::lua::read_script(s, s->penv.lua.manager.get(), + s->bucket_tenant, s->yield, + rgw::lua::context::postAuth, script); + if (rc == -ENOENT) { + // no script, nothing to do + } else if (rc < 0) { + ldpp_dout(op, 5) << + "WARNING: failed to execute post authorization script. " + "error: " << rc << dendl; + } else { + int script_return_code = 0; + rc = rgw::lua::request::execute(s->penv.rest, s->penv.olog.get(), s, op, script, script_return_code); + if (rc < 0) { + ldpp_dout(op, 5) << + "WARNING: failed to execute post authorization script. " + "error: " << rc << dendl; + } + if (script_return_code == -EPERM) { + return script_return_code; + } + } + } + } ldpp_dout(op, 2) << "executing" << dendl; { auto span = tracing::rgw::tracer.add_span("execute", s->trace); diff --git a/src/test/cli/radosgw-admin/help.t b/src/test/cli/radosgw-admin/help.t index fb68f164e344..b9bd5809a56b 100644 --- a/src/test/cli/radosgw-admin/help.t +++ b/src/test/cli/radosgw-admin/help.t @@ -408,7 +408,7 @@ --notification-id bucket notifications id Script options: - --context context in which the script runs. one of: prerequest, postrequest, background, getdata, putdata + --context context in which the script runs. one of: prerequest, postauth, postrequest, background, getdata, putdata --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 diff --git a/src/test/rgw/lua/test_lua.py b/src/test/rgw/lua/test_lua.py index bf051d439c52..67e643406789 100644 --- a/src/test/rgw/lua/test_lua.py +++ b/src/test/rgw/lua/test_lua.py @@ -208,7 +208,7 @@ class UnixSocket: @pytest.mark.basic_test def test_script_management(): - contexts = ['prerequest', 'postrequest', 'background', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'background', 'getdata', 'putdata'] scripts = {} for context in contexts: script = 'print("hello from ' + context + '")' @@ -232,7 +232,7 @@ def test_script_management(): def test_script_management_with_tenant(): tenant = 'mytenant' conn2 = another_user(tenant) - contexts = ['prerequest', 'postrequest', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'getdata', 'putdata'] scripts = {} for context in contexts: for t in ['', tenant]: @@ -297,7 +297,7 @@ end # cleanup conn.delete_object(Bucket=bucket_name, Key=key) conn.delete_bucket(Bucket=bucket_name) - contexts = ['prerequest', 'postrequest', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'getdata', 'putdata'] for context in contexts: result = admin(['script', 'rm', '--context', context]) assert result[1] == 0 @@ -323,7 +323,7 @@ end RGWDebugLog("op was: "..Request.RGWOp) ''' - contexts = ['prerequest', 'postrequest', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'getdata', 'putdata'] for context in contexts: footer = '\nRGWDebugLog("context was: '+context+'\\n\\n")' result = put_script(script+footer, context) @@ -349,7 +349,7 @@ RGWDebugLog("op was: "..Request.RGWOp) # cleanup delete_all_objects(conn, bucket_name) conn.delete_bucket(Bucket=bucket_name) - contexts = ['prerequest', 'postrequest', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'getdata', 'putdata'] for context in contexts: result = admin(['script', 'rm', '--context', context]) assert result[1] == 0 @@ -406,7 +406,7 @@ RGWDebugLog("payload size of chunk of: " .. full_name .. " is: " .. #Data) # cleanup delete_all_objects(conn, bucket_name) conn.delete_bucket(Bucket=bucket_name) - contexts = ['prerequest', 'postrequest', 'background', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'background', 'getdata', 'putdata'] for context in contexts: result = admin(['script', 'rm', '--context', context]) assert result[1] == 0 @@ -477,7 +477,7 @@ end socket_server.shutdown() delete_all_objects(conn, bucket_name) conn.delete_bucket(Bucket=bucket_name) - contexts = ['prerequest', 'postrequest', 'background', 'getdata', 'putdata'] + contexts = ['prerequest', 'postauth', 'postrequest', 'background', 'getdata', 'putdata'] for context in contexts: result = admin(['script', 'rm', '--context', context]) assert result[1] == 0 @@ -506,6 +506,38 @@ def test_interrupt_request(): 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) + +@pytest.mark.example_test +def test_interrupt_request_postauth(): + script = ''' + return RGW_ABORT_REQUEST + ''' + + conn = connection() + bucket_name = gen_bucket_name() + conn.create_bucket(Bucket=bucket_name) + + result = put_script(script, "postauth") + 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', 'postauth']) + assert err == 0 + try: conn.get_object(Bucket=bucket_name, Key=key) pytest.fail("The object was written to the bucket despite the error.")