How to allow certain groups only to create and edit but not delete (drop) tables in PostgreSQL? - postgresql

I'm building an app using AWS RDS PostgreSQL.
I need to allow one group to use CREATE, INSERT INTO and UPDATE statements, but at the same time not allow them to use DROP TABLE or DROP DATABASE.
Is there a way to do this on the database layer, as I would prefer not to do it on server-side layer? Thank you.

You can create one privileged user that can do all these things – let's call it creator – and a normal user luser that is used to log into the database.
creator then defines PL/pgSQL functions with SECURITY DEFINER that perform the required CREATE statements (probably using dynamic SQL with an EXECUTE statement).
Then creator runs
REVOKE EXECUTE ON FUNCTION ... FROM PUBLIC;
GRANT EXECUTE ON FUNCTION ... TO luser;
so that luser can execute the functions.
INSERT and UPDATE privileges are granted to luser inside the functions.
Documentation links:
Language PL/pgSQL to write functions in PostgreSQL
Executing dynamic statements in PL/pgSQL
CREATE FUNCTION SQL sommand
Privileges in PostgreSQL

Related

How to revoke administrator functions for specific user while create postgres user

I want to revoke the administrator or executive functions to a specific user in Postgres in user creation in java.
for example, let's have a user with read-only privileges "readonlyuser" and I don't want him to execute these functions. So I tried below to revoke, first all functions and then pg_sleep alone.
REVOKE EXECUTE ON FUNCTIONS FROM readonlyuser, public;
REVOKE EXECUTE ON FUNCTION pg_sleep FROM readonlyuser;
like below,
String sql = "CREATE USER 'readonlyuser' WITH ENCRYPTED PASSWORD 'pass';";
Statement stmt = connection.createStatement();
stmt.execute(sql);
stmt.execute("REVOKE EXECUTE ON FUNCTIONS FROM 'readonlyuser', public");
or
stmt.execute("REVOKE EXECUTE ON FUNCTION pg_sleep FROM readonlyuser;");
But still, I can able to execute functions from readonlyuser.
please, help me out to know how can I achieve this.
REVOKE EXECUTE ON FUNCTIONS is not correct SQL, so let me take pg_sleep as an example.
pg_sleep has the default permissions, since
SELECT proacl FROM pg_proc WHERE proname = 'pg_sleep';
returns NULL. The documentation has that
the default privileges granted to PUBLIC are as follows: [...] EXECUTE privilege for functions and procedures
So the privilege is granted to PUBLIC. Now you can only revoke a privilege that was granted, so the attempt to revoke the privilege from readonlyuser achieves nothing. You'd have to revoke the privilege from PUBLIC.
Be warned that you should not change the privileges of system functions. Such changes will be lost during an upgrade. Also, it may break client tools that expect to be able to use these functions. Note that functions that are considered dangerous are restricted to privileged users anyway.
If your goal is to keep users that can run SQL statements on your database from performing denial-of-service attacks, forget it. There is no way you can do that. Restricting access to certain functions is your least worry in that case. Don't let untrustworthy users run their own SQL statements on your database.

Create readonly user on all 1000 postgres databases and schema's

