PostgreSQL: Serializable transactions with GRANT/DROP role - postgresql

I have been reading a lot about serializable transactions in Postgres, but came across an issue that I haven't been able to resolve. Let's assume that I have two Postgres sessions A and B from different psql processes, and that before starting any transactions, I create roles role1 and role2
myuser=# CREATE ROLE role1 ;
CREATE ROLE
myuser=# CREATE ROLE role2 ;
CREATE ROLE
At this point, I can start transactions in both of the sessions via myuser=# BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ; and verify that both sessions see both roles, via SELECT * FROM pg_catalog.pg_roles;
Let's assume that I drop the role role2 from the session B, verify that it's not shown and commit:
myuser=# DROP ROLE role2 ;
DROP ROLE
myuser=# SELECT * FROM pg_catalog.pg_roles;
rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolconnlimit | rolpassword | rolvaliduntil | rolbypassrls | rolconfig | oid
-----------------------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+-------------+---------------+--------------+-----------+-------
role1 | f | t | f | f | f | f | -1 | ******** | | f | | 16563
myuser=# commit ;
COMMIT
Now, let's go back to session A:
Even after dropping role2 and committing inside session B, we can still see that the transaction in session A still sees both roles (as expected, as this is serializable):
myuser=# SELECT * FROM pg_catalog.pg_roles;
rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolconnlimit | rolpassword | rolvaliduntil | rolbypassrls | rolconfig | oid
-----------------------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+-------------+---------------+--------------+-----------+------
role1 | f | t | f | f | f | f | -1 | ******** | | f | | 16563
role2 | f | t | f | f | f | f | -1 | ******** | | f | | 16564
Now, let's try the GRANT command. Here is the weird part:
myuser=# GRANT role2 TO role1 ;
ERROR: role "role2" does not exist
Right before running the GRANT, we can see that both roles role1 and role2 exist, but now we see such error. Why is that?
Thanks!

With “normal” (i.e. non-catalog) tables conflicting operations like this would lead to a serialization error. While a serializable transaction sees an old state of the database, any attempt to modify a value that has been changed since would lead to such an error.
What makes this case different is that the tables that store roles and role membership (pg_authid and pg_auth_members) are catalog tables (even shared catalogs since roles apply to all databases), and for these processing and error messages are slightly different.
It is good and necessary that the operation fails, even if the error message is surprising.

Related

Postgres: How Can a Role Both Have INSERT Permissions and Not?

