Using PostgreSQL row level security (RLS) policies with current_setting() function - postgresql

I've applied RLS policy to the "users" table and expect only records with tenant_id=2 to be retrieve:
CREATE TABLE "users" ("name" text UNIQUE NOT NULL, "tenant_id" int NOT NULL DEFAULT current_setting('app.current_tenant')::int);
--Enable Row Security Policies
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE users FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON users USING (tenant_id = current_setting('app.current_tenant')::int);
--Set "111" as the current tenant.
SET app.current_tenant TO 1;
INSERT INTO users VALUES ('admin');
INSERT INTO users VALUES ('bob');
--Set "222" as the current tenant.
SET app.current_tenant TO 2;
INSERT INTO users VALUES ('alice');
--Data output
SELECT * FROM users;
But I get all users in the result:
name tenant_id
admin 1
bob 1
alice 2
Why is this happening?
Here is the dbFiddle of what I am stuck with:
https://www.db-fiddle.com/f/iFktvVsDNYKggUNT2oDJBV/0

There are four reasons why row level security can be bypassed:
The user is the owner of the table.
You can subject the table owner to row level security with
ALTER TABLE users FORCE ROW LEVEL SECURITY;
The user is a superuser.
The user was created with BYPASSRLS.
The database parameter row_security is set to off.
Note that using row level security with a placeholder parameter is inherently insecure: if an attacker can issue an SQL statement (say, through SQL injection), they can just change the value to what they like.

Related

PostgreSQL RLS policy to specific user is not working as expected and applies to all users

