Thread: [GENERAL] Recursive row level security policy

[GENERAL] Recursive row level security policy

From
Simon Charette
Date:
Hello there,

I'm not sure I'm posting to the appropriate mailing list so don't hesitate to
redirect me to the appropriate one.

I've been trying to setup a policy that allows "accounts" table rows to only be
seen by their owner by using the current_user to compare them by name.

Unfortunately it looks like I'm either missing something or there's a limitation
in the current row level security implementation that prevents me from doing
this.

Here's the actual SQL to reproduce the issue:

CREATE TABLE "accounts" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(50) NOT NULL UNIQUE,
    "owner_id" integer NOT NULL
);

INSERT INTO accounts(id, name, owner_id)
    VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);

GRANT SELECT ON accounts TO PUBLIC;

ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;

CREATE POLICY account_ownership ON accounts FOR SELECT
    USING (owner_id = (SELECT id FROM accounts WHERE name = current_user));

CREATE ROLE foo;
SET ROLE foo;
SELECT * FROM accounts;
-- ERROR:  infinite recursion detected in policy for relation "accounts"

Is there any way to alter the "account_ownership" policy's USING clause to avoid
this infinite recursion or a way to model my schema to prevent this from
happening?

Thank you for your time,
Simon


Re: [GENERAL] Recursive row level security policy

From
"Charles Clavadetscher"
Date:
Hello

> -----Original Message-----
> From: pgsql-general-owner@postgresql.org [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Simon Charette
> Sent: Freitag, 16. Dezember 2016 06:15
> To: pgsql-general@postgresql.org
> Subject: [GENERAL] Recursive row level security policy
>
> Hello there,
>
> I'm not sure I'm posting to the appropriate mailing list so don't hesitate to redirect me to the appropriate one.
>
> I've been trying to setup a policy that allows "accounts" table rows to only be seen by their owner by using the
> current_user to compare them by name.
>
> Unfortunately it looks like I'm either missing something or there's a limitation in the current row level security
> implementation that prevents me from doing this.
>
> Here's the actual SQL to reproduce the issue:
>
> CREATE TABLE "accounts" (
>     "id" integer NOT NULL PRIMARY KEY,
>     "name" varchar(50) NOT NULL UNIQUE,
>     "owner_id" integer NOT NULL
> );
>
> INSERT INTO accounts(id, name, owner_id)
>     VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);
>
> GRANT SELECT ON accounts TO PUBLIC;
>
> ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
>
> CREATE POLICY account_ownership ON accounts FOR SELECT
>     USING (owner_id = (SELECT id FROM accounts WHERE name = current_user));

I think that should be:

CREATE POLICY account_ownership ON accounts FOR SELECT
    USING (name = current_user);

Regards
Charles

>
> CREATE ROLE foo;
> SET ROLE foo;
> SELECT * FROM accounts;
> -- ERROR:  infinite recursion detected in policy for relation "accounts"
>
> Is there any way to alter the "account_ownership" policy's USING clause to avoid this infinite recursion or a way to
> model my schema to prevent this from happening?
>
> Thank you for your time,
> Simon
>
>
> --
> Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-general



Re: [GENERAL] Recursive row level security policy

From
Simon Charette
Date:
Hello Charles,

Unfortunately this will only return accounts matching the current_user's name.

I would expect "SET ROLE foo; SELECT name FROM accounts" to return "foo" and
"bar" and not only "foo" like your proposed solution would do.

Simon