I have a table called resources and a user/role called public_anonymous ... and as far as I can tell, the user doesn't have INSERT permissions on that table. Here's DBeaver showing as much:
And here's the output of \z resources (at the psql command line):
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+-----------+-------+--------------------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------
public | resources | table | admin=arwdDxt/admin +| | select_resources (r):
+
| | | public_postgraphile=arwd/admin+| | (u): true
+
| | | public_anonymous=r/admin +| | update_resources (w):
+
| | | public_admin_user=arwd/admin +| | (u): (( SELECT resource_authors.user_id
+
| | | public_user=ar/admin +| | FROM resource_authors
+
| | | =a/admin | | WHERE ((resource_authors.resource_id = resource_authors.resource_id) AND (resource_authors.user_id = (NULLIF(current_setting('jwt.claims.person_id'::text, true), ''::text))::integer))) IS NOT NULL)+
| | | | | to: public_user
+
| | | | | delete_resources (d):
+
| | | | | (u): (( SELECT resource_authors.user_id
+
| | | | | FROM resource_authors
+
| | | | | WHERE ((resource_authors.resource_id = resource_authors.resource_id) AND (resource_authors.user_id = (NULLIF(current_setting('jwt.claims.per
son_id'::text, true), ''::text))::integer))) IS NOT NULL)+
| | | | | to: public_user
The key part of that is:
public_anonymous=r/admin
ie. the role has only been granted read ("r") permissions by the admin role.
Furthermore, I have repeatedly tried to remove INSERT permissions by running:
REVOKE INSERT ON resources FROM public_anonymous;
Nevertheless, when I tried using pgTAP to verify my permissions, I was surprised to find that it reported that the user did have INSERT permissions. When I asked the maintainer why it thought that, he explained that it used the pg_catalog.has_table_privilege function ... and sure enough, when I ran:
select pg_catalog.has_table_privilege('public_anonymous', 'resources', 'INSERT');
I saw:
has_table_privilege
---------------------
t
I'm not a Postgres expert, so forgive me if this is a dumb question. Also I should note that I have been using both GRANT statements and row-level security policies on this table ...
... but still, I don't understand why multiple sources all say the user doesn't have INSERT permissions, AND I explicitly REVOKE-ed them ... yet pg_catalog.has_table_privilege still thinks I do have the permission.
Can anyone explain what's going on, and possibly how I might remove this permission (for good)?
This ACL item
=a/admin
means that PUBLIC (that is, everyone) has INSERT privileges on the relation. REVOKE that if you don't want it.

Why does selecting all tables from postgres gives different results based on syntax used

If I log into postgres and switch to the postgres database, when I check for tables with this command I get nothing back:
postgres=# \c postgres
You are now connected to database "postgres" as user "postgres".
postgres=# \dt+
Did not find any relations.
However if I do a select on the DB tables I do see that the tables are there:
postgres=# SELECT *
postgres-# FROM pg_catalog.pg_tables
postgres-# WHERE schemaname != 'pg_catalog' AND
postgres-# schemaname != 'information_schema';
schemaname | tablename | tableowner | tablespace | hasindexes | hasrules | hastriggers | rowsecurity
------------+----------------+------------+------------+------------+----------+-------------+-------------
pgagent | pga_jobclass | postgres | | t | f | t | f
pgagent | pga_job | postgres | | t | f | t | f
pgagent | pga_jobagent | postgres | | t | f | t | f
pgagent | pga_jobstep | postgres | | t | f | t | f
pgagent | pga_schedule | postgres | | t | f | t | f
pgagent | pga_exception | postgres | | t | f | t | f
pgagent | pga_joblog | postgres | | t | f | t | f
pgagent | pga_jobsteplog | postgres | | t | f | t | f
(8 rows)
Why do I get nothing when I use \dt+ but I can see the tables using a select statement?
I think that this will help LINK
\d [NAME] describe table, index, sequence, or view
\d{t|i|s|v|S} [PATTERN] ("+" detail) list tables/indexes/sequences/views/system tables
\da [PATTERN] list aggregate functions
\db [PATTERN] list tablespaces (add "+" for more detail)
use \dt+ *
the * to match all as the command needs a pattern
The reason is that the pgagent schema is not on your search_path. \dt will only show those tables that you can access without schema qualification.

Relation between roles and pg_terminate_backend and/or pg_cancel_backend admin functions in PostgreSQL

Consider the following example:
A superuser admin executes the following commands in a PostgreSQL database:
CREATE DATABASE admindb;
CREATE ROLE dbo WITH CREATEDB CREATEROLE;
GRANT ALL PRIVILEGES ON DATABASE admindb TO dbo WITH GRANT OPTION;
CREATE ROLE user1 WITH LOGIN PASSWORD 'user1pw';
GRANT dbo TO user1;
ALTER ROLE user1 SET ROLE dbo;
In the next step, user1 does the following:
psql -h 10.11.4.32 -d admindb -U user1
# creates a table in admindb database
CREATE TABLE test1 ( a INT, b INT);
# check pg_stat_activity
datid | datname | pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | xact_start | query_start | state_change | wait_event_type | wait_event | state | backend_xid | backend_xmin | query
17816 | admindb | 21314 | 17819 | user12 | psql | | | | | | | | | | | | 625 |<insufficient privilege>
(1 row)
Please see that query field shows insufficient privilege.
If I don't run ALTER ROLE user1 SET ROLE dbo while creating user1, the query field displays it correctly as given below.
17816 | admindb | 18386 | 17819 | user12 | psql | 192.168.0.12 | | 58794 | 2018-08-10 06:13:48.762903+00 | 2018-08-10 06:14:57.119916+00 | 2018-08-10 06:14:57.119916+00 | 2018-08-10 06:14:57.119917+00 | | | active | | 624 | select * from pg_stat_activity;
(1 row)
I would like to understand what causes this behavior.
As a consequence of this, if user1 runs a long query and tries to cancel it by connecting using psql to admindb as user1 and running select pg_cancel_backend(pid), it fails with the error.
ERROR: must be a member of the role whose query is being canceled or member of pg_signal_backend
If I remove ALTER ROLE user1 SET ROLE dbo, then user1 is able to cancel the query.
The reason is that the SET ROLE changes your active user context to dbo right after you connect, but your “session user” still is user1:
SELECT current_user, session_user;
current_user | session_user
--------------+--------------
dbo | user1
(1 row)
In pg_stat_activity you can only see the sessions whose session user equals your currently active user, and pg_cancel_backend and pg_terminate_backend allow you only to affect backends whose session user equals your current user.
You can execute RESET ROLE to revert to user1 and perform these operations.

Check if a role in PostgreSQL has a password set

I wonder how I can verify whether or not a role (users are just a specific kind of role) has a password set in PostgreSQL 9.1.
I tried the command \dg+ and \du+ but they don't show you anything password related. I also used the following query, but it doesn't help either because of its indifference (I'm quite sure that the postgresql user has no password set in this case):
SELECT * FROM pg_user;
usename | usesysid | usecreatedb | usesuper | usecatupd | userepl | passwd | valuntil | useconfig
----------+----------+-------------+----------+-----------+---------+----------+----------+-----------
postgres | 10 | t | t | t | t | ******** | |
aef | 16201 | t | t | t | t | ******** | |
Passwords are stored in pg_shadow
In documentation:
Password (possibly encrypted); null if none. See pg_authid for details
of how encrypted passwords are stored.
So you should select * from pg_shadow;
You should also check pg_authid table.

user with CREATEUSER can create database?

Team, I am using Redshift 8.0.2. I created one user as below:
create user user004 with password 'User123#' CREATEUSER valid until '2015-03-31';
redshift3=# select * from pg_user where usename = 'user004' ;
usename | usesysid | usecreatedb | usesuper | usecatupd | passwd | valuntil | useconfig
---------+----------+-------------+----------+-----------+----------+------------------------+-----------
user004 | 104 | f | t | f | ******** | 2015-03-31 00:00:00+00 |
(1 row)
I did not grant CREATEDB to this user. However, this user able to create a database.
redshift3=# create database mydb4 ;
CREATE DATABASE
redshift3=# \list
List of databases
name | owner | encoding | access privileges
----------------+---------+----------+-------------------
mydb4 | user004 | UNICODE |
In pg_user, property shows false. Please guide me. Is this default behaviour in Redshift ?
You gave the user the CREATEUSER privilege which makes the user into a superuser and therefore they can do whatever they want. See http://docs.aws.amazon.com/redshift/latest/dg/r_superusers.html for details.