[LXD] ReBAC authorization using OpenFGA

Project LXD
Status Draft
Author(s) @monstermunchkin
Approver(s) @stgraber @tomp
Release 5.16
Internal ID LXXXX

Abstract

This adds authorization on top of OIDC using OpenFGA Relationship Based Access Control (ReBAC) which allows fine-grained access control.

Rationale

When using OIDC in LXD, there currently is no way of having access control. The already present RBAC feature is tied to Candid authentication. By adding an independent access control service, this limitation will be resolved.

Specification

Design

LXD will use OpenFGA for authorization. It’s a solution that allows building granular access control using an easy-to-read modeling language.

The following configuration keys will be added:

  • openfga.api.url
  • openfga.api.key
  • openfga.store.id

The value of openfga.api.url represents the API URL of the OpenFGA server. If needed, openfga.api.key can be set which contains the API token for accessing the OpenFGA server. Lastly, openfga.store.id is needed as that’s the store containing the relationship tuples.

Setting both openfga.api.url and openfga.store.id enables OpenFGA, given that OIDC authentication is used.

Authorization Model

The following definition is the authorization model which is used to define the permission model of a system. The JSON representation of this model is hard-coded in LXD and cannot be configured. The reason is that LXD relies on the correctness of the naming of types and relations.

model
  schema 1.1
type user
type group
  relations
    define member: [user, group#member]
type project
  relations
    define manager: [user, group#member]
    define viewer: [user, group#member] or manager
    define instance_manager: [user, group#member] or manager
    define instance_operator: [user, group#member] or instance_manager
    define instance_viewer: [user, group#member] or instance_operator
    define image_manager: [user, group#member] or manager
    define image_viewer: [user, group#member] or image_manager
    define profile_manager: [user, group#member] or manager
    define profile_viewer: [user, group#member] or profile_manager
    define network_manager: [user, group#member] or manager
    define network_viewer: [user, group#member] or network_manager
    define network_acl_manager: [user, group#member] or manager
    define network_acl_viewer: [user, group#member] or network_acl_manager
    define network_zone_manager: [user, group#member] or manager
    define network_zone_viewer: [user, group#member] or network_zone_manager
    define storage_volume_manager: [user, group#member] or manager
    define storage_volume_viewer: [user, group#member] or storage_volume_manager
type instance
  relations
    define project: [project]
    define manager: [user, group#member] or instance_manager from project
    define operator: [user, group#member] or manager or instance_operator from project
    define viewer: [user, group#member] or operator or instance_viewer from project
type image
  relations
    define project: [project]
    define manager: [user, group#member] or image_manager from project
    define viewer: [user, group#member] or manager or image_viewer from project
type profile
  relations
    define project: [project]
    define manager: [user, group#member] or profile_manager from project
    define viewer: [user, group#member] or manager or profile_viewer from project
type network
  relations
    define project: [project]
    define manager: [user, group#member] or network_manager from project
    define viewer: [user, group#member] or manager or network_viewer from project
type network_acl
  relations
    define project: [project]
    define manager: [user, group#member] or network_acl_manager from project
    define viewer: [user, group#member] or manager or network_acl_viewer from project
type network_zone
  relations
    define project: [project]
    define manager: [user, group#member] or network_zone_manager from project
    define viewer: [user, group#member] or manager or network_zone_viewer from project
type storage_volume 
  relations
    define project: [project]
    define manager: [user, group#member] or storage_volume_manager from project
    define viewer: [user, group#member] or manager or storage_volume_viewer from project

Workflow

Each API request to LXD will be checked, and return HTTP status code 403 (forbidden) if not authorized. If a new entity (instance, profile, etc.) is created, LXD will create a new tuple for it, so that the relationship between projects and other entities are known. This is not done for projects themselves. If an entity is deleted, the tuple will be deleted, too.

Example:
If a new instance c1 is created in project foo, the following new tuple will be created:

user: project:foo
relation: project
object: instance:foo_c1

Given a user u1 is an instance_manager in the foo project, this will allow the user to manage the created instance (or any instance in the project). If this tuple didn’t exist, the user wouldn’t automatically have access to instance c1.

All entities (except projects) are identified using <projectName>_<entityName> (e.g. foo_c1) because entities of the same name can exist in different projects.

Users who need admin privileges, can be added to the special admin group. LXD will perform the following check for admin privileges:

user: user:alice
relation: member
object: group:admin

If OpenFGA is enabled, and there are existing entities, all required tuples will be automatically created.

API changes

No API changes.

CLI changes

No CLI changes.

Database changes

No database changes.

Upgrade handling

No upgrade handling.

Further information

  • Instead of using <projectName>_<entityName> we could use <projectName>/<entityName>.
  • Should automatic tuple creation be configurable, e.g. openfga.sync_tuples?
3 Likes

Do I remember correctly that it is the administrator job to deploy OpenFGA and its backend independently outside of LXD?

In addition to that the document looks good to me and I don’t have any comments (with the caveat that I would consider myself a general LXD user and lack in depth knowledge). Looking forward to testing the solution!

While this is a great functionality to have, it is important to have UX view of how the admin experience will look like as people are not used to think about authorizations as graphs.

Yes, the admin needs to deploy OpenFGA independently, and also manage the permissions outside of LXD. The value of openfga.api.url then needs to point to the OpenFGA API.

1 Like

With this would it be possible to give a user instance_viewer on one instance and not the entire project?

Yes, that will be possible. The admin will need to create the following tuple:

user: user:<userName>
relation: viewer
object: instance:<projectName>_<instanceName>

For entire projects, that would change into:

user: user:<userName>
relation: instance_viewer
object: project:<projectName>
1 Like