Hello, Hyperfiddlers. Behold, an Electric https://github.com/CloudAfrica/eacl Starter App forked from Electric v3 Starter App: https://github.com/theronic/eacl-electric-starter-app.
EACL (Enterprise Access ControL) is a SpiceDB-compatible* ReBAC authorization system built in Clojure and backed by Datomic:
• https://authzed.com/spicedb is a faithful open-source implementation of https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/, Google's Globally Consistent Authorization System, which powers YouTube, Google Drive & Google Calendar.
• EACL's IAuthorization idiomatic Clojure interface maps cleanly to the SpiceDB https://buf.build/authzed/api/docs/main:authzed.api.v1:
◦ (eacl/can? client subject permission resource) => true|false, maps to Spice's CheckPermission,
▪︎ e.g. (eacl/can client (->user "dustin") :restart (->server "production")) => true
◦ (eacl/lookup-subjects client filters) => {:cursor ... :data [SpiceObjects...]} , where SpiceObject has {:keys [type id]} = LookupSubjects
◦ (eacl/lookup-resources client filters) => {:cursor ... :data [SpiceObjects...]} , where SpiceObject has {:keys [type id]} = LookupResources, supports cursor-based pagination for direct index access,e.g.
(eacl/lookup-resources client
{:subject (->user "dustin")
:permission :view
:resource/type :server
:limit 500
:cursor ...})
=> {:data [{:type :server, :id "server-1"}
{:type :server, :id "server-2"}
...}],
:cursor next-cursor} ; nil cursor is 1st page
▪︎ Note that :limit N consumes N memory because we have to deduplicate object IDs that are found along multiple paths in the graphs. Return order is stable, but undefined. We can't sort matching objects without materializing the full index.
◦ (eacl/count-resources client filters) is not supported by SpiceDB. It counts all matching resources, but is expensive because it materializes the full index, so use sparingly.
◦ (eacl/read-relationships client filters) maps to ReadRelationships in Spice
◦ (eacl/write-relationships client filters) maps to WriteRelationships in Spice
◦ (eacl/write-schema client schema) is not currently implemented because EACL schema lives in Datomic. But can be added.
• Project State: Alpha, but we are using it at my employer (CloudAfrica), who generously funded this open-source work.
• EACL is aimed at Electric Clojure applications with <10M resources and offers a clean migration path to SpiceDB once you hit scale or need consistency semantics, which requires complex graph caching via ZedTokens.EACL Rationale:
• Most enterprise Electric Clojure apps need authorization.
• A situated / embedded permission graph avoids network I/O to an external authorization system, so one less system to deal with and keep in sync.
• EACL gives you sophisticated ReBAC permissions from day one with a clear migration path to SpiceDB once you hit scale or need consistency semantics (complex graph caching).
• Modelling SpiceDB data structures directly in Datomic simplifies Relationship syncing from Datomic to SpiceDB by resolving the impedance mismatch and avoiding complex diffing. I spent a few months last year on knowing what to sync and when and it gets complicated.
• For smaller data sets, EACL should be faster than SpiceDB because you avoid the network I/O and have to hit Datomic anyway to hydrate your entities.
This open-source work was generously funded by my employer, https://cloudafrica.net/.
On Spice compatibility:
• No consistency semantics in EACL because all EACL queries are fully-consistent as per latest db value, but more sophisticated caching could be added by holding onto older db values.
• Spice Caveats are not supported.
• Spice Exclusion permissions are not supported.
• For arrow permissions to work, you need to define a Permission on the the parent resource because EACL traverses Relations via Permission. This is a small hack and is illustrated in the schema test fixtures.
• There are currently some unused tuple index attrs that can be dropped since they are not used (was part of optimization work).
• :entity/id & :resource/type will be renamed to :eacl/type and unique :eacl/id. IDs are strings to retain compatibility with Spice.
Re: Licence. EACL is currently AGPL but we'll relicence it soon to something more permissive – just need to get sign-off from the boss 👌
I limited the seed data to 500 accounts because my in-memory Datomic falls over with >750 accounts + a bunch of entities. You can switch to dev transactor for more.
you need to run (dev/-main) first
conn is mount component
README has the steps, but can be improved
I could also add the seeding to startup
you'll see this once seeded
startup instructions
(dev/-main) ; returns control
(require '[electric-starter-app.data.config :as data.config :refer [conn]])
(require '[electric-starter-app.data.seed :as seed])
(seed/install-schema+fixtures! conn) ; dev/-main runningThe schema fixtures for this demo:
• User
• Platform which has relation super_admin: user
• Account has relation owner: user and relation platform: platform (for super user access)
• Server has relation account: account
• And a bunch of permissions inferred from these.
Basically, you can manage all the Server resources under an Account if your user is an :owner of an Account, or if you are a super admin of the :platform linked to that Account.
After seeding data, you'll see see 5 columns:
1. Users: All users in DB via d/q, so slow because sorted & paginated. You can change the signed in user by clicking on a user.
2. User Accounts: cursor-paginated list of Accounts that the selected user can :view via fast lookup-resources which uses direct index access. Click to change the selected account.
3. User Servers: cursor-paginated list of all Servers that the selected user can :view via lookup-resources.
a. The total server count is slow for super-user because it uses count-resources (not supported by SpiceDB), and materializes the full index, so use sparingly. Eventually when I implement caching, this can be cached or estimated. Click to change selected server.
4. Account Servers: a list of server resources that the account (the subject, in this case) can :view via lookup-resources. Click to change selected server.
5. Subject Lookup: a list of subjects who can :view the selected server via lookup-subjects which does not use direct index access yet (WIP), but these results are typically small. Click to change signed in user.
The goal here is to demonstrate the performance with several thousands users, 500 accounts, and 300k+ servers, as well as the super-user's administrative access, which is inferred via the platform relation.
An improved demo would also demonstrate sharing servers or accounts with other users who are not the owner, and have more accounts per user. Right now seed data is ~500 accounts, each with 10-15 users per account, and each account has 500-1000 servers.
The pagination UI impl. is very thin so may have bugs. Bit hacky because lookup-* does not return the prev cursor yet (todo).
I've started work on parsing SpiceDB schema to EACL schema, but for now you'll have to translate your Spice schema to EACL.
You can use any Datomic entity in EACL by adding :eacl/type :keyword and :eacl/id "unique-string".
Last year I looked at all the open-source permission systems out there, and SpiceDB is IMO the only one that faithfully implements the consistency semantics in the Zanzibar paper via ZedTokens ala Zookies.
Pushed a breaking schema change to EACL and Electric EACL starter app:
• :resource/type is now :eacl/type because an object can be a subject or a resource in a Relationship
• :entity/id is now :eacl/id to conform to :eacl/type above.
Wanted to get that out before anyone starts adopting it.
There should be no more breaking schema changes, but I'll remove any unused tuple indices.
Benchmarks: I've benchmarked EACL with 750k server resources against Datomic H2 Dev Transactor with acceptable speed:
• eacl/can? is fast enough, but can be made faster with direct index access usage and parallelised graph search
• eacl/lookup-resources is fast because of direct index access. You can expect:
◦ pages of 1k results out of 100k matching resources in ~6ms
◦ two pages of 600 + 400 resources in ~4ms with performance that is insensitive to cursor offset
◦ :limit 100000 resources in ~1s, but faster if paginated because larger pages consume more memory to deduplicate
• eacl/count-resources is slow because it materializes the full index
• eacl/lookup-subjects is still relatively slow pending direct index impl. (WIP), but subjects are typically sparse so usually fast enough.