What select statement can I use to determine 1) the roles that a user belongs to and 2) the tables that a user has access to using PostgreSQL? - postgresql

Using psql, \dg and \du tells me the roles that each user (role) belongs to. I want to determine this programmatically for a single user but cannot find which system tables are used to generate the results returned for \dg.
Will I need special privileges to execute this query?
As a related question, I want to determine what tables a role can update.
I've read the documentation on CREATE ROLE and GRANT and surrounding documentation which tell me how to set up roles and privileges, but not how to test for role membership or table access privileges.

There are built-in system information functions for both. See pg_has_role, etc.
You can also query the information_schema to get role membership data, in particular information_schema.applicable_roles, information_schema.enabled_roles and information_schema.administrable_role_authorizations.
Finally, for any psql \d command you can find out what exactly psql is doing to get the information by running psql with the -E option to print the sql it runs. I don't recommend using psql's sql when there's a builtin function or an information_schema view for the same information though. psql's approach may be faster but it's also more PostgreSQL-version-specific and may not work with PostgreSQL versions other than the one you're running. In the case of \du and \dg psql will be querying pg_catalog.pg_roles as part of a join against other tables. It's much simpler and more portable to use the information_schema views instead.
This really should be the documentation entries on role membership; I'll post a patch that adds appropriate cross-references.

Related

Why do PSQL \z and information_schema.table_privileges show different details?

I'm stumbling into an accidental DBA role at my current company and SO has been a huge help in getting me set up with useful queries.
I'm currently trying to create some views to assist with UAR (user access review) to satisfy compliance requirements and assist users with troubleshooting access. To do so I'm trying to create a view that lists user names and their privileges on all tables in all schemas in the database. I started with a view against information_schema.table_privileges but noticed it was incomplete. If it's relevant, this is in an AWS RDS Postgres implementation.
To dive in, I performed \z schema_name.table_name and I got the below output. All looks well: I see the owning role analytics_owner with all privileges and six group roles with varying levels of privilege. (All employees and applications accessing the database are granted membership to one of these six roles.)
This is also shown by a query against pg_class:
But if I query against information_schema.table_privileges, there are no results. (In fact, if I drop the where relname = '[table]' clause there are only two tables with detail in the relevant schema in the table_privileges output — but there are about a hundred tables in the actual schema.)
The privilege detail from the \z command just isn't showing in information_schema.table_privileges. I'd prefer to write a view off of table_privileges rather than pg_class because I wouldn't have to decode and separate the aggregated ACL details. However, it looks like there is something very specific to the table_privileges view given that only a very small subset of the actual tables are in there.
Is there a query that produces the same detail in the same format as contained in information_schema.table_privileges but takes its data from pg_class? Alternatively, what is different about table_privileges that makes it not show the full detail, and is there something I can do about the granted privileges to make them visible there for ease in review?
Per Adrian Klaver's comment, the information_schema depends on the role you are logged in on when the query was run.

Create a user who cannot see other bank objects

How do I create a user who does not see any bank object. Only the materialized view that I will release to him via grant?
I create the user like this:
CREATE USER PAPER WITH LOGIN PASSWORD 'secure password' NOSUPERUSER NOINHERIT NOCREATEDB NOCREATEROLE NOREPLICATION VALID UNTIL 'infinite';
GRANT SELECT ON vw_visao TO user;
but when I logged in with him on pgadmin3 they can see the entire structure of the bank.
Like seeing how many tables you have, the functions and the sql within the functions and the ddl of the tables. Do not select anything for lack of privilege.
I am trying to prevent the user from being allowed to execute these commands.
There are several interesting options, see some examples:
\d list the database tables
\dv list database views
\di list enter code here the indexes of the database
\db list the tablespaces
\l list the databases
\dg list existing roles (users or groups)
\conninfo displays information about the current connection
\h list SQL commands
\h command displays details about the command
There is no way to do that inside a single database in PostgreSQL.
One possibility would be to use a postgres_fdw foreign table to refer to a table in a different database, whose definition could not be seen.

Postgres on Linux, Need passwordless way of granting access to a table

