]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: Add new Lua postAuth hook that is being run after authorization 67548/head
authorEmin Mert Sunacoglu <emin.sunacoglu@clyso.com>
Tue, 24 Feb 2026 18:13:39 +0000 (19:13 +0100)
committermertsunacoglu <emin.sunacoglu@clyso.com>
Thu, 9 Apr 2026 20:10:15 +0000 (22:10 +0200)
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 <emin.sunacoglu@clyso.com>
doc/radosgw/lua-scripting.rst
src/rgw/radosgw-admin/radosgw-admin.cc
src/rgw/rgw_lua.cc
src/rgw/rgw_lua.h
src/rgw/rgw_process.cc
src/test/cli/radosgw-admin/help.t
src/test/rgw/lua/test_lua.py

index e71aa6a59dd9c86e95976dd0ef0ae95735c24f4b..e925d2a798538132a215443d562db8d6b8e08463 100644 (file)
@@ -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.
 
index 4e2c39d6a9cf5452192b070bdc5750e271511f6f..daa83e4db0f992d5e66506c8ae58c70b8f9b1bd1 100644 (file)
@@ -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()
 {
index 3b3899bf2976be15535d0356111b88853d252574..a51b8d6fee5c81e97a9b1f9c3a4c90904045b471 100644 (file)
@@ -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:
index a4ee24ff83188b921b5e202b59ab207593ecf46c..8a1b9d386e0fa4e91f956d04882a5914b57aa0e5 100644 (file)
@@ -21,6 +21,7 @@ namespace rgw::lua {
 
 enum class context {
   preRequest,
+  postAuth,
   postRequest,
   background,
   getData,
index 507d4e3ff7b1125380e8a03546d9e09f529120d3..28214296c50ab4f50722198c6b36435615f6f628 100644 (file)
@@ -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);
index fb68f164e344ca7695887a50b5eeeaed605f4c0d..b9bd5809a56bd9715d505dadee4d5515a92a59a6 100644 (file)
      --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
   
index bf051d439c52b8d0b17ffe88dcc7831ac7b5dfc0..67e6434067896a03b31aafaa7799e465f233cc69 100644 (file)
@@ -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.")