Introduction

A limited form of role-based api access control for the CloudStack management server API is provided through a properties file, commands.properties, embedded in the WAR distribution. Therefore, customizing API permissions requires unpacking the distribution and modifying this file consistently on all servers. This system also does not permit the specification of additional roles. The aim of the db-backed dynamic role based api-access checker feature is to improve upon the existing static mechanism, make it dynamic while maintaining backward compatibility, usage and assumptions around account type; while we explore for a better solution.

This feature will make the following modifications CloudStack’s role-based api access checker system to increase its robustness and flexibility:

  • Move the storage of role API access definitions from commands.properties to the management server database

  • Allow the definition of custom roles (e.g. a read-only ROOT Admin) beyond the current set of four (4) and enable/disable APIs to a role at runtime without restarting management server

  • All roles while can have different sets of enabled APIs, will resolve one of four role types - Admin, Resource Admin, Domain Admin and User

  • Provide APIs and a user interface to define roles and manage API permissions at runtime without requiring the admin to restart management server(s)

Bug Reference

CLOUDSTACK-8562

Document History

Version

Author / Reviewer

Date

1.0

Rohit Yadav / ACS-dev community

24/March/2016

  

 

Use cases

The following are some common use cases for db-backed dynamic role based api-access checker feature:
  • Define new roles in CloudStack and associate APIs with them
  • Allow root admins to define a read-only admin, this can be then used to create accounts for monitoring services that are of the read-only admin role
  • Maintain consistency of roles and enabled APIs per role across management servers

Feature Specification

Currently, the APIChecker interface defines the mechanism to enforce role-based access controls on API calls. This interface is implemented by API plugins to provide alternative permission storage and enforcement schemes. To support this enhancement a new plugin will implement a DynamicRoleBasedAPIAccessChecker that will store the permission definitions in the management server database. This plugin will also contain API endpoints and user interface extensions to support the run time modification of the available roles, as well as, their API permission mappings.

When upgrading, CloudStack will continue to use the StaticRoleBasedAPIChecker, but will provide the ability to switch to DynamicRoleBasedAPIAccessChecker using a global setting. All new installations though will default to the DynamicRoleBasedAPIAccessChecker with a goal to deprecate the current StaticRoleBasedAPIChecker with future releases. For insuring the root admin is not locked out of the system, user/account that are root admin (of role type Admin and role id:1) will be allowed to access all APIs.

The client UI will only be modified to determine the user’s role type dynamically. It will not be modified to enable/disable elements based on a user’s permissions. Therefore, all actions would be rendered in the UI, say for a read-only admin. If a user attempts to invoke actions for which they do have access, an error message will be displayed. Finally, the plugin will be built assuming that all management servers in a cluster use the same APIChecker plugin implementation (e.g. StaticRoleBaseAPIChecker, DynamicRoleBasedAPIAccessChecker, etc).

CloudStack UI and management server have assumptions around role types. Therefore, to support this all dynamic roles will resolve to one of the four role types:  Admin, ResourceAdminDomainAdmin and User. To maintain this semantic, the system will ensure that four default roles always exist tied to these four role types.

Implementation

APIs

Both roles and role-permissions related APIs can only be allowed for and executed by root admins.

Role Management Related 

  • listRoles Lists dynamic roles in CloudStack
    Parameters
    ==========
    id = (uuid) List role by role ID.
    type = (string) List role by role type, valid options are: Admin, ResourceAdmin, DomainAdmin, User.
    name = (string) List role by role name.

  • createRole Creates a role
    Required params are type name
    Parameters
    ==========
    name = (string) creates a role with this unique name
    type = (string) The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User
    description = (string) The description of the role

  • updateRole Updates a role
    Required params are id
    Parameters
    ==========
    id = (uuid) ID of the role
    name = (string) creates a role with this unique name
    type = (string) The type of the role, valid options are: Admin, ResourceAdmin, DomainAdmin, User
    description = (string) The description of the role

  • deleteRole Deletes a role
    Required params are id
    Parameters
    ==========
    id = (uuid) ID of the role

    The API will not allow deletion if there are existing usages of the role in any of the accounts. Deletion would simply mark the role as removed in DB. 

Role Permissions Related

  • listRolePermissions Lists role permissions
    Parameters
    ==========
    roleid = (uuid) ID of the role

  • createRolePermission Adds a API permission to a role
    Required params are rule permission roleid
    Parameters
    ==========
    rule = (string) The API name or wildcard rule such as list*
    permission = (string) The rule permission, allow or deny. Default: deny.
    roleid = (uuid) ID of the role

  • updateRolePermission Updates a role permission
    Required params are id
    Parameters
    ==========
    rule = (string) The name of the API or wildcard rule such as list*
    id = (uuid) ID of the role permission
    description = (string) The description of the role permission
    permission = (string) The rule permission, allow or deny. Default: deny.

  • deleteRolePermission Deletes a role permission
    Required params are id
    Parameters
    ==========
    id = (uuid) ID of the role permission

Note: A role permission rule can be a string with a wildcard (*) which will be expanded as \w* to do regex based matching against requested API's name.