I have 2 users in my DB, one is "strong" and one is "weak".
I want to apply RLS policy only for one of them, the weak user.
Meaning, when strong user queries the table, it should get all rows. But when weak user queries the table, the policy will be applied and it will return only allowed rows.
I have created a table, and applied the RLS policy only to the weak user.
But even when querying with the strong user, the policy is executed and prevents me from getting all rows.
I'm using PostgreSQL version 11.4.
Here is how I created the policy (I've created the policy with another 3rd user which is an admin and the owner of the table)
CREATE TABLE account_test
(
id bigserial not null,
description varchar(200),
tenant_id UUID not null
);
ALTER TABLE account_test ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_policy ON account_test TO weak_user
USING (tenant_id = current_setting('rls.tenant_id')::uuid);
account=# select * from pg_policies;
schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
------------+--------------+---------------+------------+---------------+-----+--------------------------------------------------------------+------------
account | account_test | tenant_policy | PERMISSIVE | **{weak_user}** | ALL | (tenant_id = (current_setting('rls.tenant_id'::text))::uuid) |
Now, inserting and selecting with admin user always works, because it is the owner:
insert into account_test (description, tenant_id) VALUES ('desc111', '11111111-c929-462e-ade4-074c81643191');
select * from account_test;
no problem here and all rows returned.
When trying to login with weak_user and select, I get no rows as expected:
select * from account_test;
-- returns 0 rows as expected (weak_user).
If I set the parameter, policy applies and I get the data as expected:
select set_config('rls.tenant_id', '11111111-c929-462e-ade4-074c81643191',true);
select * from account_test;
-- returns 1 row as expected
Now, when I login with strong_user and perform the select * from account_test query, I expect all rows to be returned because policy applies only for weak_user.
However, I get the same behavior as for weak_user and no rows return.
Also the query with set_config does not return anything.
What am I missing?
Is that the expected behavior?
Can someone explain?
The moment you enable RLS on a table with ALTER TABLE account_test ENABLE ROW LEVEL SECURITY, a default-deny all policy is used for all users.
When [RLS] is enabled on a table, all normal access to the table for selecting rows or modifying rows must be allowed by a row security policy. (However, the table's owner is typically not subject to row security policies.) If no policy exists for the table, a default-deny policy is used, meaning that no rows are visible or can be modified.
– https://www.postgresql.org/docs/current/ddl-rowsecurity.html
Each policy has a name and multiple policies can be defined for a table. As policies are table-specific, each policy for a table must have a unique name. Different tables may have policies with the same name.
In order for strong_user to have access, you will need to add another rule, e.g.
CREATE POLICY tenant_policy ON account_test TO strong_user USING (true);
When multiple policies apply to a given query, they are combined using either OR (for permissive policies, which are the default) or using AND (for restrictive policies). This is similar to the rule that a given role has the privileges of all roles that they are a member of. Permissive vs. restrictive policies are discussed further below.

Postgres row level policy issue

I am trying out the Postgres row level security feature and not being able to see it working. Not sure what I am missing.
CREATE TABLE tenants (id uuid PRIMARY KEY, name TEXT);
INSERT INTO tenants (id, name) values ('ec5e9a6b-ed71-4e41-bc1e-11dac6808e41', 'Tenant1'), ('a684edc2-19b2-40d6-b679-519a6f736981', 'Tenant2');
ALTER TABLE tenants ENABLE ROW LEVEL SECURITY ;
ALTER TABLE tenants FORCE ROW LEVEL SECURITY;
SET app.tenant_id = 'ec5e9a6b-ed71-4e41-bc1e-11dac6808e41';
CREATE POLICY tenants_policy ON tenants FOR ALL USING ( current_setting('app.tenant_id')::uuid = id );
SELECT * FROM tenants;
For the last select, I expected it to return only one row with id 'ec5e9a6b-ed71-4e41-bc1e-11dac6808e41' but it is returning both rows. What am I missing? Thank you!
Your example works for me. There are a few possibilities:
The current user is a superuser.
The current user is defined with BYPASSRLS.
The configuration parameter row_security is off.

postgres "force row level security" executed, but associated policies disabled [duplicate]

I have a table, customer on which I did the following:
ALTER TABLE customer FORCE ROW LEVEL SECURITY;
CREATE POLICY customer_rls ON customer USING (false);
However, doing SELECT * FROM customer still returns all the rows.
The current role is myrole
\dg myrole
List of roles
Role name | Attributes | Member of
-----------+------------+-----------
my_role | | {}
As you can see it's not a superuser and it RLS isn't disabled on it.
What am I doing wrong?
You forgot to enable row level security for the table.
ALTER TABLE customer enable ROW LEVEL SECURITY;
force only makes sure that RLS is applied if enabled, it does not enable RLS on the table.
Online example: https://rextester.com/TCLZ82421

Row level security does not work for table owner

I have a table, customer on which I did the following:
ALTER TABLE customer FORCE ROW LEVEL SECURITY;
CREATE POLICY customer_rls ON customer USING (false);
However, doing SELECT * FROM customer still returns all the rows.
The current role is myrole
\dg myrole
List of roles
Role name | Attributes | Member of
-----------+------------+-----------
my_role | | {}
As you can see it's not a superuser and it RLS isn't disabled on it.
What am I doing wrong?
You forgot to enable row level security for the table.
ALTER TABLE customer enable ROW LEVEL SECURITY;
force only makes sure that RLS is applied if enabled, it does not enable RLS on the table.
Online example: https://rextester.com/TCLZ82421

How Do I Insert Into a Table With Row Level Security?

I have the following table:
Table "api_v1.person"
Column | Type | Modifiers
---------------+--------+-------------------------------------------------------
person_id | bigint | not null default...
name | text | not null
date_of_birth | date |
api_user | text | not null default "current_user"()
That has the following policy:
POLICY "api_user_only" FOR ALL
USING ((api_user = ("current_user"())::text))
WITH CHECK ((api_user = ("current_user"())::text))
My understanding is that the FOR ALL portion of the policy means that it covers inserts and the WITH CHECK ensures that the value inserted into api_user is the same as the current user, eg the role name. The USING clause should only effect SELECTS or other data that is returned. However, when I try to insert I get the following results:
demo=> INSERT INTO api_v1.person (name, api_user) VALUES ('Greg', current_user);
ERROR: query would be affected by row-level security policy for table "person"
How do I do this insert?
I'm running PostgreSQL 9.6.8.
Here is the SQL necessary to reproduce:
BEGIN;
CREATE SCHEMA api_v1;
CREATE TABLE api_v1.person (
person_id BIGSERIAL PRIMARY KEY,
"name" TEXT NOT NULL,
date_of_birth DATE,
api_user TEXT NOT NULL DEFAULT current_user
);
ALTER TABLE api_v1.person ENABLE ROW LEVEL SECURITY;
CREATE POLICY
api_user_only
ON
api_v1.person
USING
(api_user = CURRENT_USER)
WITH CHECK
(api_user = CURRENT_USER)
;
CREATE ROLE test_role;
GRANT USAGE ON SCHEMA api_v1 TO test_role;
GRANT ALL ON api_v1.person TO test_role;
GRANT USAGE ON SEQUENCE api_v1.person_person_id_seq TO test_role;
COMMIT;
SET ROLE test_role;
INSERT INTO api_v1.person ("name") VALUES ('Greg');
There is a setting in postgresql.conf, row_security. If this is set to off then any query that would be effected by a row level security policy fails with the error: ERROR: query would be affected by row-level security policy for table "table_name". However, queries from superusers, the table owner (if you don't force RLS), and roles with bypassrls will work.
The row_security setting needs to be set to on and then PostgreSQL needs to be restarted for regular user statements to be processed against tables with row level security policies.
From the source code:
/*
* We should apply RLS. However, the user may turn off the row_security
* GUC to get a forced error instead.
*/
if (!row_security && !noError)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("query would be affected by row-level security policy for table \"%s\"",
get_rel_name(relid)),
amowner ? errhint("To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.") : 0));