I have a simple table "users", for which I want to block access to the password for some role "alice".
-- create table and some example data
create table users (
username text,
password text
);
insert into users(username, password) values
('foo', 'foopass'),
('bar', 'barpass');
-- create a role and limit its access
create role "alice";
grant select("username") on users to "alice";
So far so good. Using the "alice" role will successfully run select "username" from users
but not select "password" from users
.
However, problems arise when I introduce a view:
-- create a simple view
create view users_view with (security_invoker) as (
select * from users
);
grant select on "users_view" to public;
Now if I run (as "alice") select "username" from users_view
, it fails because "permission denied for table users".
This is not what I expect from security_invoker
. Is there a way this could still work, without completely overhauling the definition of the view?
2
Answers
The only ways to avoid the problem are
define the view with the default
security_invoker = off
create a view that only selects the "allowed" columns
That’s kind of asking if this could work without changing things that make this not work. No.
You restricted Alice’s access to just one column
username
in tableuser
.You defined a view that relies on her access privileges.
You told Alice to
select username from (select * from users)
where*
includespassword
column she doesn’t have sufficient access privileges for. Unsurprisingly:and she doesn’t, so that fails.
The only scenario where this could be expected to work is if the query rewrite system merged the view definition into the statement and reduced the query to
select username
first, then only verified privileges on its final form, after that. Here’s another illustration: demo at db<>fiddleThe way PostgreSQL works, Eve isn’t allowed to even smell the forbidden tree, no matter how much she promises to behave. We’re not taking any more risks.