Search code examples
postgresqlamazon-aurorarow-level-security

INSERT statement returns a policy violation (USING expression)


I am using AWS Aurora Postgres 14 (14.5) and have this table set-up (with the help of Diesel in Rust) ->

create table contacts (
    email TEXT NOT NULL,
    user_id TEXT NOT NULL,
    contact_data JSONB,
    user_groups TEXT[],
    tenant_groups TEXT[],
    tags TEXT[],
    PRIMARY KEY (email, user_id)
);

With policies ->

ALTER TABLE contacts FORCE ROW LEVEL SECURITY;
CREATE POLICY select_contacts_policy ON contacts 
FOR SELECT
USING (user_id = current_setting('myapp.user_id'));
ALTER TABLE contacts ENABLE ROW LEVEL SECURITY;

CREATE POLICY insert_contacts_policy ON contacts 
FOR INSERT
WITH CHECK (true);
ALTER TABLE contacts ENABLE ROW LEVEL SECURITY;

While inserting new data to this table, I first set a session parameter called user_id (a string) (using Diesel's sql_query function)->

sql_query(format!("SET SESSION myapp.user_id = '{user_id}';")).execute(pg_conn)?;

Then I insert using diesel ->

diesel::insert_into(contacts::table)
    .values(&contacts) # a custom struct
    .on_conflict((contacts::email, contacts::user_id))
    .do_update()
    .set(&contacts)
    .execute(pg_conn)?;

After this query, the user_id is reverted to a default ->

sql_query("SET SESSION myapp.user_id = -1;").execute(pg_conn)?;

The diesel function call translates to this SQL Query (got it from postgres logs) ->

INSERT INTO "contacts" ("email", "user_id", "contact_data", "user_groups", "tenant_groups", "tags") VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT ("email", "user_id") DO UPDATE SET "email" = $7, "user_id" = $8, "contact_data" = $9, "user_groups" = $10, "tenant_groups" = $11, "tags" = $12

Upon running this query, I get this error message when there is a conflict and the update branch is triggered ->

new row violates row-level security policy (USING expression) for table "contacts"

I have a somewhat similar (without multiple users but the same schema) postgres installation (v 14.8) on my local which is able to upsert without any issues.

Additional information ->

The user working on this is a different one called backend user

The access privileges -> Snapshot of access privileges for the contacts table

I tried to run an EXPLAIN ANALYZE query and log it to see where the error occurs but that query results in policy violation before completion. If I run without the ANALYZE flag, it just prints the query plan (but I want to know where and what is resulting in that violation)


Solution

  • The problem here is the ON CONFLICT ... DO UPDATE. A plain INSERT or an INSERT ... ON CONFLICT DO NOTHING would succeed.

    You can only update rows that you can see, that is, rows for which you have a FOR SELECT policy, as the documentation describes:

    [...] queries that require SELECT permissions, such as UPDATE, will also only see those records that are allowed by the SELECT policy.

    So it is the FOR SELECT policy and the (missing) FOR UPDATE policy that give you trouble here. This is somewhat surprising, since one could argue that you should only get that error if there is a conflict during the INSERT. However, to determine whether there is a conflict or not, you already need to be able to SELECT from the table. Adding a FOR SELECT policy would make the statement succeed if there is no conflict. If there is a conflict, you need an additional FOR UPDATE policy.

    You'll have to add policies that allow the inserting role unconditional updates and selects:

    CREATE POLICY inserter_may_select ON contacts FOR SELECT TO inserter
       USING (TRUE);
    CREATE POLICY inserter_may_update ON contacts FOR UPDATE TO inserter
       USING (TRUE);