Granting SELECT on all tables to a user in Firebird 2.1 - select

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

Related

What minimum permissions requires to execute an undocumented stored procedure called SYSPROC.DB2LK_GENERATE_DDL?

I don't want to execute the stored procedure with admin privileges or with the instance owner. I am looking for the minimum permissions required to execute an undocumented stored procedure called SYSPROC.DB2LK_GENERATE_DDL for extracting the DDL statements from a DB2 database.
For example: I tried with SELECT and EXCEUTE permissions but it's not working:
db2 grant SELECT on SYSTOOLS.DB2LOOK_INFO to user devuser
db2 grant execute on procedure SYSPROC.DB2LK_GENERATE_DDL to user devuser`
You should call this routine once as some administrative user with appropriate privileges to create necessary objects implicitly in the database.
Grant the following privileges to your user afterwards.
grant usage on sequence SYSTOOLS.DB2LOOK_TOKEN to user myuser;
grant select on table SYSTOOLS.DB2LOOK_INFO_V to user myuser;
grant select, update, delete, insert on table SYSTOOLS.DB2LOOK_INFO to user myuser;

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 permission to select from information_schema tables

I'm trying to lock down the user permissions used by an application to connect to its Postgres database. The idea is that the application just needs to access data but not create or drop tables. I created a role called readwrite and assigned the role to the user. I configured the role like this:
CREATE ROLE readwrite;
GRANT CONNECT ON DATABASE corre TO readwrite;
GRANT USAGE, CREATE ON SCHEMA public TO readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE ON SEQUENCES TO readwrite;
I’ve discovered that the role breaks a specific select that's done in a trigger function. The select is:
SELECT c.column_name::text
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name)
JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema
AND tc.table_name = c.table_name AND ccu.column_name = c.column_name
WHERE constraint_type = 'PRIMARY KEY' and tc.table_name = TG_TABLE_NAME;
This is to find out the name of the PK column of the table. The select works fine for the user postgres because it’s an admin. It returns a single row (I don't have any composite PKs). If I run the select as a user with the readwrite role, it runs but returns no rows.
I think I need to grant the role some additional permission for the select to work but I have no idea which one.
Any ideas how I can get this to work as intended?
UPDATE: I originally noticed the issue on Postgres 10.6 but I've also confirmed the same behavior on 11.5
UPDATE 2: Breaking down the select above, the role can't see any rows in information_schema.constraint_column_usage. It also misses a handful of rows in the other two tables (compared to selecting as the admin user postgres) but they don't seem relevant. I tried granting REFERENCES permission but that didn't make any difference:
GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES ON ALL TABLES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES ON TABLES TO readwrite;
Just a side note about reworking default privileges. I might be wrong (someone please correct me), but I remember getting erratic results if I didn't REVOKE defaults before resetting them. GRANTS can be applied in so many places, and they interact in some (at least to me) confusing ways. So, I strip everything down to the metal, and then build it up again. It's been months since I looked at this, but I ended up having to write a script to build up all of the GRANTS statements, here's a sample:
------------------------------------------------------------------------------
-- REVOKE ALL on each schema.
------------------------------------------------------------------------------
REVOKE ALL PRIVILEGES ON SCHEMA api FROM PUBLIC; -- -- Clear out the magic PUBLIC pseudo-user.
REVOKE ALL PRIVILEGES ON SCHEMA api FROM group_admins;
REVOKE ALL PRIVILEGES ON SCHEMA api FROM group_api_users;
REVOKE ALL PRIVILEGES ON SCHEMA api FROM group_developers;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM PUBLIC;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM group_admins;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM group_api_users;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM group_developers;
------------------------------------------------------------------------------
-- GRANT USAGE on each schema and CREATE selectively.
-- Note: The api group only gets access to the api schema.
------------------------------------------------------------------------------
GRANT USAGE, CREATE ON SCHEMA api TO group_admins;
GRANT USAGE ON SCHEMA api TO group_api_users;
GRANT USAGE, CREATE ON SCHEMA api TO group_developers;
------------------------------------------------------------------------------
-- REGRANT tables/views.
------------------------------------------------------------------------------
-- REVOKE ALL on tables/views.
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM PUBLIC;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM group_admins;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM group_api_users;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA api FROM group_developers;
-- GRANT rights that can be applied to all tables/views in a schema.
GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER, TRUNCATE ON ALL TABLES IN SCHEMA api TO group_admins;
GRANT SELECT ON ALL TABLES IN SCHEMA api TO group_api_users;
GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA api TO group_developers;
-- GRANT full CRUD rights selectively by table.
-- Note: group_admins and group_developers are granted full CRUD rights on all tables above.
-- Snip
------------------------------------------------------------------------------
-- REGRANT DEFAULT privileges
------------------------------------------------------------------------------
-- Clear any existing table defaults from each schema.
ALTER DEFAULT PRIVILEGES IN SCHEMA api REVOKE ALL PRIVILEGES ON TABLES FROM PUBLIC;
ALTER DEFAULT PRIVILEGES IN SCHEMA api REVOKE ALL PRIVILEGES ON TABLES FROM group_admins;
ALTER DEFAULT PRIVILEGES IN SCHEMA api REVOKE ALL PRIVILEGES ON TABLES FROM group_api_users;
ALTER DEFAULT PRIVILEGES IN SCHEMA api REVOKE ALL PRIVILEGES ON TABLES FROM group_developers;
-- ALTER DEFAULT PRIVILEGES that can be applied to all tables/views in a schema
ALTER DEFAULT PRIVILEGES IN SCHEMA api GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER, TRUNCATE ON TABLES TO group_admins;
ALTER DEFAULT PRIVILEGES IN SCHEMA api GRANT SELECT ON TABLES TO group_api_users;
ALTER DEFAULT PRIVILEGES IN SCHEMA api GRANT SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRIGGER ON TABLES TO group_developers;
After that, I tend to double-check table and view rights. Here's a function I adapted from code I found to summarize table grants:
CREATE OR REPLACE FUNCTION data.show_table_rights(t_name text)
RETURNS TABLE("Table_Name" name, "User_Name" name, "SELECT" text, "INSERT" text, "UPDATE" text, "DELETE" text, "TRUNCATE" text, "REFERENCES" text, "TRIGGER" text)
LANGUAGE sql
STABLE
AS $function$
SELECT
t.tablename,
u.usename,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'select') = TRUE then 'X' ELSE ' ' END AS select,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'insert')= TRUE then 'X' ELSE ' ' END AS insert,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'update') = TRUE then 'X' ELSE ' ' END AS update,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'delete') = TRUE then 'X' ELSE ' ' END AS delete,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'truncate') = TRUE then 'X' ELSE ' ' END AS truncate,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'references') = TRUE then 'X' ELSE ' ' END AS references,
CASE WHEN has_table_privilege(u.usename, concat(schemaname, '.', t.tablename), 'trigger') = TRUE then 'X' ELSE ' ' END AS trigger
FROM pg_tables t,
pg_user u
WHERE t.tablename = t_name
ORDER BY u.usename;
$function$
I don't love that function...but I don't hate it enough that I ever get around to rewriting it. (The bits I hate are my fault, not whoever I adapted it from.) If I were to rewrite it, I'd get rid of the uppercase column titles and make the input a regclass. Live and learn. Anyway, to call it:
select * from show_table_rights('item');
That spits out a cross-tab with rolls down the left and rights as columns. I've got one for views too. The difference there is that you're joining against and use pg_views instead of pg_tables. I see that I've got versions for schema and database rights, but rarely ever use those.
Another side note since GRANTs and DEFAULT grants are coming up. I got rid of the public schema pretty early in the piece. I found the default behaviors there...confusing. Plus, thinking about a space where multiple users can share in that way feels really easy to screw up. Later, a privilege escalation CVE that hinged on PUBLIC popped up...so I was glad to have gotten rid of the public schema.
I love Postgres, but don't really grok the permissions system fully. Grants can be set at the database, schema, table, and more...and then on the user (roll) who may inherit from other rolls. With the object hierarchy, the grants are cumulatively restrictive. So, you can be granted SELECT on a table, but that's meaningless unless you have USAGE on the database and access to the schema. On the other hand, inherited roll privileges are additive. So, rights are a restrictive funnel on the database-schema-table side, and an expansive (more permissive) system on the rolls side. If I've got that right, it's a fundamentally confusing design. Hence my code to spit out ~1,000 GRANTs to rebuild everything. And I've got rolls for users and groups (rolls with no log on) and try to put all of the rights into the groups.
Chances are, I'm missing something obvious. My setup has the distinct smell of something where you keep pouring code on top until it stops moving because you don't understand the system well enough to do the simple thing. Granted ;-) I'll circle back to it eventually. Postgres is huge, I'm constantly studying as tasks come to hand, but there are only so many days in the week.
The best piece I remember running into on GRANTs in Postgres is this one:
https://illuminatedcomputing.com/posts/2017/03/postgres-permissions/
Too long an answer for comments...
I've never used row- or column-level security features.There are some incredibly knowledgeable people here who seem to monitor the questions 24/7. I'd be curious if anyone else could comment.
I did look into row-level security for a multi-tenant setup. I remember coming to the conclusion that it is complicated. I figured that I'd use views as they're simple to review, edit, and understand. And you can find them in any SQL database. The row level security features I found...more complicated to understand, more hidden under the hood, and a bit Postgres-specific. With that said it's a super cool idea. As I understand it, you're bolting a policy (filter) onto a base table, and then that rule flows through to any view, etc. automatically. You can't subvert it if you try:
Here are some articles I found useful when looking into this:
https://www.2ndquadrant.com/en/blog/application-users-vs-row-level-security/
https://www.citusdata.com/blog/2018/04/04/raw-sql-access-with-row-level-security/
https://medium.com/#cazzer/practical-application-of-row-level-security-b33be18fd198
https://info.crunchydata.com/blog/a-postgresql-row-level-security-primer-creating-large-policies
Here's a simple policy to limit users to viewing scans by their department:
create policy filter_scan
on data.scan
using (department_id = user_get_department_id(current_user));
The content of the policy above is the USING clause. It' a WHERE clause, so far as I can see. You can also add an optional CHECK clause on some operations.
Nearly every example I found assumes you're filtering by current_user() and that there is a column to match. The RLS system is based on roles, so this makes sense. And while it makes sense to use the role name, it's not realistic to assume you'll have such a matching column in every table. Or, for that matter, that such a column would even make sense. Examples commonly use chat systems, etc. where the current user is a care part of the data. In my organization's case, that rarely makes sense. (We're tracking physical objects, users are just slow, error-prone peripherals.) Hence the fake stored function call to user_get_department_id. The idea here is that you've got some utility tables to map roles to specific IDs or other attributes for specific tables. Then a function such as user_get_department_id or perhaps user_get_visible_department_ids queries the utility table for the user's allowed ID or IDs and returns them as a list. Slick!
But, like I said, I only ever tested this out on scratch tables and threw it all away. Views seem good enough for the small number of tables, etc. we're dealing with. For folks with multi-tenant setups with 10,000 clients, life would be different. (The Citus folks suggest splitting the tables physically into different databases, as they would.)

Have access to table and function, but not the table within the function

I ran into a peculiar problem with my database that I haven't been able to solve since yesterday.
So I created a user that has access to the function "write_match_history()" and access to the table "match_history," and I can use this user to query match history as well as write to it directly using sql. However, when I try to run "write_match_history," I get the following error:
error: error: permission denied for relation match_history
Here are the accesses I've granted to this user:
drop OWNED by d_write;
drop user if exists d_write;
create user d_write with encrypted password 'supersecret';
grant execute on function write_match_history(a,b,c,d,e,f) to d_write;
grant usage on schema d to d_write;
grant insert on table d.match_history to d_write;
grant select on table d.match_history to d_write;
grant select on all SEQUENCES in SCHEME d to d_write;
grant insert on all tables in schema d to d_write;
grant select on all talbes in schema d to d_write;
These permissions are everything I've tried so far. Let me know if you need more information.
Thanks!
Well, what is the owner of the function write_match_history(a,b,c,d,e,f). Do it allow to insert/update on table match_history. If not, please try it. I think the problem is here.

Postgresql ignores lack of EXECUTE permission if USAGE permission is granted on containing schema

From http://www.postgresql.org/docs/9.1/static/sql-grant.html:
USAGE
...
For schemas, allows access to objects contained in the specified schema (assuming that the objects' own privilege requirements are also met). Essentially this allows the grantee to "look up" objects within the schema. Without this permission, it is still possible to see the object names, e.g. by querying the system tables. ...
...
However, running the following script through psql seems to show that although the lack of a SELECT permission causes an error, lack on an EXECUTE permission does not, which contradicts the documentation as the 'own privilege requirement' is not met.
CREATE DATABASE testdb WITH OWNER postgres ENCODING 'UTF8';
\connect testdb
CREATE ROLE testrole;
CREATE SCHEMA testschema;
GRANT USAGE ON SCHEMA testschema TO testrole;
SET search_path TO testschema;
CREATE FUNCTION testfunc ()
RETURNS VOID
AS $$
BEGIN
RAISE NOTICE 'IN TESTFUNC';
RAISE NOTICE 'Current user: %', current_user;
END;
$$
LANGUAGE plpgsql;
CREATE TABLE testtable
(
testrow INT
);
INSERT INTO testtable (testrow) VALUES (1), (2), (3);
SET ROLE testrole;
SELECT testfunc();
SELECT * FROM testtable;
RESET ROLE;
Output:
$ psql -f usage.sql
CREATE DATABASE
You are now connected to database "testdb" as user "postgres".
CREATE ROLE
CREATE SCHEMA
GRANT
SET
CREATE FUNCTION
CREATE TABLE
INSERT 0 3
SET
psql:usage.sql:27: NOTICE: IN TESTFUNC
psql:usage.sql:27: NOTICE: Current user: testrole
testfunc
----------
(1 row)
psql:usage.sql:28: ERROR: permission denied for relation testtable
RESET
Have I missed something or am using the permissions incorrectly?
The schema is not what matters here, what you're seeing is the default execute permissions of functions.
Consider this excerpt from the CREATE FUNCTION documentation:
Another point to keep in mind is that by default, execute privilege is
granted to PUBLIC for newly created functions (see GRANT for more
information). Frequently you will wish to restrict use of a security
definer function to only some users. To do that, you must revoke the
default PUBLIC privileges and then grant execute privilege
selectively.