SkillAgentSearch skills...

Eacl

🦅 EACL: Enterprise Access ControL is a ReBAC Authorization system based on SpiceDB, built in Clojure and backed by Datomic

Install / Use

/learn @theronic/Eacl
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

🦅 EACL: Enterprise Access ControL

EACL is a situated ReBAC authorization library based on SpiceDB, built in Clojure and backed by Datomic.

Situated here means that your permission data lives next to your application data in Datomic, which has some benefits:

  1. Avoids a network hop. To leverage SpiceDB's consistency semantics, you need to hit your DB (or cache) to retrieve the latest stored ZedToken anyway, so you might as well query the DB directly, which is what EACL does.
  2. One less external dependency to deploy & sync relationships.
  3. Fully consistent queries – an external authz system necessitates eventual consistency.

EACL is pronounced "EE-kəl", like "eagle" with a k because it keeps a watchful eye on permissions.

Goals

  • Best-in-class ReBAC authorization for Clojure/Datomic applications that is fast for 10M permissioned Datomic entities.
  • Clean migration path to SpiceDB once you need consistency semantics with a heavily optimized cache.
  • Retain compatibility with SpiceDB gRPC API to enable 1-for-1 Relationship syncing by tailing Datomic transactor queue.

Rationale

Please refer to eacl.dev.

Authentication vs Authorization

  • Authentication or AuthN means, "Who are you?"
  • Authorization or AuthZ means "What can <subject> do?", so AuthZ is all about permissions.

Why EACL?

Situated AuthZ offers some advantages for typical use-cases:

  1. If you want ReBAC authorization without an external system, EACL is your only option.
  2. Storing permission data directly in Datomic avoids network I/O to an external AuthZ system, reducing latency.
  3. An accurate ReBAC model syncing Relationships 1-for-1 from Datomic to SpiceDB in real-time without complex diffing, for when you need SpiceDB performance or features.
  4. Queries are fully consistent. If you need consistency semantics like at_least_as_fresh, use SpiceDB.
  5. EACL is fast. You may be tempted to roll your own ReBAC system using recursive Datomic child rules, but you will find the eager Datalog engine too slow and unable to handle all the grounding cases. The first version of EACL was implemented with Datalog rules, but it was simply too slow and materialized all intermediate results. Correct cursor-pagination is also non-trivial, because parallel paths through the permission graph can yield duplicate resources. EACL does this for you with good performance.

Performance

  • EACL recursively traverses the ReBAC permission graph via low-level Datomic d/index-range & d/seek-datoms calls to efficiently yield cursor-paginated resources in the order they are stored at-rest. Results are always returned in the order they stored in at-rest, which are internal Datomic eids.
    • I have investigated implementing custom Sort Keys, but they are not currently feasible without adding a lot of storage & write costs.
  • EACL is fast, but makes no strong performance claims at this time. For typical workloads, EACL should be as fast as, or faster than, SpiceDB. EACL is not meant for hyperscalers.
  • EACL is internally benchmarked against ~800k permissioned resources with good latency (5-30ms per query). You can scale Datomic Peers horizontally and dedicate peers to EACL as needed.
  • The performance goal for EACL is to handle 10M permissioned entities with real-time performance.
  • EACL does not support all SpiceDB features. Please refer to the limitations section to decide if EACL is right for you.
  • Presently, EACL has no cache because graph traversal is fast enough over Datomic's aggressive datom caching even for ~1M permissioned resources. A cache is planned and once it lands, should bring query latency down to ~1-2ms per API call, even for large pages.
  • Performance should scale roughly with permission graph complexity * O(logN) for N resources in terminal resource Relationship indices. Parallel paths through the graph that return the same resources will slow EACL down, because these resources need to be deduplicated in stable order. In a simple graph, performance should approach O(logN) for N permissioned resources. Subjects are typically sparse compared to resources, i.e. 1k users will have access to 1M resources – rarely the other way around.

Note that to retain future compatibility with the SpiceDB gRPC, the EACL Datomic client calls (d/db conn) on each API call, which means that if your DB changes inbetween EACL queries, you may see inconsistent results when cursor paginating. You can pass a stable db basis and shave off a few milliseconds by calling the internals in eacl.datomic.impl.indexed directly – these functions take db as an argument directly instead of conn. If you do this, you will need to coerce internal Datomic eids to/from your desired external IDs yourself.

Project Status

