sAMAccountName
in LDAP servers.memeberOf
in LDAP servers. Nested group may be complex at the first glance, but actually they can be flatten via SQL recursive query(CTE).allow:secretsmanager
to describle the read capability.I’d recommand to enumerate all capabilities and checkpoints before any development, as the capabilities may be embedded in the code and difficult to be modified over time.
Following checklist must be accomplished before your permission design:
Capability and granularity: mostly for specialized domains, the design can’t be reused among projects. Ensure the design being stateless so that developers can write unit tests independently.
Membership: Who can manage the membership? Is your system design vulnerable to privilege escalation?
Ownership can be complex in GDPR compliance, especially personal data that is held, processed, and redistribed by companies, which requires massive checklists. To manage data produced by a person, It’s better to ensure each object can be managed at the lowest granularity. Before all else, it’s critical to assign each object to only one person who has the legal right, as the owner might not process the data directly. Even the matter being discussed here is the design of authorization, the design should also be considered for big data or AI trainings in the future.Moreover, the design of the grant should be managed appropriately. Never assign an object without the owner’s explicit consent regardless of the supervisory role such as super admins.
Audit:
Rules engine
Role-based access control, which is abbreviated to RBAC, is widely taught in computer courses. It provides an indirect mechanism that assigns an attribute(such as a job title called “Accounting Manager”) to a person and then binds the attribute to the capabilities in the managed scopes.
Prior to the discussing, it’s imperative we first address three types of role definitions. In the real-world development, I’ve seen the real-world users who couldn’t distinguish following opeartions.
ProjectAccountant
role, mimicking real-world job functions; then appropriate data permissions would be programmatically assigned to that role, granting User A access automatically.project_accountant_data
, then assign the data permission to the group directly.AccountingReader
, while the role is just a set of capabilities of namespaces and resources, which is unrelative to the real-world project accountant.Role definitions can take either an intensional and extensional form. An intensional definition specifies a read-world role while extensional definitions just enumerate the explicit members or a set of abstract capabilities.
The first definition uses the declarative notations for roles, that is to say, the conceptualization of the roles. In a nature language statement, the role utilizes with a restrictive relative clause that function as a pronoun.
1 | - [Kyon ] can view sales data. |
The intensional definition of a role can be an attribule.
1 | var MarketingDept = [ |
Building a real-world title comes with the developer overhead, there are a few considerations to keep in mind:
To reduce the complexity, the fundamental action is to remove attributes – a role can be merely considered a named subgroup. We can enumerate all roles without any conceptualization!
1 | var MarketingManager = ['John']; |
In my view, the enumerative way is a better alternative because it can be learned by users instinctively.
It’s more flexible to externalize the real-world roles as enumerative groups (nested groups are acceptable) into third party systems (such as OpenLDAP, HR Service), while our applications connecting to these services are abstracted from the underlying definition of departments.
Membership validation or synchronization can also be implemented via centralized authorization framework such as traefik’s forward-auth or other aspect oriented middleware.
Another alternative is to create build-in roles via capabilities
1 | var S3Reader = ['read:s3']; |
It’s the most widely used in cloud services, we can also classify it as ACL (Access controll list). However, an ambiguous role name can also leading to a steep learning curve, as examplified by using “Owner”, “Manager”, and “Admin” together.
For example, Gitlab, a well-known code management software that represents the design of nested RBAC authorization. When we want to assign a read-only permission to someone, we found that the role Reporter
and Guest
are both semantic symbols of observation, which meaned they were not mutually exclusive in the real world, making it impossible to configure the roles correctly without an official cheat sheet.
In contrast to the prior case, Sonarqube, a code scanner platform, chooses to manage roles concisely. Sonarqube defines two roles: the project administrator and the project member. Everyone can infer the administrator has a higher permission.
Build-in roles must be concise enough, such as admin, read-write and read-only. If you insist on designing additional roles, the names of role must be derived from the capabilities inside the system rather than from the real world.
The design of an ACL can be challenging as it requires linear growth in database storage as the amount of data and owners being managed increases over time.
RBAC provides coarse-grained access control by attributes while ACL uses fine-grained policies at an individual resource level. Complications arise when they are used together, here are some hints.
To implement the requirements, here is the pesudo code
1 | // test |
It’s deemed high-risk to allow an admin to access tanant or subordinate data as it violates isolation boundaries. Intersection and union caculations can be applied to constrain privileges for the admin role, enabling admins to read item lists while denying access to details.
1 | // enabling admins to read item lists while denying access to details. |
The cost of developing SoD is relatively expensive and time-consuming, we insert hundreds of checkpoints and audit logs inside the source code. Don’t take first priority over it.
Recenlty some frameworks focus on low-code role definitions, claiming to be pluggable, declarative, and unified. Meanwhile, these methods don’t always guarantee the flexibility.
The following table compares the notations across diversified authorization frameworks.
Notation | Paradigm | Logic |
---|---|---|
Tries traversal: Open Policy Agent | Declarative | Combinational |
Relation tuple: Ory Permissions | Declarative | Combinational |
Relation tuple: Google Zanzibar | Declarative | Combinational |
Spring Security Annotations | Declarative | Combinational |
Casbin Go like DSL | Declarative | Sequential |
Rete algrorithm: Drools | Mostly declarative | Combinational |
JSON Schema | Mostly declarative | Sequential |
Hard-coded programming | Imperative | Sequential |
The key to classify the frameworks is to check if it supports sequential logic during the policy evaluation. For instance, the if-else statements implicate the priorities during the calculation while bit operations do not require the state storage.
If you need if-else statements during the policy evaluation, I’d recommand not to use any low-code solutions to torture yourself.
In recent work, I tried to use “Open Policy Agent(OPA)” so that I could modify the policies on the fly. Unexpectedly, the requirements from customers were not as declarative as low-code, including following rules.
After I tries OPA, I found that
After times of reworks, I gave up using OPA because our works were merely a translation between “customer’s prompt” and “handmade code”, which was an AI capable of.
From my perspective, the most common mistake in system design I’ve made is over abstraction. There is no unified way to notate the constrains. Conversely, the only approach to reduce the expenditure is to minify the code at an engineering level.
In this article, we walked through RBAC, ACL, and low-code.
Ultimately, choosing the best solution depends on compliance requirements.