Postgres seems to have some weird semantics around which user is actually invoking check functions defined in RLS policies. Specifically, if the check function is SECURITY INVOKER
, it appears that the function permissions are applied with respect to CURRENT_USER
, irrespective of whether the policy applies to a different user (as might be the case if the query goes via a view with a different owner). If the check function is SECURITY DEFINER
, it appears that the functions are executed by the policy user.
First, some context:
- I have a database that uses RLS to gate access to data (PG v13, if that's relevant).
- There is a
main
schema which holds all the underlying data. - There is an
api
schema, which holds views over the underlying data. In the simplest case, these views are just pass-thrus (e.g. `CREATE VIEW api.stuff AS SELECT a, b, c FROM main.stuff) - Objects in the
api
schema are owned by anapi_owner
user - The client that accesses the API is
api_user
, which is granted access to these views viaGRANT
- RLS policies on tables in the
main
schema apply toapi_owner
, becauseapi_owner
owns the views that allow access to data stored onmain
- RLS policies typically involve a check function, e.g.
CREATE POLICY api_owner_select ON main.stuff FOR SELECT TO api_owner USING (main.current_user_id() = user_id);
.
So, data access from a client's perspective looks something like the following:
[Client]
GET /stuff
↓
api_user (current_user)
SELECT * FROM api.stuff
↓
api_owner (policy user, via api.stuff query)
SELECT * FROM main.stuff
↓
Process query:
- Check RLS policies on main.stuff that apply to api_owner
- e.g. USING (main.current_user_id() = user_id)
- filter rows that don't pass
- Return resulting rows to client ⤴
So far, so good. However, the security status of the check function behaves very unintuitively:
- If the check function (
main.current_user_id()
in my examples above) isSECURITY DEFINER
, everything works as expected. In particular, there is no issue with being able to callmain.current_user_id()
. I assume this is becauseapi_owner
hasUSAGE
on themain
schema, the policies apply toapi_owner
, and therefore the function is executed byapi_owner
. - However, if the check function is
SECURITY INVOKER
, then I get the following error:
The query here is from the internals of theERROR: permission denied for schema main LINE 3: FROM main.users ^ QUERY: SELECT id FROM main.users WHERE users.email = current_setting('some_injected_state.email')
current_user_id()
.
This leads to a bit of a paradox:
- If the policy is being executed as the user accessing the data (i.e.
api_owner
via the view), then why should there be any issue?api_owner
does have permission to access themain
schema (and indeed hasSELECT
access on theusers
table), so the query should execute fine. - If the policy is instead being executed as
CURRENT_USER
(i.e.api_user
), then why does setting the policy toSECURITY DEFINER
work?api_user
doesn't have direct access tomain
, so I would expect an issue with accessing objects (including functions) on the schema.- Indeed, if I
SET ROLE api_user
and try to callmain.current_user_id()
, I get exactly the error I expect:my_db=# ALTER FUNCTION main.current_user_id SECURITY DEFINER; ALTER FUNCTION my_db=> SET ROLE api_user; SET my_db=> SELECT main.current_user_id(); ERROR: permission denied for schema main LINE 1: SELECT main.current_user_id();
- Indeed, if I
So, can anyone shed any light on the rules that determine which user Postgres RLS policies are executed as?
current_user_id()
belongs to the owner of schemamain
. that would explain the behavior.current_user_id()
does indeed belong to the owner ofmain
. Sorry if I'm being a bit thick here, but I guess I still don't see how that explains the (to my mind) paradoxical behaviour of how the policies appear to be executed. In particular, making the functionSECURITY DEFINER
appears to not only allow the body of the function to be executed with elevated permissions, but also bypasses the need for aGRANT
on the function, which is very counterintuitive (and, I'm sure, an incorrect explanation of what's happening).SECURITY DEFINER
, the function is running with the security context of the owner ofmain
, so the SQL statements in the function have no problem accessing tables inmain
.SELECT
. Even if that user does not have permissions to execute the function.