postgres - two roles working on the same objects - postgresql

I have a DB with many objects. All are owned by a role called app. This role is currently used by everyone and everything (manual users, rollout scripts, app itself ...).
The goal now is to have a shorter idle_in_transaction_session_timeout for manual users than for the rollout scripts.
I was told that creating a new role for manual users is the way to go. For simplicity, let's say there is only one manual user frank.
The goal would be that frank can create/alter/drop every object he or app created/owns and that app can create/alter/drop every object he or frank created/owns.
This seems difficult in postgres due to the way it works (creator = owner, only owner can alter/drop etc...).
Searching the internet for a solution only brings partial and quite complicated solutions such as event trigger on create table or not forgetting to issue set role always.
What is the best practice here?
*** Update: one suggestion below is to grant app to frank, but that does not do the trick. It causes the scenario that app can not modify objects created by frank:
CREATE ROLE "app" LOGIN;
CREATE ROLE "frank" LOGIN;
GRANT "app" TO "frank";
set role app;
create table created_by_app (id int);
set role frank;
create table created_by_frank (id int);
drop table created_by_app; -- works
set role app;
drop table created_by_frank; -- does not work
-- SQL Error [42501]: ERROR: must be owner of table created_by_frank

Use a single owner, like the "app" role, and let other inherit this role. Something like this:
CREATE ROLE "app" LOGIN;
CREATE ROLE "frank" LOGIN;
GRANT "app" TO "frank";
When "app" is the owner of a table, "frank" can still make changes to this table or even drop this table.
An event trigger could solve the issue that user might forget to set the ownership of a table. The function and the trigger could be something like this:
CREATE OR REPLACE FUNCTION tr_set_table_owner()
RETURNS event_trigger
LANGUAGE plpgsql
AS
$$
DECLARE
_table_name TEXT;
_schema_name TEXT;
_ddl_owner TEXT;
BEGIN
SELECT
objid::regclass
, schema_name
FROM pg_event_trigger_ddl_commands()
WHERE object_type = 'table'
AND command_tag = 'CREATE TABLE'
INTO _table_name, _schema_name;
IF FOUND THEN
_ddl_owner := format('ALTER TABLE %I.%I OWNER TO app;', _schema_name, _table_name);
RAISE NOTICE 'DDL: %', _ddl_owner;
EXECUTE _ddl_owner;
END IF;
END;
$$;
CREATE EVENT TRIGGER name
ON ddl_command_end
EXECUTE FUNCTION tr_set_table_owner();
Any new table created in your database, should now be assigned to the role "app". If you want this for other objects as well, just change the code in the trigger function.

Related

PostgreSQL: How to automatically set table owner when creating new table

In PostgreSQL, table owner is automatically set to user who created the table when creating new table. In this situation, the other users can not dropped the table.
So I want to automatically change table owner for newly created tables to specific role. By using this approach, anyone who has this role can drop the tables which table owner is the same role.
To achieve this goal, I used the same approach as described in this blog. https://blog.hagander.net/setting-owner-at-create-table-237/
I am currently using this function. This function should be triggered when new table is created in a schema analytics_custom and change table owner to analytics_custom_readwrite.
But it looks like the functions is not working as expected. Table owner of newly created table is an user who created the table.
Am I missing something? Any problem with this function?
CREATE OR REPLACE FUNCTION analytics_custom.trg_create_set_owner()
RETURNS event_trigger
LANGUAGE plpgsql
AS $function$
DECLARE
obj record;
BEGIN
FOR obj IN
SELECT *
FROM pg_event_trigger_ddl_commands()
WHERE schema_name='analytics_custom' AND command_tag='CREATE TABLE'
LOOP
if obj.object_identity ~ 'analytics_custom.*'
THEN
EXECUTE format('ALTER TABLE %s OWNER TO analytics_custom_readwrite', obj.object_identity);
end if;
END LOOP;
END;
$function$
;
CREATE EVENT TRIGGER analytics_custom_trg_create_set_owner
ON ddl_command_end
WHEN tag IN ('CREATE TABLE')
EXECUTE PROCEDURE analytics_custom.trg_create_set_owner();

How to grant permissions / set role with ddl_command_start event trigger without explicit sql execution?

