-
Notifications
You must be signed in to change notification settings - Fork 11
Authorization
Authorization is the security process that determines a user's level of access to the application. In technology, we use authorization to give users or services permission to access some data or perform a particular action. It is vital to note the difference here between authentication and authorization. Authentication verifies the user's identity before allowing them access, and authorization determines what they can do once the system has granted them access.
In PROCEED we leverage RBAC (Role-based Access Control). According to that, it is possible to create roles and attach permissions for a specific resource to the role. Once a role is assigned to a user, the user implicitly inherits the role's permissions. A resource can be anything, but they are typically the assets of your application. In PROCEED, we currently have the following resources:
Process
Project
Template
Task
Machine
Execution
Role
User
Setting
EnvConfig
All
All
represents all resources together! This is useful to grant full access to every resource in PROCEED.
Once a user wants to access one of the resources, he or she creates a request to the backend REST API (MS Server). The backend checks the user's permissions for that resource, by offloading the decision to an external service, which is called OPA (Open Policy Agent). OPA can grant or deny access to a resource and decides if the request will be fulfilled, or denied with a 403 FORBIDDEN
error.
The Open Policy Agent (OPA, pronounced “oh-pa”) is an open source, general-purpose policy engine that unifies policy enforcement across the stack. OPA provides a high-level declarative language called Rego that lets you specify policy as code and simple APIs to offload policy decision-making from your software.
This is not a Rego documentation! Read this page to learn about the core concepts in OPA’s policy language (Rego).
OPA decouples policy decision-making from policy enforcement. When your software needs to make policy decisions, it queries OPA and supplies structured data (e.g., JSON) as input. OPA accepts arbitrary structured data as input. It generates policy decisions by evaluating the query input against policies and data.
Read this to learn more about policies, policy decoupling and OPA.
In PROCEED you can create arbitrary resource
-permission
combinations. A resource
-permission
combination enforces specific permissions on a resource. Consider the following example:
"permissions": {
"Role": 16,
"User": 3,
"Process": 49
}
The above permissions assigns a role with manage
permissions for Roles
, view
& update
permissions for User
and view
, manage
& share
permissions for Process
. In PROCEED all permissions are represented as numbers. Behind every number, there is an action (verb). All numbers must come from a binary number sequence (0,1,2,4,8,16,...). The following values are currently available:
Constant | Action (Verb) | Value |
---|---|---|
PERMISSION_NONE |
none |
0 |
PERMISSION_VIEW |
view |
1 |
PERMISSION_UPDATE |
update |
2 |
PERMISSION_CREATE |
create |
4 |
PERMISSION_DELETE |
delete |
8 |
PERMISSION_MANAGE |
manage |
16 |
PERMISSION_SHARE |
share |
32 |
PERMISSION_MANAGE_ROLES |
manage-roles |
64 |
PERMISSION_MANAGE_GROUPS |
manage-groups |
128 |
PERMISSION_MANAGE_PASSWORD |
manage-password |
256 |
PERMISSION_ADMIN |
admin |
9007199254740991 |
If multiple permissions are assigned to one resource, they are added. For example, if you want to assign view (1)
+ manage (16)
permissions to a resource, it is calculated by view (1)
+ manage (16)
= 17. It is always possible to reverse the number, so that a system knows 17 = manage
and view
permissions. This is important for frontend UI adjustments, as described in Frontend.
The reason for this binary row is, that it is possible to represent arbitrary resource
-permission
combinations with just a number and the resource name. According to that, OPA can execute some bitwise calculations for policy decisions. This is similar to the unix file permissions system. The following example shows, how it works:
# check if user has permission according to bitfields
# example: user has role x with permission { Role: 80 }; wants to delete = 8; role z
# calculation:
# user permission for roles in binary: 1010000 = 80
# necessary delete permissions in binary: 1000 = 8
# AND 0000000 = 0 -> result: not allowed
admin
is equivalent to admin rights on some resource. It is represented by the highest available number, which exists of only ones (11111...). This means that every AND operation with this number will never result into 0.
In PROCEED, it is currently possible to create the following resource
-permission
combinations to assign it to a role. The maximum value is the sum of the available permissions for a resource.
Resource | Action (Verb) | Value (max) |
---|---|---|
Process |
view , manage , share , admin
|
9007199254740991 |
Project |
view , manage , share , admin
|
9007199254740991 |
Template |
view , manage , share , admin
|
9007199254740991 |
Task | view |
1 |
Machine |
view , manage
|
17 |
Execution | view |
1 |
Role | manage |
16 |
Setting | admin |
9007199254740991 |
EnvConfig | admin |
9007199254740991 |
User |
manage , manage-roles
|
80 |
All | admin |
9007199254740991 |
When PROCEED is deployed for the first time, the following roles and respective permissions are generated:
Role | Process | Project | Template | Task | Machine | Execution | Role | User | Setting | EnvConfig | All |
---|---|---|---|---|---|---|---|---|---|---|---|
@admin |
x | x | x | x | x | x | x | x | x | x | admin |
@everyone |
x | x | x | x | x | x | x | x | x | x | x |
@guest |
x | x | x | x | x | x | x | x | x | x | x |
@process_admin |
admin |
admin |
admin |
x | x | x | x | x | x | x | x |
@environment_admin |
x | x | x | x | x | x | x | x | admin |
admin
|
x |
Once PROCEED is launched for the first time, an admin user is created with the @admin
role, to have full access to everything in PROCEED. The admin user can now set up the entire Identity - and Access Management behavior of PROCEED in the IAM view by creating roles, and assigning users and permissions. The admin
role is not removable.
The @everyone
role initially has no configuration. An admin user is able to change that. This role is intended for every authenticated PROCEED user and is not removable. Hence, every authenticated user inherits the permissions of the @everyone role.
The @guest
role initially has no configuration. An admin user is able to change that. This role is intended for every unauthenticated PROCEED user and is not removable. Hence, every unauthenticated user inherits the permissions of the @everyone role.
Next to RBAC, there is also ABAC (Attribute-based Access Control). The difference is, that ABAC uses arbitrary attributes to make policy decisions. ABAC somehow deviates from the "standardized" role management behaviour, because there can be arbitrary policies existing. PROCEED also makes use of additional ABAC rules, which are not possible to list here, but can be viewed in the repository.
The frontend uses the 2 following libraries to adapt the frontend UI according to the users permissions. Please note that this may be manipulated by attackers. However, this won't be an issue because the backend is the final instance to protect resources against unauthorized access.
CASL is a library, that can be used in both the frontend and backend.
CASL operates on the abilities level, that is what a user can actually do in the application. An ability itself depends on the 4 parameters (last 3 are optional):
- User Action
Describes what user can actually do in the app. User action is a word (usually a verb) which depends on the business logic (e.g., prolong
, read
). Very often it will be a list of words from CRUD - create
, read
, update
and delete
.
- Subject
The subject or subject type which you want to check user action on. Usually this is a business (or domain) entity (e.g., Subscription
, Article
, User
). The relation between subject and subject type is the same as relation between an object instance and its class.
- Fields
Can be used to restrict user action only to matched subject's fields (e.g., to allow moderator to update status
field of an Article
and disallow to update description
or title
)
- Conditions
Criteria which restricts user action only to matched subjects. This is useful when you need to give a permission on specific subjects (e.g., to allow user to manage own Article)
It is possible to define rules in a .json format, especially if rules are dynamic. In such cases, the preferred way is to use JSON objects. You can directly pass array of JSON rules into Ability constructor. Such rules are called raw rules:
import { Ability } from '@casl/ability';
export default new Ability([
{
action: 'read',
subject: 'Post'
},
{
action: 'delete',
subject: 'Post',
conditions: { published: true }
}
])
If the above rules are passed to an ability constructor, it means that someone can read
a Post
and delete
a specific Post
, where published
is true
.
When a user is logging into the PROCEED MS, the MS Server reads the user's permissions from the database and passes them to the frontend in the following format:
{
"permissions": {
"Process": 16,
"Project": 1
}
}
The frontend transforms the above permissions into CASL rules as defined above. It subsequently passes them into the ability constructor to define the permissions for the active authenticated user, with the following function:
ability.update(unpackRules(rules));
It is now possible to hide or show components in the UI or to prevent unauthorized access to functions via the following shorthand function, that comes with the casl-vue
library:
$can('view', 'Process')
This essentially asks, if someone can view
an arbitrary Process
and decides, if a user can perform the action based on the ability instance.
Syntax is
$can(action, resource)
. Note thatresource
can also be a javascript object, so the$can
function can additionally validate conditions based on the object. The resource is now not arbitrary anymore, but a specific one.
ATTENTION: When using objects as a resource, the type of the object has to match the
Subject
fields that are specified in the CASL Rules. According to that, it is recommended to map the objects to a readonly, non-enumerable and non-configurable__caslSubjectType__
property, with the following function for example.
import { subject } from '@casl/ability';
// maps all processes to __caslSubjectType__ Process
processes.map((object) => subject('Process', object));
This is the Dev Wiki