How to use default schema privileges on functions in Postgres in right way? - postgresql

I am struggling to comprehend how default schema privileges work in Postgres. To me, they are something that supposed to ease administration load by issuing permissions automatically, but I found them bit unusable. I discovered several things that are not at all obvious from documentation.
I want several users to be able to create and modify objects in schema. I create a role who gonna be the owner and grant this role to multiple (in general) users:
create schema my_schema;
create role my_schema_owner;
alter schema my_schema owner to my_schema_owner;
create user my_user password 'xxx';
grant my_schema_owner to my_user;
create role my_role;
alter default privileges in schema my_schema grant execute on functions to my_role;
create function my_schema.my_func1() returns int as
$$ begin return 3; end; $$ language plpgsql;
Please note that I do this under my own (administration) account.
Next, I check what I got. I use this view:
create or replace view pg_functions_grants as
select proname, n.nspname, coalesce(nullif(s[1], ''), 'public') as grantee,
s[2] as privileges, s[3] as grantor
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
join pg_roles r on r.oid = p.proowner
join pg_type rt on p.prorettype = rt.oid,
unnest(coalesce(p.proacl::text[], format('{%s=arwdDxt/%s}', r.rolname, r.rolname)::text[])) acl,
regexp_split_to_array(acl, '=|/') s
and request permissions for created objects:
select * from pg_functions_grants where proname = 'my_func1' order by 1;
my_func1 my_schema public X <me>
my_func1 my_schema <me> X <me>
my_func1 my_schema my_role X <me>
a) We see it granted execute on func1 to PUBLIC. It's OK, documentation says it's by default.
b) It granted execute permission to me. It's OK, but it seems redundant since I am already the owner.
c) It granted execute to my_role as I asked. Perfect.
Now I pretend that I am a user to whom ownership was granted:
set role my_user;
create function my_schema.my_func2() returns int as
$$ begin return 3; end; $$ language plpgsql;
select * from pg_functions_grants where proname = 'my_func2' order by 1;
my_func2 my_schema my_user arwdDxt my_user
d) Why did not it granted execute to PUBLIC?
e) Why the hell it did not apply default privileges?
I try to figure out what's going on:
create or replace view pg_namespaces_default_grants as
select n.nspname, r.rolname, d.defaclobjtype, coalesce(nullif(s[1], ''), 'public') as grantee,
s[2] as privileges, s[3] as grantor
from pg_default_acl d
join pg_namespace n on d.defaclnamespace = n.oid
join pg_roles r on r.oid = n.nspowner,
unnest(coalesce(d.defaclacl::text[], format('{%s=arwdDxt/%s}', r.rolname, r.rolname)::text[])) acl,
regexp_split_to_array(acl, '=|/') s;
select * from pg_namespaces_default_grants where nspname = 'my_schema';
my_schema my_schema_owner f my_role X <me>
Hmmm... I see the grantor mentioned here... May be this is important? Let's set up defaults under my user:
set role my_user;
alter default privileges in schema my_schema grant execute on functions to my_role;
create function my_schema.my_func3() returns int as
$$ begin return 3; end; $$ language plpgsql;
select * from pg_functions_grants where proname = 'my_func3' order by 1;
my_func3 my_schema public X my_user
my_func3 my_schema my_user X my_user
my_func3 my_schema my_role X my_user
Now it worked as expected.
OK, may be it inherits default privileges through granted roles?
set role my_schema_owner;
alter default privileges in schema my_schema grant execute on functions to my_role;
set role my_user;
alter default privileges in schema my_schema revoke execute on functions from my_role;
Let's verify it:
select * from pg_namespaces_default_grants where nspname = 'my_schema';
my_schema my_schema_owner f my_role X my_schema_owner
my_schema my_schema_owner f my_role X <me>
Correct. And now:
set role my_user;
create function my_schema.my_func7() returns int as
$$ begin return 3; end; $$ language plpgsql;
select * from pg_functions_grants where proname = 'my_func7' order by 1;
my_func7 my_schema my_user arwdDxt my_user
Damn, it does not!
To conclude: default privileges work only when creating objects under the user (explicit) who set the default privileges and does not work under users that were granted with role who set default privileges.
Now questions:
Is the fact above is mentioned in some place in documentation which I failed to find?
Why is it so inconvenient? May be I misuse it? Is there way to set default privileges in schema that would work for every user with some granted role? For all (existing and future) users?
It is completely unclear situation with PUBLIC. Why did not it grant EXECUTE to PUBLIC in d)? I conducted few more experiments and discovered that if a user have any default grants set for a schema, they get augmented by EXECUTE for PUBLIC. But if there are no default privileges no EXECUTEs granted to PUBLIC on functions. It looks completely illogical to me. Is there an explanation for this?