Account Creation Changes

  • createAccount
    roleid: (uuid) ID of the role
    API will accept a required `roleid` argument, which override accounttype when both are provided.
    For backward compatibility, `roleid` is not a required paramenter while accounttype will be made optional API parameter. When both are passed, the passed accounttype will be ignored and accounttype based on a valid roleid passed will be used instead. When only accounttype is passed, the roleid will be one of the four default roles based on the passed accounttype. In case none of them are passed, the API would thrown a parameter exception. 

APIChecker Implementation 

Implement  DynamicRoleBasedAPIAccessChecker that implements checkAccess() based on whether the user's role was allowed to request an API by querying the database.

The process of checking user account access for a request API will be as follows:

  • Check requested API against list of allow/deny permissions for the account as per the returned order of allow/deny rules. It allows if requested API matches an allow rule, or denies if it matches a deny rule. If it does not match with any of the rules, go to next step.
  • Check requested API against API's 'authorized' attribute from its @Param annotation, allow access if caller account role type matches requested API's authorize annotation.
  • Deny access.

A PermissionDeniedException will be thrown when API needs to be denied for a caller user account.

Example - to create a read-only role, such a role need to first allow all list operation, i.e. list*-allow rule and then explicitly say a *-deny rule to avoid any API that may be allowed/enabled due to annotation rules.

Schema

A. Table for Dynamic roles

roles:
id (bigint, auto increment),
uuid (varchar, unique),
name (varchar, unique),
role_type (enum/string),
description (text),
removed (datetime)

B. Table for mappings between roles and enabled apis

role_permissions:
id (bigint, auto increment),
role_id (bigint),
rule (varchar),
permission (allow or deny, enum/string),
description (text) 

C. Changes to existing tables

Introduce new column in cloud.accounts table: role_id
The getRoleType() method for Accounts will use the dynamic roles table to translate the account to a role type.
Introduction of the role_id column in accounts table would also require us to make rebuild view with the column: account_view and user_view.

D. Data migration and Upgrade Strategy

Older installations can migrate to the new DynamicRoleBasedAPIAccessChecker, for this the old rules from commands.properties will need to be migrated to the role_permissions table. This can be done in a Java based upgrade path.

As an example, this python script shows how we can read rules from the commands.properties file and insert the enabled APIs in the role_permissions table: https://gist.github.com/bhaisaab/646f9bf27ab2198f8b2d

DB upgrade path process:

  • The feature will be enabled by default for new installations, while existing deployments will continue to use the older StaticRoleBasedAPIChecker. Old/existing deployments can enable the feature using a global setting or API.
  • During fresh installation or upgrade, the upgrade paths will add four default roles based on the four role types (Admin, ResourceAdmin, DomainAdmin and User)
  • For ease of migration, at the time of upgrade the upgrade path will read existing commands.properties to add existing set of permissions to the default roles.
  • cloud.account will have a new role_id column which will be populated based on default roles

E. Upgrade recommendations for future API/Plugin author

New APIs can be enabled by default by setting list of allowed roletypes in @ApiCommand authroized field to set default rules. So, after upgrading CloudStack when new plugins will bring in new APIs; if those APIs don't match any pre-defined allow/deny rules, their annotations will be used to check against calling user account's role type to determine API access. Alternatively, for a given set of roles by listing roles of a given type (admin, resource admin, domain admin or user) and they can add upgrade path which will add permission to allow their new apis for a set of roles in role_permissions table. Set of new APIs per release can be listed in all future release notes and after an upgrade the root admins can choose to add/remove API permissions for roles at their discretion. 

API Docs

The purpose of the API doc is to share information about an API to its users. With this feature, there might be various roles with various API permissions. Also the previous API docs for root admin, domain admin and user were based on the default commands.properties file, which could be different in different deployments. It is proposed that with this feature, we can remove commands.properties file and fix the ApiXmlDocWriter such that it outputs a unified API doc covering all the APIs available in a release irrespective of available user type or roles (root domain, domain or user etc).

UI Changes

List of available roles to shown in dropdown, in ui/scripts/accounts.js, so that admin define roles can be selected when an account is created. Role and role-rule management from UI.

Testing

Marvin tests cases

  • CRUD tests:
    • Add role, list roles to confirm addition
    • Update role, list roles to cofirm change
    • Delete role, list roles to confirm deletion
  • Enable and disable a set of APIs for a role, create an account and using that account/user call listApis and those actual APIs to confirm
  • Multiple management server test: (test using a test account/user)
    • Enable/Disable a set of APIs for a role on a single mgmt server, disable through direct DB query, execute APIs and do a listApis to confirm
    • For this test, we can simulate an extra mgmt server by injecting the rules directly in the DB and running against the mgmt server in-us

Regression tests

  • Verify backward compatibility, i.e. API access for old/statically configured roles: admin, resource admin, domain admin, user by creating custom account/user and listAPIs for such accounts/user against a known commands.properties file
  • SAML:
    • Verify (old) SAML user authentication and usage after upgrade to this feature
    • Create new account/user with new dynamic roles and enable them as SAML users. Verify SAML authentication and UI/API usage.
  • LDAP:
    • Verify (old) LDAP user usage after upgrade to this feature
    • Import new LDAP users that use new dynamic roles, verify LDAP account usage for newly import accounts
  • Project:
    • Verify creation and usage of project before and after upgrading to this feature
    • Create project after upgrade and verify the project feature
  • No labels