FROST-Server icon indicating copy to clipboard operation
FROST-Server copied to clipboard

Implement Entity-based security

Open GDue opened this issue 5 years ago • 15 comments

This is not so much an issue but a wish to communicate and discuss with others having the same problem...

We need "entity-based-security", i.e. we need to protect resources (Things, Observations, ... you name it) against reading and or writing based on some properties of the request (like Auth-Headers, ...)

After a discussion with Hylke we decided to clone the current version of FROST. The clone can be found here: https://github.com/GDue/FROST-Server

After having implemented we will issue a pull request to incorporate any changes back into the main stream.

If you want to join in the efforts, leave a note (an issue) on the cloned repo.

GDue avatar Apr 03 '19 12:04 GDue

Just for clarification, and correct me if I'm wrong: It's not just "some properties of the request", it is also relations of the entity with other entities. So a user/group (identified by those "properties of the request") has only access to Observations belonging to a certain set of Things, but not to Observations belonging to another set of Things.

hylkevds avatar Apr 03 '19 12:04 hylkevds

as Hylke assumed by putting a reference into #110 , we are interested and invested a considerable amount of thought into the topic. Our conclusion is that it makes most sense to simply implement path based access control by using strongly hierarchical references like https://example.com/v1.0/Things(1)/Datastreams(2)/Observations . This way sth like SVN path based access control is easily feasible if you allow following only true containment (1:n relations)

I think a full security scheme with inheritance rules is a monster (good luck with that). Using https://example.com/v1.0/Things(1)/Datastreams(2)/Observations(3)/FeatureOfInterest/Observations(4)/... i can easily cross to another realm (much more with ObservedProperty or Sensor). The problem to me is, that there is no strict hierarchy in sensor things, which makes it difficult to define ownership: who owns a feature of interest? On the other hand, I don't want to specify complex access control on every thing I create explicitly.

However, If you want to go the full way: It even makes sense to make single properties non-readable. We have the use case, that we like to store the email of a sensor owner non-visible in the database.

Whatever you do I think the most important thing before anything would be writing a concise spec of the API and a suite of test cases.

My first go atm is to use access_by_lua_file in an nginx reverse proxy, but I only think I need Thing-based access control (planning to use the thing id also as user name).

riedel avatar Apr 03 '19 17:04 riedel

Yes, Hylke, you are correct. The intention is to take entity relations into account which means, e.g., that access to an Observation might depend on properties of the Thing related to the Datastream an Observation belongs to.

The approach we want to try out is to put some hooks into FROST which can be configured to call a plugin. The plugin is NOT defined by FROST; only the interface is. This way a plugin can be adapted to special needs of a use case.

GDue avatar Apr 04 '19 05:04 GDue

The big issue with using paths for authorising GET requests is that one has to disable 90% of the features that make the SensorThings API great. You can't have $filter or $expand. The selfLinks become useless, the generated nextLinks as well. Rewriting those in the reverse proxy is not that straightforward.

In the end, I think it's easier to have some hooks that can modify the query while it is being constructed, so that the database never returns entities the user is not allowed to see. The problem of defining what the user is allowed to see, and what not, is no different between the two approaches.

hylkevds avatar Apr 04 '19 07:04 hylkevds

Great to see progress on this one! Just to be clear, are both of the following use cases now covered?

  1. As a User, I want to see all the values of the building automation system's sensor located in my apartment

  2. As a User, I want to grant read access to only the temperature value of my living room sensor to third parties for processing

timoruohomaki avatar Apr 04 '19 07:04 timoruohomaki

Depends on what you mean by "covered". There will be two things that we plan to develop:

  1. An API (Factory, Interface and all that) for the Frost and the entry points within the frost.
  2. A plugin to demonstrate how it works.

Yes. I think, with a plugin that does that, the above use case can be covered. No. Out of the box this use case will not be covered.

So the answer to your question depends on whether you (or somebody else) is willing to write a plugin to cover your specific use case.

GDue avatar Apr 04 '19 08:04 GDue