I'll try to answer the questions raised towards the end:
The documentation says:
ALTER DEFAULT PRIVILEGES
[ FOR { ROLE | USER } target_role [, ...] ]
[ IN SCHEMA schema_name [, ...] ]
abbreviated_grant_or_revoke
target_role
The name of an existing role of which the current role is a member. If FOR ROLE is omitted, the current role is assumed.
You always define default privileges for a certain role, that is, the privileges only apply when that role creates an object.
That's the way it is. The best thing is to have only a single role that is allowed to create objects in a schema.
Any granted privileges are added to the existing privileges.
All functions are created with EXECUTE privileges for PUBLIC, and I don't believe your result in d). You'll have to come up with a simple reproducible test case for that.
The only way to change that is to have a default privilege (not restricted to a schema!) that revokes the EXECUTE privilege.

Related

How to get PUBLIC role's granted privilege list in postgres?

As the documentation said:
PUBLIC can be thought of as an implicitly defined group that always includes all roles. Any particular role will have the sum of privileges granted directly to it, privileges granted to any role it is presently a member of, and privileges granted to PUBLIC.
We can grant privileges for PUBLIC like this:
GRANT SELECT ON table_1 TO PUBLIC;
GRANT USAGE ON SCHEMA schema_1 TO PUBLIC;
GRANT EXECUTE ON FUNCTION func_1 TO PUBLIC;
If I want to revoke such public privileges, I should know the granted privilege list first. But I can't find a good way to get the list because PUBLIC is not a role, thus many builtin functions like has_table_privilege cannot be used.
Now I have found some tables in information_schema may help, but there are still some attributes like SCHEMA I cannot find a list for them.
How to get the PUBLIC's granted SCHEMA privilege list? Or is there a better way to get all the privileges?
-- get granted table and view privileges
SELECT table_schema, table_name, string_agg(privilege_type, ',') AS privileges
FROM information_schema.table_privileges
WHERE grantee='PUBLIC' AND table_schema NOT LIKE 'pg_%' AND table_schema != 'information_schema'
GROUP BY table_schema, table_name;
-- get granted function privileges
SELECT routine_schema, routine_name, string_agg(privilege_type, ',') AS privileges
FROM information_schema.routine_privileges
WHERE grantee='PUBLIC' AND routine_schema NOT LIKE 'pg_%' AND routine_schema != 'information_schema'
GROUP BY routine_schema, routine_name;
Privileges for a table are stored in pg_class.relacl which is an array of aclitem.
The content of such an aclitem is documented in the manual, specifically:
An empty grantee field in an aclitem stands for PUBLIC.
So to find all tables (or other objects) that have something granted to the public role, one needs to find entries where at least one aclitem starts with = ("empty grantee field")
There is a contains operator #> for aclitems that can be used to check for specific privileges. But
I couldn't find a way to specify an aclitem value that would search match all privileges granted to public (relacl #> '=*' doesn't seem to work in all cases).
So a workaround might be to simply convert the items to text and use a LIKE condition:
select c.relnamespace::regnamespace::text as table_schema,
c.relname as table_name,
c.relacl
from pg_class c
where relnamespace not in ('pg_catalog'::regnamespace, 'information_schema'::regnamespace)
and exists (select *
from unnest(c.relacl) as x(acl)
where x.acl::text like '=%')

troubles querying information schema

