Neo4j Security and Extension - Neo4j Essentials (2015)

Neo4j Essentials (2015)

Chapter 8. Neo4j Security and Extension

Enterprises in today's world have various security requirements where they not only restrict access to data but also need to comply with various security norms published by the government.

For example, the companies in the healthcare domain need to abide by HIPAA (The Health Insurance Portability and Accountability Act) security rule, which defines the national standards for securing the data pertaining to health information of patients.

To meet these compliance levels and to protect confidential data from unauthorized access, it is important that enterprise solutions provide flexibility to apply standard security measures and various hooks for defining the customized security rules.

This also introduces another important aspect of a software solution—extensibility of a proposed solution where it defines approaches to extensibility and provides flexibility to users to insert their own program routines.

Every business is different, with its own needs and path of growth. As the business grows, we may need to extend or add functionality to the existing system for providing ease to the users or may be comply with new security measures introduced by the government (such as HIPAA), and for all these reasons, it is important that the proposed solution provides the flexibility to introduce new APIs/functionalities.

Unlike security, extensibility may not be an explicit requirement, but for enterprises, it continues to remain as one of the evaluation criteria for any software solution.

In this chapter, we will discuss the security and extension provided by the Neo4j server, which not only provides measures to protect from unauthorized access but also provides flexibility to implant customized routines and extend the functionality of the Neo4j server.

This chapter will cover the following topics:

· Neo4j security

· API extension

Neo4j security

Neo4j does not enforce any security at data levels but provides various means to protect unauthorized access to our Neo4j deployment.

Let's discuss the security measures and custom hooks provided by Neo4j for protecting the Neo4j deployment.

Securing access to Neo4j deployment

The first step in any enterprise security is to restrict direct access to the production servers and allow only secure communication.

Neo4j is bundled with a web server that provides the access to the Neo4j browser. We can modify the web server parameters and control access to the Neo4j browser.

Let's discuss the various web server configurations defined in <$NEO4J_HOME>/conf/neo4j-server.properties and their role in securing the access of the Neo4j browser:

· org.neo4j.server.webserver.address=0.0.0.0: This property defines the IP address on which the web server will accept the incoming connections. The default value is 0.0.0.0, which means it will accept connections only from the local box hosting the Neo4j server.

· org.neo4j.server.webserver.https.enabled=true: This is a Boolean parameter used to enable or disable the support for HTTPS. We should set it to true to enable and accept HTTPS connections.

· org.neo4j.server.webserver.https.port=7473: This parameter defines the port for accepting the HTTPS request. The default value is 7473.

· org.neo4j.server.webserver.port=7474: This is the default HTTP port for unsecured communication with the web server or the Neo4j browser. We should disable this property by appending # and stop all unsecured communication.

· org.neo4j.server.webserver.https.cert.location=conf/ssl/snakeoil.cert, org.neo4j.server.webserver.https.key.location=conf/ssl/snakeoil.key, and org.neo4j.server.webserver.https.keystore.location=data/keystore: These properties define the location of the certificate, key, and Keystore, used in secured communication on the HTTPS protocol. We should change the default values and define our own certificate, key, and Keystore. Refer to the link https://docs.oracle.com/cd/E19798-01/821-1751/ghlgv/index.htmlto generate the certificate, key, and Keystore.

Note

For sensitive deployments/applications, it is recommended to buy and install certificates from a trusted certificate authority (CA) such as Verisign https://www.verisigninc.com/, Thawte http://www.thawte.com/, and so on.

Restricting access to Neo4j server / cluster with the proxy server

Neo4j provides various security measures for secure communication, but at the same time, it is recommended and sensible to place a proxy server such as Apache in front of our Neo4j servers. Proxy servers will act as a gateway to the outside world for accessing our Neo4j server. The following are the advantages of having proxy servers:

· It controls access to the Neo4j server to specific IP addresses, URL patterns, and IP ranges. For example, it can allow access to the Neo4j browser only from specific IP addresses or a range of IP addresses. Refer to the following link to configure Apache as a proxy server: http://httpd.apache.org/docs/2.2/mod/mod_proxy.html.

· It hides all sensitive IP addresses and URLs from the outside world by using URL rewriting. Refer to the following link to configure and enable URL rewriting in Apache: http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html.

· It can also act as a load balancer for Neo4j HA cluster. Refer to the following link to configure and enable load balancing in Apache: http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html.

Feature deactivation