2016-12-16 0:57 GMT-05:00 Charles Clavadetscher <clavadetscher@swisspug.org>:
> Hello
>
>> -----Original Message-----
>> From: pgsql-general-owner@postgresql.org [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Simon Charette
>> Sent: Freitag, 16. Dezember 2016 06:15
>> To: pgsql-general@postgresql.org
>> Subject: [GENERAL] Recursive row level security policy
>>
>> Hello there,
>>
>> I'm not sure I'm posting to the appropriate mailing list so don't hesitate to redirect me to the appropriate one.
>>
>> I've been trying to setup a policy that allows "accounts" table rows to only be seen by their owner by using the
>> current_user to compare them by name.
>>
>> Unfortunately it looks like I'm either missing something or there's a limitation in the current row level security
>> implementation that prevents me from doing this.
>>
>> Here's the actual SQL to reproduce the issue:
>>
>> CREATE TABLE "accounts" (
>>     "id" integer NOT NULL PRIMARY KEY,
>>     "name" varchar(50) NOT NULL UNIQUE,
>>     "owner_id" integer NOT NULL
>> );
>>
>> INSERT INTO accounts(id, name, owner_id)
>>     VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);
>>
>> GRANT SELECT ON accounts TO PUBLIC;
>>
>> ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
>>
>> CREATE POLICY account_ownership ON accounts FOR SELECT
>>     USING (owner_id = (SELECT id FROM accounts WHERE name = current_user));
>
> I think that should be:
>
> CREATE POLICY account_ownership ON accounts FOR SELECT
>     USING (name = current_user);
>
> Regards
> Charles
>
>>
>> CREATE ROLE foo;
>> SET ROLE foo;
>> SELECT * FROM accounts;
>> -- ERROR:  infinite recursion detected in policy for relation "accounts"
>>
>> Is there any way to alter the "account_ownership" policy's USING clause to avoid this infinite recursion or a way to
>> model my schema to prevent this from happening?
>>
>> Thank you for your time,
>> Simon
>>
>>
>> --
>> Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
>> http://www.postgresql.org/mailpref/pgsql-general
>


Re: [GENERAL] Recursive row level security policy

From
"Charles Clavadetscher"
Date:
Hello Simon