Hi: I have an interesting problem that needs a clever solution :-)
PG 9.6.5 on RHEL6_64
Constraints I have to live with:
- I'm not a superuser, but I do have createrole.
- Password is required for login
Let's say I have a table called "approvals"...
create table approvals (approval_id text);
And another table called "granters"...
create table granters (linux_uid text);
I need a perl/DBI script that must allow all users select, but only those linux_uids in the "granters" table can insert/update/delete into the "approvals" table. But when someone who is in the "granters" table runs the perl script, I do not want them to have to enter a password. The perl script can validate that an authorized user is running it using 'getlogin' and that's good enough.
I can create roles for the granters and grant them select/insert/update/delete on the "approvals" table, but they'd have to enter a password to log in first (one of the constraints on the DB is that pawwsords are required). So I can't see how this traditional method would work.
I could create before insert, update and delete triggers that can check the id of the person running the perl script against the list of uids in the "granters" table, then allow/reject the attempt on that basis. But I can't pass the linux uid into the stored procedure associated with the trigger(s). Something like perlplu might allow me to sniff out the linux uid, but again, I'm not a superuser, so (I believe) I can't create something like that.
Any ideas ?
Thanks in Advance !
Users are authenticated before any permissions inside the database are checked, so you cannot do that just within the database. Also, since you need to do something outside the database, you need superuser privileges.
First, if I were you, I'd rethink that requirement. It looks complicated and cumbersome; there should be a better solution.
You could use trust or ident authentication for password-less authentication, but you cannot make that dependent on the contents of a table in the database. It is governed by the pg_hba.conf file.
So what you'd have to do is to create a trigger on the table that runs with superuser privileges through SECURITY DEFINER and edits (and reloads!) pg_hba.conf whenever the table is modified. This is complicated, error-prone and dangerous.

Allow postgres user to only list his own database

I'm using a postgresql server and I want to forbid my users to see what other databases are on the same server.
Essentially a \l should only list his own database.
I'm pretty sure that there is a right which I need to revoke from the user but I can't find it in the docs.
This seems to work but might have unforeseen consequences. It requires tinkering with system catalogues, which isn't really a good idea!
First off, you have to permit superusers to update system catalogues by adding this to your postgresql config:
allow_system_table_mods = on
and restart.
Now, you can use DDL statements to modify system catalogues (you should be afraid). Connect to one of the user databases (a test one would be a good idea) and:
alter table pg_catalog.pg_database rename to pg_database_catalog;
create view pg_catalog.pg_database as
select oid, 1262::oid as tableoid, pg_database_catalog.*
from pg_catalog.pg_database_catalog
where has_database_privilege(pg_database_catalog.oid, 'connect');
grant select on pg_catalog.pg_database to public;
You should now find that if you connect to that database as a low-priv user, the \l command will just list the databases that that user can connect to.
The problem is you now need to guess which database the users connect to initially to fetch their database list from. If they connect to their own database initially, then you're probably done at this point. If they connect to postgres or template1 first, then you need to make this change on that database instead.
It seems to me that this should work, since the pg_database catalog is referred to by postgres backends directly by oid, rather than by name, so moving it out of the way and changing which rows are shown in it should be invisible to them. In particular, you can't stop the server distinguishing to the user between a database not existing and them not having connection privilege.
I'm not going to make any promises that this sort of change doesn't screw something else up down the line. If it breaks, you get to keep the pieces.
You probably want to make this change in a template database, and create user databases from that in future, and deactivate the allow_system_table_mods setting when you're done (which requires a server restart, remember).
Also, I tested this on 9.0: it seems to me it should work on some earlier versions too, caveat emptor.
There's no such setting in pgsql. There are settings to prevent users from connecting to databases that they shouldn't (grant / revoke connect). Being able to see there's a database is no big deal. Being able to connect / have edit rights etc. is.
I would imagine this might have negative repercussions for the user, such as not being able to connect to the database since the system does not have access to the sytem tables, not sure though. But as far as figuring out what table to revoke - this a good general way to see what the psql meta commands are doing:
To see what \l is doing you can also use the -E flag from the command line with psql.
~$ psql -E -c '\l'
********* QUERY **********
SELECT d.datname as "Name",
pg_catalog.pg_get_userbyid(d.datdba) as "Owner",
pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding",
d.datcollate as "Collation",
d.datctype as "Ctype",
pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges"
FROM pg_catalog.pg_database d
ORDER BY 1;
**************************
So if the user does not have access to pg_database they will not be able to use the \l command.
#araqnid 's answer above seems to be the way to go except for one problem: select oid, 1262::oid as tableoid, pg_database_catalog.* will have the oid column defined twice in its results, once as expicitly given via select oid and once taken from pg_database_catalog.*. At least on Postgresql 12 create view pg_catalog.pg_database will complain that the column oid is being defined twice and will abort.
Thus the corrected code would be:
alter table pg_catalog.pg_database rename to pg_database_catalog;
create view pg_catalog.pg_database as
select 1262::oid as tableoid, pg_database_catalog.*
from pg_catalog.pg_database_catalog
where has_database_privilege(pg_database_catalog.oid, 'connect');
grant select on pg_catalog.pg_database to public;
Please refer to the original answer for all other information.
I'd be glad if somebody could confirm that my findings here are correct (or refute them).