Neo4j provides flexibility to add and execute arbitrary pieces of code through unmanaged extensions (see the next section for details about unmanaged extensions), which can produce severe security threat to your infrastructure. It is advisable to think twice before you decide to move ahead with unmanaged extensions and also make sure that all offending plugins or extensions are removed from the server classpath. Alternatively, we can also secure the access to these custom plugins/extensions through proxies or authorization rules.

Neo4j is implemented in Java, so we can also introduce and implement security manager from http://docs.oracle.com/javase/7/docs/technotes/guides/security/index.html to secure the different parts of the code base.

Fine-grained authorization

Security requirements may vary from organization to organization and to make it more complex, there are instances where the country laws, local authorities, or various government policies govern the security requirements. It is difficult (if not impossible) to provide a single framework that meets all security requirements of various organizations. Therefore, for all those instances where standard security measures are not enough, Neo4j provides the flexibility to implement and inject custom security rules / policies to secure various REST endpoints. It exposes the org.neo4j.server.rest.security.SecurityRule interface for implementing custom security policies / rules; furthermore, it can be configured in <$NEO4J_HOME>/conf/neo4j-server.properties.

Let's extend our Spring-Neo4j example, which we created in Chapter 6, Spring Data and Neo4j and perform the following steps to implement and configure a custom security rule to deny access to all HTTP REST endpoints, which creates or updates a node or relationship:

1. Open Spring-Neo4j/pom.xml and add the following dependency within the <dependencies> and </dependencies> tags:

2. <dependency>

3. <groupId>org.neo4j.app</groupId>

4. <artifactId>neo4j-server</artifactId>

5. <version>${neo4j.version}</version>

</dependency>

6. Create a new package org.neo4j.custom.security.rules and create a new class DenyCreateRequestSecurityRule.java within this package.

7. Add the following source code within DenyCreateRequestSecurityRule:

8. package org.neo4j.custom.security.rules;

9.

10.import javax.servlet.http.HttpServletRequest;

11.import org.neo4j.server.rest.security.SecurityFilter;

12.import org.neo4j.server.rest.security.SecurityRule;

13.

14.public class DenyCreateRequestSecurityRule implements SecurityRule{

15.

16. //Read about RFC - 2617 for more information about REALM -

17. //http://tools.ietf.org/html/rfc2617RFC 2617

18. public static final String REALM = "WallyWorld";

19.

20. @Override

21. public boolean isAuthorized(HttpServletRequest request) {

22. //Deny all other Type of Request except GET

23. if(request.getMethod().equalsIgnoreCase("GET")){

24. return true;

25. }

26. return false;

27. }

28.

29. @Override

30. public String forUriPath() {

31.

32. //This security rule will apply only on the URL which starts /db/data/

33. return "/db/data/*";

34. }

35.

36. @Override

37. public String wwwAuthenticateHeader() {

38. //Read about RFC - 2617 for more information about REALM -

39. //http://tools.ietf.org/html/rfc2617RFC 2617

40. return SecurityFilter.basicAuthenticationResponse(REALM);

41. }

}

The preceding rule implements SecurityRule and defines three methods:

· wwwAuthenticateHeader(): This method defines the security REALM and adds it to the security filter used by Neo4j for basic authentication.

· forUriPath(): This method defines the URL on which this security needs to be applied or evaluated.

· isAuthorized(): This is the main method that defines the custom logic for denying or approving the access to the specific URI requested by the user. For example, we have allowed only GET requests and denied access to all other types of HTTP requests.

42. Open your console, browse the root directory of your project, that is, Spring-Neo4j, and execute the following Maven command:

43.$M2_HOME/bin/mvn clean install

44. Now open <$Spring-Neo4j>/target/ and place the project's JAR file in the server's classpath or simply copy to <$NEO4J_HOME>/lib/.

45. Next, open <$NEO4J_HOME>/conf/neo4j-server.properties and add the following property to configure the security rule:

org.neo4j.server.rest.security_rules=org.neo4j.custom.security.rules.DenyCreateRequestSecurityRule

Note

We can define more than one rule by separating them with a comma (,).

46. Next, start your Neo4j server by executing <$NEO4J_HOME>/bin/neo4j start.

And we are done!!! Our first security rule is deployed and now each time a user executes an HTTP or REST request, our security rule will be evaluated, and based on the type of request, access to the server will be denied or approved.

47. Next, execute any REST request from SoapUI and the security rule will deny access to the application or Neo4j server for all other types of requests except GET.

48. Open SoapUI or any other tool for executing a GET REST request with the following configurations:

· Request method type: GET