postgresql server 8.4
With a user with attribute "super user", I can perform this query :
SELECT
ccu.table_name AS master_table, ccu.column_name AS master_column,
tc.table_name AS child_table, kcu.column_name AS child_column
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY'
ORDER BY master_table, master_column
With a normal user, I have no errors but also no results.
Which are the minimal permissions ... grants ... to allow a normal user to query the information schema ?
I tried unsuccesfully
GRANT USAGE ON SCHEMA information_schema to user
and also
grant select on information_schema.constraint_column_usage to user
(and the other two used)
You will only see objects on which you have some permissions:
You cannot see other users' temporary objects.
You can see objects whose owner is a role you belong to.
You can see objects if you have any privileges on the table or its columns.
To bypass these restrictions, you could create a function with SECURITY DEFINER that belongs to a superuser and runs the query for you.
Then revoke EXECUTE on that function from PUBLIC and grant it to the user who needs it.
CREATE FUNCTION info_schema_query()
RETURNS TABLE (
master_table information_schema.sql_identifier,
master_column information_schema.sql_identifier,
child_table information_schema.sql_identifier,
child_column information_schema.sql_identifier
)
LANGUAGE sql STABLE SECURITY DEFINER
SET search_path = information_schema
AS $$SELECT ...$$;
REVOKE EXECUTE ON FUNCTION info_schema_query() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION info_schema_query() TO j_random_user;

Give a user permission to ALTER a function

I try to ALTER a function with a new user and I get the error:
ERROR: must be owner of function ACases
********** Error **********
ERROR: must be owner of function ACases
SQL state: 42501
What permission do I have to give to a user so he can ALTER that function?
The only way I found was to make the user the OWNER of the function.
But if that is the case, only one user (owner) can ALTER the function. So how would I change the OWNER for all functions?
CREATE OR REPLACE FUNCTION public."ACases"(caseid integer)
RETURNS boolean AS
$BODY$
DECLARE
BEGIN
RETURN FALSE;
END;
$BODY$
LANGUAGE plpgsql;
ALTER FUNCTION public."ACases"(integer) OWNER TO postgres;
GRANT ALL PRIVILEGES ON FUNCTION public."ACases"(integer) TO user_name;
The manual on ALTER FUNCTION is clear on that:
You must own the function to use ALTER FUNCTION. To change a function's
schema, you must also have CREATE privilege on the new schema.
To alter the owner, you must also be a direct or indirect
member of the new owning role, and that role must have CREATE
privilege on the function's schema. (These restrictions enforce that
altering the owner doesn't do anything you couldn't do by dropping and
recreating the function. However, a superuser can alter ownership of
any function anyway.)
Bold emphasis mine.
You also need a couple of basic privileges to create functions. Per documentation:
To be able to define a function, the user must have the USAGEprivilege on the language.
...
To be able to create a function, you must have USAGE privilege on the argument types and the return type.
The simple solution would be make changes to functions as superuser. (Default superuser is postgres, but any user can be made superuser.)
If you really need to change ownership on all functions, this would do the trick:
SELECT string_agg('ALTER FUNCTION '
|| quote_ident(n.nspname) || '.'
|| quote_ident(p.proname) || '('
|| pg_catalog.pg_get_function_identity_arguments(p.oid)
|| ') OWNER TO foo;'
, E'\n') AS _sql
FROM pg_catalog.pg_proc p
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public';
-- AND p.relowner <> (SELECT oid FROM pg_roles WHERE rolname = 'foo')
-- AND p.proname ~~ 'f_%'
Restricted to the public schema.
For more details and explanation refer to this more complete answer on dba.SE.
Also closely related:
DROP FUNCTION without knowing the number/type of parameters?

how to create duplicate role of a user in postgres

