> as once the DB user has permission to execute "SET ROLE", it's got the same security issues with accessing other user data as the current "WHERE company_id=3" style of separation?
DB users can only SET ROLE to roles they belong to. If each tenant has its own role it isn't possible to switch between them.
# CREATE ROLE bob;
CREATE ROLE
# CREATE ROLE alice;
CREATE ROLE
# SET SESSION AUTHORIZATION bob;
SET
> SET ROLE alice;
ERROR: permission denied to set role "alice"
"SET SESSION AUTHORIZATION <role>" changes the possible SET ROLE targets to those allowed for <role> (so only the roles it's a member of), whereas SET ROLE keeps the original possible roleset (which is every role if you SET ROLE from an admin account), so SET SESSION AUTHORIZATION is a better proxy for "logged as <role>".
The biggest annoyance is with connection pooling and the inability (to my knowledge) to prevent RESET SESSION AUTHORIZATION at the DB-level: when pooling connections you'll want to have a "base" role which is a NOINHERIT member of all "effective" (tenant) roles, so it can only SET ROLE/SET SESSION AUTHORIZATION but can't do anything (so it can't be exploited), the first thing you do when you get a connection from the pool is SSA to the effective user you want, and the last thing you do when you put the connection back in the pool is a RSA.
# CREATE TABLE foo ( id serial, c1 varchar, c2 varchar );
# GRANT SELECT (id, c1, c2) ON foo TO public;
# GRANT UPDATE (c1) ON foo TO bob;
# GRANT UPDATE (c2) ON foo TO alice;
# INSERT INTO foo (c1, c2) VALUES ('a', 'b');
# SELECT * FROM foo;
id | c1 | c2
----+----+----
1 | a | b
(3 rows)
# SET SESSION AUTHORIZATION base;
> UPDATE foo SET c1 = 'd' WHERE id = 1;
ERROR: permission denied for relation foo
> SET SESSION AUTHORIZATION bob;
> UPDATE foo SET c1 = 'd' WHERE id = 1;
UPDATE 1
> SELECT * FROM foo;
id | c1 | c2
----+----+----
1 | d | b
(3 rows)
That'll prevent honest mistakes, though an attacker could still RESET SESSION AUTHORIZATION then switch the connection to a different role and go to town. It might be possible to mitigate that at the application level, but that's less convenient.