· Request URL: https://localhost:7473/db/data/ or http://localhost:7474/db/data/

· Request headers: Accept: application/json; charset=UTF-8 and Content-Type: application/json

Execute the REST request and you will see results as a JSON response, as shown in the following screenshot:

Fine-grained authorization

49. Let's execute a POST request and see the results. Open SoapUI or any other tool for executing a POST REST request with the following configurations:

· Request method type: POST

· Request URL: https://localhost:7473/db/data/cypher

· Request headers: Accept: application/json; charset=UTF-8 and Content-Type: application/json

· JSON request: {"query" : "Create (x:restNode) return x","params" : {}}

50. Execute the REST request and you will not get any response as the request is denied by our custom security rule, as shown in the following screenshot:

Fine-grained authorization

API extensions – server plugins and unmanaged extensions

Neo4j provides flexibility to extend and add new functionality to the Neo4j server via custom server plugins and extensions. Let's discuss each of these features and see the process for implementing / deploying plugins and extensions:

· Server plugins: Server plugins are user-defined code or program routines that extend the capabilities of the database, nodes, or relationships. These plugins are then advertised as new REST endpoints to the end users.

· Unmanaged extensions: Neo4j also provides flexibility to develop and deploy unmanaged extensions, which provide full control over the exposed APIs. Unmanaged extensions are a way of deploying arbitrary JAX-RS code into the Neo4j server. If not used carefully, then it can be disastrous to an extent where a poorly written code can bring down the whole server.

Let's see sample code and examples for developing and deploying server plugins and unmanaged extensions.

Server plugins

Server plugins provide an architectural pattern exposed by Neo4j for extending its functionality and exposing the new REST endpoints for its consumers.

These new REST endpoints are automatically exposed and advertised in the representations and clients can discover the extension implemented by the plugins by performing a GET on the default database URI: http://localhost:7474/db/data/ orhttps://localhost:7473/db/data/.

Let's extend our Spring-Neo4j project and perform the following steps to implement a server plugin for traversing the graph from a given node on the movie dataset that we created in Chapter 3, Pattern Matching in Neo4j, under the Read-only Cypher queries section:

1. Remove the project's JAR file from <$NEO4J_HOME>/lib/, which we copied during our previous example stated in the Fine-grained authorization section.

2. Disable all custom security rules (if any). Open <$NEO4J_HOME>/conf/neo4j-server.properties and comment the entry, which starts with org.neo4j.server.rest.security_rules.

3. Create a new package, org.neo4j.custom.server.plugins, and define a new Java class GraphTraversal.java under this new package.

4. Add the following code in GraphTraversal.java and follow the comments provided in the code to understand the implementation logic:

5. package org.neo4j.custom.server.plugins;

6.

7. import java.util.ArrayList;

8. import java.util.List;

9. import org.neo4j.cypher.*;

10.import org.neo4j.graphdb.*;

11.import org.neo4j.graphdb.factory.Description;

12.import org.neo4j.graphdb.traversal.*;

13.import org.neo4j.kernel.impl.util.StringLogger;

14.import org.neo4j.server.plugins.*

15.import scala.collection.Iterator;

16.

17.@Description("An extension to the Neo4j Server for Traversing graph till 3 levels in all directions from a given Node")

