March 20, 2023

User Management via RBAC in HarperDB

Welcome to Community Posts
Click below to read the full article.
Arrow
Summary of What to Expect
Table of Contents

HarperDB comes with built-in Basic and JWT authentication, and Role-Based Access-Control (RBAC) model for authorization. The granular permission model allows for a higher degree of security as permissions can be assigned to different users either at the table or attribute level. 

In this two-part series, we will see how to secure access to HarperDB by creating custom users and roles. In the first article, we will do a deep dive into HarperDB’s RBAC model. We will then integrate with an external provider in Part II via Custom Functions to create a full authentication and authorization system. 

HarperDB Setup

Before we begin, we need to provision a HarperDB instance. The easiest way to get started is to deploy a managed AWS instance via HarperDB Studio. Follow the instructions on “Deploying HarperDB on AWS and GCP” to create a free instance. 

NOTE: local instance of HarperDB has limitations that restrict the number of roles to two (super_user and cluster_user). Deleting any of these roles to create a custom role causes issues. If you wish to use a local instance, make sure to register the database via HarperDB Studio. 

Once you have a running instance, let’s create a `dev` schema and a `dog` table:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic SERCX0FETUlOOnBhc3N3b3Jk' \
--data '{
 "operation": "create_schema",
 "schema": "dev"
}'
curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic SERCX0FETUlOOnBhc3N3b3Jk' \
--data '{
 "operation": "create_table",
 "schema": "dev",
 "table": "dog",
 "hash_attribute": "id"
}'

HarperDB Roles

HarperDB ships with three built-in roles:

  1. Super_user: admin role with full access to all operations
  2. Cluster_user: internal role to allow clustering of databases
  3. Structure_user: role that allows for the creation and deletion of data (e.g., creation of schemas and tables) 

In addition to these built-in roles, we can create custom roles. Role permissions for CRUD operations can be defined at table-level or at the attribute-level. If the permissions for attributes are not set, table-level permissions will be assumed. 

Creating a Custom Role

So without further ado, let’s create some custom roles. We will create a `developer` role with read and write access to the dog table. We will also create a `restricted` role with only insert permissions for the owner_name attribute. 

First, here’s the command to create the `developer` role:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic SERCX0FETUlOOnBhc3N3b3Jk' \
--data '{
 "operation": "add_role",
 "role": "developer",
 "permission": {
     "super_user": false,
     "dev": {
         "tables": {
             "dog": {
                 "read": true,
                 "insert": true,
                 "update": true,
                 "delete": false,
                 "attribute_permissions": []
             }
         }
     }
 }
}'

Note that the `attribute_permissions` is an empty array, meaning that it will inherit table-level permissions. Here the developer role has access to read, insert, and update in the dog table, but cannot delete. 

Next, we have the `restricted` role:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic SERCX0FETUlOOnBhc3N3b3Jk' \
--data '{
 "operation": "add_role",
 "role": "restricted",
 "permission": {
     "super_user": false,
     "dev": {
         "tables": {
             "dog": {
                 "read": true,
                 "insert": true,
                 "update": true,
                 "delete": false,
                 "attribute_permissions": [
                   {
                       "attribute_name": "owner_name",
                       "read": false,
                       "insert": true,
                       "update": false
                   }
               ]
             }
         }
     }
 }
}'

Associating Roles to Users

Now that we have our custom roles defined, we need to create custom users. We can invoke the `add_user` operation with username, password define as well as the associated role. 

Let’s create a `developer` user that assumes the `developer` role:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic SERCX0FETUlOOnBhc3N3b3Jk' \
--data '{
 "operation": "add_user",
 "role": "developer",
 "username": "developer",
 "password": "password",
 "active": true
}'

Likewise, create the restricted_user:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic SERCX0FETUlOOnBhc3N3b3Jk' \
--data '{
 "operation": "add_user",
 "role": "restricted",
 "username": "restricted_user",
 "password": "password",
 "active": true
}'

Testing Roles

Now we can test if our roles are working. Let’s insert a record to the `dog` table as `developer` user:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic ZGV2ZWxvcGVyOnBhc3N3b3Jk' \
--data '{
 "operation": "insert",
 "schema": "dev",
 "table": "dog",
 "records": [
     {
         "id": 1,
         "dog_name": "Penny",
         "owner_name": "Kyle",
         "breed_id": 154,
         "age": 7,
         "weight_lbs": 38
     }
 ]
}'

We should see a successful message:

{"message":"inserted 1 of 1 records","inserted_hashes":[1],"skipped_hashes":[]}

We can also read from the table:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic ZGV2ZWxvcGVyOnBhc3N3b3Jk' \
--data '{
 "operation": "sql",
 "sql": "SELECT * FROM dev.dog where id = 1"
}'

We should see a JSON output with the same data that we inserted. 

Now, if we try to insert as the restricted user, we will see failures:

curl --location 'https://' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic cmVzdHJpY3RlZF91c2VyOnBhc3N3b3Jk' \
--data '{
 "operation": "insert",
 "schema": "dev",
 "table": "dog",
 "records": [
     {
         "id": 2,
         "dog_name": "James",
         "owner_name": "John",
         "breed_id": 154,
         "age": 4,
         "weight_lbs": 21
     }
 ]
}'

{

    "error": "This operation is not authorized due to role restrictions and/or invalid schema items",

    "unauthorized_access": [],

    "invalid_schema_items": [

        "Attribute 'dog_name' does not exist on 'dev.dog'",

        "Attribute 'breed_id' does not exist on 'dev.dog'",

        "Attribute 'age' does not exist on 'dev.dog'",

        "Attribute 'weight_lbs' does not exist on 'dev.dog'"

    ]

}

The error message shows that we are not authorized to insert. This is because we only have insert permissions to the `owner_name` attribute and not the others. HarperDB’s RBAC model, when defined at the attribute-level, is useful for granular permissions either for security or compliance measures. 

Next Steps

In Part I of this series, we saw how the RBAC model works in HarperDB. We created some custom roles and tied it to custom users. In the next article, we will extend this to integrate with external auth providers to offload authentication and restrict various endpoints per role.