[!WARNING] EACL is under active development. I try hard not to introduce breaking changes, but if data structures change, the major version will increment. v6 is the current version of EACL. Releases are not tagged yet, so pin the Git SHA.

ReBAC: Relationship-based Access Control

In a ReBAC system like EACL, objects (Subjects & Resources) are related via Relationships.

A Relationship is just a 3-tuple of [subject relation resource], e.g.

  • [user1 :owner account1] means subject user1 is the :owner of resource account1, and
  • [account1 :account product1] means subject account1 is the :account for resource product1.

EACL models two core concepts to model the permission graph: Schema & Relationship.

  1. Schema consists of Relations and Permissions:
    • Relation defines how a <subject> & <resource> can be related via a Relationship.
    • Permission defines which permissions are granted to a subject via a chain of Relationships between subjects & resources.
      • Permissions can be Direct Permissions or indirect, known as Arrow Permissions. An arrow implies a graph traversal.
  2. A Relationship defines how a <subject> and <resource> are related via a named relation, e.g. [(->user alice) :owner (->account "acme")] means that
    • (->user "alice") is the Subject,
    • :owner is the name of the Relation (as defined in the schema)
    • (->account "acme") is the Resource
    • so this reads as (->user "alice") is the :owner of (->account "acme").
    • In EACL, this is expressed as (->Relationship (->user "alice") :owner (->account "acme")), i.e. (Relationship subject relation resource)
    • Subjects & Resources are just maps of {:keys [type id]}, e.g. {:type :user, :id "user-1"}, or (->user "user-1") when using a helper function.

Schema & Relationships

To create a Relationship, first define valid Relations to describe how subjects & resources can be related, e.g.

; definition account {
;   relation owner: user
;   relation viewer: user
; }
(Relation :account :owner :user)      ; an :account can have :owner(s) of type :user
(Relation :account :viewer :user)     ; an :account can have :viewer(s) of type :user

; definition product {
;   relation account: account
; }
(Relation :product :account :account) ; a :product has an :account of type :account.

Given that an <account> has an :owner, and a <product> can have an :account, we can now define a permission schema that grants the :edit permission to all owners of the account, and the :view permission to all viewers of the account:

; definition account {
;   permission admin = owner
; }
(Permission :account :admin {:relation :owner})

; definition account {
;   permission edit = account->admin
; }
(Permission :account :edit {:arrow :account :permission :admin})

; definition  product {
;   permission view = admin + account->viewer
; }
(Permission :product :view {:arrow :account :permission :admin})
(Permission :product :view {:arrow :account :relation :viewer})

In EACL, multiple permission definitions with the same resource_type & name, but different permission spec, imply Unification or OR-logic. EACL does not support negation yet, only Unification.

EACL API

The IAuthorization protocol in src/eacl/core.clj defines an idiomatic Clojure interface that maps to and extends the SpiceDB gRPC API:

Queries

  • (eacl/can? acl subject permission resource) => true | false
  • (eacl/lookup-subjects acl filters) => {:data [subjects...], cursor 'next-cursor}
  • (eacl/lookup-resources acl filters) => {:data [resources...], :cursor 'next-cursor}.
  • (eacl/count-resources acl filters) => {:keys [count limit cursor]} supports limit & cursor for iterative counting. Use sparingly with :limit -1 for all results.
  • (eacl/count-subjects acl filters) => {:keys [count limit cursor]} supports limit & cursor for iterative counting. Use sparingly with :limit -1 for all results.

Relationship Maintenance

  • (eacl/read-relationships acl filters) => [relationships...]
  • (eacl/write-relationships! acl updates) => {:zed/token 'db-basis},
    • where updates is a collection of [operation relationship], and operation is one of :create, :touch or :delete.
  • (eacl/create-relationships! acl relationships) simply calls write-relationships! with :create operation.
  • (eacl/delete-relationships! acl relationships) simply calls write-relationships! with :delete operation.

Schema Maintenance

  • (eacl/write-schema! acl schema-string) is not implemented yet because schema lives in Datomic. Use d/transact to write schema for now. This is a high priority to suport.
  • (eacl/read-schema acl) => "schema-string" is not implemented because schema lives in D

Related Skills

View on GitHub
GitHub Stars75
CategoryDevelopment
Updated22h ago
Forks6

Languages

Clojure

Security Score

100/100

Audited on Mar 26, 2026

No findings