@hylkevds I agree totally for the case of GET (we only need POST security to not allow spamming or destruction of the database, e.g. also if a sensor was stolen). The idea of paths stems from the problem of specifying access control and ownership of resources in a light-weight form without breaking the database in the end and/or having to expose rather difficult settings/policies. We are happy to use any solution that is usable. If it ends up a mess like e.g. on network shares (I don't know how many actually effectively can use setfacl ).

@GDue It would be great if your example would cover the simple use case of Thing ownership in a simple way (i.e. solving how to deal e.g. with the "private" FeaturesOfInterest) One solution is to actually actively propagate ACLs on POST to all posted entities. One feasible solution would then be to implement an ABAC scheme with custom properties (like owner and user/role based acl policies per thing).

It would be great if you could look at something like https://github.com/casbin/jcasbin for this instead of reinventing the wheel. This would e.g. give you a policy editor and some semantics. Minimum Things needed to do ABAC with jcasbin (from my limited research):

  • A iteratable list of entities effected by the request (probably need to make sure that aviator works for anything relevant to the policy as jcasbin uses it)
  • the type of request (GET, POST, ...) -- should be easy
  • the username in the request. then a plugin based on casbin would be as easy as
import org.casbin.jcasbin.main.Enforcer;
Enforcer enforcer = new Enforcer("path/to/model.conf", "path/to/policy.csv");

...

for (Object entity: entities) {
    if enforcer.Enforce(userName, entity, restAction) == false
     {
       throw new AccessDeniedException()
      } 
} 

For complex schemes based on parent entities, the ACL mechanism would need to be able to access any linked entity (which might not be resolved yet). Here I see a small challenge, since the used aviator library probably is to dumb to do lazy evaluation here... (the documentation is in chinese :( )

A start would be getting a list of all affected entities within the API you are proposing.

In any case: Really looking forward to it!

riedel avatar Apr 04 '19 09:04 riedel

Since the last post a year has passed, so I was wondering if there are news to this issue.

Actually, at our institute we're facing the same challenge of trying to restrict the access to individual entities (e.g. devices, data streams) only to specific users. Has somebody developed in the meanwhile an implementation or workaround that for instance covers the use cases of granting reading access to a device for only a limited, explicitely specified list of users? This is similar to what timoruohomak posted earlier.

HEFLoRa avatar May 20 '20 13:05 HEFLoRa

FYI, Casbin (jCasbin for Java) has supported the scaling ABAC rules feature. See our docs: https://casbin.org/docs/en/abac#scaling-the-model-for-complex-and-large-number-of-abac-rules

hsluoyz avatar May 20 '20 13:05 hsluoyz

No news here. Unfortunately the project of GDue went into a different direction, and so far none of our clients found this issue pressing enough to fund development.

The hard part is ensuring the implementation does not leak information though the query language, only filtering on the returned entities is not enough.

Imagine two Things (1,2) with two Datastreams (1,2) both measuring Temperature (ObservedProperty 1). I have access to Datastreams & Observations of Thing 1, but not of Thing 2, and this access is bound on Thing level. I obviously have access to OP 1, since it is bound to a Thing I have access to. I can now construct the following query:

v1.0/ObservedProperties?$filter=Datastreams/id eq 2 and Datastreams/Observations/result gt 5

The generated query will fetch the OPs, join the Datastreams (filtered on id = 2) and join Observations (filtered on result gt 5)

This query will only ever return the ObservedProperty that I am allowed to see, but filtered on data of Datastream 2 (that I am not allowed to see). So if the access control mechanism only takes the returned entities into account, it will allow me to deduce information about the entities that I am not allowed to see. Instead, for every join, the access control mechanism will have to modify the join to only join those entities that I am allowed to see.

hylkevds avatar May 20 '20 14:05 hylkevds

@HEFLoRa , our use cases got a bit more complicated because this pilot should be a proof of concept of both GDPR and MyData compliance. So that means there can be more than one actor who can grant the access right to the same datastream and observations. In the room sensor example the housing company has the legal base of legitimate interest to grant access to data processor for maintenance reasons and the resident can do that to the same data with consent for any reason because he is the data subject identifiable by location. Because of this complexity we started to work on Kafka with separate topics for datastreams and observations where both can have ACL's and user-specific keys. We started this work in February and won't be able to do it in full time so probably nothing useful will come up until later this year (if ever).

timoruohomaki avatar May 22 '20 13:05 timoruohomaki

No news here. Unfortunately the project of GDue went into a different direction, and so far none of our clients found this issue pressing enough to fund development.

The hard part is ensuring the implementation does not leak information though the query language, only filtering on the returned entities is not enough.

Imagine two Things (1,2) with two Datastreams (1,2) both measuring Temperature (ObservedProperty 1). I have access to Datastreams & Observations of Thing 1, but not of Thing 2, and this access is bound on Thing level. I obviously have access to OP 1, since it is bound to a Thing I have access to. I can now construct the following query:

v1.0/ObservedProperties?$filter=Datastreams/id eq 2 and Datastreams/Observations/result gt 5

The generated query will fetch the OPs, join the Datastreams (filtered on id = 2) and join Observations (filtered on result gt 5)

This query will only ever return the ObservedProperty that I am allowed to see, but filtered on data of Datastream 2 (that I am not allowed to see). So if the access control mechanism only takes the returned entities into account, it will allow me to deduce information about the entities that I am not allowed to see. Instead, for every join, the access control mechanism will have to modify the join to only join those entities that I am allowed to see.

Would the use of UUIDs instead of integers alleviate this issue? A user would only know the IDs of the returned entities and would not be able to filter by other (unknown to them and non-predictable) IDs.

henfiber avatar Nov 04 '21 02:11 henfiber

Using UUIDs doesn't help, since the user could use the same method to find the UUID of the unknown entities. If the user even needs to know those. The above query can also be written by the user excluding the known Datastreams, instead of specifying the hidden one. The result is the same, the user can deduce information about hidden entities without actually requesting those entities.

It is possible to property implement entity-based security, but it does require care and planning and is thus not something I can simply implement in a few spare hours...

hylkevds avatar Nov 04 '21 08:11 hylkevds

There are now two ways to implement entity-based security in Frost server:

  • either with EntityType validators and a plugin as in FROST-Server-PLUS
  • or with PostgreSQL Row-Level Security as proposed in the new PR #1304

pbaumard avatar Nov 28 '22 11:11 pbaumard

It's important to note here that currently PostgreSQL Row-Level Security does not work for MQTT subscriptions. In most cases the implementation optimises away any database queries, so the database never gets to do any access checks.

hylkevds avatar Jan 09 '23 10:01 hylkevds