> -----Original Message-----
> From: pgsql-general-owner@postgresql.org [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Simon Charette
> Sent: Freitag, 16. Dezember 2016 07:02
> To: Charles Clavadetscher <clavadetscher@swisspug.org>
> Cc: pgsql-general@postgresql.org
> Subject: Re: [GENERAL] Recursive row level security policy
>
> Hello Charles,
>
> Unfortunately this will only return accounts matching the current_user's name.
>
> I would expect "SET ROLE foo; SELECT name FROM accounts" to return "foo" and "bar" and not only "foo" like your
> proposed solution would do.

True. I did oversee the real target.

The problem is that the policy for select on the table will be cheked each time a select is performed. So having a
selectin the using condition will check the policy again, and so on. 

I am not sure how to solve this with policies and I assume that somebody else may come up with an idea. One thing I can
thinkof is to check the condition in a security definer function where you temporarily disable row level security. But
thisis quite a nasty thing to do... 

A workaround would be the "old way" using views:

CREATE VIEW public.v_accounts AS
  SELECT * FROM accounts
  WHERE owner_id = (SELECT owner_id FROM accounts WHERE name = CURRENT_USER);

REVOKE SELECT ON accounts FROM public;
GRANT SELECT ON v_accounts TO public;

charles@charles=# set role foo;
SET

charles@charles=> SELECT * FROM accounts;
ERROR:  permission denied for relation accounts

charles@charles=> select * from v_accounts ;
 id | name | owner_id
----+------+----------
  1 | foo  |        1
  2 | bar  |        1
(2 rows)

Instead of granting select on the table you only grant it on the view.

Hope this helps.
Bye
Charles

>
> Simon
>
> 2016-12-16 0:57 GMT-05:00 Charles Clavadetscher <clavadetscher@swisspug.org>:
> > Hello
> >
> >> -----Original Message-----
> >> From: pgsql-general-owner@postgresql.org
> >> [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Simon
> >> Charette
> >> Sent: Freitag, 16. Dezember 2016 06:15
> >> To: pgsql-general@postgresql.org
> >> Subject: [GENERAL] Recursive row level security policy
> >>
> >> Hello there,
> >>
> >> I'm not sure I'm posting to the appropriate mailing list so don't hesitate to redirect me to the appropriate one.
> >>
> >> I've been trying to setup a policy that allows "accounts" table rows
> >> to only be seen by their owner by using the current_user to compare them by name.
> >>
> >> Unfortunately it looks like I'm either missing something or there's a
> >> limitation in the current row level security implementation that prevents me from doing this.
> >>
> >> Here's the actual SQL to reproduce the issue:
> >>
> >> CREATE TABLE "accounts" (
> >>     "id" integer NOT NULL PRIMARY KEY,
> >>     "name" varchar(50) NOT NULL UNIQUE,
> >>     "owner_id" integer NOT NULL
> >> );
> >>
> >> INSERT INTO accounts(id, name, owner_id)
> >>     VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);
> >>
> >> GRANT SELECT ON accounts TO PUBLIC;
> >>
> >> ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
> >>
> >> CREATE POLICY account_ownership ON accounts FOR SELECT
> >>     USING (owner_id = (SELECT id FROM accounts WHERE name =
> >> current_user));
> >
> > I think that should be:
> >
> > CREATE POLICY account_ownership ON accounts FOR SELECT
> >     USING (name = current_user);
> >
> > Regards
> > Charles
> >
> >>
> >> CREATE ROLE foo;
> >> SET ROLE foo;
> >> SELECT * FROM accounts;
> >> -- ERROR:  infinite recursion detected in policy for relation "accounts"
> >>
> >> Is there any way to alter the "account_ownership" policy's USING
> >> clause to avoid this infinite recursion or a way to model my schema to prevent this from happening?
> >>
> >> Thank you for your time,
> >> Simon
> >>
> >>
> >> --
> >> Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
> >> http://www.postgresql.org/mailpref/pgsql-general
> >
>
>
> --
> Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-general



Re: [GENERAL] Recursive row level security policy

From
"Charles Clavadetscher"
Date:
Hello again

> -----Original Message-----
> From: pgsql-general-owner@postgresql.org [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Charles
> Clavadetscher
> Sent: Freitag, 16. Dezember 2016 07:41
> To: 'Simon Charette' <charette.s@gmail.com>
> Cc: pgsql-general@postgresql.org
> Subject: Re: [GENERAL] Recursive row level security policy
>
> Hello Simon
>
> > -----Original Message-----
> > From: pgsql-general-owner@postgresql.org
> > [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Simon
> > Charette
> > Sent: Freitag, 16. Dezember 2016 07:02
> > To: Charles Clavadetscher <clavadetscher@swisspug.org>
> > Cc: pgsql-general@postgresql.org
> > Subject: Re: [GENERAL] Recursive row level security policy
> >
> > Hello Charles,
> >
> > Unfortunately this will only return accounts matching the current_user's name.
> >
> > I would expect "SET ROLE foo; SELECT name FROM accounts" to return
> > "foo" and "bar" and not only "foo" like your proposed solution would do.
>
> True. I did oversee the real target.
>
> The problem is that the policy for select on the table will be cheked each time a select is performed. So having a
> select in the using condition will check the policy again, and so on.
>
> I am not sure how to solve this with policies and I assume that somebody else may come up with an idea. One thing I
> can think of is to check the condition in a security definer function where you temporarily disable row level
> security. But this is quite a nasty thing to do...

Forget this. It would not work anyway.

> A workaround would be the "old way" using views:
>
> CREATE VIEW public.v_accounts AS
>   SELECT * FROM accounts
>   WHERE owner_id = (SELECT owner_id FROM accounts WHERE name = CURRENT_USER);
>
> REVOKE SELECT ON accounts FROM public;
> GRANT SELECT ON v_accounts TO public;
>
> charles@charles=# set role foo;
> SET
>
> charles@charles=> SELECT * FROM accounts;
> ERROR:  permission denied for relation accounts
>
> charles@charles=> select * from v_accounts ;  id | name | owner_id
> ----+------+----------
>   1 | foo  |        1
>   2 | bar  |        1
> (2 rows)
>
> Instead of granting select on the table you only grant it on the view.
>
> Hope this helps.
> Bye
> Charles
>
> >
> > Simon
> >
> > 2016-12-16 0:57 GMT-05:00 Charles Clavadetscher <clavadetscher@swisspug.org>:
> > > Hello
> > >
> > >> -----Original Message-----
> > >> From: pgsql-general-owner@postgresql.org
> > >> [mailto:pgsql-general-owner@postgresql.org] On Behalf Of Simon
> > >> Charette
> > >> Sent: Freitag, 16. Dezember 2016 06:15
> > >> To: pgsql-general@postgresql.org
> > >> Subject: [GENERAL] Recursive row level security policy
> > >>
> > >> Hello there,
> > >>
> > >> I'm not sure I'm posting to the appropriate mailing list so don't hesitate to redirect me to the appropriate
> one.
> > >>
> > >> I've been trying to setup a policy that allows "accounts" table
> > >> rows to only be seen by their owner by using the current_user to compare them by name.
> > >>
> > >> Unfortunately it looks like I'm either missing something or there's
> > >> a limitation in the current row level security implementation that prevents me from doing this.
> > >>
> > >> Here's the actual SQL to reproduce the issue:
> > >>
> > >> CREATE TABLE "accounts" (
> > >>     "id" integer NOT NULL PRIMARY KEY,
> > >>     "name" varchar(50) NOT NULL UNIQUE,
> > >>     "owner_id" integer NOT NULL
> > >> );
> > >>
> > >> INSERT INTO accounts(id, name, owner_id)
> > >>     VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);
> > >>
> > >> GRANT SELECT ON accounts TO PUBLIC;
> > >>
> > >> ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
> > >>
> > >> CREATE POLICY account_ownership ON accounts FOR SELECT
> > >>     USING (owner_id = (SELECT id FROM accounts WHERE name =
> > >> current_user));
> > >
> > > I think that should be:
> > >
> > > CREATE POLICY account_ownership ON accounts FOR SELECT
> > >     USING (name = current_user);
> > >
> > > Regards
> > > Charles
> > >
> > >>
> > >> CREATE ROLE foo;
> > >> SET ROLE foo;
> > >> SELECT * FROM accounts;
> > >> -- ERROR:  infinite recursion detected in policy for relation "accounts"
> > >>
> > >> Is there any way to alter the "account_ownership" policy's USING
> > >> clause to avoid this infinite recursion or a way to model my schema to prevent this from happening?
> > >>
> > >> Thank you for your time,
> > >> Simon
> > >>
> > >>
> > >> --
> > >> Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
> > >> http://www.postgresql.org/mailpref/pgsql-general
> > >
> >
> >
> > --
> > Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
> > http://www.postgresql.org/mailpref/pgsql-general
>
>
>
> --
> Sent via pgsql-general mailing list (pgsql-general@postgresql.org) To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-general



Re: [GENERAL] Recursive row level security policy

From
Joe Conway
Date:
On 12/16/2016 01:02 AM, Simon Charette wrote:
> Unfortunately this will only return accounts matching the current_user's name.
>
> I would expect "SET ROLE foo; SELECT name FROM accounts" to return "foo" and
> "bar" and not only "foo" like your proposed solution would do.

Perhaps:

8<--------------------------
CREATE TABLE "accounts" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(50) NOT NULL UNIQUE,
    "owner_id" integer NOT NULL
);

INSERT INTO accounts(id, name, owner_id)
    VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);

