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