Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Role-Based Access Control (RBAC) #95

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 285 additions & 0 deletions accepted/00000-rbac.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# RFC: Role-Based Access Control

- Feature Name: Role-Based Access Control (RBAC)
- Start Date: 2024-11-12
- Author: @cbbayburt

# Summary
[summary]: #summary

This RFC proposes implementing role-based access control (RBAC) in Uyuni to meet users' needs for fine-grained access control and configuration.
The solution is extensible, integrates with existing features of the codebase, and is independent of the ongoing efforts to migrate from the Struts framework.


# Motivation
[motivation]: #motivation

Currently, access control rules are defined by static role information and controlled individually within each action.
By default, each new feature must define its own access rules; if no rules are explicitly defined, the feature is available to all authenticated users.
Etheryte marked this conversation as resolved.
Show resolved Hide resolved

This proposal moves access control rules to a centrally managed location.
The code implementation is generic and relies on configurable access policy definitions, allowing new use cases and rule definitions to be addressed through configuration rather than code changes.
The Uyuni Server deploys with sensible defaults and enables users to define custom access policies (user access groups) to meet their specific needs.

The goal of access control is to answer the following question: "Can user U perform operation O on resource R?"

Access control operates on three distinct levels, each with specific goals and constraints:

1. **Authentication:** Identifies the user calling this action.
2. **Authorization:** Determines if the user is allowed to call the endpoints for this action.
3. **Accounting:** Tracks and logs actions taken by users on specific resources.

This proposal specifically addresses level 2, authorization. However, managing access control across different instances or sets of the same resource type is considered beyond the scope of this RFC.


# Detailed design
[design]: #detailed-design

## Overview

This solution stores all available system endpoints in the database and determines which endpoints each user can access, except the **Uyuni Administrator** role, which bypasses access control.

Endpoints are organized into functional groups called **namespaces**, allowing administrators to modify access control rules easily without needing to navigate the complex endpoint structure.
This approach reduces the risk of misconfiguration.

The namespaces are further categorized by access mode into "View" and "Modify" to provide a clear distinction of access levels.
Access policies are defined by mapping namespaces to access control groups.

All existing endpoints must be mapped and grouped into namespaces:

- **308** endpoints in Spark framework used by the web UI
- **757** XML-RPC and JSON over HTTP API endpoints (excluding overloads)
- **563** Struts entry points

The proposed solution is based on the following key components:

1. Access control data
2. User access information
3. Access control filters
4. Management interface
5. Development resources

## 1. Access Control Data

Every access point in Uyuni is mapped as an **endpoint** in the database and grouped into **namespaces**.
Access control is enforced by defining rules for these namespaces, either per **user** or **access group**.

Below is a simplified ER diagram illustrating the proposed structure for access information in the database:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we are not discussing implementation details here, but seeing the ER diagram I can't help myself.

Please NO NUMERIC IDs, I can't stress this enough.

Use BIGINT with identity generated always. Exception being already existing IDs.
And btw. VARCHAR is the same as TEXT in postgres, so use TEXT everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. Thanks for the tip.


