Manage Permissions with RBAC
What is RBAC
Role Based Access Control (RBAC) is an authorization mechanism built on top of user Roles useful to decouple actions that user can perform (generally known as permissions) and their higher-level Role inside an information system. RBAC allows you to define your custom permissions, group them by Roles and then assign those Roles to your users (or even groups of users). Therefore, enabling RBAC in Console allows you to empower the level of your Console permission management.
Taking aside the security implications that adopting RBAC provides to your solution, one of the most important benefits (in comparison to a simplest ACL mechanism) is the governance capabilities it provides to your system administrators when dealing with what users using your platform may or may not be allowed to do.
Leveraging Mia-Platform already existing authentication and authorization solutions RBAC is made available to your projects in the Console through a dedicated UI; all you need to do is head to the RBAC section (in the Design area) and enable it for your services.
In this page you can find further details on how RBAC works and how to properly configure it in your project.
How to configure RBAC in your project
Inside the Design area a new section is available to manage RBAC configurations. This new section will present four different tabs:
- Overview: in this tab, you can enable and configure the RBAC sidecar for each service of your project;
- General Settings: in this tab, you can control shared settings between all of your RBAC instances in the project (such as the version of the RBAC service or the storage configuration);
- Policies: in this tab, you can write and test your own policies (policies are written using the Rego language, more info here;
- Manual Routes: in this tab, you can manually configure the policies required by your service APIs (useful when your service does not expose an OpenAPI Specification).
Overview tab
Inside the Overview tab you can enable RBAC sidecar injection for any service that is available in your project. Moreover, since a new container will be deployed in your namespace, you can view and configure the following information:
- Memory Request and Limit: Memory request and Memory limit of the sidecar container
- CPU Request and CPU Limit: CPU request and CPU limit of the container of the sidecar
To enable RBAC sidecar injection for a specific service you have to use the Enable RBAC switch available in the table editing drawer. After enabling the RBAC you will be able to customize resources request and limit.
API Permission definition
Once you have enabled RBAC on one of your services, what you want to do is defining which policy is required by your service APIs; if your service
exposes OpenAPI 3 Specification you can use the custom attribute x-permission
described here, otherwise if your service does not expose its API documentation and you don't plan to add it you can use the Manual Routes configuration tab
General Settings Tab
Inside the General Settings tab you can change the RBAC sidecar service version and configure RBAC Storage information.
Policies Tab
In the Policies tab you can write your own policies that will be used by RBAC service to evaluate the incoming requests.
You will find a read-only section that shows your current policies; if you want to modify them or create new ones you can click on the Edit policies
button that will open a modal with two different editors.
In the leftmost one you may write your policies using the Rego language (more info here). The rightmost instead lets you write tests for your policies.
Once you had written your policies you can test them using the Test button.
We strongly suggest you to test your policies, you may notice that if you have written some tests you won't be able to save your changes until your tests are passed.
You can find more information about writing your own policies here.
Manual Routes Tab
If, for any reason, the service you wish to apply RBAC to does not expose any API documentation, or you don't have access to the codebase in order to implement your own documentation API, you can always provide a manual routes configuration to the RBAC sidecar.
In order to do so, you can go to the Manual Routes configuration tab in the RBAC section and define your own routes by selecting the Add New
button.
In the creation form, you will be asked for a microservice name (to be chosen among the ones that you have selected RBAC sidecar injection enabled for), then you'll have to provide a verb and a path that identify the API invocation and at last the Allow Policy
that should be required for that API.
It's possible to add new manual route to register a permission for a group of paths using the wildcard (e.g. "/foo/*") and also to select the method ALL to register the same permission for all http methods of the inserted path, but pay attention to the routes priority rules
During or after the creation of a manual route, you will also be able to associate to it a Rows Filter or a Response Filter, described in depth in their dedicated sections:
To activate the filters, you can use the corresponding switch and then configuring them. In particular:
Row Filtering
uses theacl_rows
value as headerName by default, but you can set a custom value to replace it.Response Filtering
requires instead the name of the filter policy used to evaluate the response received by RBAC sidecar.
While normally the RBAC sidecar will self-configure itself by consuming the API documentation of its target service when you add a new manual route to the RBAC configuration for a microservice, that self-configuration functionality gets disabled, thus you're required to register all the routes that your service exposes.
Routes Priority
When there are more manual routes that match a specific URL path, the priority rules help to resolve the conflict:
- Rule1: If the manual routes paths are different, that more specific wins.
- Rule2: If the manual routes paths are the same, the manual route with the most specific method wins.
EXAMPLE
Defined Manual routes
- GET /foo/bar permission1
- ALL /foo/* permission2
- POST /foo/* permission3
- ALL /* permission4
Paths to resolve
- GET /foo/bar --> permission1 (Rule1)
- GET /foo/bar/ --> permission2 (Rule2)
- POST /foo/bar/ --> permission3 (Rule2)
RBAC rows filtering
It may happen that for some of your APIs you need to filter out some results based on the permissions of the requesting user. This is the case in which we talk about rows filtering.
The RBAC service allows you to retrieve automatically a query for your DBMS coming from the evaluation of the permissions of a user. This query will be passed as a header to the requested service that should know how to handle it properly.
To let the RBAC service knows it has to perform this query generation, you need to configure it in the api configuration or go to Manual Routes tab.
The headerName field
will be used to provide the name of the header that will contain the final query and will be passed to the requested service. If you don't specify any value for headerName, by default it will take the acl_rows
value.
The rows filtering, in order to correctly work, needs the definition of a permission evaluation, that has to be written and tested in the properly Policies tab of RBAC section.
How to write a query in rego for permission evaluation
Any RBAC policies is provided with the iterable data.resources[_]
. This structure is used to build up the query.
You can use it to perform any kind of comparison between it and anything you want from the input or maybe a constant value.
We remind you that the query will be built from the field name of the resource object accessed in the permission.
To build your query remember to use assign in the data.resources
iterable the same properties you have defined in the data model you need to be queried.
In the example below, given a valid rows filtering configuration, the allow
policy requires that the requested resource details can be retrieved by the user and by its manager.
allow {
input.request.method == "GET"
resource := data.resources[_]
resource.name == input.user
resource.description == "this is the user description"
}
allow {
input.request.method == "GET"
resource := data.resources[_]
resource.manager == input.user
resource.name == input.path[1]
}
Policy for row filtering does not support the default
declaration. E.g. default allow = true
. Using it will prevent any filter to be generated.
Given as input to the permission evaluator
[
{
"input": {
"method": "GET",
"path": ["resource", "bob"],
"user": "alice"
}
}
]
We succeed in obtaining the following object representing a mongo query
[
{
"$or":[
{
"$and":[
{
"name":{
"$eq":"alice"
}
},
{
"description":{
"$eq":"this is the user description"
}
}
]
},
{
"$and":[
{
"manager":{
"$eq":"alice"
}
},
{
"name":{
"$eq":"bob"
}
}
]
}
]
}
]
RBAC response filtering
For some of your APIs you may need to manipulate data, based on the permissions of the requesting user, before sending it to the final target. This is the case in which we talk about response filtering.
Whenever this filtering is performed on the results obtained by a HTTP request, we talk about response filtering on response.
RBAC service allows you to manipulate the response body directly in a policy.
As shown in the RBAC policies section, your policies will be provided with the original response body, allowing you to manipulate it.
After the policy is written, you need to register it in the api configuration or in the Manual Routes section.
Response filtering is applied only if the response Content Type is application/json
; if any other Content Type is found (based on the Content-Type
header value), an error will be sent to the caller.
Be careful, the written policy must respect the syntax
policy_name[return_value]{
somePolicyContent==true
}
and must return the new value of the modified data.
filter_response_example[result] {
body := input.request.body
result := object.remove(body, ["someField"])
}
Technical details
RBAC Service
The RBAC Service is the core service that is responsible for handling policies evaluations; it is thought to run alongside your application container (as a sidecar), to intercept traffic directed to your service and to reject unwanted/unauthorized API invocation.
In order to know which API should be exposed RBAC sidecar will try to fetch from the application services an OpenAPI 3 compliant specification. At this point RBAC Service will expose all your application service APIs and after performing policy evaluation decide whether the API invocation should be forwarded to the application service.
Below a sequence diagram that describes the main flow between a client and the final custom service:
Be careful: the value of the environment variable LOG_LEVEL
is set by the Console with the value of the LOG_LEVEL
environment variable of the service to which the sidecar is attached to.
RBAC Storage
RBAC is configured by design to load Roles and Bindings from MongoDB, in order to properly configure MongoDB connection string and collection names head to the General Settings tab
change the values in the RBAC Storage card. These values are set by default to {{MONGODB_URL}}
, rbac-bindings
and rbac-roles
; feel free to change the names to better suite your
naming conventions and standards.
You have to provide the collection names you wish to use in your project, when you save the configuration the new collections will be created and will be visible in the MongoDB CRUD section.
If you wish to change the collection names in a second occasion you must manually delete the old ones.
RBAC Data Models
As previously said Roles and Bindings collections are created when saving your configuration according to the ones specified in the General Settings, the collection data models are described here.
Roles
This collection contains all the roles needed in an organization with their specific permissions.
The collection fields are:
- roleId (string, required): the unique id of the roles
- name (string,required): name of the role
- description (string): description of the role
- permissions (string array, required): list of permissions ids for the role
[
{
"roleId": "roleUniqueIdentifier",
"name": "TL",
"description": "company tech leader",
"permissions": [
"console.project.view",
"console.environment.deploy",
"console.company.billing.view"
]
}
]
Bindings
A Binding represents an association between a set of Subjects (or groups), a set of Roles and (optionally) a Resource.
A Subject may represent a user or another application; its identifier is retrieved by the RBAC Service from the Mia-Platform standard header miauserid
Groups are retrieved by the RBAC Service from the Mia-Platform standard header miausergroups
)
In this collection there are stored all the bindings between users or groups of users and a resource with a list of roles. The fields for this collections are:
- bindingId (string, required): unique id of the binding
- groups (string array): list of user group identifiers
- subject (string array): list of user ids
- roles (string array): list of role ids
- permissions: (strings) list of permission ids
- resource: (object) with properties
type
andid
[
{
"bindingId": "bindingUniqueIdentifier",
"groups": [
"team1"
],
"subjects": [
"bob"
],
"roles": [
"TLRoleId"
],
"resource": {
"resourceId": "project1",
"resourceType": "project"
}
}
]