You can use Relations-Based Access Control (ReBAC) to define and check fine-grained permissions between entities as a relations graph. Relations graphs enable Ruek to derive implicit relations when there aren't any direct relations.
For example, in the following relations graph there's no direct relation created between user:jane
and doc:notes.txt
. But by examining the relations, Ruek can derive user:jane -> reader -> doc:notes.txt
relation.
A relation, which is a connection between a left entity and a right entity, is represented by a relation
tuple. A relation tuple can be expressed using '['⟨strand⟩']'⟨entity⟩'/'⟨relation⟩'/'⟨entity⟩
text
notation, where;
⟨strand⟩
is a relation that links two tuples together which can be empty⟨entity⟩
is a principal or an outside entity with a type and an id⟨relation⟩
is a string describing the relation between the two entities
Text notation | Semantics |
---|---|
[]user:alice/member/team:writers |
User user:alice is a member of team:writers |
[member]team:writers/edit/doc:notes.txt |
Members of team:writers can edit doc:notes.txt |
[owner]folder:F/owner/doc:notes.txt |
Owners of folder:F are also owners of doc:notes.txt |
[]folder:F/parent/doc:notes.txt |
Entity folder:F has a parent relation to doc:notes.txt |
Two or more relation tuples can be linked (left to right) using a strand. To be able to link two tuples and consider them to in a single relations chain,
- First tuple's right entity must match the second tuple's left entity (
t1.right == t2.left
) - And, first tuple's relation must match second tuple's strand (
t1.relation == t2.strand
)
For example, []user:alice/member/team:writers
can be chained with [member]team:writers/edit/doc:notes.txt
but not with [owner]team:writers/edit/doc:notes.txt
(since first tuple's relation, member
, doesn't
match second tuple's strand, owner
).
ReBAC gives you a significant amount of flexibility to define fine-grained permissions but depending on how complex your relations graph is, there can be read/write bottlenecks.
For example, let's consider the following relation tuples.
[]user:jane/member/group:writers
[member]group:writers/member/group:readers
[member]group:readers/reader/doc:notes.txt
group:writers
haswriter
relation to 10K documents
To derive the relation []user:jane/reader/doc:notes.txt
using a BFS1 graph traversal algorithm
(which has O(v+e) complexity), we will need to read 10,003 tuples. This can be really slow depending
on DB load and number of concurrent requests.
Tip
This is only an illustrative example. In reality, Ruek traverse the relations graphs from right to left which will result in only 3 reads in this instance.
In order to maintain a consistent and a predictable throughput (QPS), Ruek offers different optimisation strategies to suite different shapes of relations graphs.
Tip
Best for reads (O(1)), can be worst for writes (O(1+l+r)).
Direct strategy optimise for O(1) relations checks at the expense of computing and storing derived relations during creation.
For example if the following tuples are created sequencially, when creating t2
Ruek will evaluate
the relations graph and compute and store the derived tuple t2-1
. This ensures
user:jane -> parent -> group:viewers
relations check can be performed with just one lookup.
Id | Strand | Left Entity | Relation | Right Entity | Computed |
---|---|---|---|---|---|
t1 |
user:jane | member | group:editors | ||
t2 |
member | group:editors | parent | group:viewers | |
t2-1 |
user:jane | parent | group:viewers | ✓ |
Tip
Best for writes (O(1)), can be worst for reads (O(1+v+e)).
Graph strategy does not perform any additional computations when creating relations resulting in O(1) writes. When checking relations, if a direct relation does not exists Ruek will use a graph traversal algorithm to compute and check derived tuples which can result in slow reads depending on the complexity of the relations graph.
Tip
A balance between reads and writes (O(1+n+m)), best for large datasets.
Set strategy require relations to be defined between principals (e.g. users, groups) and entities. When creating relations, Ruek will analyse the relations graph and compute and store derived relations between principals if necessary. This enables Ruek to use a set intersection algorithm (we call it spot) to efficiently check relations with O(1+n+m) complexity.
Consider the following tuples created sqeuncially.
Id | Strand | Left Entity | Relation | Right Entity | Computed |
---|---|---|---|---|---|
t1 |
user:jane | member | group:writers | ||
t2 |
member | group:writers | member | group:readers | |
t2-1 |
user:jane | member | group:readers | ✓ | |
t3 |
member | group:readers | reader | doc:notes.txt | |
t4 |
owner | folder:home | parent | doc:notes.txt | |
t5 |
user:jane | owner | folder:home |
When t2
is created, similar to direct strategy, Ruek compute and store the derived tuple t2-1
.
However when t3
, t4
and t5
are created although they are part of the same relations graph, Ruek
does not store any additional derived tuples.
This is because when Ruek evaluates the relations graph left to right, it can identify t2
's left
and right entities are principals and storing the derived tuple t2-1
will optimise reads. However
while t3
and t5
's left entities are principals their right entities are not, so storing additional
derived tuples would increase writes. t4
's left and right entities are not principals so Ruek doesn't
consider it for any further optimisations when using set strategy.
When checking if user:jane -> reader -> doc:notes.txt
relation exists using set strategy, Ruek
look for all the groups user:jane
is a member of and compare that list with all the groups that has
a reader
relation to doc:notes.txt
using the spot algorithm.