RBAC Endpoint Mapping - uyuni-project/uyuni GitHub Wiki

Registering endpoints for RBAC

With the introduction of Role-Based Access Control in 5.1 Beta 2, we now have to add mappings for any new/updated web endpoints to the relevant tables in the DB.

This guide explains the steps needed to be taken in order to make sure all the new endpoints are allowed access through the new RBAC filters.

For more information on how RBAC works under the hood, please refer to the Role-Based Access Control (RBAC) RFC document.

Important note

RBAC is effectively bypassed for "Satellite Admin" user. Because of this, please make sure you test your features with a separate "Org Admin" user instead of the default admin/admin.

How does RBAC work?

Role-Based Access Control is configured via namespaces, which are groups of web endpoints that serve a specific purpose like an action of a feature or rendering of a complex web page. With the initial version, all the existing endpoints in MLM are already mapped to namespaces. To browse the existing namespace tree, you can query the access.namespace table or the access.endpointCatalog view.

When a user requests an endpoint either in the web UI or the API, a Java servlet filter called the authorization filter checks the DB to determine which namespaces this endpoint belongs to, and checks if the user has access to this namespace.

User access to namespaces is provided via access groups (formerly "roles"). This relation is defined in access.accessGroupNamespace table. Depending on the desired access level, you might want to add the new namespace to any or all of the available access groups. Keep in mind that the initial data in access.accessGroup table only includes the predefined access groups, but in a typical setup, there might be extra access groups defined by the administrators.

If you don't add an entry to access.accessGroupNamespace, your namespace won't be accessible by any user group, but it will be available to administrators who can provide access to this namespace by creating additional access groups and assigning the namespace to them.

Why do I need to add mappings for an endpoint?

As a failsafe mechanism, an RBAC filter automatically rejects any endpoint that is not added to the database with a proper mapping. If you skip this part, your endpoints won't be accessible to MLM users.

How do I register my endpoints?

At the time of the Beta release, we have mapped a total of 1785 endpoints to a namespace tree of 987 individual namespaces. It means that if you are adding or updating an endpoint for an existing page or feature, a namespace that suits the purpose of your endpoint probably already exists. In that case you only need to add one entry for the endpoint, and one additional entry for the relation between the new endpoint and the existing namespace.

If you think the endpoints you're adding deserves granularity of access control, you should map them to a new namespace at any level of the namespace tree.

A new namespace is only accssible to a user group if a corresponding access.accessGroupNamespace entry is also added.

Below are the most common 5 cases that require changes in RBAC data:

  1. I have a new page/feature
  2. I made changes to an existing page/feature
  3. I haven't changed any endpoint URLs
  4. I implemented a new API endpoint
  5. I have a special endpoint that doesn't require authentication

1. I have a new page/feature

You need to add entries to the following DB tables:

  • access.endpoint: The entry that describes your web endpoint

    • class_method: Reserved for XMLRPC endpoints, must be an empty string
    • endpoint: The URL of the endpoint starting after the /rhn part
    • http_method: The expected HTTP method of the request (GET, POST, etc.)
    • scope: W for web UI
    • auth_required: true if this is an authenticated endpoint
  • access.namespace: The entry that describes your namespace

    • namespace: The namespace as a dot-separated string
    • access_mode: R for read/view, W for write/modify. Regardless of the HTTP method, if the endpoint does any change in the system, this value should be W.
    • description: A short paragraph to describe the purpose of the namespace. RBAC UI will display these descriptions to help users with administration. We also plan a full-text search field on this field to make browsing easier.
  • access.endpointNamespace: The entry for endpoint to namespace relation

  • access.accessGroupNamespace: Specifies which access groups have access to the namespace

Example

-- New endpoints
INSERT INTO access.endpoint (class_method, endpoint, http_method, scope, auth_required)
    VALUES ('', '/manager/systems/details/ansible/playbooks', 'GET', 'W', True);

INSERT INTO access.endpoint (class_method, endpoint, http_method, scope, auth_required)
    VALUES ('', '/manager/api/systems/details/ansible/discover-playbooks/:pathId', 'GET', 'W', True);

INSERT INTO access.endpoint (class_method, endpoint, http_method, scope, auth_required)
    VALUES ('', '/manager/api/systems/details/ansible/paths/save', 'POST', 'W', True);

-- 'View' namespace
INSERT INTO access.namespace (namespace, access_mode, description)
    VALUES ('systems.ansible', 'R', 'Browse ansible playbooks and inventories');