18.public class GraphTraversal extends ServerPlugin {

19. @Name("graph_traversal")

20. @org.neo4j.server.plugins.Description("Traversing Graphs from a given Node")

21. @PluginTarget(GraphDatabaseService.class)

22. public Iterable<Path> executeTraversal(@Source GraphDatabaseService graphDb,

23. @org.neo4j.server.plugins.Description("Value of 'Name' property, considered as RootNode for searching ")

24. @Parameter(name = "name") String name) {

25. return startTraversing(graphDb, name);

26.

27. }

28.

29. public enum RelTypes implements RelationshipType {

30. ACTED_IN, DIRECTED

31. }

32.

33. private List<Path> startTraversing(GraphDatabaseService graphDb, String name) {

34. List<Path> allPaths = new ArrayList<Path>();

35. // Start a Transaction

36. try (Transaction tx = graphDb.beginTx()) {

37. // get the Traversal Descriptor from instance of Graph DB

38. TraversalDescription trvDesc = graphDb.traversalDescription();

39. // Defining Traversals needs to use Depth First Approach

40. trvDesc = trvDesc.depthFirst();

41. // Instructing to exclude the Start Position and include all

42. // other Nodes while Traversing

43. trvDesc = trvDesc.evaluator(Evaluators.excludeStartPosition());

44. // Defines the depth of the Traversals. Higher the Integer, more

45. // deep would be traversals. Default value would be to traverse //complete Tree

46. trvDesc = trvDesc.evaluator(Evaluators.toDepth(3));

47. //Define Uniqueness in visiting Nodes while Traversing

48. trvDesc = trvDesc.uniqueness(Uniqueness.NODE_GLOBAL);

49. //Traverse only specific type of relationship

50. trvDesc = trvDesc.relationships(RelTypes.ACTED_IN, Direction.BOTH);

51. // Get a Traverser from Descriptor

52. Traverser traverser = trvDesc.traverse(getStartNode(graphDb,name));

53.

54. // Let us get the Paths from Traverser and start iterating or

55. //moving along the Path

56. for (Path path : traverser) {

57. //Add Paths

58. allPaths.add(path);

59. }

60. //Return Paths

61. return allPaths;

62.}}

63.

64./**

65.* Get a Root Node as a Start Node for Traversal.

66.* @param graphDb

67.* @param name

68.* @return Root Node

69.*/

70.private Node getStartNode(GraphDatabaseService graphDb, String name) {

71. try (Transaction tx = graphDb.beginTx()) {

72. ExecutionEngine engine = new ExecutionEngine(graphDb, StringLogger.SYSTEM);

73. String cypherQuery = "match (n)-[r]->() where n.Name=\""+name+"\" return n as RootNode";

74. ExecutionResult result = engine.execute(cypherQuery);

75. Iterator<Object> iter = result.columnAs("RootNode");

76. //Check to see that we do not have empty Iterator

77. if(iter==null || iter.isEmpty()){

78. return null;

79. }

80. return (Node) iter.next();

81. }}

}

The preceding code extends ServerPlugin, which manages the lifecycle of our custom plugin and adds it as an extension to the already exposed REST endpoints.

There are a few important points that we need to remember or implement while creating the server plugin:

· We need to ensure that our plugin produces or returns an instance or list of node, relationship, or path, or an instance of org.neo4j.server.rest.repr.Representation that can be iterated. For example, the preceding code returns Iterable<Path>.

· It specifies request parameters using the @Parameter annotation. Similar to what we used in the preceding code @Parameter (name = "name"). You can skip this annotation if your service does not need any user inputs.

· Specify the point of extension as we specified in the preceding code using the @Name annotation.

· Specify the discovery point type in the @PluginTarget annotation and the @Source parameter, which are of the same type.

· Needless to mention, we also need to define the application logic, which in the preceding code is defined in the startTraversing method.

82. Add the META-INF and services directories under <$Spring-Neo4j>/src/main/resources.

83. Add a new file org.neo4j.server.plugins.ServerPlugin under <$Spring-Neo4j>/src/main/resources/META-INF/services. The content of this file should be the fully qualified name of your plugin: org.neo4j.custom.server.plugins.GraphTraversal.

We can add more plugins but every plugin needs to be defined on a new line.

84. Open the console, browse <$Spring-Neo4j>, and execute the following command to compile the code:

85.<$M2_HOME>/bin/mvn clean install

86. Copy the project's JAR file from <$Spring-Neo4j>/target/ and place it in <$NEO4J_HOME>/plugins/.

87. Stop your Neo4j server by pressing Ctrl + C (in case it is already running) and execute <$NEO4J_HOME>/bin/neo4j start and your Neo4j server will be up and running with your custom plugin.

88. Open SoapUI or any other tool for executing a GET REST request with the following configurations:

· Request method type: GET

· Request URL: https://localhost:7473/db/data/ or http://localhost:7474/db/data/

· Request headers: Accept: application/json; charset=UTF-8 and Content-Type: application/json

89. Execute the REST request and you will see that your plugin is listed as a new REST endpoint under <extensions>, as shown in the following screenshot:

Server plugins

90. Open your <$NEO4J_HOME>/bin/neo4j-shell and execute the following cypher statements to clean up your database:

91.match (x)-[r]-() delete r;

92.match (x) delete x;

93. Next, execute the Cypher queries given in Chapter 3, Pattern Matching in Neo4j, under the Read-only Cypher queries section for creating a sample movie dataset.

94. Open SoapUI or any other tool for executing a POST REST request with the following configurations:

· Request method type: POST

· Request URL: https://localhost:7473//db/data/ext/GraphTraversal/graphdb/graph_traversal

· Request headers: Accept: application/json; charset=UTF-8 and Content-Type: application/json

· JSON as requests parameter: {"name" : "Sylvester Stallone"}

