Keeping Properties Secret in Neo4j

We’re an open source company with nothing to hide, but some of our customers have things they need to keep close to their chest. Sometimes you don’t want everybody to have access to salary information, or future predictions. Maybe you want to hide Personally identifiable information (PII) or Health Insurance Portability and Accountability Act (HIPPA) data. In Neo4j 3.4 we are introducing more security controls. We are starting with Role based Database wide property key blacklists. That’s a bit of a mouthful but let’s walk through and example to see one of the ways it can be utilized. Imagine you are working in “Area 51” and have to deal with very important information.

You want your boss who has “Top Secret” access to know the truth, you want your friend James from “Area 50” who has “Secret” access to know a little less than they whole truth. You want those like Tim who have “Confidential” access to know a little less than that, and finally you want the public to know the least and only the Unclassified information. We will need to change the “neo4j.conf” file in the config directory of our Neo4j installation , add a couple of lines, save the file and restart Neo4j (on all cluster members):

 
dbms.security.property_level.enabled=true
dbms.security.property_level.blacklist=Secret=top_secret;Confidential=top_secret,secret;Unclassified=top_secret,secret,confidential

In Neo4j Enterprise edition you will need to create a few accounts. The format is:

 
CALL dbms.security.createUser(username, password, requirePasswordChange)

So for example:

 
CALL dbms.security.createUser("james", "1234", false); 
CALL dbms.security.createUser("tim", "5678", false);
CALL dbms.security.createUser("public", "password", false);

Next we will create Security Roles for these accounts:

 
CALL dbms.security.createRole("Secret");
CALL dbms.security.createRole("Confidential");
CALL dbms.security.createRole("Unclassified");

… and we will add the roles to the users:

 
CALL dbms.security.addRoleToUser("Secret", "james");
CALL dbms.security.addRoleToUser("Confidential", "tim");
CALL dbms.security.addRoleToUser("Unclassified", "public");

But before they can read anything from the database, they also need reader access:

 
CALL dbms.security.addRoleToUser("reader","james");
CALL dbms.security.addRoleToUser("reader","tim");
CALL dbms.security.addRoleToUser("reader","public");

We can call “listRoles” to see how it looks:

 
CALL dbms.security.listRoles();

Now that everything is set, we will create a report on the existence of “Aliens”.

We have different versions of the truth, so we will create multiple properties to answer the question in our document:

 
CREATE (u:Document {name:'Aliens?', 
                    top_secret:'They hate us!', 
                    secret:'They like us!', 
                    confidential:'They exist!', 
                    public:'They do not exist.'})

We can query for the truth using COALESCE. It will use the first non-null property it finds. Since we are logged on as the Neo4j admin user with full access when we ask:

 
MATCH (u:Document {name:'Aliens?'})
RETURN COALESCE (u.top_secret, u.secret, u.confidential, u.public) AS truth

We get the real answer “They hate us!”. Now let’s disconnect and try a different account.

 
:server disconnect

Log back in as James, and rerun the query and we get “They like us!”. Disconnect again, and log back in as Tim and you get “They exist!”. One more time as user public, and you get “They do not exist”. Pretty neat right? So you can use this feature to keep sensitive data away from people and also show different levels of detail. If you want to try it out, you can get a pre-release version of Neo4j 3.4 here.

Tagged , , , , ,

10 thoughts on “Keeping Properties Secret in Neo4j

  1. What if the user in the confidential role tries to query based on the property? something like:
    MATCH (u:Document {secret:’They like us!’}) return u.name

    Will that work?

  2. koen says:

    Hi, great extension ! So are the other roles pre-defined roles ? What do they stand for ? And would it be possible to have read access on one or more selected properties (i guess you need the reader role for that .. ) while having write authorization on other properties ? Thanks .. regards Koen

  3. Wil Parton says:

    Any plans to extend this to “Keeping Nodes Secet in Neo4j”. We have model segragation on our To Do list; essentially node ACLs based upon user roles/permissions to allow all or a subset of the graph to be visible to a user. Last time I asked support for this capabilty wasn’t on the product roadmap, just wondering if there was any change.

    • maxdemarzi says:

      We have more security features in the pipe, it’s just a matter of priority and engineering time while balancing performance. I would like to see more fine grained security as a plugin rather than built into Neo4j so people can choose to have it with the possible performance hit or not.

      • koen says:

        externalizing authorization decisions sounds like a good idea since that will also enable ABAC / NGAC kind of solutions to be placed in front (PEP/RAP combined with a PDP/PAP/PIP).

      • RJB says:

        Neo4J has come a long way in adding security features to the graph database (4.x) but I have a requirement that I cannot find an easy solution:

        1. Access control (on nodes) must be user based (not role based)
        2. It is a large web application that uses a single user to connect to the Neo4J database (thousand of users on the web application)
        3. Users has a node representing the account on the database (:User { username: ‘u1’})
        4. Permissions are granted on the User or Group, having a schema:

        (:User)->[:IN_GROUP]->(:Group)-[:HAS_PERMISSION { read: true }]->(:Resource { type: Y})

        and

        (:User)-[:HAS_PERMISSION { read: true, write: true }]->(:Resource { type: X })

        My first attempt was adding a WHERE clause to CYPHER queries:

        MATCH (n:X) WHERE (path from Resource X to User exists with read=true)

        That works but it gets a little complicated when permissions are assigned using wildcards, e.g. User 1 can perform any action on Resource X, or Group 3 can read on any Resource… The WHERE clause is an OR of different paths. On top of that if we add the requirement of DENY { read: false } to the configuration the WHERE clause gets larger.

        Doing a traverse and adding WHERE clauses to the Resource types that require it makes a simple query very slow.

        Second attempt was using a User Function that evaluates the predicate but it is not much better:

        MATCH (u:User { username: ‘u1’ })
        MATCH (n:X) WHERE has.permission(u, n)

        Do you have a recommendation on this type of requirement?

Leave a comment