diff --git a/docs/clustering/how-to-cluster.md b/docs/clustering/how-to-cluster.md new file mode 100644 index 00000000..f3b6041c --- /dev/null +++ b/docs/clustering/how-to-cluster.md @@ -0,0 +1,473 @@ +# How to Cluster + +To create a cluster you must have two or more nodes* (aka instances) of HarperDB running. + +**A node is a single instance/installation of HarperDB. A node of HarperDB can operate independently with clustering on or off.* + + +Below are the steps, in order, that should be taken to set up a HarperDB cluster. + +--- + +### Creating a Cluster User + +Inter-node authentication takes place via HarperDB users. There is a special role type called `cluster_user` that exists by default and limits the user to only clustering functionality. + +A `cluster_user` must be created and added to the `harperdb-config.yaml` file for clustering to be enabled. + +All nodes that are intended to be clustered together need to share the same `cluster_user` credentials (i.e. username and password). + +There are multiple ways a `cluster_user` can be created, they are: + +1) Through the operations API by calling `add_user` + +```json +{ + "operation": "add_user", + "role": "cluster_user", + "username": "cluster_account", + "password": "letsCluster123!", + "active": true +} +``` + + +When using the API to create a cluster user the `harperdb-config.yaml` file must be updated with the username of the new cluster user. + +This can be done through the API by calling `set_configuration` or by editing the `harperdb-config.yaml` file. + +```json +{ + "operation": "set_configuration", + "clustering_user": "cluster_account" +} +``` + + +In the `harperdb-config.yaml` file under the top-level `clustering` element there will be a user element. Set this to the name of the cluster user. + +```yaml +clustering: + user: cluster_account +``` + + +_Note: When making any changes to the `harperdb-config.yaml` file, HarperDB must be restarted for the changes to take effect._ + +2) Upon installation using **command line variables**. This will automatically set the user in the `harperdb-config.yaml` file. + +_Note: Using command line or environment variables for setting the cluster user only works on install._ + +``` +harperdb install --CLUSTERING_USER cluster_account --CLUSTERING_PASSWORD letsCluster123! +``` + +3) Upon installation using **environment variables**. This will automatically set the user in the `harperdb-config.yaml` file. + +``` +CLUSTERING_USER=cluster_account CLUSTERING_PASSWORD=letsCluster123 +``` + +--- + +### Naming a Node +Node name is the name given to a node. It is how nodes are identified within the cluster and must be unique to the cluster. + +The name cannot contain any of the following characters: `.,*>` . Dot, comma, asterisk, greater than, or whitespace. + +The name is set in the `harperdb-config.yaml` file using the `clustering.nodeName` configuration element. + +_Note: If you want to change the node name make sure there are no subscriptions in place before doing so. After the name has been changed a full restart is required._ + +There are multiple ways to update this element, they are: + +1) Directly editing the `harperdb-config.yaml` file. + +```yaml +clustering: + nodeName: Node1 +``` + +_Note: When making any changes to the `harperdb-config.yaml` file HarperDB must be restarted for the changes to take effect._ + +2) Calling `set_configuration` through the operations API + +```json +{ + "operation": "set_configuration", + "clustering_nodeName":"Node1" +} +``` + +3) Using command line variables. + +``` +harperdb --CLUSTERING_NODENAME Node1 +``` + +4) Using environment variables. + +``` +CLUSTERING_NODENAME=Node1 +``` + +--- + +### Enabling Clustering + +Clustering does not run by default, so it needs to be enabled. + +To enable clustering the `clustering.enabled` configuration element in the `harperdb-config.yaml` file must be set to `true`. + +There are multiple ways to update this element, they are: + +1) Directly editing the `harperdb-config.yaml` file and setting enabled to `true` +```yaml +clustering: + enabled: true +``` + +_Note: when making any changes to the `harperdb-config.yaml` file HarperDB must be restarted for the changes to take effect._ + +2) Calling `set_configuration` through the operations API + +```json +{ + "operation": "set_configuration", + "clustering_enabled":"test_server" +} +``` + +_Note: When making any changes to HarperDB configuration HarperDB must be restarted for the changes to take effect._ + + +3) Using **command line variables**. + +``` +harperdb --CLUSTERING_ENABLED true +``` + +4) Using **environment variables**. + +``` +CLUSTERING_ENABLED=true +``` + +An efficient way to **install HarperDB**, **create the cluster user**, **set the node name** and **enable clustering** in one operation is to combine the steps using command line and/or environment variables. Here is an example using command line variables. + +``` +harperdb install --CLUSTERING_ENABLED true --CLUSTERING_NODENAME Node1 --CLUSTERING_USER cluster_account --CLUSTERING_PASSWORD letsCluster123! +``` + +--- + +### Understanding Routes + +A route is a connection between two nodes. It is how the clustering network is established. + +Routes do not need to cross connect all nodes in the cluster. You can select a leader node or a few leaders and all nodes connect to them, you can chain, etc… As long as there is one route connecting a node to the cluster all other nodes should be able to reach that node. + +Using routes the clustering servers will create a mesh network between nodes. This mesh network ensures that if a node drops out all other nodes can still communicate with each other. That being said, we recommend designing your routing with failover in mind, this means not storing all your routes on one node but dispersing them throughout the network. + +A simple route example is a two node topology, if Node1 adds a route to connect it to Node2, Node2 does not need to add a route to Node1. That one route configuration is all that’s needed to establish a bidirectional connection between the nodes. + +A route consists of a `port` and a `host`. + +`port` - the clustering port of the remote instance you are creating the connection with. This is going to be the `clustering.hubServer.cluster.network.port` in the HarperDB configuration on the node you are connecting with. + +`host` - the host of the remote instance you are creating the connection with.This can be an IP address or a URL. + +Routes are set in the `harperdb-config.yaml` file using the `clustering.hubServer.cluster.network.routes` element, which expects an object array, where each object has two properties, `port` and `host`. + +```yaml +clustering: + hubServer: + cluster: + network: + routes: + - host: 3.62.184.22 + port: 9932 + - host: 3.735.184.8 + port: 9932 +``` + +![figure 1](/Users/terraroush/documentation/images/clustering/figure1.png "diagram displaying a three node cluster") + +This diagram shows one way of using routes to connect a network of nodes. Node2 and Node3 do not reference any routes in their config. Node1 contains routes for Node2 and Node3, which is enough to establish a network between all three nodes. + +There are multiple ways to set routes, they are: + +1) Directly editing the `harperdb-config.yaml` file (refer to code snippet above). +2) Calling `cluster_set_routes` through the API. + +```json +{ + "operation": "cluster_set_routes", + "server": "hub", + "routes":[ {"host": "3.735.184.8", "port": 9932} ] +} +``` + +_Note: When making any changes to HarperDB configuration HarperDB must be restarted for the changes to take effect._ + +3) From the command line. +```bash +--CLUSTERING_HUBSERVER_CLUSTER_NETWORK_ROUTES "[{\"host\": \"3.735.184.8\", \"port\": 9932}]" +``` + +4) Using environment variables. + +```bash +CLUSTERING_HUBSERVER_CLUSTER_NETWORK_ROUTES=[{"host": "3.735.184.8", "port": 9932}] +``` + +The API also has `cluster_get_routes` for getting all routes in the config and `cluster_delete_routes` for deleting routes. +```json +{ + "operation": "cluster_delete_routes", + "routes":[ {"host": "3.735.184.8", "port": 9932} ] +} +``` + +--- + +### Subscriptions + +A subscription defines how data should move between two nodes. They are exclusively table level and operate independently. They connect a table on one node to a table on another node, the subscription will apply to a matching schema name and table name on both nodes. + +_Note: ‘local’ and ‘remote’ will often be referred to. In the context of these docs ‘local’ is the node that is receiving the API request to create/update a subscription and remote is the other node that is referred to in the request, the node on the other end of the subscription._ + +A subscription consists of: + +`schema` - the name of the schema that the table you are creating the subscription for belongs to. +`table` - the name of the table the subscription will apply to. +`publish` - a boolean which determines if transactions on the local table should be replicated on the remote table. +`subscribe` - a boolean which determines if transactions on the remote table should be replicated on the local table. + +#### Publish subscription + +![figure 2](/Users/terraroush/documentation/images/clustering/figure2.png "diagram example of a publish subscription from the perspective of Node1") + +This diagram is an example of a `publish` subscription from the perspective of Node1. + +The record with id 2 has been inserted in the dog table on Node1, after it has completed that insert it is sent to Node 2 and inserted in the dog table there. + +#### Subscribe subscription + +![figure 3](/Users/terraroush/documentation/images/clustering/figure3.png "diagram example of a subscribe subscription from the perspective of Node1") + +This diagram is an example of a `subscribe` subscription from the perspective of Node1. + +The record with id 3 has been inserted in the dog table on Node2, after it has completed that insert it is sent to Node1 and inserted there. + +#### Subscribe and Publish + +![figure 4](/Users/terraroush/documentation/images/clustering/figure4.png "diagram shows both subscribe and publish but publish is set to false") + +This diagram shows both subscribe and publish but publish is set to false. You can see that because subscribe is true the insert on Node2 is being replicated on Node1 but because publish is set to false the insert on Node1 is **_not_** being replicated on Node2. + +![figure 5](/Users/terraroush/documentation/images/clustering/figure5.png "shows both subscribe and publish set to true") + +This shows both subscribe and publish set to true. The insert on Node1 is replicated on Node2 and the update on Node2 is replicated on Node1. + +### Creating subscriptions + +Subscriptions can be added, updated, or removed through the API. + +_Note: The schema and tables in the subscription must exist on either the local or the remote node. Any schema and tables that do not exist on one particular node, for example, the local node, will be automatically created on the local node._ + +To add a single node and create one or more subscriptions use `add_node`. + +```json +{ + "operation": "add_node", + "node_name": "Node2", + "subscriptions": [ + { + "schema": "dev", + "table": "dog", + "publish": false, + "subscribe": true + }, + { + "schema": "dev", + "table": "chicken", + "publish": true, + "subscribe": true + } + ] +} +``` + +This is an example of adding Node2 to your local node. Subscriptions are created for two tables, dog and chicken. + +To update one or more subscriptions with a single node use `update_node`. + +```json +{ + "operation": "update_node", + "node_name": "Node2", + "subscriptions": [ + { + "schema": "dev", + "table": "dog", + "publish": true, + "subscribe": true + } + ] +} +``` + +This call will update the subscription with the dog table. Any other subscriptions with Node2 will not change. + +To add or update subscriptions with one or more nodes in one API call use `configure_cluster`. + + +```json +{ + "operation": "configure_cluster", + "connections": [ + { + "node_name": "Node2", + "subscriptions": [ + { + "schema": "dev", + "table": "chicken", + "publish": false, + "subscribe": true + }, + { + "schema": "prod", + "table": "dog", + "publish": true, + "subscribe": true + } + ] + }, + { + "node_name": "Node3", + "subscriptions": [ + { + "schema": "dev", + "table": "chicken", + "publish": true, + "subscribe": false + } + ] + } + ] +} +``` + +_Note: `configure_cluster` will override **any and all** existing subscriptions defined on the local node. This means that before going through the connections in the request and adding the subscriptions, it will first go through **all existing subscriptions the local node has** and remove them. To get all existing subscriptions use `cluster_status`._ + +#### Start time + +There is an optional property called `start_time` that can be passed in the subscription. This property accepts an ISO formatted UTC date. + +`start_time` can be used to set from what time you would like to source transactions from a table when creating or updating a subscription. + +```json +{ + "operation": "add_node", + "node_name": "Node2", + "subscriptions": [ + { + "schema": "dev", + "table": "dog", + "publish": false, + "subscribe": true, + "start_time": "2022-09-02T20:06:35.993Z" + } + ] +} +``` + +This example will get all transactions on Node2’s dog table starting from `2022-09-02T20:06:35.993Z` and replicate them locally on the dog table. + +If no start time is passed it defaults to the current time. + +_Note: start time utilizes clustering to back source transactions. For this reason it can only source transactions that occurred when clustering was enabled._ + +#### Remove node + +To remove a node and all its subscriptions use `remove_node`. + +```json +{ + "operation":"remove_node", + "node_name":"Node2" +} +``` + +#### Cluster status + +To get the status of all connected nodes and see their subscriptions use `cluster_status`. + +```json +{ + "node_name": "Node1", + "is_enabled": true, + "connections": [ + { + "node_name": "Node2", + "status": "open", + "ports": { + "clustering": 9932, + "operations_api": 9925 + }, + "latency_ms": 65, + "uptime": "11m 19s", + "subscriptions": [ + { + "schema": "dev", + "table": "dog", + "publish": true, + "subscribe": true + } + ], + "system_info": { + "hdb_version": "4.0.0", + "node_version": "16.17.1", + "platform": "linux" + } + } + ] +} +``` + +#### Transactions + +Transactions that are replicated across the cluster are: +* Insert +* Update +* Upsert +* Delete +* Bulk loads + * CSV data load + + * CSV file load + + * CSV URL load + + * Import from S3 + +When adding or updating a node any schemas and tables in the subscription that don’t exist on the remote node will be automatically created. + +**Destructive schema operations do not replicate across a cluster**. Those operations include `drop_schema`, `drop_table`, and `drop_attribute`. If the desired outcome is to drop schema information from any nodes then the operation(s) will need to be run on each node independently. + +Users and roles are not replicated across the cluster. + +--- + +### Queueing + +HarperDB has built-in resiliency for when network connectivity is lost within a subscription. When connections are reestablished, a catchup routine is executed to ensure data that was missed, specific to the subscription, is sent/received as defined. + +--- + +### Topologies + +HarperDB clustering creates a mesh network between nodes giving end users the ability to create an infinite number of topologies. subscription topologies can be simple or as complex as needed. + +![figure 6](/Users/terraroush/documentation/images/clustering/figure6.png "example topology of mesh network") diff --git a/docs/clustering/index.md b/docs/clustering/index.md index b6ea4d87..84246211 100644 --- a/docs/clustering/index.md +++ b/docs/clustering/index.md @@ -1 +1,36 @@ # Clustering + +HarperDB clustering is the process of connecting multiple HarperDB databases together to create a database mesh network that enables users to define data replication patterns. + +HarperDB’s clustering engine replicates data between instances of HarperDB using a highly performant, bi-directional pub/sub model on a per-table basis. Data replicates asynchronously with eventual consistency across the cluster following the defined pub/sub configuration. Individual transactions are sent in the order in which they were transacted, once received by the destination instance, they are processed in an ACID-compliant manor. Conflict resolution follows a last writer wins model based on recorded transaction time on the transaction and the timestamp on the record on the node. + +--- +### Common Use Case + +A common use case is an edge application collecting and analyzing sensor data that creates an alert if a sensor value exceeds a given threshold: + +* The edge application should not be making outbound http requests for security purposes. + +* There may not be a reliable network connection. + +* Not all sensor data will be sent to the cloud--either because of the unreliable network connection, or maybe it’s just a pain to store it. + +* The edge node should be inaccessible from outside the firewall. + +* The edge node will send alerts to the cloud with a snippet of sensor data containing the offending sensor readings. + + +HarperDB simplifies the architecture of such an application with its bi-directional, table-level replication: + +* The edge instance subscribes to a “thresholds” table on the cloud instance, so the application only makes localhost calls to get the thresholds. + +* The application continually pushes sensor data into a “sensor_data” table via the localhost API, comparing it to the threshold values as it does so. + +* When a threshold violation occurs, the application adds a record to the “alerts” table. + +* The application appends to that record array “sensor_data” entries for the 60 seconds (or minutes, or days) leading up to the threshold violation. + +* The edge instance publishes the “alerts” table up to the cloud instance. + + +By letting HarperDB focus on the fault-tolerant logistics of transporting your data, you get to write less code. By moving data only when and where it’s needed, you lower storage and bandwidth costs. And by restricting your app to only making local calls to HarperDB, you reduce the overall exposure of your application to outside forces. \ No newline at end of file diff --git a/docs/harperdb-cloud/index.md b/docs/harperdb-cloud/index.md index 8089fc60..cfa4d3ca 100644 --- a/docs/harperdb-cloud/index.md +++ b/docs/harperdb-cloud/index.md @@ -1,9 +1,2 @@ # HarperDB Cloud -HarperDB Cloud is the easiest way to test drive HarperDB, it’s HarperDB-as-a-Service. Cloud handles deployment and management of your instances in just a few clicks. HarperDB Cloud is currently powered by AWS with additional cloud providers on our roadmap for the future. - - - -HarperDB Cloud is managed within the [HarperDB Studio](https://harperdb.io/developers/documentation/harperdb-studio/). It’s free to sign up, and you even get a free instance- forever! - - diff --git a/docs/harperdb-studio/index.md b/docs/harperdb-studio/index.md index d816966a..6db82fe6 100644 --- a/docs/harperdb-studio/index.md +++ b/docs/harperdb-studio/index.md @@ -5,7 +5,7 @@ HarperDB Studio is the web-based GUI for HarperDB. Studio enables you to adminis --- ## How does Studio Work? -While HarperDB Studio is web based and hosted by us, all database interactions are performed locally. The HarperDB Studio loads in your browser, at which point you login to your HarperDB instances. Credentials are stored in your browser cache and are not transmitted back to HarperDB. All database interactions are made via the HarperDB REST API directly from your browser to your instance. +While HarperDB Studio is web based and hosted by us, all database interactions are performed locally. The HarperDB Studio loads in your browser, at which point you login to your HarperDB instances. Credentials are stored in your browser cache and are not transmitted back to HarperDB. All database interactions are made via the HarperDB Operations API directly from your browser to your instance. ## What type of instances can I manage? HarperDB Studio enables users to manage both HarperDB Cloud instances and privately hosted instances all from a single UI. All HarperDB instances feature identical behavior whether they are hosted by us or by you. \ No newline at end of file diff --git a/docs/security/basic-auth.md b/docs/security/basic-auth.md new file mode 100644 index 00000000..9cf0678b --- /dev/null +++ b/docs/security/basic-auth.md @@ -0,0 +1,64 @@ +# Authentication + +HarperDB uses Basic Auth and JSON Web Tokens (JWTs) to secure our HTTP requests. In the context of an HTTP transaction, **basic access authentication** is a method for an HTTP user agent to provide a user name and password when making a request. + + + +** ***You do not need to log in separately. Basic Auth is added to each HTTP request like create_schema, create_table, insert etc… via headers.*** ** + + + +A header is added to each HTTP request. The header key is **“Authorization”** the header value is **“Basic <>”** + + + + + +## Authentication in HarperDB Studio + +Below is a code sample from HarperDB Studio. On Line 14 you will see where we are adding the authorization header to each request. This needs to be added for each and every HTTP request for HarperDB. + +```javascript +function callHarperDB(call_object, operation, callback){ + + const options = { + "method": "POST", + "hostname": call_object.endpoint_url, + "port": call_object.endpoint_port, + "path": "/", + "headers": { + "content-type": "application/json", + "authorization": "Basic " + btoa(call_object.username + ':' + call_object.password), + "cache-control": "no-cache" + + } + }; + + const http_req = http.request(options, function (hdb_res) { + let chunks = []; + + hdb_res.on("data", function (chunk) { + chunks.push(chunk); + }); + + hdb_res.on("end", function () { + const body = Buffer.concat(chunks); + if (isJson(body)) { + return callback(null, JSON.parse(body)); + } else { + return callback(body, null); + + } + + }); + }); + + http_req.on("error", function (chunk) { + return callback("Failed to connect", null); + }); + + http_req.write(JSON.stringify(operation)); + http_req.end(); + +} +``` \ No newline at end of file diff --git a/docs/security/configuration.md b/docs/security/configuration.md new file mode 100644 index 00000000..708cd573 --- /dev/null +++ b/docs/security/configuration.md @@ -0,0 +1,57 @@ +# Configuration + +HarperDB was set up to require very minimal configuration to work out of the box. There are, however, some best practices we encourage for anyone building an app with HarperDB. + + + +## CORS + +HarperDB allows for managing [cross-origin HTTP requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). By default, HarperDB enables CORS for all domains if you need to disable CORS completely or set up an access list of domains you can do the following: + +1) Open the harperdb-config.yaml file this can be found in /, the location you specified during install. + +2) In harperdb-config.yaml there should be 2 entries under `operationsApi.network`: cors and corsAccessList. + * `cors` + + 1) To turn off, change to: `cors: false` + + 2) To turn on, change to: `cors: true` + + * `corsAccessList` + + 1) The `corsAccessList` will only be recognized by the system when `cors` is `true` + + 2) To create an access list you set `corsAccessList` to a comma-separated list of domains. + + i.e. `corsAccessList` is `http://harperdb.io,http://products.harperdb.io` + + 3) To clear out the access list and allow all domains: `corsAccessList` is `[null]` + + +## SSL + +HarperDB provides the option to use an HTTP or HTTPS interface. The default port for the server is 9925. + + + +These default ports can be changed by updating the `operationsApi.network.port` value in `/harperdb-config.yaml` + + + +By default, HTTPS is turned off and HTTP is turned on. It is recommended that you never directly expose HarperDB's HTTP interface through a publicly available port. HTTP is intended for local or private network use. + + + +You can toggle HTTPS and HTTP in the settings file. By setting `operationsApi.network.https` to true/false. When `https` is set to `false`, the server will use HTTP. + + + +HarperDB automatically generates a certificate and a privateKey file which live at `/keys/`. + + + +You can replace these with your own certificate and key. + + + +**If any of these settings are changed please make sure to run `harperdb restart` as they will not take effect until a restart.** \ No newline at end of file diff --git a/docs/security/index.md b/docs/security/index.md index 8dbb2f9b..9bba60da 100644 --- a/docs/security/index.md +++ b/docs/security/index.md @@ -1 +1,9 @@ # Security + +HarperDB uses role-based, attribute-level security to ensure that users can only gain access to the data they’re supposed to be able to access. Our granular permissions allow for unparalleled flexibility and control, and can actually lower the total cost of ownership compared to other database solutions, since you no longer have to replicate subsets of your data to isolate use cases. + +* [JWT Authentication](https://harperdb.io/docs/security/jwt-authentication/) +* [Basic Authentication](https://harperdb.io/docs/security/authentication/) +* [Configuration](https://harperdb.io/docs/security/configuration/) +* [Users and Roles](https://harperdb.io/docs/security/users-roles/) + diff --git a/docs/security/jwt-auth.md b/docs/security/jwt-auth.md new file mode 100644 index 00000000..a7ff19ea --- /dev/null +++ b/docs/security/jwt-auth.md @@ -0,0 +1,93 @@ +# JWT Authentication +HarperDB uses token based authentication with JSON Web Tokens, JWTs. + +This consists of two primary operations `create_authentication_tokens` and `refresh_operation_token`. These generate two types of tokens, as follows: + +* The `operation_token` which is used to authenticate all HarperDB operations in the Bearer Token Authorization Header. The default expiry is one day. + +* The `refresh_token` which is used to generate a new `operation_token` upon expiry. This token is used in the Bearer Token Authorization Header for the `refresh_operation_token` operation only. The default expiry is thirty days. + +The `create_authentication_tokens` operation can be used at any time to refresh both tokens in the event that both have expired or been lost. + +## Create Authentication Tokens + +Users must initially create tokens using their HarperDB credentials. The following POST body is sent to HarperDB. No headers are required for this POST operation. + +```json +{ + "operation": "create_authentication_tokens", + "username": "username", + "password": "password" +} +``` + +A full cURL example can be seen here: + +```bash +curl --location --request POST 'http://localhost:9925' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "operation": "create_authentication_tokens", + "username": "username", + "password": "password" +}' +``` + +An example expected return object is: + +```json +{ + "operation_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjA0OTc4MjAwLCJleHAiOjE2MDUwNjQ2MDAsInN1YiI6Im9wZXJhdGlvbiJ9.MpQA-9CMjA-mn-7mHyUXSuSC_-kqMqJXp_NDiKLFtbtMRbodCuY3DzH401rvy_4vb0yCELf0B5EapLVY1545sv80nxSl6FoZFxQaDWYXycoia6zHpiveR8hKlmA6_XTWHJbY2FM1HAFrdtt3yUTiF-ylkdNbPG7u7fRjTmHfsZ78gd2MNWIDkHoqWuFxIyqk8XydQpsjULf2Uacirt9FmHfkMZ-Jr_rRpcIEW0FZyLInbm6uxLfseFt87wA0TbZ0ofImjAuaW_3mYs-3H48CxP152UJ0jByPb0kHsk1QKP7YHWx1-Wce9NgNADfG5rfgMHANL85zvkv8sJmIGZIoSpMuU3CIqD2rgYnMY-L5dQN1fgfROrPMuAtlYCRK7r-IpjvMDQtRmCiNG45nGsM4DTzsa5GyDrkGssd5OBhl9gr9z9Bb5HQVYhSKIOiy72dK5dQNBklD4eGLMmo-u322zBITmE0lKaBcwYGJw2mmkYcrjDOmsDseU6Bf_zVUd9WF3FqwNkhg4D7nrfNSC_flalkxPHckU5EC_79cqoUIX2ogufBW5XgYbU4WfLloKcIpb51YTZlZfwBHlHPSyaq_guaXFaeCUXKq39_i1n0HRF_mRaxNru0cNDFT9Fm3eD7V8axFijSVAMDyQs_JR7SY483YDKUfN4l-vw-EVynImr4", + "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjA0OTc4MjAwLCJleHAiOjE2MDc1NzAyMDAsInN1YiI6InJlZnJlc2gifQ.acaCsk-CJWIMLGDZdGnsthyZsJfQ8ihXLyE8mTji8PgGkpbwhs7e1O0uitMgP_pGjHq2tey1BHSwoeCL49b18WyMIB10hK-q2BXGKQkykltjTrQbg7VsdFi0h57mGfO0IqAwYd55_hzHZNnyJMh4b0iPQFDwU7iTD7x9doHhZAvzElpkWbc_NKVw5_Mw3znjntSzbuPN105zlp4Niurin-_5BnukwvoJWLEJ-ZlF6hE4wKhaMB1pWTJjMvJQJE8khTTvlUN8tGxmzoaDYoe1aCGNxmDEQnx8Y5gKzVd89sylhqi54d2nQrJ2-ElfEDsMoXpR01Ps6fNDFtLTuPTp7ixj8LvgL2nCjAg996Ga3PtdvXJAZPDYCqqvaBkZZcsiqOgqLV0vGo3VVlfrcgJXQImMYRr_Inu0FCe47A93IAWuQTs-KplM1KdGJsHSnNBV6oe6QEkROJT5qZME-8xhvBYvOXqp9Znwg39bmiBCMxk26Ce66_vw06MNgoa3D5AlXPWemfdVKPZDnj_aLVjZSs0gAfFElcVn7l9yjWJOaT2Muk26U8bJl-2BEq_DSclqKHODuYM5kkPKIdE4NFrsqsDYuGxcA25rlNETFyl0q-UXj1aoz_joy5Hdnr4mFELmjnoo4jYQuakufP9xeGPsj1skaodKl0mmoGcCD6v1F60" +} +``` + +## Using JWT Authentication Tokens + +The `operation_token` value is used to authenticate all operations in place of our standard Basic auth. In order to pass the token you will need to create an Bearer Token Authorization Header like the following request: + +```bash +curl --location --request POST 'http://localhost:9925' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjA0OTc4MjAwLCJleHAiOjE2MDUwNjQ2MDAsInN1YiI6Im9wZXJhdGlvbiJ9.MpQA-9CMjA-mn-7mHyUXSuSC_-kqMqJXp_NDiKLFtbtMRbodCuY3DzH401rvy_4vb0yCELf0B5EapLVY1545sv80nxSl6FoZFxQaDWYXycoia6zHpiveR8hKlmA6_XTWHJbY2FM1HAFrdtt3yUTiF-ylkdNbPG7u7fRjTmHfsZ78gd2MNWIDkHoqWuFxIyqk8XydQpsjULf2Uacirt9FmHfkMZ-Jr_rRpcIEW0FZyLInbm6uxLfseFt87wA0TbZ0ofImjAuaW_3mYs-3H48CxP152UJ0jByPb0kHsk1QKP7YHWx1-Wce9NgNADfG5rfgMHANL85zvkv8sJmIGZIoSpMuU3CIqD2rgYnMY-L5dQN1fgfROrPMuAtlYCRK7r-IpjvMDQtRmCiNG45nGsM4DTzsa5GyDrkGssd5OBhl9gr9z9Bb5HQVYhSKIOiy72dK5dQNBklD4eGLMmo-u322zBITmE0lKaBcwYGJw2mmkYcrjDOmsDseU6Bf_zVUd9WF3FqwNkhg4D7nrfNSC_flalkxPHckU5EC_79cqoUIX2ogufBW5XgYbU4WfLloKcIpb51YTZlZfwBHlHPSyaq_guaXFaeCUXKq39_i1n0HRF_mRaxNru0cNDFT9Fm3eD7V8axFijSVAMDyQs_JR7SY483YDKUfN4l-vw-EVynImr4' \ +--data-raw '{ + "operation":"search_by_hash", + "schema":"dev", + "table":"dog", + "hash_values":[1], + "get_attributes": ["*"] +}' +``` + +## Token Expiration + +`operation_token` expires at a set interval. Once it expires it will no longer be accepted by HarperDB. This duration defaults to one day, and is configurable in [harperdb-config.yaml](https://harperdb.io/docs/reference/configuration-file/). To generate a new `operation_token`, the `refresh_operation_token` operation is used, passing the `refresh_token` in the Bearer Token Authorization Header. A full cURL example can be seen here: + +```bash +curl --location --request POST 'http://localhost:9925' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXJuYW1lIiwiaWF0IjoxNjA0OTc4MjAwLCJleHAiOjE2MDc1NzAyMDAsInN1YiI6InJlZnJlc2gifQ.acaCsk-CJWIMLGDZdGnsthyZsJfQ8ihXLyE8mTji8PgGkpbwhs7e1O0uitMgP_pGjHq2tey1BHSwoeCL49b18WyMIB10hK-q2BXGKQkykltjTrQbg7VsdFi0h57mGfO0IqAwYd55_hzHZNnyJMh4b0iPQFDwU7iTD7x9doHhZAvzElpkWbc_NKVw5_Mw3znjntSzbuPN105zlp4Niurin-_5BnukwvoJWLEJ-ZlF6hE4wKhaMB1pWTJjMvJQJE8khTTvlUN8tGxmzoaDYoe1aCGNxmDEQnx8Y5gKzVd89sylhqi54d2nQrJ2-ElfEDsMoXpR01Ps6fNDFtLTuPTp7ixj8LvgL2nCjAg996Ga3PtdvXJAZPDYCqqvaBkZZcsiqOgqLV0vGo3VVlfrcgJXQImMYRr_Inu0FCe47A93IAWuQTs-KplM1KdGJsHSnNBV6oe6QEkROJT5qZME-8xhvBYvOXqp9Znwg39bmiBCMxk26Ce66_vw06MNgoa3D5AlXPWemfdVKPZDnj_aLVjZSs0gAfFElcVn7l9yjWJOaT2Muk26U8bJl-2BEq_DSclqKHODuYM5kkPKIdE4NFrsqsDYuGxcA25rlNETFyl0q-UXj1aoz_joy5Hdnr4mFELmjnoo4jYQuakufP9xeGPsj1skaodKl0mmoGcCD6v1F60' \ +--data-raw '{ + "operation":"refresh_operation_token" +}' +``` + +This will return a new `operation_token`. An example expected return object is: + +```bash +{ + "operation_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6eyJfX2NyZWF0ZWR0aW1lX18iOjE2MDQ5NzgxODkxNTEsIl9fdXBkYXRlZHRpbWVfXyI6MTYwNDk3ODE4OTE1MSwiYWN0aXZlIjp0cnVlLCJyb2xlIjp7Il9fY3JlYXRlZHRpbWVfXyI6MTYwNDk0NDE1MTM0NywiX191cGRhdGVkdGltZV9fIjoxNjA0OTQ0MTUxMzQ3LCJpZCI6IjdiNDNlNzM1LTkzYzctNDQzYi05NGY3LWQwMzY3Njg5NDc4YSIsInBlcm1pc3Npb24iOnsic3VwZXJfdXNlciI6dHJ1ZSwic3lzdGVtIjp7InRhYmxlcyI6eyJoZGJfdGFibGUiOnsicmVhZCI6dHJ1ZSwiaW5zZXJ0IjpmYWxzZSwidXBkYXRlIjpmYWxzZSwiZGVsZXRlIjpmYWxzZSwiYXR0cmlidXRlX3Blcm1pc3Npb25zIjpbXX0sImhkYl9hdHRyaWJ1dGUiOnsicmVhZCI6dHJ1ZSwiaW5zZXJ0IjpmYWxzZSwidXBkYXRlIjpmYWxzZSwiZGVsZXRlIjpmYWxzZSwiYXR0cmlidXRlX3Blcm1pc3Npb25zIjpbXX0sImhkYl9zY2hlbWEiOnsicmVhZCI6dHJ1ZSwiaW5zZXJ0IjpmYWxzZSwidXBkYXRlIjpmYWxzZSwiZGVsZXRlIjpmYWxzZSwiYXR0cmlidXRlX3Blcm1pc3Npb25zIjpbXX0sImhkYl91c2VyIjp7InJlYWQiOnRydWUsImluc2VydCI6ZmFsc2UsInVwZGF0ZSI6ZmFsc2UsImRlbGV0ZSI6ZmFsc2UsImF0dHJpYnV0ZV9wZXJtaXNzaW9ucyI6W119LCJoZGJfcm9sZSI6eyJyZWFkIjp0cnVlLCJpbnNlcnQiOmZhbHNlLCJ1cGRhdGUiOmZhbHNlLCJkZWxldGUiOmZhbHNlLCJhdHRyaWJ1dGVfcGVybWlzc2lvbnMiOltdfSwiaGRiX2pvYiI6eyJyZWFkIjp0cnVlLCJpbnNlcnQiOmZhbHNlLCJ1cGRhdGUiOmZhbHNlLCJkZWxldGUiOmZhbHNlLCJhdHRyaWJ1dGVfcGVybWlzc2lvbnMiOltdfSwiaGRiX2xpY2Vuc2UiOnsicmVhZCI6dHJ1ZSwiaW5zZXJ0IjpmYWxzZSwidXBkYXRlIjpmYWxzZSwiZGVsZXRlIjpmYWxzZSwiYXR0cmlidXRlX3Blcm1pc3Npb25zIjpbXX0sImhkYl9pbmZvIjp7InJlYWQiOnRydWUsImluc2VydCI6ZmFsc2UsInVwZGF0ZSI6ZmFsc2UsImRlbGV0ZSI6ZmFsc2UsImF0dHJpYnV0ZV9wZXJtaXNzaW9ucyI6W119LCJoZGJfbm9kZXMiOnsicmVhZCI6dHJ1ZSwiaW5zZXJ0IjpmYWxzZSwidXBkYXRlIjpmYWxzZSwiZGVsZXRlIjpmYWxzZSwiYXR0cmlidXRlX3Blcm1pc3Npb25zIjpbXX0sImhkYl90ZW1wIjp7InJlYWQiOnRydWUsImluc2VydCI6ZmFsc2UsInVwZGF0ZSI6ZmFsc2UsImRlbGV0ZSI6ZmFsc2UsImF0dHJpYnV0ZV9wZXJtaXNzaW9ucyI6W119fX19LCJyb2xlIjoic3VwZXJfdXNlciJ9LCJ1c2VybmFtZSI6InVzZXJuYW1lIn0sImlhdCI6MTYwNDk3ODcxMywiZXhwIjoxNjA1MDY1MTEzLCJzdWIiOiJvcGVyYXRpb24ifQ.qB4FS7fzryCO5epQlFCQe4mQcUEhzXjfsXRFPgauXrGZwSeSr2o2a1tE1xjiI3qjK0r3f2bdi2xpFlDR1thdY-m0mOpHTICNOae4KdKzp7cyzRaOFurQnVYmkWjuV_Ww4PJgr6P3XDgXs5_B2d7ZVBR-BaAimYhVRIIShfpWk-4iN1XDk96TwloCkYx01BuN87o-VOvAnOG-K_EISA9RuEBpSkfUEuvHx8IU4VgfywdbhNMh6WXM0VP7ZzSpshgsS07MGjysGtZHNTVExEvFh14lyfjfqKjDoIJbo2msQwD2FvrTTb0iaQry1-Wwz9QJjVAUtid7tJuP8aBeNqvKyMIXRVnl5viFUr-Gs-Zl_WtyVvKlYWw0_rUn3ucmurK8tTy6iHyJ6XdUf4pYQebpEkIvi2rd__e_Z60V84MPvIYs6F_8CAy78aaYmUg5pihUEehIvGRj1RUZgdfaXElw90-m-M5hMOTI04LrzzVnBu7DcMYg4UC1W-WDrrj4zUq7y8_LczDA-yBC2-bkvWwLVtHLgV5yIEuIx2zAN74RQ4eCy1ffWDrVxYJBau4yiIyCc68dsatwHHH6bMK0uI9ib6Y9lsxCYjh-7MFcbP-4UBhgoDDXN9xoUToDLRqR9FTHqAHrGHp7BCdF5d6TQTVL5fmmg61MrLucOo-LZBXs1NY" +} +``` + +The `refresh_token` also expires at a set interval, but a longer interval. Once it expires it will no longer be accepted by HarperDB. This duration defaults to thirty days, and is configurable in [harperdb-config.yaml](https://harperdb.io/docs/reference/configuration-file/). To generate a new `operation_token` and a new `refresh_token` the `create_authentication_tokensoperation` is called. + +## Configuration + +Token timeouts are configurable in [harperdb-config.yaml](https://harperdb.io/docs/reference/configuration-file/) with the following parameters: + +* `operationsApi.authentication.operationTokenTimeout`: Defines the length of time until the operation_token expires (default 1d). + +* `operationsApi.authentication.refreshTokenTimeout`: Defines the length of time until the refresh_token expires (default 30d). + +A full list of valid values for both parameters can be found here: https://github.com/vercel/ms \ No newline at end of file diff --git a/docs/security/users-and-roles.md b/docs/security/users-and-roles.md new file mode 100644 index 00000000..68ec76c7 --- /dev/null +++ b/docs/security/users-and-roles.md @@ -0,0 +1,278 @@ +# Users & Roles + +HarperDB utilizes a Role-Based Access Control (RBAC) framework to manage access to HarperDB instances. A user is assigned a role that determines the user’s permissions to access database resources and run core operations. + +## Roles in HarperDB + +Role permissions in HarperDB are broken into two categories – permissions around database manipulation and permissions around database definition. + + + +**Database Manipulation**: A role defines CRUD (create, read, update, delete) permissions against database resources (i.e. data) in a HarperDB instance. + +1) At the table-level access, permissions must be explicitly defined when adding or altering a role – *i.e. HarperDB will assume CRUD access to be FALSE if not explicitly provided in the permissions JSON passed to the `add_role` and/or `alter_role` API operations.* + +2) At the attribute-level, permissions for attributes in all tables included in the permissions set will be assigned based on either the specific attribute-level permissions defined in the table’s permission set or, if there are no attribute-level permissions defined, permissions will be based on the table’s CRUD set. + + +**Database Definition**: Permissions related to managing schemas, tables, roles, users, and other system settings and operations are restricted to the built-in `super_user` role. + + + +**Built-In Roles** +There are two built-in roles within HarperDB. See full breakdown of operations restricted to only super_user roles [here](https://harperdb.io/docs/security/users-roles/). + +* `super_user` – this role provides full access to all operations and methods within a HarperDB instance, this can be considered the admin role. + + * This role provides full access to all Database Definition operations and the ability to run Database Manipulation operations across the entire database schema with no restrictions. +* `cluster_user` – this role is an internal system role type that is managed internally to allow clustered instances to communicate with one another. + + * This role is an internally managed role to facilitate communication between clustered instances. + + +**User-Defined Roles** +In addition to built-in roles, admins (i.e. users assigned to the super_user role) can create customized roles for other users to interact with and manipulate the data within explicitly defined tables and attributes. + +* Unless the user-defined role is given `super_user` permissions, permissions must be defined explicitly within the request body JSON. + +* Describe operations will return metadata for all schemas, tables, and attributes that a user-defined role has CRUD permissions for. + + +**Role Permissions** + +When creating a new, user-defined role in a HarperDB instance, you must provide a role name and the permissions to assign to that role. *Reminder, only super users can create and manage roles.* + +* `role` name used to easily identify the role assigned to individual users. + + *Roles can be altered/dropped based on the role name used in and returned from a successful `add_role` , `alter_role`, or `list_roles` operation.* + +* `permissions` used to explicitly defined CRUD access to existing table data. + + +Example JSON for `add_role` request + +```json +{ + "operation":"add_role", + "role":"software_developer", + "permission":{ + "super_user":false, + "schema_name":{ + "tables": { + "table_name1": { + "read":true, + "insert":true, + "update":true, + "delete":false, + "attribute_permissions":[ + { + "attribute_name":"attribute1", + "read":true, + "insert":true, + "update":true + } + ] + }, + "table_name2": { + "read":true, + "insert":true, + "update":true, + "delete":false, + "attribute_permissions":[] + } + } + } + } +} +``` + +**Setting Role Permissions** + +There are two parts to a permissions set: + +* `super_user` – boolean value indicating if role should be provided super_user access. + + *If `super_user` is set to true, there should be no additional schema-specific permissions values included since the role will have access to the entire database schema. If permissions are included in the body of the operation, they will stored within HarperDB, but ignored, as super_users have full access to the database.* + +* `permissions`: Schema tables that a role should have specific CRUD access to should be included in the final, schema-specific `permissions` JSON. + + *For user-defined roles (i.e. non-super_user roles, blank permissions will result in the user being restricted from accessing any of the database schema.* + + +**Table Permissions JSON** + +Each table that a role should be given some level of CRUD permissions to must be included in the `tables` array for its schema in the roles permissions JSON passed to the API (*see example above*). + +```json +{ + "table_name": { // the name of the table to define CRUD perms for + "read": boolean, // access to read from this table + "insert": boolean, // access to insert data to table + "update": boolean, // access to update data in table + "delete": boolean, // access to delete row data in table + "attribute_permissions": [ // permissions for specific table attributes + { + "attribute_name": "attribute_name", // attribute to assign permissions to + "read": boolean, // access to read this attribute from table + "insert": boolean, // access to insert this attribute into the table + "update": boolean // access to update this attribute in the table + } + ] +} +``` + + +**Important Notes About Table Permissions** + +1) If a schema and/or any of its tables are not included in the permissions JSON, the role will not have any CRUD access to the schema and/or tables. + +2) If a table-level CRUD permission is set to false, any attribute-level with that same CRUD permission set to true will return an error. + + +**Important Notes About Attribute Permissions** + +1) If there are attribute-specific CRUD permissions that need to be enforced on a table, those need to be explicitly described in the `attribute_permissions` array. + +2) If a non-hash attribute is given some level of CRUD access, that same access will be assigned to the table’s `hash_attribute`, even if it is not explicitly defined in the permissions JSON. + + *See table_name1’s permission set for an example of this – even though the table’s hash attribute is not specifically defined in the attribute_permissions array, because the role has CRUD access to ‘attribute1’, the role will have the same access to the table’s hash attribute.* + +3) If attribute-level permissions are set – *i.e. attribute_permissions.length > 0* – any table attribute not explicitly included will be assumed to have not CRUD access (with the exception of the `hash_attribute` described in #2). + + *See table_name1’s permission set for an example of this – in this scenario, the role will have the ability to create, insert and update ‘attribute1’ and the table’s hash attribute but no other attributes on that table.* + +4) If an `attribute_permissions` array is empty, the role’s access to a table’s attributes will be based on the table-level CRUD permissions. + + *See table_name2’s permission set for an example of this.* + +5) The `__createdtime__` and `__updatedtime__` attributes that HarperDB manages internally can have read perms set but, if set, all other attribute-level permissions will be ignored. + +6) Please note that DELETE permissions are not included as a part of an individual attribute-level permission set. That is because it is not possible to delete individual attributes from a row, rows must be deleted in full. + + * If a role needs the ability to delete rows from a table, that permission should be set on the table-level. + + * The practical approach to deleting an individual attribute of a row would be to set that attribute to null via an update statement. + +## Role-Based Operation Restrictions + +The table below includes all API operations available in HarperDB and indicates whether or not the operation is restricted to super_user roles. + +*Keep in mind that non-super_user roles will also be restricted within the operations they do have access to by the schema-level CRUD permissions set for the roles.* + +| Schemas and Tables | Restricted to Super_Users | +|--------------------|:---------------------------:| +| describe_all | | +| describe_schema | | +| describe_table | | +| create_schema | X | +| drop_schema | X | +| create_table | X | +| drop_table | X | +| create_attribute | | +| drop_attribute | X | + + +| NoSQL Operations | Restricted to Super_Users | +|----------------------|:---------------------------:| +| insert | | +| update | | +| upsert | | +| delete | | +| search_by_hash | | +| search_by_value | | +| search_by_conditions | | + +| SQL Operations | Restricted to Super_Users | +|-----------------|:---------------------------:| +| select | | +| insert | | +| update | | +| delete | | + +| Bulk Operations | Restricted to Super_Users | +|------------------|:---------------------------:| +| csv_data_load | | +| csv_file_load | | +| csv_url_load | | +| import_from_s3 | | + +| Users and Roles | Restricted to Super_Users | +|-----------------|:---------------------------:| +| list_roles | X | +| add_role | X | +| alter_role | X | +| drop_role | X | +| list_users | X | +| user_info | | +| add_user | X | +| alter_user | X | +| drop_user | X | + +| Clustering | Restricted to Super_Users | +|-----------------------|:---------------------------:| +| cluster_set_routes | X | +| cluster_get_routes | X | +| cluster_delete_routes | X | +| add_node | X | +| update_node | X | +| cluster_status | X | +| remove_node | X | +| configure_cluster | X | + + +| Custom Functions | Restricted to Super_Users | +|----------------------------------|:---------------------------:| +| custom_functions_status | X | +| get_custom_functions | X | +| get_custom_function | X | +| set_custom_function | X | +| drop_custom_function | X | +| add_custom_function_project | X | +| drop_custom_function_project | X | +| package_custom_function_project | X | +| deploy_custom_function_project | X | + +| Registration | Restricted to Super_Users | +|-------------------|:---------------------------:| +| registration_info | | +| get_fingerprint | X | +| set_license | X | + +| Jobs | Restricted to Super_Users | +|----------------------------|:---------------------------:| +| get_job | | +| search_jobs_by_start_date | X | + +| Logs | Restricted to Super_Users | +|--------------------------------|:---------------------------:| +| read_log | X | +| read_transaction_log | X | +| delete_transaction_logs_before | X | +| read_audit_log | X | +| delete_audit_logs_before | X | + +| Utilities | Restricted to Super_Users | +|-----------------------|:---------------------------:| +| delete_records_before | X | +| export_local | | +| export_to_s3 | | +| system_information | X | +| restart | X | +| restart_service | X | +| get_configuration | X | +| configure_cluster | X | + +| Token Authentication | Restricted to Super_Users | +|------------------------------|:---------------------------:| +| create_authentication_tokens | | +| refresh_operation_token | | + +## Error: Must execute as User + +**You may have gotten an error like,** `Error: Must execute as <>`. + +This means that you installed HarperDB as <>. Because HarperDB stores files natively on the operating system, we only allow the HarperDB executable to be run by a single user. This prevents permissions issues on files. + + + +For example if you installed as user_a, but later wanted to run as user_b. User_b may not have access to the hdb files HarperDB needs. This also keeps HarperDB more secure as it allows you to lock files down to a specific user and prevents other users from accessing your files. \ No newline at end of file diff --git a/images/clustering/figure1.png b/images/clustering/figure1.png new file mode 100644 index 00000000..00c64580 Binary files /dev/null and b/images/clustering/figure1.png differ diff --git a/images/clustering/figure2.png b/images/clustering/figure2.png new file mode 100644 index 00000000..688a3ba0 Binary files /dev/null and b/images/clustering/figure2.png differ diff --git a/images/clustering/figure3.png b/images/clustering/figure3.png new file mode 100644 index 00000000..b21712e9 Binary files /dev/null and b/images/clustering/figure3.png differ diff --git a/images/clustering/figure4.png b/images/clustering/figure4.png new file mode 100644 index 00000000..f578f632 Binary files /dev/null and b/images/clustering/figure4.png differ diff --git a/images/clustering/figure5.png b/images/clustering/figure5.png new file mode 100644 index 00000000..f19e4de0 Binary files /dev/null and b/images/clustering/figure5.png differ diff --git a/images/clustering/figure6.png b/images/clustering/figure6.png new file mode 100644 index 00000000..eff93613 Binary files /dev/null and b/images/clustering/figure6.png differ