-- 'Modify' namespace
INSERT INTO access.namespace (namespace, access_mode, description)
    VALUES ('systems.ansible', 'W', 'Modify, delete ansible paths, schedule playbook executions');

-- Endpoints for 'View' namespace
INSERT INTO access.endpointNamespace (namespace_id, endpoint_id)
    SELECT ns.id, ep.id FROM access.namespace ns, access.endpoint ep
    WHERE ns.namespace = 'systems.ansible' AND ns.access_mode = 'R'
    AND ep.endpoint = '/manager/systems/details/ansible/playbooks' AND ep.http_method = 'GET';

INSERT INTO access.endpointNamespace (namespace_id, endpoint_id)
    SELECT ns.id, ep.id FROM access.namespace ns, access.endpoint ep
    WHERE ns.namespace = 'systems.ansible' AND ns.access_mode = 'R'
    AND ep.endpoint = '/manager/api/systems/details/ansible/discover-playbooks/:pathId' AND ep.http_method = 'GET';

-- Endpoint for 'Modify' namespace
INSERT INTO access.endpointNamespace (namespace_id, endpoint_id)
    SELECT ns.id, ep.id FROM access.namespace ns, access.endpoint ep
    WHERE ns.namespace = 'systems.ansible' AND ns.access_mode = 'W'
    AND ep.endpoint = '/manager/api/systems/details/ansible/paths/save' AND ep.http_method = 'POST';

-- Rule for general access
-- (relates all access groups to the new 'systems.ansible' namespaces)
INSERT INTO access.accessGroupNamespace
    SELECT ag.id, ns.id
    FROM access.accessGroup ag, access.namespace ns
    WHERE ns.namespace = 'systems.ansible';

-- Or for access to a specific group
INSERT INTO access.accessGroupNamespace
    SELECT ag.id, ns.id
    FROM access.accessGroup ag, access.namespace ns
    WHERE ns.namespace = 'systems.ansible'
    AND ag.label = 'system_group_admin';

2. I made changes to an existing page/feature

If the new/modified endpoints fit an existing namespace, you don't have to insert into the namespace table. If you're changing an endpoint URL, you must write an update to the endpoint table. If it's a new URL, you must write an insert. Finally, for each new endpoint, you must insert the relation to its namespace to the endpointNamespace table.

3. I haven't changed any endpoint URLs

You should still have a look at the existing endpoint and namespace records and see if they're still accurate (HTTP method, namespace desciption, access mode).

4. I implemented a new API endpoint

While Web UI namespaces are logical groupings of endpoints that are intended to work together, API namespaces map individual API endpoints for top granularity. In other words, every API endpoint has its separate namespace and has a 1-to-1 mapping to its endpoint.

The process is very similar to Case 1, with slight changes in the table values:

  • access.endpoint: One entry per method

    • class_method: The fully qualified class name of the handler + . + the name of the handler method
    • endpoint: The JSON over HTTP URL of the API endpoint
    • http_method: GET if annotated with @ReadOnly, POST otherwise
    • scope: A for API
    • auth_required: true if the method expects a session key
  • access.namespace: A new entry for each method

    • namespace: api.<api_namespace>.<api_method_in_snake_case>
    • access_mode: Most likely R if annotated with @ReadOnly, W otherwise
    • description: A short version of the method's apidoc description.
  • access.endpointNamespace: The 1-to-1 relation between the endpoint and the namespace

  • access.accessGroupNamespace: An entry for every access group that's allowed access to the method

5. I have a special endpoint that doesn't require authentication

These endpoints will bypass RBAC, so there's no need to connect them to a namespace, but for RBAC to recognize these endpoints, a record in the endpoint is still necessary:

  • access.endpoint: The entry that describes your web endpoint
    • class_method: An empty string
    • endpoint: The URL of the endpoint starting after the /rhn part
    • http_method: The expected HTTP method of the request (GET, POST, etc.)
    • scope: W for web UI
    • auth_required: false to bypass RBAC authorization

Example

INSERT INTO access.endpoint (class_method, endpoint, http_method, scope, auth_required)
    VALUES ('', '/hub/ping', 'POST', 'W', False)

Debugging

To see what actual endpoints a user has access to, query the access.userAccessTable view. This is the master view that shows the access information.

com.redhat.rhn.frontend.servlets.AuthorizationFilter writes debug messages to the rhn_web_ui.log whenever an access has been granted or denied. Consider enabling debug level logging for this class.

RBAC responds with a 403 when a request is denied access. Therefore, tomcat access logs can be useful as well.