Using Vault I'm trying to create an on-demand temporary read-only users in a Postgres 11.8 instance.
I will have:
1000+ databases (one per customer aka tenant)
Each database has a public and reporting schema.
So I'm trying to find a way to grant this read-only user access to every database, and on all tables in both schemas.
While I came up with the following snippet:
-- Create a new user
CREATE ROLE "my-readonly-user" WITH LOGIN PASSWORD 'test123';
-- Grant access to the two schema's we have
GRANT USAGE ON SCHEMA public TO "my-readonly-user";
GRANT USAGE ON SCHEMA reporting TO "my-readonly-user";
-- Grant access to all tables in our two schema's
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "my-readonly-user";
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO "my-readonly-user";
-- Grant access to sequences
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO "my-readonly-user";
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA reporting TO "my-readonly-user";
-- Grant access to future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "my-readonly-user";
ALTER DEFAULT PRIVILEGES IN SCHEMA reporting GRANT SELECT ON TABLES TO "my-readonly-user";
It will only apply to 1 single database (the current one). If I login with the readonly user and switch to some databases, I don't see any tables except in the first database.
A cluster holds many databases, which hold many schemas. Schemas (even
with the same name) in different DBs are unrelated. Granting
privileges for a schema only applies to this particular schema in the
current DB (the current DB at the time of granting).
https://stackoverflow.com/a/24923877/1409047
Because of that limitation, it seems to make the grant snippet from above more complex. Should I iterate all my databases somehow and run the snippet? How would I do the database switch? Is it even possible in plain SQL (as required by Vault's API)? Anyone did this before?
Note: doing the same in MySQL only requires 2 lines of code, using wildcards *.* which is not supported in Postgres afaik:
CREATE USER '{{name}}'#'10.0.0.0/255.0.0.0' IDENTIFIED BY '{{password}}';
GRANT SELECT, SHOW DATABASES, SHOW VIEW ON *.* TO '{{name}}'#'10.0.0.0/255.0.0.0';
It is a deliberate design decision that an SQL statement cannot affect objects in a database different from the one that you are connected to.
Yes, you will have to iterate through all databases in your cluster and run your script there.
Note that there is a mistake in your script: you shouldn't grant a read-only user USAGE on sequences, else they can modify the sequence value. SELECT is fine.
What I would do is create a read_only_group (with NOLOGIN) and grant all these permissions to that role. Then, when there is a request for a read-only user, create a user and add it to that group, so that it inherits the group privileges. Don't grant anything to the user itself, so that you can easily DROP it when it is no longer needed.
If you want to grant readonly access to an user using only a Postgres script, you can do that:
CREATE EXTENSION IF NOT EXISTS dblink;
DO
$$
DECLARE nome_banco TEXT;
DECLARE template_conexao TEXT;
DECLARE string_conexao TEXT;
DECLARE nome_usuario TEXT;
BEGIN
template_conexao = 'user=foo password=bar dbname=';
nome_usuario = 'baz';
FOR nome_banco IN
SELECT datname FROM pg_database
WHERE datistemplate = false
LOOP
string_conexao = template_conexao || nome_banco;
perform dblink_exec(string_conexao, 'GRANT CONNECT ON DATABASE "' || nome_banco || '" TO ' || nome_usuario);
perform dblink_exec(string_conexao, 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO ' || nome_usuario);
END LOOP;
END
$$
--DROP EXTENSION IF EXISTS dblink;
It connects to each database, and executes the script; it can be easily adapted for other situations where you need to execute a database-local command on all of the databases.
Remember that, for security purposes, after executing the script above, you should drop the dblink extension created by the script above, unless, of course, you are already using the extension for other purposes.

postgres grant on functions issue

So I am having an issue that I wanted to clear up. I am granting access to a user in postgres, but I only want that user to be able to execute functions.
I have the following code:
GRANT USAGE ON SCHEMA not_public TO test_id;
GRANT USAGE ON SCHEMA public TO test_id;
GRANT EXECUTE ON FUNCTION testFunction TO web_reporting_id;
When this is ran, though, and I try to run the function under that user, I get a permissions error on the first table the function tries to read from.
I know some db languages you only need to grant permissions for the functions/procedures and not the underlying objects. In postgres, do I need to grant permissions on the tables too? Or do I need to update my grant scripts?
Thanks!
Normally, functions run with the privileges of the user calling them, so all SQL statements in them will be executed by that user.
You could define the function as SECURITY DEFINER to have it run with the privileges of the function owner, but then you must use the SET clause of CREATE FUNCTION to fix the search_path for the duration of the function execution for security reasons.
Also note that by default, everybody (PUBLIC) has execute privileges on functions, so you might want to revoke that.

PostgreSQL: insufficient privilege

I'm using postgreSQL as a database for an application that I build. When I run the application, I open dbeaver and typed the SQL query:
select * from pg_stat_activity
in order to see a list of SQL queries that has been processed during the application running.
In "query" column I got "insufficient privilege" for some queries. Do you know what I can do to see the actual SQL query?
You would have to run the query as a root-user to see all the queries. This is usually the postgres user.
On certain managed postgresql services (AWS), access to the root-user is restricted, and it is not possible to see queries executed by that user by design.
In general, the more privileges your user has, the more queries you can see in pg_stat_activity
If you are using Postgresql 10 and above, you can grant pg_read_all_stats role to your user from superuser account (postgres)
GRANT pg_read_all_stats TO username;
You do not necessarily have to run the query as a root-user. Giving someone root user access could have very bad security implications, as they could literally do anything. There is a workaround where you can create a function as root that has access to pg_stat_statements, and then grant the read-only user access to the function. For example:
CREATE OR REPLACE FUNCTION your_schema.get_stat_statements(showtext boolean = true) RETURNS SETOF pg_stat_statements AS
$$
/* pganalyze-collector */ SELECT * FROM public.pg_stat_statements(showtext) LIMIT 100;
$$ LANGUAGE sql VOLATILE SECURITY DEFINER;
GRANT USAGE on SCHEMA your_schema TO your_readonly_user;
GRANT EXECUTE on FUNCTION your_schema.get_stat_statements(boolean) TO your_readonly_user;
Thanks to https://pganalyze.com/docs/install/amazon_rds/02_create_monitoring_user
You can find an example of a similar approach here.
One possible workaround to this is defining a MATERIALIZED VIEW using a user that has enough privileges to view the data, and granting access to the view to the use that does not have the privileges.
One downside to this is that the view will have to be refreshed periodically using REFRESH MATERIALIZED VIEW, also by the owner of the view (or some other user with privileges).

Granting SELECT on all tables to a user in Firebird 2.1

I've added a user to a Firebird 2.1 instance using gsec, but now I wanted to grant SELECT on all tables to this new user. I could find how to grant this permission on specific tables, but not to them all:
GRANT SELECT ON TABLE table TO USER user;
If I try to use the new user I get the following error on isql:
no permission for read/select access to TABLE table
Is there a way to do that on Firebird 2.1?
Something like this:
EXECUTE BLOCK
AS
DECLARE VARIABLE tablename VARCHAR(32);
BEGIN
FOR SELECT rdb$relation_name
FROM rdb$relations
WHERE rdb$view_blr IS NULL
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
INTO :tablename DO
BEGIN
EXECUTE STATEMENT ('GRANT SELECT ON TABLE ' || :tablename || ' TO USER TEST');
END
END
I could find how to grant this permission on specific tables, but not to them all
You can grant specific privileges to all users, already existing and yet to be created. That is made by granting privilege to "PUBLIC" pseudo-user. Or you could grant privileges to some ROLE, then grant this ROLE to given users, then during database connection specify the option, that the user impersonate this ROLE in this session.
However there is no syntax to grant privileges to all generators or all procedures or all views or all tables, etc.
See the GRANT statement syntax in documentation.
What you can do though, is creating a simple script (anonymous and volatile aka EXECUTE BLOCK or persistent and named as STORED PROCEDURE) and that script of yours would be querying system tables to list all non-system ( COALESCE(RDB$SYSTEM_FLAG,0)=0 ) tables (together with views, or tables but not views: check if RDB$RELATION_TYPE is 0 or is 0 or 1) and then form a sequence of GRANT statements and execute them one by one.
See documentation about EXECUTE BLOCK and FOR SELECT ... INTO ... DO ... and EXECUTE STATEMENT ( or maybe FOR EXECUTE STATEMENT also, but I do not think it already was there in FB 2.1. There is unofficial wiki tracking which statements were available since which FB versions, but it is not thorough and may contain errors: http://firebirdsql.su/doku.php ).
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-security-privs.html#fblangref25-security-privs-grant
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref-appx04-relations.html
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-execstmt
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-execblock.html