I need a new user but it should be granted all those privileges that the other existing user/role has.
e.g.
User A has SELECT privileges on Table1
User A has EXECUTE privileges on Table2
...
If a new User B is created, I need the same privileges as,
User B has SELECT privileges on Table1
User B has EXECUTE privileges on Table2
...
Dont ask why :/
Actually User A has custom privileges on different tables, schemas, and functions; so its very tedious and lengthy process to manually grant permissions to the new user. Any help would be good.
Try something like:
GRANT A TO B;
It will grant all right of role A to B.
For details read this chapter of the manual.
First understand that roles and users are the same thing. In fact there isn't a thing called a user really, it's just a ROLE with a LOGIN option.
Second roles can be granted to other roles.
Third priviledges on roles can be inherited.
So assuming you have created your user a like:
CREATE ROLE A LOGIN;
GRANT SELECT ON table1 TO a;
GRANT EXECUTE ON FUNCTION xxx TO a;
You should be able to create a second role that mirrors the first role like:
CREATE ROLE b LOGIN;
GRANT a TO b;
I had to write the pgpsql code to loop through the privileges of User A and grant it to User B. It was done without any problem.
create or replace function update_user_privileges() returns text as
$$
declare
info record;
str text;
begin
/*Grant privileges to user B the same as with user A for a given table schema*/
str:='';
FOR info IN
select * from information_schema.table_privileges where table_schema='public' and grantee = 'A'
LOOP
/*append the tables' name, for which we are assigning privileges from user A to B*/
str:= str ||info.table_name || ',';
/*this is the main statement to grant any privilege*/
execute 'GRANT '|| info.privilege_type ||' on table public.'|| info.table_name || ' to B';
END LOOP;
return str;
end
$$ language 'plpgsql';
Usage: Copy/paste this code to crate this function and then do
select update_user_privileges();
**You have to adapt it for your table-schema and table-names. Hope it helps anyone
Here's a quick way to create grant statements for newuser, by copying all grants on db mydb to grantee myuser.
pg_dump mydb -s | egrep '^(GRANT|REVOKE).+TO "myuser"' | sed -E "s/\"myuser\"/\"newuser\"/g"
Note: The -s flag makes pg_dump execute quickly, because it's only dumping schema info.
Example output
GRANT SELECT,INSERT,UPDATE ON TABLE tabl1e TO "newuser";
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE table2 TO "newuser";
GRANT ALL ON PROCEDURE myprocedure(ids bigint[]) TO "newuser";
Simply run the output SQL grants or pipe them to psql and you're all set.
I used following method to create a new user same as an existing user using Ubuntu.
Get a full dump of existing database.
Use the following command to extract every line with the user you want to clone.
cat /path/to/db_dump_file | grep "existing_user_name" >> /path/to/extract.sql
Open extract.sql with a text editor and replace existing username with new username.
Remove unwanted queries (if any).
Now you have new SQL queries to create the new user.
This worked for me just fine. Hope this will help someone.

How to revoke all group roles from login role

How to revoke all group roles from login role? Is there a way how to do this automatically?
Since you can GRANT / REVOKE several roles at once, a single DO command with dynamic SQL would be simpler / faster (set-based operations are regularly faster in RDBMS than looping):
DO
$do$
DECLARE
_role regrole := 'my_role'; -- provide valid role name here
_memberships text := (
SELECT string_agg(m.roleid::regrole::text, ', ')
FROM pg_auth_members m
WHERE m.member = _role
);
BEGIN
IF _memberships IS NULL THEN
RAISE NOTICE 'No group memberships found for role %.', _role;
ELSE
RAISE NOTICE '%',
-- EXECUTE
format('REVOKE %s FROM %s', _memberships, _role);
END IF;
END
$do$;
The code is in debug mode. Comment RAISE NOTICE '%', and un-comment EXECUTE to prime the bomb.
DO and string_agg() require Postgres 9.0 or later.
The object identifier type regrole was added with Postgres 9.5
Casting to regrole verifies role names on input and double-quotes where necessary when outputting text - so no SQL-injection possible.
Effectively executes a command like:
REVOKE role_a, role_b FROM my_user;
Doesn't break with maliciously formed role names:
REVOKE role_a, role_b, "FROM postgres; DELETE * FROM usr; --" FROM my_user;
Note the double quotes around the trick-name.
Raises a notice if no role memberships are found.
This revokes all memberships in other roles. It's all just roles to Postgres, some have the LOGIN privilege ("user roles"), others don't ("group roles").
Think need to query all the roles
select usename, rolname
from pg_user
join pg_auth_members on (pg_user.usesysid=pg_auth_members.member)
join pg_roles on (pg_roles.oid=pg_auth_members.roleid)
and LOOP through the result to REVOKE rolname FROM usename;