-
Notifications
You must be signed in to change notification settings - Fork 233
RBAC Endpoint Mapping
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.
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.
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.
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.
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:
- I have a new page/feature
- I made changes to an existing page/feature
- I haven't changed any endpoint URLs
- I implemented a new API endpoint
- I have a special endpoint that doesn't require authentication
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/rhnpart -
http_method: The expected HTTP method of the request (GET,POST, etc.) -
scope:Wfor web UI -
auth_required:trueif this is an authenticated endpoint
-
-
access.namespace: The entry that describes your namespace-
namespace: The namespace as a dot-separated string -
access_mode:Rfor read/view,Wfor write/modify. Regardless of the HTTP method, if the endpoint does any change in the system, this value should beW. -
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
In many cases, you'll want to add your new endpoint(s) to an existing namespace.
In that case, most likely no changes are required in the namespace and accessGroupNamespace tables.
Note: In some cases, one endpoint might be mapped to multiple namespaces. This is often necessary for some API endpoints common to multiple pages (e.g. highstate API). In this case, having access to at least one of the namespaces grants access to the endpoint.
Another case is with Struts dispatch endpoints that handle multiple functions in a single endpoint.
For example, Search toolbar action is mapped to all of systems.search, software.search and patches.search namespaces.
Having access to any of these namespaces will grant access to the Search action. Inside the action,
a helper method called ensureRoleBasedAccess further controls the access according to the conditional logic inside the handler.
-- 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';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.
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).
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:GETif annotated with@ReadOnly,POSTotherwise -
scope:Afor API -
auth_required:trueif 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 likelyRif annotated with@ReadOnly,Wotherwise -
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
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/rhnpart -
http_method: The expected HTTP method of the request (GET,POST, etc.) -
scope:Wfor web UI -
auth_required:falseto bypass RBAC authorization
-
INSERT INTO access.endpoint (class_method, endpoint, http_method, scope, auth_required)
VALUES ('', '/hub/ping', 'POST', 'W', False)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.