Wednesday, 12 February 2020

Experimenting with Conflicting access privileges in Neo4j 4.0

In the past couple of weeks, I have been playing around with the shiny new security features of Neo4j 4.0. They are truly interesting - both for childproofing beergraphs and for ensuring that your sensitive fraud databases are properly secured. Take a look at the previous post, and I think you will understand why.

In this post, I wanted to talk about something that I have seen so many times in my previous lives in the security industry, and that also became evident in my 4.0 research. It's got to do with conflicting security privileges. In a nutshell, this is to do with the case where

  • a specific user / role would receive a particular set of privileges from one policy
  • the same user / role would receive a different, and contradictory privilege from another policy. 
In that case, we need clear rules to understand what would happen. In the case of Neo4j 4.0, this is reasonably well explained as part of the documentation - see the documentation site on this topic - but in this post I will try to give you a realistic, but simple example.


Creating Conflict

We'll start working on this with the same database as the previous post, the fraud dataset. If you don't have it yet, just download it from this link. Once we have the database up and running as a separate user database, we can switch to the system database and create a separate user for these tests.

//create a separate user for engineering the conflicting privileges
CREATE USER conflicted_user SET PASSWORD "changeme" CHANGE NOT REQUIRED;
CREATE ROLE conflicted_role AS COPY OF reader;


Next we will create the conflicted role

GRANT ROLE conflicted_role TO conflicted_user;

Once we have that we will create two conflicting privileges. First, we assign a  MATCH privilege to the role.

GRANT MATCH {*} ON GRAPH `fraudgraph` NODES SSN to conflicted_role;

As you can see from the documentation site, a MATCH privilege is essentially shorthand for giving BOTH the TRAVERSE and the READ privilege to a role/user, in one command. You can see that below in the user privileges overview: there is no separate access level for the MATCH privilege - it's just the combination of TRAVERSE and READ.

In order to "create the conflict". we can then DENY the READ privilege on exactly the same resources that we have just granted the READ on, with the MATCH command. That looks like this:

DENY READ {*} ON GRAPH `fraudgraph` NODES SSN to conflicted_role;

Once we do that, we get this interesting result:


Our "conflicted_user" now has both a GRANT and a DENIAL of the read privilege to all the properties of the SSN nodes in the fraudgraph. I wonder what will happen when we start testing this?

Testing the conflicting privileges

I am going to use the same queries for testing as in the previous post, looking for shared / synthetic identities that would be a good indicator for potential fraud cases:

//find shared identity graphs
MATCH path = (accountHolder:AccountHolder)-[:HAS_ID|HAS_ADDRESS|HAS_PHONENUMBER|HAS_SSN]->(contactInformation)<-[]-(accountHolder2:AccountHolder)
RETURN path;

When I run this query I get the following results:
Which clearly highlihts that the conflicting "read" privileges will be DENIED, in the case of conflict. The user can still traverse the graph (as per the overview above), but the read is of the SSN property is denied. Good!

Switching the conflict of privileges around

To make this even more obvious, I did another simple test using the same fraud dataset. I wanted to see what would happen if the READ was GRANTED, but the MATCH was DENIED. Let's take a look.

//first remove the conflicting privileges
REVOKE GRANT READ {*} ON GRAPH `fraudgraph` NODES SSN from conflicted_role;
REVOKE GRANT TRAVERSE ON GRAPH `fraudgraph` NODES SSN from conflicted_role;
REVOKE DENY READ {*} ON GRAPH `fraudgraph` NODES SSN from conflicted_role;

and then we  switch them around as follows:

DENY MATCH {*} ON GRAPH `fraudgraph` NODES SSN to conflicted_role;
GRANT READ {*} ON GRAPH `fraudgraph` NODES SSN to conflicted_role;

Then we get the following privileges overview for our conflicted_user/conflicted_role:

We can see that the READ GRANT is there, and that the DENIAL of the MATCH has been translated into a READ DENIAL and a TRAVERSAL DENIAL. So that means that now, when we redo our test with the synthetic identities query above, we will get a very different result. SSNs are now no longer part of the subgraphs that we can find, as the traversals are denied even though we have read privileges granted. It looks like this:

So it really does seem like the policy that is outlined in the security administration documentation seems to work:
All other combinations of GRANT and DENY will result in the matching subgraph being invisible. It will appear to the user as if they have a smaller database (smaller graph).
This does seem like to prudent way to handle this - an explicit denial of access should always trump an explicit grant of access.

That concludes my experiments with this at this point. As always, you will find the queries above on github, so please do go there and test it out for your own benefit.


Hope this was useful - as always, feedback would be extremely welcome.

Cheers

Rik

No comments:

Post a comment