When i create role Dev and then 2 users inherit all priveleges of Dev. But actually, when first user create test table, the other one can't do any query to this table. How can i reach my purpose?
create role Dev;
grant all privileges on schema public to Dev;
ALTER DEFAULT PRIVILEGES
IN SCHEMA public GRANT all privileges ON TABLES TO Dev;
create user User1;
grant Dev to User1;
create user User2;
grant Dev to User2;
set role user1;
drop table if exists public.test;
create table public.test (
q int
);
set role user2;
select * from public.test; -- ERROR: permission denied for table test
set role Dev;
select * from public.test; -- ERROR: permission denied for table test (even if Dev!!!)
I created a role R_CLIENTE:
CREATE ROLE R_CLIENTE IDENTIFIED BY RolCliente;
Then I granted some privileges on it:
GRANT SELECT ON alquiler.CLIENTE TO R_CLIENTE;
(Schema alquiler and table CLIENTE already exist). Then I created an user U_Cliente1:
CREATE USER U_Cliente1 IDENTIFIED BY Cliente1 DEFAULT TABLESPACE table_def TEMPORARY TABLESPACE table_temp QUOTA 2M ON table_def PASSWORD EXPIRE;
(Both tablespaces already exist). I granted U_Cliente1 to R_CLIENTE privileges:
GRANT R_CLIENTE TO U_Cliente1;
When I login as U_Cliente1 I am not able to select any data from the table alquiler.CLIENTE:
SQL> desc alquiler.CLIENTE;
ERROR:
ORA-04043: object alquiler.CLIENTE does not exist
However, if I grant directly the privilege to the user U_Cliente1:
GRANT SELECT ON alquiler.CLIENTE TO U_Cliente1;
Now I am able to select the table alquiler.CLIENTE:
SQL> desc alquiler.CLIENTE;
Name Null? Type
----------------------------------------- -------- ----------------------------
K_CODCLIENTE NOT NULL NUMBER(5)
N_NOMBRE1 NOT NULL VARCHAR2(15)
N_NOMBRE2 VARCHAR2(15)
N_APELLIDO1 NOT NULL VARCHAR2(15)
N_APELLIDO2 VARCHAR2(15)
N_DIRECCION NOT NULL VARCHAR2(50)
Q_TELEFONO NOT NULL NUMBER(10)
K_CODREF NUMBER(5)
I_TIPOID NOT NULL VARCHAR2(2)
Q_IDENTIFICACION NOT NULL VARCHAR2(10)
How can I properly link a role and user(s) so they can all share the same privileges?
The reason that the direct grant worked the way it did is because direct grants are always active. Roles can be activated and deactivated within a session. In your example you didn't configure the role as a default role, so it must be explicitly activated after you login, like this:
set role r_cliente;
alternatively, after granting the role set it as a default for the user:
alter user U_Cliente1 default role r_cliente;
or
alter user U_Cliente1 default role all;
Then the role will be active automatically when you login.
I've trying to apply RLS on my PostgreSQL 14. But it seems didn't work. Here it's my SQL (i run it on PgAdmin4):
CREATE TABLE IF NOT EXISTS public.employee
(
tenant_id character varying(255) NOT NULL,
username character varying(255) NOT NULL,
CONSTRAINT employee_pkey PRIMARY KEY (username)
)
ALTER TABLE IF EXISTS employee
OWNER to postgres;
ALTER TABLE IF EXISTS employee
ENABLE ROW LEVEL SECURITY;
CREATE POLICY employee_tenant_isolation_policy
ON employee
AS PERMISSIVE
FOR ALL
TO public
USING (((tenant_id)::text = ((current_setting('app.tenant_id'::text))::character varying)::text));
i'm also already have to insert the data:
INSERT INTO public.employee(
tenant_id, username)
VALUES ('tenant1', 'tenant1');
INSERT INTO public.employee(
tenant_id, username)
VALUES ('tenant2', 'tenant2');
As you can see, i have enabled the RLS and created the policy. But, when i try this query:
SET app.tenant_id to 'tenant1';
SELECT * FROM employee;
I got two values (tenant1, and tenant2). I expect it to only return row with 'tenant1' values.
Any ideas?
Thanks.
As per the documentation, superusers bypass RLS:
Superusers and roles with the BYPASSRLS attribute always bypass the row security system when accessing a table. Table owners normally bypass row security as well, though a table owner can choose to be subject to row security with ALTER TABLE ... FORCE ROW LEVEL SECURITY.
If you create a non-superuser, you should be able to see the filtering:
edb=# create user foobar with login;
CREATE ROLE
edb=# grant select on employee to foobar;
GRANT
edb=# \c edb foobar;
You are now connected to database "edb" as user "foobar".
edb=> SET app.tenant_id to 'tenant1';
SET
edb=> select * from employee ;
tenant_id | username
-----------+----------
tenant1 | tenant1
(1 row)
Background
I'm working these days on organizing the Postgres database of a project in my work.
EDIT:
This database is connected to a NodeJS server that runs Postgraphile on it, in order to expose the GraphQL interface for the client. Therefore, I have to use RLS in order to forbid the user to query and manipulate rows that he/she doesn't have permission.
One of the tasks that I've got is to add a deleted field for each table, and using RLS to hide the records that deleted = true.
Code Example
To explain my problem, I'll add an SQL code for building a fake database:
Roles
For this example, I'll use these roles:
Superuser role named admin
Role called app_users
2 Users inherit from app_users:
bob
alice
CREATE ROLE admin WITH
LOGIN
SUPERUSER
INHERIT
CREATEDB
CREATEROLE
NOREPLICATION
ENCRYPTED PASSWORD 'md5f6fdffe48c908deb0f4c3bd36c032e72'; -- password: admin
GRANT username TO admin;
CREATE ROLE app_users WITH
NOLOGIN
NOSUPERUSER
NOINHERIT
NOCREATEDB
CREATEROLE
NOREPLICATION;
CREATE ROLE bob WITH
LOGIN
NOSUPERUSER
INHERIT
NOCREATEDB
NOCREATEROLE
NOREPLICATION
ENCRYPTED PASSWORD 'md5e8557d12f6551b2ddd26bbdd0395465c';
GRANT app_users TO bob;
CREATE ROLE alice WITH
LOGIN
NOSUPERUSER
INHERIT
NOCREATEDB
NOCREATEROLE
NOREPLICATION
ENCRYPTED PASSWORD 'md5579e43b423b454623383471aeb85cd87';
GRANT app_users TO alice;
Database
This example will hold a database named league for a mock database for an American football league.
CREATE DATABASE league
WITH
OWNER = admin
ENCODING = 'UTF8'
LC_COLLATE = 'en_US.utf8'
LC_CTYPE = 'en_US.utf8'
TABLESPACE = pg_default
CONNECTION LIMIT = -1;
GRANT CREATE, CONNECT ON DATABASE league TO admin;
GRANT TEMPORARY ON DATABASE league TO admin WITH GRANT OPTION;
GRANT TEMPORARY, CONNECT ON DATABASE league TO PUBLIC;
Scheme: public
I've added some minor changes in the scheme, so in default, role app_users allow any command, type, execute function, etcetera.
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO app_users;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO app_users;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT EXECUTE ON FUNCTIONS TO app_users;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE ON TYPES TO app_users;
Creating Tables
Table: TEAMS
CREATE TABLE IF NOT EXISTS public."TEAMS"
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
deleted boolean NOT NULL DEFAULT false,
name text COLLATE pg_catalog."default" NOT NULL,
owner text COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT "TEAMS_pkey" PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE public."TEAMS"
OWNER to admin;
ALTER TABLE public."TEAMS"
ENABLE ROW LEVEL SECURITY;
GRANT ALL ON TABLE public."TEAMS" TO admin;
GRANT ALL ON TABLE public."TEAMS" TO app_users;
CREATE POLICY teams_deleted
ON public."TEAMS"
AS RESTRICTIVE
FOR SELECT
TO app_users
USING (deleted = false);
CREATE POLICY teams_owner
ON public."TEAMS"
AS PERMISSIVE
FOR ALL
TO app_users
USING (owner = CURRENT_USER);
Table: PLAYERS
CREATE TABLE IF NOT EXISTS public."PLAYERS"
(
id text COLLATE pg_catalog."default" NOT NULL,
deleted boolean NOT NULL DEFAULT false,
first_name text COLLATE pg_catalog."default" NOT NULL,
last_name text COLLATE pg_catalog."default" NOT NULL,
team_id integer NOT NULL,
jersey_number integer NOT NULL,
CONSTRAINT "PLAYERS_pkey" PRIMARY KEY (id),
CONSTRAINT fkey_team_id FOREIGN KEY (team_id)
REFERENCES public."TEAMS" (id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT check_player_number CHECK (jersey_number > 0 AND jersey_number < 100)
)
TABLESPACE pg_default;
ALTER TABLE public."PLAYERS"
OWNER to admin;
ALTER TABLE public."PLAYERS"
ENABLE ROW LEVEL SECURITY;
GRANT ALL ON TABLE public."PLAYERS" TO admin;
GRANT ALL ON TABLE public."PLAYERS" TO app_users;
CREATE POLICY players_deleted
ON public."PLAYERS"
AS RESTRICTIVE
FOR SELECT
TO app_users
USING (deleted = false);
CREATE POLICY players_owner
ON public."PLAYERS"
AS PERMISSIVE
FOR ALL
TO app_users
USING ((( SELECT "TEAMS".owner
FROM "TEAMS"
WHERE ("TEAMS".id = "PLAYERS".team_id)) = CURRENT_USER));
Test Case (Edited for better understanding)
Run this code using user bob:
INSERT INTO "TEAMS" (name, owner)
VALUES ('Jerusalem Lions', 'bob')
RETURNING id; -- We'll save this id for the next command
INSERT INTO "PLAYERS" (id, first_name, last_name, jersey_number, role, team_id)
VALUES ('99999', 'Eric', 'Cohen', 29, 'linebacker', 888) -- Replace 888 with the returned id from the previous command
RETURNING *;
-- These commands will work
SELECT * FROM "PLAYERS";
UPDATE "PLAYERS"
SET last_name = 'Levi'
WHERE id = '99999'
RETURNING *;
-- This is the command that won't work. I can't change the deleted.
UPDATE "PLAYERS"
SET deleted = true
WHERE id = '99999'
RETURNING *;
EDIT: Now, it's important to understand that The policies as defined above works when I do any query, as long as:
INSERT INTO doesn't include deleted = true (that's ok).
UPDATE that includes SET deleted = true. (This is the main issue).
I want to:
Allow bob to delete a record using deleted = true on an UPDATE command.
Hide in SELECT commands all records that deleted = true.
What should I do? 🤷♂️
From the documentation:
When a mix of permissive and restrictive policies are present, a record is only accessible if at least one of the permissive policies passes, in addition to all the restrictive policies.
It means that you cannot permit in one policy something restricted in another policy. For this reason, restrictive policies should be used with extreme caution. When there is no policy defined, everything is restricted, so you should focus on permitting what should be allowed.
Simplified example:
create table my_table(
id int primary key,
user_name text,
deleted bool);
alter table my_table enable row level security;
create policy rule_for_select
on my_table
as permissive
for select
to app_users
using (not deleted);
create policy rule_for_all
on my_table
as permissive
for all
to app_users
using (user_name = current_user and not deleted);
insert into my_table(id, user_name, deleted) values
(1, 'alice', false),
(2, 'bob', true),
(3, 'celine', true)
The user bob will see row 1. He would be able to update row 2 if deleted is false.
What I can gather is, that
UPDATE "PLAYERS" SET deleted=true
will not fail for "Bob". But:
UPDATE "PLAYERS" SET deleted=true RETURNING *
will. Postgraphile will usually "return" something when doing mutations. After updating the row - you basically forbid the select statement.
The usual hack (feature) in Postgraphile land is usually creating a custom mutation and define it as security definer.
Maybe there are other ways around this - but this is the hammer->nail approach in Postgraphile, in my experience.
Similar problem can be seen here: Unable to update row of table with Row Level Security where I proposed a workaround by implementing a grace period.
This is my init.sql file :
DROP DATABASE IF EXISTS my_data;
CREATE DATABASE my_data;
DROP USER IF EXISTS u;
CREATE USER u WITH PASSWORD 'secret';
GRANT ALL ON ALL TABLES IN SCHEMA public TO u;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO u;
GRANT ALL ON ALL FUNCTIONS IN SCHEMA public TO u;
\c my_data;
CREATE TABLE users (
ID SERIAL PRIMARY KEY NOT NULL,
email VARCHAR NOT NULL DEFAULT '',
password VARCHAR NOT NULL DEFAULT '',
active SMALLINT NOT NULL DEFAULT '1',
created TIMESTAMP NOT NULL,
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
salt VARCHAR NOT NULL DEFAULT ''
);
Then if I :
psql -f init.sql
And..
psql -d my_data -U u
my_data=> select * from users;
ERROR: permission denied for relation users
Why would this permission be denied if I just granted it?
More info
my_data=> \z users
Access privileges
Schema | Name | Type | Access privileges | Column access privileges
--------+-------+-------+-------------------+--------------------------
public | users | table | |
(1 row)
You only gave permission on the schema, which is separate from the tables. If you want to give permissions on the tables also you can use
GRANT ALL ON ALL TABLES IN SCHEMA public TO u;
Note that if you have sequences or other objects they also need separate permissions.
This has to be set after the tables have been created since permissions are set for existing objects.
If you want to set default permissions for new objects you can use
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO u;