Context
I am using row-level security along with triggers to implement a pure SQL RBAC implementation. While doing so I encountered a weird behavior between INSERT
triggers and SELECT
row-level security policies.
For simplicity the rest of this question will discuss the issue using the following simplified tables:
CREATE TABLE a (id TEXT);
ALTER TABLE a ENABLE ROW LEVEL SECURITY;
ALTER TABLE a FORCE ROW LEVEL SECURITY;
CREATE TABLE b (id TEXT);
Issue
Consider the following policies and triggers:
CREATE POLICY aSelect ON a FOR SELECT
USING (EXISTS(
select * from b where a.id = b.id
));
CREATE POLICY aInsert ON a FOR INSERT
WITH CHECK (true);
CREATE FUNCTION reproHandler() RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE USING MESSAGE = 'inside trigger handler';
INSERT INTO b (id) VALUES (NEW.id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER reproTrigger BEFORE INSERT ON a
FOR EACH ROW EXECUTE PROCEDURE reproHandler();
Now consider the following statement:
INSERT INTO a VALUES ('fails') returning id;
The expectation I have based on reading the policies applied by command type table and general SQL understanding is that the following things should happen in order:
- A new row
('fails')
is staged forINSERT
- The
BEFORE
trigger fires withNEW
set to the new row - The row
('fails')
is inserted intob
and returned from the trigger procedure unchanged - The
INSERT
'sWITH CHECK
policytrue
is evaluated totrue
- The
SELECT
'sUSING
policyselect * from b where a.id = b.id
is evaluated. This should return true due to step 3 - Having passed all policies, the row
('fails')
is inserted in table - The id (
fails
) of the inserted row is returned
Unfortunately (as you may have guessed), rather than the above steps happening we see this:
test=> INSERT INTO a VALUES ('fails') returning id;
NOTICE: inside trigger handler
ERROR: new row violates row-level security policy for table "a"
The goal of this question is to discover why the expected behavior does not occur.
Note that the following statements operated correctly as expected:
test=> INSERT INTO a VALUES ('works');
NOTICE: inside trigger handler
INSERT 0 1
test=> select * from a; select * from b;
id
-------
works
(1 row)
id
-------
works
(1 row)
What have I tried?
- Experimented with
BEFORE
versusAFTER
in the trigger definitionAFTER
results in the trigger not executing at all
- Experimented with defining a single policy which applies to
ALL
commands (with the same using/with check expression)- results in the same behavior
Appendix
- Postgres Version
PostgreSQL 10.3 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit
- If you try to repro the issue make sure you are not running with SUPER permissions as that will ignore row-security
b
) to define whether we should insert row. We can inverse logic and store only exceptions defined earlier in tables and process condition right in RLS check funstions.In your case you unconditionally add permission to add one row with specific id and load this permission from anothe table it right after that. – Household