Transitive/nested permissions in PostgreSQL - postgresql

I have the following setup:
create role test_user noinherit nologin;
create table test_me ( attr text );
create view v_test_me as select * from test_me;
create function fn() returns trigger language plpgsql as $$
begin
insert into test_me(attr) values (NEW.attr);
return NEW;
end; $$;
create trigger tg instead of insert on v_test_me
for each row execute procedure fn();
grant insert on v_test_me to test_user;
set role test_user;
insert into v_test_me(attr) values ('hello?');
Here I have a table and a view on top of it. The view has an instead-of-trigger. I am allowing test_user to insert into the view. But i get the following error:
ERROR: permission denied for table test_me
CONTEXT: SQL statement "insert into test_me(attr) values (NEW.attr)"
It looks like I do not have permission to execute an insert statement on the test_me table, which is expected. Is there any way I can allow user to insert into test_me table without directly granting them such permission?

In this case a SECURITY DEFINER modifier on the trigger function fn() will solve the issue. As per CREATE FUNCTION documentation:
SECURITY DEFINER specifies that the function is to be executed with
the privileges of the user that created it
This will allow inserting into test_me table, provided the owner of the function has such permission.

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.

Why does PostgreSQL permissions behave differently between Triggers and Check constraints?

I'm currently having a Database with two schemas app_private & app_public (in addition to default public schema). I also have a role which has been granted usage on app_public schema, but not the app_private schema. I'm also using two functions (one trigger function and one check constraint function) on the table.
See below for code:
(1) Creation of the Schemas (and grants)
CREATE SCHEMA app_public;
CREATE SCHEMA app_private;
grant usage on schema public, app_public to "grant_test_role";
(2) Revoke grants from PUBLIC user
Then I'm having this special DDL statement. It is supposed to REVOKE permissions for any newly added function from the public user role (which all other roles inherit from).
alter default privileges revoke all on functions from public;
(3) Function Definitions (Trigger & Constraint)
-- Trigger Function
create OR replace function app_private.tg__timestamps() returns trigger as $$
begin
NEW.created_at = (case when TG_OP = 'INSERT' then NOW() else OLD.created_at end);
NEW.updated_at = (case when TG_OP = 'UPDATE' and OLD.updated_at >= NOW() then OLD.updated_at + interval '1 millisecond' else NOW() end);
return NEW;
end;
$$ language plpgsql volatile set search_path to pg_catalog, app_private, public, pg_temp;
-- Constraint Function
CREATE OR REPLACE FUNCTION app_private.constraint_max_length(
value text,
maxLength integer,
error_message text default 'The value "$1" is too long. It must be maximum $2 characters long.',
error_code text default 'MXLEN'
) RETURNS boolean
AS $$
begin
if length(value) > maxLength then
error_text = replace(replace(error_message, '$1', value), '$2', maxLength);
raise exception '%', error_text using errcode = error_code;
end if;
return true;
end;
$$ LANGUAGE plpgsql set search_path to pg_catalog, app_private, public, pg_temp;
(4) Table Definition (which uses above Trigger & Constraint functions)
create table app_public.test_tab (
id INT not null primary key,
name text not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint name_length_check check (app_private.constraint_max_length(name, 5));
);
create trigger _100_timestamps
before insert or update on app_public.test_tab
for each row
execute procedure app_private.tg__timestamps();
-- Setting some restrictions on the test_tab for the "grant_test_role"
REVOKE ALL ON TABLE app_public.test_tab FROM "grant_test_role";
GRANT SELECT, DELETE ON app_public.test_tab TO "grant_test_role";
GRANT
INSERT(id, name),
UPDATE(id, name) ON app_public.test_tab TO "grant_test_role";
(5) Code (which runs as grant_test_role)
begin;
set local role to grant_test_role;
insert into app_public.test_tab (id, name) values (1, 'Very Long Name');
commit;
I'm trying to execute this in a fresh DB each time for me to understand how PostgreSQL permissions work in different call contexts (i.e. Trigger Function, constraint check which calls a function automatically, etc.)
When I don't have the code block (2) which revokes functions permissions from PUBLIC user, the code block (5) executes without any errors. Event though the user role doesn't have grants to app_private schema where the trigger function and the constraint function exists. But with the code block (2) present, the code executes the trigger just fine, yet gives me a "permission denied for function constraint_max_length" for the check constraint.
So I'm trying to understand,
How does the Trigger function which exists in a schema where the user role does not have usage grants, execute successfully always?
If the trigger function executes, why does the the CHECK constraint function give me the above permission denied error?
What does the code block (2) really do?
I'm struggling a bit to find documentation about how permissions apply in this kind of "auto-executed" scenarios (triggers/constraints) since the User is not "explicitly" calling these functions, rather they are automatically called by the DB. So I'm not sure which ROLE is executing them.
I posted this question to the PostgreSQL mailing list and finally got the answer.
So as of now, this is how PostgreSQL works (be it compliant with SQL spec or not :)
Original mail thread - https://www.postgresql.org/message-id/CANYEAx8vZnN9eeFQfsiLGMi9NdCP0wUdriHTCGU-7jP0VmNKPA%40mail.gmail.com
Trigger Functions
Trigger Function privileges are checked at "creation" time against the role that creates them.
At runtime, privileges for the trigger function are not checked against the executing role at all, but simply the ability to parse.
The statements inside of the trigger function will go through usual privilege checks against the executing role
Check Constraint Functions
Check Constraint function privileges are checked at "creation" time against the role that creates them.
At runtime, the schema of the constraint function is only checked for the "ability to parse". As in if such a schema exists, but not if it is accessible.
However at runtime, the function itself (regardless of the schema it exists in) is checked for privileges against the executing role.
So this explains the behavior I was encountering in PostgreSQL

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