PostgreSQL: making a schema restricted/unchangable?

We like our production environment with a restricted/unchangable schema -- the development side can be owned by the developers and changed as they like -- and we like to vet changes as they are promoted.
I'm wondering if this may be a solution to making that happen:
postgres% create proddb with owner=postgres;
unixside% pg_restore --dbname=devdb [--schema-only] --no-owner proddb
/* grants to users on schema objects appear to remain intact */
/* here's the magic, I hope... */
postgres% revoke create on schema public from public;
postgres% grant usage on schema public to produser(s);
Some testing seems to show that a user in this new proddb can interact with tables normally (with appropriate grants) and cannot alter the schema (alter table, create table, drop table, etc). But I'm paranoid and very new to Postgres, so...
Q: Is this correct?
Q: Am I missing anything?
Thanks muchly.
Yes, that is correct. The only addition is that the owner of a table can always delete or modify it. So it may not work if you have existing tables in the schema.
Discovered a missing element: sequences.
The user was finding errors in his scripts; similar errors appeared in the logs:
ERROR: permission denied for sequence <sequence>
The production schema showed that although sequences were created, they were owned by postgres and no explicit grants were given to the users. As per the GRANT documentation:
Granting permission on a table does not automatically extend permissions to any sequences used by the table, including sequences tied to SERIAL columns. Permissions on sequence must be set separately.
Our fix (verbose for this demonstration) was to find all sequences:
unixside% pg_dump --schema-only proddb > proddb.schema
unixside% grep -i 'create sequence' proddb.schema
...and apply appropriate grants (select to prevent table scans, update to prevent the above errors):
postgres% grant select,update on <sequence> to produser(s);
So far, the user says it's working and errors to the log have stopped...
I forget what version PostgreSQL added the syntax, but one of the easiest ways to administer permissions in PostgreSQL is through the "GRANT foo, priv ON ALL something IN SCHEMA" syntax.
BEGIN;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA my_schema TO my_role;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA my_schema TO my_role;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA my_schema TO my_role;
COMMIT;
Very handy for making sure that permissions are always set correctly.
The EXECUTE for FUNCTIONS may seem spooky, but shouldn't be unless your functions were created with the SECURITY DEFINER attribute (and if you are using SECURITY DEFINER, you'd better be cautious since you're playing around with the PostgreSQL version of a "setuid" function). If you space out your TABLES across different SCHEMAS based on the expected permissions, then this becomes a pretty handy convention when used with the search_path variable.
ALTER ROLE my_role SET search_path = my_schema, auth_schema, public;
-- Avoid using the public schema (pretty please)
Where auth_schema has a collection of tables that my_role shouldn't have direct read or write privileges on. Assigning privs to GROUPS is also useful.
Here are some relevant docs:
http://developer.postgresql.org/pgdocs/postgres/sql-grant.html
Don't forget you can use "\h GRANT" in psql to easily figure out the syntax or remember what can be done on all objects in a schema (search for "IN SCHEMA").