2025 · Designed and built the service; largest policy contributor
A central authorization service on Cerbos
A central authorization service on Cerbos — one gRPC call that resolves who is asking and what they touch, then lets versioned policies decide.
Published · Updated
- 1
- RPC: IsAllowed
- 13
- resource kinds behind it
- 4
- roles, from viewer to admin
- Python
- gRPC
- Cerbos
- SQLAlchemy
- MySQL
- Docker
Context
Grepsr’s platform has many things a user can act on: projects, reports, schedules, runs, files, delivery destinations, digests, billing. Who may do what depended on role, organisation, team, and whether the user was assigned to the project.
Those checks lived in the services that needed them, so every new resource meant writing them again. The team had a Cerbos policy repository, but no single service that every caller could ask.
I built that service: one gRPC call, IsAllowed, that any part of the platform can make before it acts. I am also the largest contributor to the policy repository it evaluates.
Constraints
- One schema, many ownership models. A run belongs to a report, which belongs to a project, which belongs to an organisation. Every resource reaches its organisation by a different path.
- Policies change more often than code. Product rules (“viewers may download files but not delete them”) should change without a service deploy.
- Some checks have no resource yet. “May I create a project here?” has no project id to look up.
- Callers check lists. A page that shows twenty items asks about twenty items at once.
Design
The caller sends who is asking, the kind of resource, one or more ids, the action, and any request-time attributes. The service resolves the principal — role, organisation, team and assigned projects — and each resource from MySQL, then asks the Cerbos decision point. Policies are YAML, one per resource kind, plus shared role and variable definitions.
Key decisions
Facts in the service, rules in the policies. The service only answers “what is true about this user and this resource”. Every “is that enough” lives in policy. A product rule change is a policy change, reviewed and versioned on its own.
One resolver per resource kind. Each kind has its own small resolver that knows its ownership chain and returns a resolved organisation id. Adding a resource kind is one new resolver and one new policy file, not an edit to a central conditional. I grew the model this way, one kind at a time, from accounts and projects to billing, digests, services and, most recently, API keys.
Organisation comes from the owner. A project can have many collaborators from other organisations. The resolver finds a resource’s organisation through the project’s owner, so collaborating on a project never moves it into your organisation.
Parents are nested, not re-queried. A run’s attributes embed its report, which embeds its project. A policy can say “the run’s project is assigned to this user” directly, and the database work happens once, in the service.
Checks without a resource. When there is no id yet, the service builds a synthetic resource from the request’s attributes. “May this user create a project in this organisation?” goes through the same policies as everything else.
Lists are all-or-nothing. A multi-resource check is allowed only if every resource allows the action. A bulk action cannot partly succeed against items the user may not touch.
Failure handling
- Invited users. A user who has been invited but has not signed up has no organisation or team yet. The resolver reads their pending invitation instead, so invitation flows are checked like everything else.
- One row became many. Lookups assumed a report belonged to exactly one project. When multi-project reports appeared, those lookups started to fail. I changed them to tolerate several matching rows instead of failing the whole check.
- Ids are strings. Some resources are addressed by code or email, not a number, so I changed resource ids in the contract from integers to strings and let resolvers accept either form.
Result
Every protected action on the platform can be checked with one call. A new resource kind is a resolver and a policy. Product rules change in policy files, without a deploy of the services that ask.