GRANT SELECT ON accounts TO PUBLIC;

ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;

CREATE FUNCTION get_owner_id(luser text) RETURNS integer AS $$
 SELECT id FROM accounts WHERE name = luser
$$ LANGUAGE sql STRICT STABLE SECURITY DEFINER;

CREATE POLICY account_ownership ON accounts FOR SELECT
    USING (owner_id = get_owner_id(current_user));

CREATE ROLE foo;
SET ROLE foo;

SELECT * FROM accounts;
 id | name | owner_id
----+------+----------
  1 | foo  |        1
  2 | bar  |        1
(2 rows)
8<--------------------------

HTH,

Joe

--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development


Attachment

Re: [GENERAL] Recursive row level security policy

From
Simon Charette
Date:
Thanks a lot Joe, that seems to work!

I suppose this works because PostgreSQL cannot introspect the
get_owner_id procedure to detect it's querying the "accounts" table
and thus doesn't warn about possible infinite recursion?

Simon

2016-12-16 9:36 GMT-05:00 Joe Conway <mail@joeconway.com>:
> On 12/16/2016 01:02 AM, Simon Charette wrote:
>> Unfortunately this will only return accounts matching the current_user's name.
>>
>> I would expect "SET ROLE foo; SELECT name FROM accounts" to return "foo" and
>> "bar" and not only "foo" like your proposed solution would do.
>
> Perhaps:
>
> 8<--------------------------
> CREATE TABLE "accounts" (
>     "id" integer NOT NULL PRIMARY KEY,
>     "name" varchar(50) NOT NULL UNIQUE,
>     "owner_id" integer NOT NULL
> );
>
> INSERT INTO accounts(id, name, owner_id)
>     VALUES (1, 'foo', 1), (2, 'bar', 1), (3, 'baz', 3);
>
> GRANT SELECT ON accounts TO PUBLIC;
>
> ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
>
> CREATE FUNCTION get_owner_id(luser text) RETURNS integer AS $$
>  SELECT id FROM accounts WHERE name = luser
> $$ LANGUAGE sql STRICT STABLE SECURITY DEFINER;
>
> CREATE POLICY account_ownership ON accounts FOR SELECT
>     USING (owner_id = get_owner_id(current_user));
>
> CREATE ROLE foo;
> SET ROLE foo;
>
> SELECT * FROM accounts;
>  id | name | owner_id
> ----+------+----------
>   1 | foo  |        1
>   2 | bar  |        1
> (2 rows)
> 8<--------------------------
>
> HTH,
>
> Joe
>
> --
> Crunchy Data - http://crunchydata.com
> PostgreSQL Support for Secure Enterprises
> Consulting, Training, & Open Source Development
>


