The mod_admin_rest we use is a fork from wltsmrz/mod_admin_rest, for which the last commit it 7 years old. During this period, Prosody evolved a lot, and integrated better authentication and more granular roles. mod_admin_rest uses basic authentication, which is okay for us since we use it for inter-process communication, but we wouldn't say no to a more secure and granular alternative.
What?
I just had a thought: why don't we create a general-purpose REST API which could interact with any enabled prosody module?
How?
One route: POST /<module_name>/<function_name> where <module_name> is the module name without the mod_ prefix and <function_name> is the fully qualified function name (i.e. object.method is allowed).
Because Lua functions can take arguments and aren't idempotent, the route uses the POST HTTP method and accepts a body. This body is a JSON[^ct] array containing the function arguments (scalar types, arrays or objects).
The route returns a JSON[^ct] array containing all the values returned by the function (scalar types, arrays or objects). This could be done using table constructors in Lua.
Finally, since there is no standard way of handling errors in Lua, the route will always return 200 Success (unless authorization failed) and it'll be to the API user to interpret the response.
Security considerations
Of course, using such API would require the prosody:operator role (which our admin account has).
Someone using mod_admin_rest is responsible for the consequences of their module function invocations. If some module exposes a public function which should be local or which breaks things when invoked, mod_admin_rest shouldn't prohibit its usage. A Prosody operator would have broken things the same way, with or without mod_admin_rest.
To reduce the attack surface in case one operator account got compromised, we could add a module configuration listing the modules accessible via mod_admin_rest. If the module is both enabled and allowed, then the call succeeds.
We could also use Prosody permissions to give granular access to certain accounts. We won't do this at first, we'll do it if someone needs it.
Notes
We should make sure nil elements are not lost when serializing Lua objects to JSON.
Lua functions cannot be encoded therefore a module function which accepts or returns a Lua function won't be usable through mod_admin_rest.
[^ct]: At first, mod_admin_rest's route will only accept and return JSON payloads. However, given the very simple data structure it accepts and returns, we could easily support other content types.
(repost of an earlier issue by @RemiBardon in the now-removed old
prose-pod-server
repository)Why?
In https://github.com/prose-im/prose-pod-server/pull/4/commits/5252af005404a86fccbc5a3e9f2d0f7821b1f9df, I added support for 4 functions from
mod_groups_internal
tomod_admin_rest
. It took me 100 lines of Lua and 100 lines of documentation… for literally 4 lines of business logic.The
mod_admin_rest
we use is a fork from wltsmrz/mod_admin_rest, for which the last commit it 7 years old. During this period, Prosody evolved a lot, and integrated better authentication and more granular roles.mod_admin_rest
uses basic authentication, which is okay for us since we use it for inter-process communication, but we wouldn't say no to a more secure and granular alternative.What?
I just had a thought: why don't we create a general-purpose REST API which could interact with any enabled prosody module?
How?
One route:
POST /<module_name>/<function_name>
where<module_name>
is the module name without themod_
prefix and<function_name>
is the fully qualified function name (i.e.object.method
is allowed).Because Lua functions can take arguments and aren't idempotent, the route uses the
POST
HTTP method and accepts a body. This body is a JSON[^ct] array containing the function arguments (scalar types, arrays or objects).The route returns a JSON[^ct] array containing all the values returned by the function (scalar types, arrays or objects). This could be done using table constructors in Lua.
Finally, since there is no standard way of handling errors in Lua, the route will always return
200 Success
(unless authorization failed) and it'll be to the API user to interpret the response.Security considerations
prosody:operator
role (which our admin account has).mod_admin_rest
is responsible for the consequences of their module function invocations. If some module exposes a public function which should belocal
or which breaks things when invoked,mod_admin_rest
shouldn't prohibit its usage. A Prosody operator would have broken things the same way, with or withoutmod_admin_rest
.mod_admin_rest
. If the module is both enabled and allowed, then the call succeeds.Notes
nil
elements are not lost when serializing Lua objects to JSON.mod_admin_rest
.[^ct]: At first,
mod_admin_rest
's route will only accept and return JSON payloads. However, given the very simple data structure it accepts and returns, we could easily support other content types.