Sample Header Ad - 728x90

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