Re: [GENERAL] Recursive row level security policy

From
Joe Conway
Date:
On 12/17/2016 01:01 PM, Simon Charette wrote:
> Thanks a lot Joe, that seems to work!

Good to hear.

> I suppose this works because PostgreSQL cannot introspect the
> get_owner_id procedure to detect it's querying the "accounts" table
> and thus doesn't warn about possible infinite recursion?

Not exactly. RLS does not get applied to the superuser, and the
get_owner_id procedure was 1) SECURITY DEFINER, and 2) created/owned by
postgres. Thus the procedure executes without invoking the RLS policy
and avoids the infinite recursion.

Joe

--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development


Attachment

Re: [GENERAL] Recursive row level security policy

From
Simon Charette
Date:
Ahh makes sense, thanks for the explanation!

I was assuming USING() clauses were executed in the context of the
owner of the policy, by passing RLS.

2016-12-17 13:18 GMT-05:00 Joe Conway <mail@joeconway.com>:
> On 12/17/2016 01:01 PM, Simon Charette wrote:
>> Thanks a lot Joe, that seems to work!
>
> Good to hear.
>
>> I suppose this works because PostgreSQL cannot introspect the
>> get_owner_id procedure to detect it's querying the "accounts" table
>> and thus doesn't warn about possible infinite recursion?
>
> Not exactly. RLS does not get applied to the superuser, and the
> get_owner_id procedure was 1) SECURITY DEFINER, and 2) created/owned by
> postgres. Thus the procedure executes without invoking the RLS policy
> and avoids the infinite recursion.
>
> Joe
>
> --
> Crunchy Data - http://crunchydata.com
> PostgreSQL Support for Secure Enterprises
> Consulting, Training, & Open Source Development
>


Re: [GENERAL] Recursive row level security policy

From
Stephen Frost
Date:
Simon,

* Simon Charette (charette.s@gmail.com) wrote:
> Ahh makes sense, thanks for the explanation!
>
> I was assuming USING() clauses were executed in the context of the
> owner of the policy, by passing RLS.

No, as with views, a USING() clause is executed as the caller not the
owner of the relation.  Security Definer functions can be used to
execute actions in the policy as another user.

Note that RLS won't be applied for the table owner either (unless the
relation has 'FORCE RLS' enabled for it), so you don't have to have
functions which are run as superuser to use the approach Joe
recommended.

Thanks!

Stephen

Attachment

Re: [GENERAL] Recursive row level security policy

From
Joe Conway
Date:
On 12/17/2016 02:04 PM, Stephen Frost wrote:
> Note that RLS won't be applied for the table owner either (unless the
> relation has 'FORCE RLS' enabled for it), so you don't have to have
> functions which are run as superuser to use the approach Joe
> recommended.

Good point, thanks, I should have mentioned that. You would be better
off having a different user own both the table and the function in order
to avoid using/abusing the superuser for that purpose. Just be aware
that FORCE RLS would break that solution.


--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development


Attachment