I encountered an issue while resetting the table owner whenever an index is created on table in psql. Am doing this programatically by defining ddl_command_start.
Flow of events:
On /create api execution, backend creates a table using role table_role and resets the owner to superuser/admin
On /createIndex api execution ,backend tries to create index using role table_role , in doing so it cannot execute ddl_command_start as table_role is not the owner of created table in step1.
Is there a way to grant temporary access to the created table as owner in second step ? ( without executing an explicit sql from code )
Is it possible to invoke db function or other means to reset the role before permission check happens on a table ?
Please let me know your comments. Thank you.
permForIndexEndFunc := `CREATE OR REPLACE FUNCTION internal.trg_create_index_set_owner()
RETURNS event_trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
obj record;
tablename text;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag='CREATE INDEX' LOOP
tablename := substring(obj.object_identity from 1 for 10);
EXECUTE format('SET ROLE %s','admin');
EXECUTE format('ALTER TABLE %s OWNER TO table_role', tablename);
END LOOP;
END;
$$;`
_, err = db.Exec(permForIndexEndFunc)
if err != nil {
return err
}
permForIndexEndTrigger := `CREATE EVENT TRIGGER trg_create_index_set_owner
ON ddl_command_start
WHEN tag IN ('CREATE INDEX')
EXECUTE PROCEDURE internal.trg_create_index_set_owner();`
_, err = db.Exec(permForIndexEndTrigger)
if err != nil {
return err
}
Basically my app creates generic ingestion db schemas based on client configuration (either through grpc / rest api calls),so customer would send the schema in the form of json & it creates the identical db schema in backend , then onwords customer sends data in the pre-defined json format.So basically when schema is created on db event triggers maintain the rbac of so and so schema,tables.Schema,table creation itself happens by assuming creation only role by resetting owner to superuser using event trigger, so by the time index creation owner of table has been reset & fail.
Let's call the superuser/admin that owns the table from step 1 “user a”.
Then, for table_role to be allowed to create an index on the table, it must be a member of a. So you have to grant role membership temporarily:
BEGIN;
GRANT a TO table_role;
CREATE INDEX ...;
REVOKE a FROM table_role;
COMMIT;
Now if a is really a superuser, then of course table_role is not allowed to do that unless it is also a superuser (but in that case, the problem wouldn't exist, because superusers are allowed to do anything).
So make sure that a is no superuser. Then table_role needs to have CREATEUSER to be allowed to do tha above.

Can event triggers be used to alter the owner of a newly created table to a role the creator is not a member of?

I want to alter the owner of a postgreSQL table upon creation. So far so good.
CREATE OR REPLACE FUNCTION trg_create_set_table_owner()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag='CREATE TABLE' LOOP
EXECUTE format('ALTER TABLE %s OWNER TO blubb', obj.object_identity);
END LOOP;
END;
$$;
However, I would like to change the owner to a role that the user creating the table is not a part of. Is that possible at all?
https://www.postgresql.org/docs/current/static/sql-createfunction.html#SECURITY DEFINER
Because a SECURITY DEFINER function is executed with the privileges of
the user that owns it, care is needed to ensure that the function
cannot be misused
Apart of this - just create a function with SU that will accept tablename and rolename as arguments and will run ALTER TABLE. grant execute to the role, consuming trg_create_set_table_owner and it will work. But the concern that the role can use the ability to alter table outside of trg_create_set_table_owner remains...
Of course you can check the possible role names and table names from a list of allowed before passing them to execute format('ALTER TABLE %I OWNER TO %I')

Row-based security in Postgresql 9.4 based on views for read and write access

For a system, where the users have full shell access, we want to manage the folders shared between the users via access control lists, using an postgresql database. Therefore each user has an personal database account and we use row based security based on views. Here is our current draft:
REVOKE ALL ON jobs_admin FROM public;
REVOKE ALL ON share_admin FROM public;
CREATE VIEW share
WITH (security_barrier)
AS
SELECT *
FROM share_admin
WHERE username = current_user
OR owner = current_user
WITH CHECK OPTION;
CREATE VIEW jobs_read
WITH (security_barrier)
AS
SELECT *
FROM jobs_admin j, share_admin s
WHERE s.username = current_user
AND j.project LIKE s.project || '%'
WITH CHECK OPTION;
CREATE VIEW jobs_write
WITH (security_barrier)
AS
SELECT *
FROM jobs_admin j, share_admin s
WHERE (s.username = current_user
AND s.permission = 'w'
AND j.project LIKE s.project || '%')
OR j.project LIKE current_user || '%'
WITH CHECK OPTION;
ALTER VIEW share OWNER TO admin;
ALTER VIEW jobs_read OWNER TO admin;
ALTER VIEW jobs_write OWNER TO admin;
GRANT ALL ON share TO public;
GRANT SELECT ON jobs_read TO public;
GRANT ALL ON jobs_write TO public;
RESET ROLE;
Now we have two issues:
A user could add the entry '/' to his share view and share this folder with himself, then he would be able to see all database entries of all other users.
At the moment we need to switch in our interface between the jobs_read and jobs_write table.
So I thought about using triggers like:
CREATE TRIGGER insert_trigger
INSTEAD OF INSERT ON jobs_read
FOR EACH ROW EXECUTE PROCEDURE insert_trigger_func();
CREATE FUNCTION insert_trigger_func() RETURNS TRIGGER AS $BODY$
BEGIN
INSERT INTO jobs_write VALUES (NEW.*);
RETURN NEW;
END; $BODY$ LANGUAGE plpgsql;
but while this is easy for the insert statement, it is much more complicated for UPDATE and DELETE.
As all this feels rather buggy, I was wondering if anybody could point me to an good example or some book which handles these topics.
Best
Jan

Prevent query assigning ID when column has a default

I have a table that has a column defined as id serial. Therefore, it has a default on the column to call a sequencer. Which is what I want. However, my boss (and owner of the company) insists on having full access to the database and likes to write his own queries (including inserts) on this table. And even more unfortunately, he seems to always forgets that these IDs should be auto-generated for him, and he creates the record with max(id) + 1. Then the sequencer is out of sync with the table and the next time someone calls the API we get a 500.
Question: Given that I don't have the ability to lock him out of the database, what is the best way to prevent him from doing this?
My first idea was create a before insert trigger and raise an error if the ID was assigned. Here is what I did:
CREATE OR REPLACE FUNCTION md.tf_no_manual_keys()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
BEGIN
IF NEW.id IS NOT NULL THEN
RAISE EXCEPTION 'ID must be assigned from sequencer';
END IF;
END;
$function$
trigger_no_manual_keys BEFORE INSERT ON vendor FOR EACH ROW EXECUTE PROCEDURE tf_no_manual_keys()
But, this doesn't work because the sequencer has already fired (I didn't think that was the case for a "BEFORE" trigger).
I guess I could always call the trigger on BEFORE INSERT, but that seems like it would use 2 IDs whenever it's called.
I'm using PostgreSQL 9.4
If you have grant privilege on the table you can revoke a role's insert privilege on a certain column:
revoke insert (id) on table t from role_name;
But that will not work if that role is a member of another role that has that privilege.