95. Execute the REST request and you will see the results as a JSON response.

Unmanaged extensions

Unmanaged extensions provide fine-grained control over server-side code. It provides flexibility to deploy an arbitrary piece of JAX-RS code into your Neo4j server and then further exposes it as a new REST endpoint. We need to be careful while developing and deploying unmanaged extensions, as it can directly impact the performance of the server, in case of poorly written code.

Let's extend our Spring-Neo4j project and perform the following steps to implement unmanaged extensions to get all the node IDs from the Neo4j database:

1. Create a new package org.neo4j.custom.server.extension.unmanaged and define a new Java class GetAllNodes.java under this new package.

2. Add the following code in GetAllNodes.java and follow the comments provided in the code to understand the implementation logic:

3. package org.neo4j.custom.server.extension.unmanagaed;

4.

5. import java.nio.charset.Charset;

6. import java.util.HashMap;

7. import java.util.Map;

8. import javax.ws.rs.*;

9. import javax.ws.rs.core.*;

10.import javax.ws.rs.core.Response.*;

11.

12.import org.neo4j.graphdb.*;

13.import org.neo4j.shell.util.json.JSONObject;

14.import org.neo4j.tooling.GlobalGraphOperations;

15.

16.//Context Path for exposing it as REST endpoint

17.@Path("/getAllNodes")

18.public class GetAllNodes {

19.

20. private final GraphDatabaseService graphDb;

21.

22. //Injected by the Neo4j Server

23. public GetAllNodes(@Context GraphDatabaseService graphDb) {

24. this.graphDb = graphDb;

25. }

26.

27. //Implementation Logic for exposing this as a GET response

28. @GET

29. @Produces(MediaType.APPLICATION_JSON)

30. public Response nodeIDs() {

31. Map<String, String> nodeIDs = new HashMap<String,String>();

32.

33. try (Transaction tx = graphDb.beginTx()) {

34. //Get all Node ID's and put them into the MAP.

35. //Key of Map needs to be appended by Node_ID, so that every entry is Unique

36. for (Node node : GlobalGraphOperations.at(graphDb).getAllNodes()) {

37. nodeIDs.put("Node_ID_"+Long.toString(node.getId()),Long.toString(node.getId()));

38. }

39. tx.success();

40. }

41. //Converting the Map Object into JSON String

42. JSONObject jsonObj = new JSONObject(nodeIDs);

43.

44. //Returning a Success Status, along with the JSON response.

45. return Response.status(Status.OK)

46. .entity(jsonObj.toString().getBytes(Charset

47. .forName("UTF-8"))).build();

48. }

}

The preceding code uses the @Context annotation to inject the dependency of GraphDatabaseService, which fetches all the nodes from the Neo4j database and wraps them into a JSON object. Finally, the code transforms JSON into string and returns it back to the user as a response.

49. Open the console, browse <$Spring-Neo4j>, and execute the following command to compile the code:

50.<$M2_HOME>/bin/mvn clean install

51. You will see the project's JAR file in <$Spring-Neo4j>/target/. Copy the JAR file and place it in <$NEO4J_HOME>/plugins/.

52. Open <$NEO4J_HOME>/conf/neo4j-server.properties and add the following configuration:

org.neo4j.server.thirdparty_jaxrs_classes=org.neo4j.custom.server.extension.unmanagaed=/examples/unmanaged

The preceding configuration defines the package name, which contains your unmanaged extensions (org.neo4j.custom.server.extension.unmanaged) and the extension point (/examples/unmanaged), which will be exposed as a new REST endpoint (/examples/unmanaged/getAllNodes).

53. Stop your Neo4j server by pressing Ctrl + C (in case it is already running) and execute <$NEO4J_HOME>/bin/neo4j start. Now your Neo4j server will be up and running with your unmanaged extension.

54. Open SoapUI or any other tool and execute the GET REST request with the following configurations:

· Request method type: GET

· Request URL: https://localhost:7473/examples/unmanaged/getAllNodes or http://localhost:7474/examples/unmanaged/getAllNodes

· Request headers: Accept: application/json; charset=UTF-8 and Content-Type: application/json

55. Execute the REST request and you will see that the request is successful and returns a JSON response containing the node IDs, as shown in the following screenshot:

Unmanaged extensions

Summary

In this chapter, we discussed various security aspects of the Neo4j server. We talked about the threats and procedures/methods to secure our Neo4j deployment. We also talked about the flexibility provided by Neo4j for extending its functionality by implementing plugins and extensions.