Re: Row-Level Security

From: KaiGai Kohei <kaigai(at)kaigai(dot)gr(dot)jp>
To: Robert Haas <robertmhaas(at)gmail(dot)com>
Cc: Josh Berkus <josh(at)agliodbs(dot)com>, Stephen Frost <sfrost(at)snowman(dot)net>, pgsql-hackers(at)postgresql(dot)org
Subject: Re: Row-Level Security
Date: 2009-12-13 08:50:53
Message-ID: 4B24AAED.2080706@kaigai.gr.jp
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

(2009/12/13 12:18), Robert Haas wrote:
> On Sat, Dec 12, 2009 at 7:41 PM, Josh Berkus<josh(at)agliodbs(dot)com> wrote:
>> Stephen,
>>
>>> Please comment, update the wiki, let us know you're interested in this..
>>
>> I blogged about this some time ago. One issue I can see is that I
>> believe that the RLS which many users want is different from the RLS
>> which SEPostgres implements.
>>
>> Links:
>>
>> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-1-30732
>> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-2-30757
>>
>> --Josh Berkus
>
> I read these blog entries a while ago but had forgotten about them.
> They're very good, and summarize a lot of my thinking on this topic as
> well. I think that we can design a framework for row-level security
> which can encompass both constraint-based security and label-based
> security. Both seem to me to be be based around doing essentially the
> following things:
>
> 1. Adding columns to the table to store access control information.
> 2. Populating those columns with additional information (owner, ACL,
> security label, etc.) which can be used to make access control
> decisions.
> 3. Injecting logic into incoming queries which uses the information
> inserted by (1) to filter out rows to which the access control policy
> does not wish to allow access.
>
> Waving my hands in the air, #1 and #2 seem pretty straightforward.
> For constraint-based security, one can imagine just adding a column to
> the table and then adding a BEFORE INSERT OR UPDATE FOR EACH ROW
> trigger that populates that column. For label-based MAC, that's not
> going to be quite sufficient, because the system needs to ensure that
> the trigger that populates the security column must run last; if it
> doesn't, some other trigger can come along afterwards and slip in a
> value that isn't supposed to be there; plus, it might be inconvenient
> to need to define this trigger for every table that needs RLS.

Right, label-based MAC need its hook being called after all the BR-Insert
triggers to assign a correct security label, not only access controls.
I'd like to point out one more thing. When we update tuples, "invisible"
tuples have to be filtered out before trigger functions.

> However, those problems don't seem insurmountable. Suppose we provide
> a hook function that essentially acts like a global BEFORE INSERT OR
> UPDATE trigger but which fires after all of the regular triggers.

Basically, right. In my branch, SE-PgSQL put its hook after all the BR
trigger invocations.

http://code.google.com/p/sepgsql/source/browse/branches/pgsql-8.4.x/sepgsql/src/backend/executor/execMain.c#1883

But we have another approach. When RelationBuildTriggers() initializes
TriggerDesc of Relation, we can inject security hook as a special BR-trigger
at the last. If we initialize it here, we don't need to modify COPY FROM
implementation, not only INSERT.

The reason why I didn't apply this approach is it needs more modification
to the core routines, so it makes harder to manage out-of-tree code.

> SE-PostgreSQL can gain control at that point and search through the
> columns of the target relation for a column called, say,
> sepg_security_label. If it finds such a column and that column is of
> the appropriate type, then (1) if an explicit security label is
> provided, it checks whether the specified label is permissible, (2)
> otherwise, if the operation is insert, it determines the appropriate
> default label for the current security context and inserts it, (3)
> otherwise, it just leaves the current label alone. This might not be
> quite the right behavior but the point is whatever behavior you want
> to have in terms of assigning/disallowing values for that column
> should be possible to implement here. The upshot is that if the
> system administrator creates an sepg_security_label column of the
> correct type, row-level security will be enabled for that table.
> Otherwise, it will not.

Basically, right. SE-PgSQL (or others) assign a new tuple either an
explicitly given or a default security label, then it checks permission
whether the client can insert a tuple with this label, or not.

One point. MAC is "mandatory", so the table owner should not be able to
control whether row-level checks are applied, or not.
So, I used a special purpose system column to represent security label.
It is generated for each tables, and no additional storage consumption
when MAC feature is disabled.

> #3 seems a little bit trickier. I don't think the GRANT ... WHERE
> syntax is going to be very easy to use. For constraint-based
> row-security, I think we should have something more like:
>
> ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
> WHERE where-clause
>
> (This suffers from the same problem as DELETE ... USING, namely that
> sometimes you want an outer join between table and othertable.)
>
> This gives the user a convenient way to insert a join against one or
> more side tables if they are so inclined.

Is it reasonably possible to implement USING clause, even if row-level
security is applied on COPY FROM/TO statement?

And, isn't it necessary to specify condition to apply the filter?
(such as select, update and delete)

> For security frameworks like SE-PostgreSQL, we might just provide a
> hook allowing the incoming query tree to be modified, and let the hook
> function check whether each table in the query has row-level security
> enabled, and if so perform a modification equivalent to the above.

One point we have to pay mention is all the row-level filter conditions
have to be evaluated before all the user given condition, except for
operators pulled-up to index accesses.
It allows malicious row-cost functions to leak "invisible" tuples anywhere.

> None of this addresses the issue of doing RLS on system catalogs,
> which seems like a much harder problem, possibly one that we should
> just ignore for the first phase of this project.

It is reasonable.

I'd like to point out a few more issues:

* TRUNCATE statement

Truncate is a good feature to clean up the contents of a table.
But it may contain unremovable tuples. So, it needs to scan a table to
be truncated once to confirm all the tuples can be removed by the current
user. It is a trade-off case between performance and security.

* Foreign Key constraint(1)

I don't think upcoming label-based MAC feature support covert channel issue.
Even if PK is invisible, we can guess PK exists from FK. In fact, commercial
database products (such as Oracle Label Security) also does not care about.

* Foreign Key constraint(2)

FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
If associated tuples are filtered out, it breaks reference integrity.
So, we have to apply special care. In SE-PgSQL case, it raises an error
instead of filtering during FK checks. And, row-level security hook is
called at the last for each tuples, unlike normal cases.

Thanks,
--
KaiGai Kohei <kaigai(at)kaigai(dot)gr(dot)jp>

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message KaiGai Kohei 2009-12-13 10:28:27 [PATCH] ACE Framework - Database, Schema
Previous Message Jaime Casanova 2009-12-13 07:18:02 Re: XLogInsert