Add tests to verify we support security invoker views (#6362)

PG15 added support for security invoker views. Relevant PG conmit:
7faa5fc84b

These views check the permissions for the underlying tables of the view
invoker user, not the view definer user.

When the view has underlying distributed tables, the queries to the
shards are sent by opening connections with the current user, which is
the view invoker, no matter what the type of the view is. This means
that, for distributed views, they were always behaving like security
invoker views. Check the following issue for more details:
https://github.com/citusdata/citus/issues/6161
So, Citus doesn't fully support security definer views.

However Citus does fully support security invoker views. We add tests to
make sure we cover different cases.

(cherry picked from commit 1ede0b9db3)
pull/6482/head
Naisila Puka 2022-09-23 10:55:46 +03:00 committed by naisila
parent 270a18ca06
commit 4e54d1f0be
2 changed files with 267 additions and 0 deletions

View File

@ -1036,6 +1036,151 @@ select min(x), max(x) from xid8_t1 GROUP BY y ORDER BY 1,2;
18446744073709551615 | 18446744073709551615
(5 rows)
--
-- PG15 introduces security invoker views
-- Citus supports these views because permissions in the shards
-- are already checked for the view invoker
--
-- create a distributed table and populate it
CREATE TABLE events (tenant_id int, event_id int, descr text);
SELECT create_distributed_table('events','tenant_id');
create_distributed_table
---------------------------------------------------------------------
(1 row)
INSERT INTO events VALUES (1, 1, 'push');
INSERT INTO events VALUES (2, 2, 'push');
-- create a security invoker view with underlying distributed table
-- the view will be distributed with security_invoker option as well
CREATE VIEW sec_invoker_view WITH (security_invoker=true) AS SELECT * FROM events;
\c - - - :worker_1_port
SELECT relname, reloptions FROM pg_class
WHERE relname = 'sec_invoker_view' AND relnamespace = 'pg15'::regnamespace;
relname | reloptions
---------------------------------------------------------------------
sec_invoker_view | {security_invoker=true}
(1 row)
\c - - - :master_port
SET search_path TO pg15;
-- test altering the security_invoker flag
ALTER VIEW sec_invoker_view SET (security_invoker = false);
\c - - - :worker_1_port
SELECT relname, reloptions FROM pg_class
WHERE relname = 'sec_invoker_view' AND relnamespace = 'pg15'::regnamespace;
relname | reloptions
---------------------------------------------------------------------
sec_invoker_view | {security_invoker=false}
(1 row)
\c - - - :master_port
SET search_path TO pg15;
ALTER VIEW sec_invoker_view SET (security_invoker = true);
-- create a new user but don't give select permission to events table
-- only give select permission to the view
CREATE ROLE rls_tenant_1 WITH LOGIN;
GRANT USAGE ON SCHEMA pg15 TO rls_tenant_1;
GRANT SELECT ON sec_invoker_view TO rls_tenant_1;
-- this user shouldn't be able to query the view
-- because the view is security invoker
-- which means it will check the invoker's rights
-- against the view's underlying tables
SET ROLE rls_tenant_1;
SELECT * FROM sec_invoker_view ORDER BY event_id;
ERROR: permission denied for table events
RESET ROLE;
-- now grant select on the underlying distributed table
-- and try again
-- now it should work!
GRANT SELECT ON TABLE events TO rls_tenant_1;
SET ROLE rls_tenant_1;
SELECT * FROM sec_invoker_view ORDER BY event_id;
tenant_id | event_id | descr
---------------------------------------------------------------------
1 | 1 | push
2 | 2 | push
(2 rows)
RESET ROLE;
-- Enable row level security
ALTER TABLE events ENABLE ROW LEVEL SECURITY;
-- Create policy for tenants to read access their own rows
CREATE POLICY user_mod ON events
FOR SELECT TO rls_tenant_1
USING (current_user = 'rls_tenant_' || tenant_id::text);
-- all rows should be visible because we are querying with
-- the table owner user now
SELECT * FROM sec_invoker_view ORDER BY event_id;
tenant_id | event_id | descr
---------------------------------------------------------------------
1 | 1 | push
2 | 2 | push
(2 rows)
-- Switch user that has been granted rights,
-- should be able to see rows that the policy allows
SET ROLE rls_tenant_1;
SELECT * FROM sec_invoker_view ORDER BY event_id;
tenant_id | event_id | descr
---------------------------------------------------------------------
1 | 1 | push
(1 row)
RESET ROLE;
-- ordinary view on top of security invoker view permissions
-- ordinary means security definer view
-- The PG expected behavior is that this doesn't change anything!!!
-- Can't escape security invoker views by defining a security definer view on top of it!
CREATE VIEW sec_definer_view AS SELECT * FROM sec_invoker_view ORDER BY event_id;
\c - - - :worker_1_port
SELECT relname, reloptions FROM pg_class
WHERE relname = 'sec_definer_view' AND relnamespace = 'pg15'::regnamespace;
relname | reloptions
---------------------------------------------------------------------
sec_definer_view |
(1 row)
\c - - - :master_port
SET search_path TO pg15;
CREATE ROLE rls_tenant_2 WITH LOGIN;
GRANT USAGE ON SCHEMA pg15 TO rls_tenant_2;
GRANT SELECT ON sec_definer_view TO rls_tenant_2;
-- it doesn't matter that the parent view is security definer
-- still the security invoker view will check the invoker's permissions
-- and will not allow rls_tenant_2 to query the view
SET ROLE rls_tenant_2;
SELECT * FROM sec_definer_view ORDER BY event_id;
ERROR: permission denied for table events
RESET ROLE;
-- grant select rights to rls_tenant_2
GRANT SELECT ON TABLE events TO rls_tenant_2;
-- we still have row level security so rls_tenant_2
-- will be able to query but won't be able to see anything
SET ROLE rls_tenant_2;
SELECT * FROM sec_definer_view ORDER BY event_id;
tenant_id | event_id | descr
---------------------------------------------------------------------
(0 rows)
RESET ROLE;
-- give some rights to rls_tenant_2
CREATE POLICY user_mod_1 ON events
FOR SELECT TO rls_tenant_2
USING (current_user = 'rls_tenant_' || tenant_id::text);
-- Row level security will be applied as well! We are safe!
SET ROLE rls_tenant_2;
SELECT * FROM sec_definer_view ORDER BY event_id;
tenant_id | event_id | descr
---------------------------------------------------------------------
2 | 2 | push
(1 row)
RESET ROLE;
-- no need to test updatable views because they are currently not
-- supported in Citus when the query view contains citus tables
UPDATE sec_invoker_view SET event_id = 5;
ERROR: cannot modify views when the query contains citus tables
CREATE TABLE set_on_default_test_referenced(
col_1 int, col_2 int, col_3 int, col_4 int,
unique (col_1, col_3)

View File

@ -627,6 +627,128 @@ select min(x), max(x) from xid8_t1 ORDER BY 1,2;
select min(x), max(x) from xid8_t1 GROUP BY x ORDER BY 1,2;
select min(x), max(x) from xid8_t1 GROUP BY y ORDER BY 1,2;
--
-- PG15 introduces security invoker views
-- Citus supports these views because permissions in the shards
-- are already checked for the view invoker
--
-- create a distributed table and populate it
CREATE TABLE events (tenant_id int, event_id int, descr text);
SELECT create_distributed_table('events','tenant_id');
INSERT INTO events VALUES (1, 1, 'push');
INSERT INTO events VALUES (2, 2, 'push');
-- create a security invoker view with underlying distributed table
-- the view will be distributed with security_invoker option as well
CREATE VIEW sec_invoker_view WITH (security_invoker=true) AS SELECT * FROM events;
\c - - - :worker_1_port
SELECT relname, reloptions FROM pg_class
WHERE relname = 'sec_invoker_view' AND relnamespace = 'pg15'::regnamespace;
\c - - - :master_port
SET search_path TO pg15;
-- test altering the security_invoker flag
ALTER VIEW sec_invoker_view SET (security_invoker = false);
\c - - - :worker_1_port
SELECT relname, reloptions FROM pg_class
WHERE relname = 'sec_invoker_view' AND relnamespace = 'pg15'::regnamespace;
\c - - - :master_port
SET search_path TO pg15;
ALTER VIEW sec_invoker_view SET (security_invoker = true);
-- create a new user but don't give select permission to events table
-- only give select permission to the view
CREATE ROLE rls_tenant_1 WITH LOGIN;
GRANT USAGE ON SCHEMA pg15 TO rls_tenant_1;
GRANT SELECT ON sec_invoker_view TO rls_tenant_1;
-- this user shouldn't be able to query the view
-- because the view is security invoker
-- which means it will check the invoker's rights
-- against the view's underlying tables
SET ROLE rls_tenant_1;
SELECT * FROM sec_invoker_view ORDER BY event_id;
RESET ROLE;
-- now grant select on the underlying distributed table
-- and try again
-- now it should work!
GRANT SELECT ON TABLE events TO rls_tenant_1;
SET ROLE rls_tenant_1;
SELECT * FROM sec_invoker_view ORDER BY event_id;
RESET ROLE;
-- Enable row level security
ALTER TABLE events ENABLE ROW LEVEL SECURITY;
-- Create policy for tenants to read access their own rows
CREATE POLICY user_mod ON events
FOR SELECT TO rls_tenant_1
USING (current_user = 'rls_tenant_' || tenant_id::text);
-- all rows should be visible because we are querying with
-- the table owner user now
SELECT * FROM sec_invoker_view ORDER BY event_id;
-- Switch user that has been granted rights,
-- should be able to see rows that the policy allows
SET ROLE rls_tenant_1;
SELECT * FROM sec_invoker_view ORDER BY event_id;
RESET ROLE;
-- ordinary view on top of security invoker view permissions
-- ordinary means security definer view
-- The PG expected behavior is that this doesn't change anything!!!
-- Can't escape security invoker views by defining a security definer view on top of it!
CREATE VIEW sec_definer_view AS SELECT * FROM sec_invoker_view ORDER BY event_id;
\c - - - :worker_1_port
SELECT relname, reloptions FROM pg_class
WHERE relname = 'sec_definer_view' AND relnamespace = 'pg15'::regnamespace;
\c - - - :master_port
SET search_path TO pg15;
CREATE ROLE rls_tenant_2 WITH LOGIN;
GRANT USAGE ON SCHEMA pg15 TO rls_tenant_2;
GRANT SELECT ON sec_definer_view TO rls_tenant_2;
-- it doesn't matter that the parent view is security definer
-- still the security invoker view will check the invoker's permissions
-- and will not allow rls_tenant_2 to query the view
SET ROLE rls_tenant_2;
SELECT * FROM sec_definer_view ORDER BY event_id;
RESET ROLE;
-- grant select rights to rls_tenant_2
GRANT SELECT ON TABLE events TO rls_tenant_2;
-- we still have row level security so rls_tenant_2
-- will be able to query but won't be able to see anything
SET ROLE rls_tenant_2;
SELECT * FROM sec_definer_view ORDER BY event_id;
RESET ROLE;
-- give some rights to rls_tenant_2
CREATE POLICY user_mod_1 ON events
FOR SELECT TO rls_tenant_2
USING (current_user = 'rls_tenant_' || tenant_id::text);
-- Row level security will be applied as well! We are safe!
SET ROLE rls_tenant_2;
SELECT * FROM sec_definer_view ORDER BY event_id;
RESET ROLE;
-- no need to test updatable views because they are currently not
-- supported in Citus when the query view contains citus tables
UPDATE sec_invoker_view SET event_id = 5;
CREATE TABLE set_on_default_test_referenced(
col_1 int, col_2 int, col_3 int, col_4 int,
unique (col_1, col_3)