![ER diagram](images/00000-rbac-er-diagram.png)
*[View in dbdiagram.io](https://dbdiagram.io/d/Uyuni-RBAC-672e18b3e9daa85acacbddd0)*

### Endpoint

An **endpoint** represents a specific access point that can be called by a client, such as a web page URL, an internal API endpoint used by a web page, or a public API endpoint.

- **`endpoint`**: The accessible URI of the endpoint.

- **`class_method`**: The Java class that handles incoming requests to the endpoint, such as a controller class for web endpoints or an API handler for API endpoints.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be required field or optional? What will we have in case of struts actions ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mandatory, especially for the XML-RPC API endpoint, where we use this field to control access. For Structs and spark-only endpoints this is not really needed, but we can populate it.


- **`http_method`**: The HTTP method (e.g., GET, POST, PUT, DELETE) accepted by the endpoint. If multiple methods are supported, each should be defined as a separate endpoint since they typically serve different purposes.

- **`auth_required`**: Indicates if the endpoint requires authorization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If endpoint, will we have a separate namespace for such endpoints? What endpoints would usually fall in this category?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simples example, login page. You don't need to be authenticated to reach it, otherwise user cannot login.


### Namespace

A **namespace** is a logical grouping of endpoints that performs a specific task and defines the smallest unit of access control.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will we make sure that we don't miss endpoints which are not part of any namespace? my question is more going forward - will we have any tooling which identify this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can develop a tool. But the side effect will be super visible, because users will not be able to access the base and feature will not work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when you're implementing a new URL for a feature, first thing you'll have to do is to add necessary entries (endpoint and namespace info) to the RBAC tables using schema migration. Otherwise you won't be able to visit the URL when testing yourself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I support the idea of Abid to have some kind of tool part of the unit tests that assure that we don't miss any. Let's not rely only to the side effect even if easy to see. But let's be sure that someone adding a new endpoint in a new Pull Request have feedback if that part is missing.


- **`namespace`**: A label representing the namespace, expressed as a dot-separated string of components. Each component corresponds to a level of hierarchy for a task, with related tasks sharing the same components at higher levels.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can an end point be part of more than 1 namespace?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have many cases already. Mostly with some reusable URLs like getting a JSON system list etc. This also aligns with the fact that we want to unify API endpoints to be used by both the public API and the frontend pages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll be able to identify if an endpoint is called for one or other namespace?
I'm thinking about telemetry from the point of view of our features.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From telemetry is not possible o see, we can only see that and endpoint is called. What we can do is we log all the called, be able to calculate a call trace and deduct what could have be the interaction path.


*Example:*
- `clm.project.list`: Allows viewing the CLM projects list.
- `clm.project.details`: Allows viewing or modifying the details of a CLM project.

- **`access_mode`**: Defines the access level for each namespace as either "View" (R) or "Modify" (W). Many namespaces will have separate entries for each mode, with different endpoints for each purpose.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe actions like export CSV will go under view(R) and delete under W, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is the goal, but Can can give more insides if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. AFAICS this separation is clear enough for any action. Management UI will provide proper descriptions for each namespace and mode so it'll be clear to users.

Ideally, the UI should have free text search so for example you'll be able to search for the keyword CSV and see what namespaces are available for any CSV related action.


For example, `clm.project.details` **[R]** allows viewing a project's details, where `clm.project.details` **[W]** allows modifying them.
Etheryte marked this conversation as resolved.
Show resolved Hide resolved

- **`description`**: A clear description of what a namespace grants access to.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can also add a flag to define if the namespace is tailored to be used from the Web application side of API (XML-RPC side), similar of what we have in the endpoint. I know we can play around with the namespace prefix, but this way would be more explicit.


- **`scope`**: Indicates whether the endpoint is accessible through the web UI (including internal API calls) or the public API.

#### Organization of namespaces
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any table to represent this in our sample database schema, right? will we have some kind of inheritance here that if I have access at the top level namespace, I get access to everything else. And if yes, can as an admin, still change it to be more granular.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I got it right, the idea is to have this in an indirect way, by playing with the label name. For example:
namespace: admin
namespace: admin.createUser

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I got it right, the idea is to have this in an indirect way, by playing with the label name.

Correct. By default, granting access to a parent namespace doesn't automatically give access to the children. For that, you'll need to explicitly say "grant access to this namespace and everything that falls below it".


Organizing namespaces in a hierarchical structure with distinct access modes simplifies management and modification of access rules for administrators across users or groups.

### Access group

An **access group** is a collection of access rules that can be assigned to users, allowing administrators to manage permissions for multiple users at once.
Access groups can be either predefined or created by administrators.
Predefined groups cannot be modified by the users and serve as replacements for existing user roles on the Uyuni server.

- **`label`**: The label of the access group.

- **`description`**: The purpose or scope of the access group.

- **`org_id`**: The ID of the organization to which the group belongs. This field will be `null` for predefined groups.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "vendor" groups cannot be changed by the users.


#### Existing roles

In Uyuni, two superuser roles grant unrestricted access to system resources:
- **Uyuni Administrator:** Grants access to every resource across the Uyuni system.
- **Organization Administrator:** Grants access to all resources within the user's organization.

Beyond feature access, these roles also control data visibility. For instance, an Uyuni Administrator can view all clients registered in Uyuni, regardless of the organization each client belongs to. Similarly, an Organization Administrator can view every client within their organization, even if managed by a different user. In contrast, regular users can only view the clients they manage.

Due to these distinctions, the logic around superuser roles will remain in place. Additionally, these roles will have access to all namespaces in the new RBAC implementation.

The other existing roles in Uyuni are as follows:

- Activation Key Administrator
- Configuration Administrator
- Image Administrator
- Channel Administrator
- System Group Administrator
- Read-only API User

Each of these roles grants access to different features in Uyuni. This static access control is enforced individually within each feature. Stripping these control checks throughout the system requires significant effort. Therefore, in the initial phase, the logic of these static roles will coexist with their RBAC counterparts, but will be bypassed by assigning them to all existing and future users. This will effectively make RBAC the exclusive access control mechanism.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not clear to me. Yes, we will keep the existing roles so we don't break the users setup when the migrate to 5.1. But wow are we planning to get rid of them? Will we allow administrators to still play with old roles ? Having new and old permissions setup could be pretty confusing for the admin to understand.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 roles that needs to stay for now, which are satadmin and orgadmin.
Other roles should be replaced with new user groups in the new system.
Internally, we may need to keep the old roles because of struts ACL configurations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though the older logic stays for now, it won't be visible to any user or admin.



## 2. User Access Information

Users can be granted access either individually or through access groups.

Users assigned to an access group inherit all permissions defined within that group.
While administrators can grant individual users additional permissions beyond those provided by their assigned group, they cannot revoke permissions set by the group. Additionally, users can belong to multiple access groups and inherit all associated access rules.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I didn't get this. Here additional permissions means which are not covered by groups or is it more in a sense that those permissions that user got through groups can be overridden by individually assigning to the user?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to grant more permissions to the user. User will always have access to all endpoints granted in all the groups, plus all individual permissions grant to the user directly.


Predefined access groups are designed to meet the needs of most environments; however, administrators can create custom groups to address specific requirements as needed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that namespaces will be static and administrator cannot change them or create new ones or will we provide the same flexibility(not considering here if that's good or bad) there as well that we are providing here in case of access groups?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespaces and access groups are just different levels of putting endpoints together. Namespaces are static and controled by us. Users can then create user groups linked to the endpoint in any shape or form, that will put together one or more namespaces.
On top of that, we will also have a few user groups controlled by us and the goal is that those groups match the existing roles.


Below is a conceptual example of how access rules can be defined for an individual user:

**Example:** Grant tailored access to content management (images) feature for user `Alice`

Available namespaces in the content management feature:

| namespace | access_mode | description
|-------------------|-------------|-------------------------------------------------------------------------
|cm.build | Modify (W) | Build container or Kiwi images
|cm.image.import | Modify (W) | Import container images from a registered image store
|cm.image.list | View (R) | List all images
|cm.image.list | Modify (W) | Delete images
|cm.image.overview | View (R) | View image details, patches, packages, build log and cluster information
|cm.image.overview | Modify (W) | Inspect, rebuild, delete images
|cm.profile.details | View (R) | View details of an image profile
|cm.profile.details | Modify (W) | Create image profiles, edit profile details
|cm.profile.list | View (R) | List all image profiles
|cm.profile.list | Modify (W) | Delete image profiles
|cm.store.details | View (R) | View details of an image store
|cm.store.details | Modify (W) | Create image stores, edit store details
|cm.store.list | View (R) | List all image stores
|cm.store.list | Modify (W) | Delete image stores

Setting up access rules for user `Alice`:

- Grant view access to the entire images feature:

```
Grant 'View' on 'cm.*' to 'Alice'
```

- Prevent access to sensitive image store information:

```
Revoke 'View' on 'cm.store.details' from 'Alice'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Alice got this 'View' access through one accessgroup but then here Alice got that permissions revoked, I expect this would take precedence or?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned here:

While administrators can grant individual users additional permissions beyond those provided by their assigned group, they cannot revoke permissions set by the group.

Permissions assigned through a group cannot be revoked individually. This is an arbitrary limitation I wanted to enforce because otherwise it could get really messy for admins after a while. If we see the need for this, we can change the logic to allow this.

So for the example above, you cannot simply make Alice an Image Admin. If you do, you cannot revoke access to cm.store.details because it's part of the Image Admin role.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's removed from User direct grant but is still present in the group, then Alice still has access to the feature. All endpoints are revoked by default, and we are always adding/grant access in all levels we can configure.

```

- Allow `Alice` to build and manage images:

```
Grant 'All' on 'cm.build' to 'Alice'
Grant 'All' on 'cm.image.*' to 'Alice'
```

- With these permissions, `Alice` can now:

- List all images
- Delete all images
- View image details, patches, packages, build log and cluster information
- List all image profiles
- View details of an image profile
- List all image stores
- Build container or Kiwi images
- Inspect, rebuild, delete images
- Import container images from a registered image store

- When permissions are no longer needed, revoke them:
```
Revoke 'All' on 'cm.*' from 'Alice'
```

*Permissions can be assigned to an access group instead of an individual user in the same manner.*


## 3. Access Control Filters

Access control is enforced at the endpoint level through a dedicated Java servlet filter.

The filter queries the database to verify if the current user is permitted to access a specified endpoint.
It does this by matching the requested URI with the `endpoint` field of allowed endpoints within any associated namespace before passing the request along the chain.

Additionally, the filter injects details of the relevant access rule, such as the namespace and the access mode, into the request context, allowing the controller processing the endpoint to access this information.
This enables the controller to serve conditional content based on each user's access rules, such as hiding unauthorized sections of the page. New ACL methods can also be used to check access to arbitrary namespaces for similar purposes.

The filter controls access to the following types of endpoints:

- Struts pages
- Spark web UI endpoints (pages and internal API)
- Spark public API endpoints (JSON over HTTP API)
- Saltboot endpoints
- Ajax endpoints
- Websocket endpoints

By default, any requested URI is denied access unless a match is found.

### XML-RPC API

Access to XML-RPC API methods is managed through the `BaseHandler` class, the base class for all XML-RPC handlers.
Access is granted if the requested method's name and handler match the `class_method` field of an allowed endpoint.

XML-RPC and HTTP APIs share the same endpoint entries in the database. However, while HTTP requests are validated against the `endpoint` field, XML-RPC requests are validated against the `class_method` field.

### Performance

Access control checks query the database on every request.
To mitigate performance impact, a user's access rules can be preloaded into memory and stored in the user's session data during login, enabling faster access control validation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small issue: if the rules that apply to a user change while they are logged in, this will lead to a mismatch, how will we address that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As first step I think we should not cache any information. We can change that if we notice any performance issue. The topical (as most simple) solution for this kind of situation is that the user must logout and login again.
Another option is to check which sessions are active in the database for user affected by the access change an invalidate those authentication sessions.

Copy link
Contributor Author

@cbbayburt cbbayburt Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. But when we decide to implement caching, I think safest and easiest solution would be to simply kick the user out and require a new login whenever their permissions change. This shouldn't happen often anyway.



## 4. Management Interface

A UI catalog is proposed for access control administration, offering a clear and user-friendly view of all namespaces along with their descriptions.

Additionally, administrators can manage access through a new API namespace called `access`, which provides methods to:

- Grant or revoke access to a namespace for a user or access group
- Add or remove a user from an access group
- Check if a user or group has access to a specific namespace
- List all available namespaces for a particular feature


## 5. Development Resources

After the initial development and mapping of all existing endpoints to the new structure, ongoing effort is required to keep endpoint mappings and the namespace structure up-to-date. To reduce RBAC overhead when implementing new features, the following additional resources are proposed:

- An extensive Wiki guide on adding necessary endpoints with proper namespace organization
- Stored procedures and scripts to easily grant/revoke user access for development
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to avoid store procedures as much as possible. They are hard to test and are hidden inside the database.

Copy link
Contributor Author

@cbbayburt cbbayburt Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. These will be just throwaway tools to help when we're implementing RFC. There's no plan to ship or persist them. Once the API is in place, they won't be needed anyway.

- CI checks to detect any unmapped endpoints when introducing new features
- A failsafe mechanism to prevent access to unmapped endpoints
rjmateus marked this conversation as resolved.
Show resolved Hide resolved
- Startup checks in the Uyuni Server to log any misconfigurations within a specific deployment

# Drawbacks
[drawbacks]: #drawbacks
Comment on lines +269 to +270

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing throughout the document is inconsistent, can you parse it through https://github.com/DavidAnson/markdownlint ?


- **Increased development complexity and overhead:** Maintaining RBAC will require additional development effort, especially to ensure that all endpoints are properly mapped and categorized. Each new feature will need to be carefully aligned with access policies, adding complexity to the process.

- **Potential for configuration drift**: Over time, there is a risk that the RBAC namespace structure and actual endpoint mappings may drift apart. If new endpoints or features are not consistently mapped or if namespace groupings are mismanaged, this can lead to security gaps or unintended access permissions.

- **Learning curve for administrators and developers**: Introducing a new RBAC model with detailed namespace mapping and access modes may require training for both developers and administrators. Adjusting to this new model and understanding the finer distinctions of access control may initially be challenging.

# Unresolved Questions
[unresolved]: #unresolved-questions

- **Granularity of access control:** Should the RBAC model include finer-grained permissions, such as access by specific actions (create, update, delete) within a namespace, or is the proposed "View" vs. "Modify" access mode sufficient for most cases?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, whenever its possible we should have as fine grain as possible, with a separation between the create, update, delete, for example.


- **External integration needs:** Will the RBAC system require compatibility with external authentication or access control systems, such as LDAP, and if so, what integration points are required to support this?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That I would say is part of authentication. However, we could define a mapping between LDAP user groups and suma internal access groups, and automatically assign users to a group at login time.


- **Additional access groups:** Should predefined access groups be expanded to cover specific use cases beyond those mirroring existing roles?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is something we will find out, and we can react according to feedback from customers. One extra groups could be the read-only user that we have now for API only. that can be a new access group and stop relying on method names and a flag on user definition.

Binary file added accepted/images/00000-rbac-er-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.