Which user executes Postgres RLS policies (CURRENT_USER vs VIEW owner)?
1
vote
0
answers
211
views
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 an api_owner
user
- The client that accesses the API is api_user
, which is granted access to these views via GRANT
- RLS policies on tables in the main
schema apply to api_owner
, because api_owner
owns the views that allow access to data stored on main
- 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) is SECURITY DEFINER
, everything works as expected. In particular, there is no issue with being able to call main.current_user_id()
. I _assume_ this is because api_owner
has USAGE
on the main
schema, the policies apply to api_owner
, and therefore the function is executed by api_owner
.
- However, if the check function is SECURITY INVOKER
, then I get the following error:
ERROR: 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')
The query here is from the internals of the 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 the main
schema (and indeed has SELECT
access on the users
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 to SECURITY DEFINER
work? api_user
doesn't have direct access to main
, so I _would_ expect an issue with accessing objects (including functions) on the schema.
- Indeed, if I SET ROLE api_user
and try to call main.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();
So, can anyone shed any light on the rules that determine which user Postgres RLS policies are executed as?
Asked by colophonemes
(55 rep)
Dec 5, 2023, 01:50 PM